|
|
|
|
@ -34,6 +34,46 @@ func (rr resolverAndDelay) String() string {
|
|
|
|
|
return fmt.Sprintf("%v+%v", rr.name, rr.startDelay)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setTCFlagInPacket sets the TC flag in a DNS packet (for testing).
|
|
|
|
|
func setTCFlagInPacket(packet []byte) {
|
|
|
|
|
if len(packet) < headerBytes {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
flags := binary.BigEndian.Uint16(packet[2:4])
|
|
|
|
|
flags |= dnsFlagTruncated
|
|
|
|
|
binary.BigEndian.PutUint16(packet[2:4], flags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clearTCFlagInPacket clears the TC flag in a DNS packet (for testing).
|
|
|
|
|
func clearTCFlagInPacket(packet []byte) {
|
|
|
|
|
if len(packet) < headerBytes {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
flags := binary.BigEndian.Uint16(packet[2:4])
|
|
|
|
|
flags &^= dnsFlagTruncated
|
|
|
|
|
binary.BigEndian.PutUint16(packet[2:4], flags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// verifyEDNSBufferSize verifies a request has the expected EDNS buffer size.
|
|
|
|
|
func verifyEDNSBufferSize(t *testing.T, request []byte, expectedSize uint16) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
ednsSize, hasEDNS := getEDNSBufferSize(request)
|
|
|
|
|
if !hasEDNS {
|
|
|
|
|
t.Fatalf("request should have EDNS OPT record")
|
|
|
|
|
}
|
|
|
|
|
if ednsSize != expectedSize {
|
|
|
|
|
t.Fatalf("request EDNS size = %d, want %d", ednsSize, expectedSize)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setupForwarderWithTCPRetriesDisabled returns a forwarder modifier that disables TCP retries.
|
|
|
|
|
func setupForwarderWithTCPRetriesDisabled() func(*forwarder) {
|
|
|
|
|
return func(fwd *forwarder) {
|
|
|
|
|
fwd.controlKnobs = &controlknobs.Knobs{}
|
|
|
|
|
fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestResolversWithDelays(t *testing.T) {
|
|
|
|
|
// query
|
|
|
|
|
q := func(ss ...string) (ipps []*dnstype.Resolver) {
|
|
|
|
|
@ -428,22 +468,16 @@ func makeLargeResponse(tb testing.TB, domain string) (request, response []byte)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Our request is a single A query for the domain in the answer, above.
|
|
|
|
|
builder = dns.NewBuilder(nil, dns.Header{})
|
|
|
|
|
builder.StartQuestions()
|
|
|
|
|
builder.Question(dns.Question{
|
|
|
|
|
Name: dns.MustNewName(domain),
|
|
|
|
|
Type: dns.TypeA,
|
|
|
|
|
Class: dns.ClassINET,
|
|
|
|
|
})
|
|
|
|
|
request, err = builder.Finish()
|
|
|
|
|
if err != nil {
|
|
|
|
|
tb.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
request = makeTestRequest(tb, domain, dns.TypeA, 0)
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports ...uint16) ([]byte, error) {
|
|
|
|
|
return runTestQueryWithFamily(tb, request, "udp", modify, ports...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runTestQueryWithFamily(tb testing.TB, request []byte, family string, modify func(*forwarder), ports ...uint16) ([]byte, error) {
|
|
|
|
|
logf := tstest.WhileTestRunningLogger(tb)
|
|
|
|
|
bus := eventbustest.NewBus(tb)
|
|
|
|
|
netMon, err := netmon.New(bus, logf)
|
|
|
|
|
@ -467,7 +501,7 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports
|
|
|
|
|
|
|
|
|
|
rpkt := packet{
|
|
|
|
|
bs: request,
|
|
|
|
|
family: "tcp",
|
|
|
|
|
family: family,
|
|
|
|
|
addr: netip.MustParseAddrPort("127.0.0.1:12345"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -483,17 +517,29 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makeTestRequest returns a new TypeA request for the given domain.
|
|
|
|
|
func makeTestRequest(tb testing.TB, domain string) []byte {
|
|
|
|
|
// makeTestRequest returns a new DNS request for the given domain.
|
|
|
|
|
// If queryType is 0, it defaults to TypeA. If ednsSize > 0, it adds an EDNS OPT record.
|
|
|
|
|
func makeTestRequest(tb testing.TB, domain string, queryType dns.Type, ednsSize uint16) []byte {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
if queryType == 0 {
|
|
|
|
|
queryType = dns.TypeA
|
|
|
|
|
}
|
|
|
|
|
name := dns.MustNewName(domain)
|
|
|
|
|
builder := dns.NewBuilder(nil, dns.Header{})
|
|
|
|
|
builder.StartQuestions()
|
|
|
|
|
builder.Question(dns.Question{
|
|
|
|
|
Name: name,
|
|
|
|
|
Type: dns.TypeA,
|
|
|
|
|
Type: queryType,
|
|
|
|
|
Class: dns.ClassINET,
|
|
|
|
|
})
|
|
|
|
|
if ednsSize > 0 {
|
|
|
|
|
builder.StartAdditionals()
|
|
|
|
|
builder.OPTResource(dns.ResourceHeader{
|
|
|
|
|
Name: dns.MustNewName("."),
|
|
|
|
|
Type: dns.TypeOPT,
|
|
|
|
|
Class: dns.Class(ednsSize),
|
|
|
|
|
}, dns.OPTResource{})
|
|
|
|
|
}
|
|
|
|
|
request, err := builder.Finish()
|
|
|
|
|
if err != nil {
|
|
|
|
|
tb.Fatal(err)
|
|
|
|
|
@ -549,6 +595,371 @@ func beVerbose(f *forwarder) {
|
|
|
|
|
f.verboseFwd = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makeTestRequestWithEDNS returns a new TypeTXT request for the given domain with EDNS buffer size.
|
|
|
|
|
// Deprecated: Use makeTestRequest with queryType and ednsSize parameters instead.
|
|
|
|
|
func makeTestRequestWithEDNS(tb testing.TB, domain string, ednsSize uint16) []byte {
|
|
|
|
|
return makeTestRequest(tb, domain, dns.TypeTXT, ednsSize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makeEDNSResponse creates a DNS response of approximately the specified size
|
|
|
|
|
// with TXT records and an OPT record. The response will NOT have the TC flag set
|
|
|
|
|
// (simulating a non-compliant server that doesn't set TC when response exceeds EDNS buffer).
|
|
|
|
|
// The actual size may vary significantly due to DNS packet structure constraints.
|
|
|
|
|
func makeEDNSResponse(tb testing.TB, domain string, targetSize int) []byte {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
// Use makeResponseOfSize with includeOPT=true
|
|
|
|
|
// Allow significant variance since DNS packet sizes are hard to predict exactly
|
|
|
|
|
// Use a combination of fixed tolerance (200 bytes) and percentage (25%) for larger targets
|
|
|
|
|
response := makeResponseOfSize(tb, domain, targetSize, true)
|
|
|
|
|
actualSize := len(response)
|
|
|
|
|
maxVariance := 200
|
|
|
|
|
if targetSize > 400 {
|
|
|
|
|
// For larger targets, allow 25% variance
|
|
|
|
|
maxVariance = targetSize * 25 / 100
|
|
|
|
|
}
|
|
|
|
|
if actualSize < targetSize-maxVariance || actualSize > targetSize+maxVariance {
|
|
|
|
|
tb.Fatalf("response size = %d, want approximately %d (variance: %d, allowed: ±%d)",
|
|
|
|
|
actualSize, targetSize, actualSize-targetSize, maxVariance)
|
|
|
|
|
}
|
|
|
|
|
return response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEDNSBufferSizeTruncation(t *testing.T) {
|
|
|
|
|
const domain = "edns-test.example.com."
|
|
|
|
|
const ednsBufferSize = 500 // Small EDNS buffer
|
|
|
|
|
const responseSize = 800 // Response exceeds EDNS but < maxResponseBytes
|
|
|
|
|
|
|
|
|
|
// Create a response that exceeds EDNS buffer size but doesn't have TC flag set
|
|
|
|
|
response := makeEDNSResponse(t, domain, responseSize)
|
|
|
|
|
|
|
|
|
|
// Create a request with EDNS buffer size
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypeTXT, ednsBufferSize)
|
|
|
|
|
verifyEDNSBufferSize(t, request, ednsBufferSize)
|
|
|
|
|
|
|
|
|
|
// Verify response doesn't have TC flag set initially
|
|
|
|
|
if truncatedFlagSet(response) {
|
|
|
|
|
t.Fatal("test response should not have TC flag set initially")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set up test DNS server
|
|
|
|
|
port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) {
|
|
|
|
|
verifyEDNSBufferSize(t, gotRequest, ednsBufferSize)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Disable TCP retries to ensure we test UDP path
|
|
|
|
|
resp := mustRunTestQuery(t, request, setupForwarderWithTCPRetriesDisabled(), port)
|
|
|
|
|
|
|
|
|
|
// Verify the response has TC flag set by forwarder
|
|
|
|
|
if !truncatedFlagSet(resp) {
|
|
|
|
|
t.Errorf("TC flag not set in response (response size=%d, EDNS=%d)", len(resp), ednsBufferSize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify response size is preserved (not truncated by buffer)
|
|
|
|
|
if len(resp) != len(response) {
|
|
|
|
|
t.Errorf("response size = %d, want %d (response should not be truncated by buffer)", len(resp), len(response))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify response size exceeds EDNS buffer
|
|
|
|
|
if len(resp) <= int(ednsBufferSize) {
|
|
|
|
|
t.Errorf("response size = %d, should exceed EDNS buffer size %d", len(resp), ednsBufferSize)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makeResponseOfSize creates a DNS response of approximately the specified size
|
|
|
|
|
// with TXT records. The response will NOT have the TC flag set initially.
|
|
|
|
|
// If includeOPT is true, an OPT record is added to the response.
|
|
|
|
|
func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT bool) []byte {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
name := dns.MustNewName(domain)
|
|
|
|
|
|
|
|
|
|
// Estimate how many TXT records we need
|
|
|
|
|
// Each TXT record with ~200 bytes of data adds roughly 220-230 bytes to the packet
|
|
|
|
|
// (including DNS headers, name compression, etc.)
|
|
|
|
|
bytesPerRecord := 220
|
|
|
|
|
baseSize := 50 // Approximate base packet size (header + question)
|
|
|
|
|
if includeOPT {
|
|
|
|
|
baseSize += 11 // OPT record adds ~11 bytes
|
|
|
|
|
}
|
|
|
|
|
estimatedRecords := (targetSize - baseSize) / bytesPerRecord
|
|
|
|
|
if estimatedRecords < 1 {
|
|
|
|
|
estimatedRecords = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start with estimated records and adjust
|
|
|
|
|
txtLen := 200
|
|
|
|
|
var response []byte
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
for attempt := 0; attempt < 10; attempt++ {
|
|
|
|
|
testBuilder := dns.NewBuilder(nil, dns.Header{
|
|
|
|
|
Response: true,
|
|
|
|
|
Authoritative: true,
|
|
|
|
|
RCode: dns.RCodeSuccess,
|
|
|
|
|
})
|
|
|
|
|
testBuilder.StartQuestions()
|
|
|
|
|
testBuilder.Question(dns.Question{
|
|
|
|
|
Name: name,
|
|
|
|
|
Type: dns.TypeTXT,
|
|
|
|
|
Class: dns.ClassINET,
|
|
|
|
|
})
|
|
|
|
|
testBuilder.StartAnswers()
|
|
|
|
|
|
|
|
|
|
for i := 0; i < estimatedRecords; i++ {
|
|
|
|
|
txtValue := strings.Repeat("x", txtLen)
|
|
|
|
|
testBuilder.TXTResource(dns.ResourceHeader{
|
|
|
|
|
Name: name,
|
|
|
|
|
Type: dns.TypeTXT,
|
|
|
|
|
Class: dns.ClassINET,
|
|
|
|
|
TTL: 300,
|
|
|
|
|
}, dns.TXTResource{
|
|
|
|
|
TXT: []string{txtValue},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optionally add OPT record
|
|
|
|
|
if includeOPT {
|
|
|
|
|
testBuilder.StartAdditionals()
|
|
|
|
|
testBuilder.OPTResource(dns.ResourceHeader{
|
|
|
|
|
Name: dns.MustNewName("."),
|
|
|
|
|
Type: dns.TypeOPT,
|
|
|
|
|
Class: dns.Class(4096),
|
|
|
|
|
}, dns.OPTResource{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response, err = testBuilder.Finish()
|
|
|
|
|
if err != nil {
|
|
|
|
|
tb.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actualSize := len(response)
|
|
|
|
|
// Stop if we've reached or slightly exceeded the target
|
|
|
|
|
// Allow up to 20% overshoot to avoid excessive iterations
|
|
|
|
|
if actualSize >= targetSize && actualSize <= targetSize*120/100 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
// If we've overshot significantly, we're done (better than undershooting)
|
|
|
|
|
if actualSize > targetSize*120/100 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjust for next attempt
|
|
|
|
|
needed := targetSize - actualSize
|
|
|
|
|
additionalRecords := (needed / bytesPerRecord) + 1
|
|
|
|
|
estimatedRecords += additionalRecords
|
|
|
|
|
if estimatedRecords > 200 {
|
|
|
|
|
// If we need too many records, increase TXT length instead
|
|
|
|
|
txtLen = 255 // Max single TXT string length
|
|
|
|
|
bytesPerRecord = 280 // Adjusted estimate
|
|
|
|
|
estimatedRecords = (targetSize - baseSize) / bytesPerRecord
|
|
|
|
|
if estimatedRecords < 1 {
|
|
|
|
|
estimatedRecords = 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure TC flag is NOT set initially
|
|
|
|
|
clearTCFlagInPacket(response)
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCheckResponseSizeAndSetTC(t *testing.T) {
|
|
|
|
|
const domain = "test.example.com."
|
|
|
|
|
logf := func(format string, args ...any) {
|
|
|
|
|
// Silent logger for tests
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
responseSize int
|
|
|
|
|
requestHasEDNS bool
|
|
|
|
|
ednsSize uint16
|
|
|
|
|
family string
|
|
|
|
|
responseTCSet bool // Whether response has TC flag set initially
|
|
|
|
|
wantTCSet bool // Whether TC flag should be set after function call
|
|
|
|
|
skipIfNotExact bool // Skip test if we can't hit exact size (for edge cases)
|
|
|
|
|
}{
|
|
|
|
|
// Default UDP size (512 bytes) without EDNS
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_noEDNS_small_should_not_set_TC",
|
|
|
|
|
responseSize: 400,
|
|
|
|
|
requestHasEDNS: false,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_noEDNS_512bytes_should_not_set_TC",
|
|
|
|
|
responseSize: 512,
|
|
|
|
|
requestHasEDNS: false,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
skipIfNotExact: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_noEDNS_513bytes_should_set_TC",
|
|
|
|
|
responseSize: 513,
|
|
|
|
|
requestHasEDNS: false,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: true,
|
|
|
|
|
skipIfNotExact: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_noEDNS_large_should_set_TC",
|
|
|
|
|
responseSize: 600,
|
|
|
|
|
requestHasEDNS: false,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: true,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// EDNS edge cases
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_EDNS_small_under_limit_should_not_set_TC",
|
|
|
|
|
responseSize: 450,
|
|
|
|
|
requestHasEDNS: true,
|
|
|
|
|
ednsSize: 500,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_EDNS_at_limit_should_not_set_TC",
|
|
|
|
|
responseSize: 500,
|
|
|
|
|
requestHasEDNS: true,
|
|
|
|
|
ednsSize: 500,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_EDNS_over_limit_should_set_TC",
|
|
|
|
|
responseSize: 550,
|
|
|
|
|
requestHasEDNS: true,
|
|
|
|
|
ednsSize: 500,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_EDNS_large_over_limit_should_set_TC",
|
|
|
|
|
responseSize: 1500,
|
|
|
|
|
requestHasEDNS: true,
|
|
|
|
|
ednsSize: 1200,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: true,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Early return paths
|
|
|
|
|
{
|
|
|
|
|
name: "TCP_query_should_skip",
|
|
|
|
|
responseSize: 1000,
|
|
|
|
|
family: "tcp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "response_too_small_should_skip",
|
|
|
|
|
responseSize: headerBytes - 1,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "response_exactly_headerBytes_should_not_set_TC",
|
|
|
|
|
responseSize: headerBytes,
|
|
|
|
|
family: "udp",
|
|
|
|
|
wantTCSet: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "response_TC_already_set_should_skip",
|
|
|
|
|
responseSize: 600,
|
|
|
|
|
family: "udp",
|
|
|
|
|
responseTCSet: true,
|
|
|
|
|
wantTCSet: true, // Should remain set
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "UDP_noEDNS_large_TC_already_set_should_skip",
|
|
|
|
|
responseSize: 600,
|
|
|
|
|
requestHasEDNS: false,
|
|
|
|
|
family: "udp",
|
|
|
|
|
responseTCSet: true,
|
|
|
|
|
wantTCSet: true, // Should remain set
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
var response []byte
|
|
|
|
|
|
|
|
|
|
// Create response of specified size
|
|
|
|
|
if tt.responseSize < headerBytes {
|
|
|
|
|
// For too-small test, create minimal invalid packet
|
|
|
|
|
response = make([]byte, tt.responseSize)
|
|
|
|
|
// Don't set any flags, just make it too small
|
|
|
|
|
} else {
|
|
|
|
|
response = makeResponseOfSize(t, domain, tt.responseSize, false)
|
|
|
|
|
actualSize := len(response)
|
|
|
|
|
|
|
|
|
|
// Only adjust expectations for UDP queries that go through size checking
|
|
|
|
|
// TCP queries and other early-return cases should keep their original expectations
|
|
|
|
|
if tt.family == "udp" && !tt.responseTCSet && actualSize >= headerBytes {
|
|
|
|
|
// Determine the maximum allowed size based on request
|
|
|
|
|
var maxSize int
|
|
|
|
|
if tt.requestHasEDNS {
|
|
|
|
|
maxSize = int(tt.ednsSize)
|
|
|
|
|
} else {
|
|
|
|
|
maxSize = 512 // default UDP size per RFC 1035
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For edge cases where exact size matters, verify we're close enough
|
|
|
|
|
if tt.skipIfNotExact {
|
|
|
|
|
// For 512/513 byte tests, we need to be very close
|
|
|
|
|
if actualSize < tt.responseSize-10 || actualSize > tt.responseSize+10 {
|
|
|
|
|
t.Skipf("skipping: could not create response close to target size %d (got %d)", tt.responseSize, actualSize)
|
|
|
|
|
}
|
|
|
|
|
// Function sets TC if response > maxSize, so adjust expectation based on actual size
|
|
|
|
|
tt.wantTCSet = actualSize > maxSize
|
|
|
|
|
} else {
|
|
|
|
|
// For non-exact tests, adjust expectation based on actual response size
|
|
|
|
|
// The function sets TC if actualSize > maxSize
|
|
|
|
|
tt.wantTCSet = actualSize > maxSize
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set TC flag initially if requested
|
|
|
|
|
if tt.responseTCSet {
|
|
|
|
|
setTCFlagInPacket(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create request with or without EDNS
|
|
|
|
|
var ednsSize uint16
|
|
|
|
|
if tt.requestHasEDNS {
|
|
|
|
|
ednsSize = tt.ednsSize
|
|
|
|
|
}
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypeTXT, ednsSize)
|
|
|
|
|
|
|
|
|
|
// Call the function
|
|
|
|
|
result := checkResponseSizeAndSetTC(response, request, tt.family, logf)
|
|
|
|
|
|
|
|
|
|
// Verify response size is preserved (function should not truncate, only set flag)
|
|
|
|
|
if len(result) != len(response) {
|
|
|
|
|
t.Errorf("response size changed: got %d, want %d", len(result), len(response))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify TC flag state
|
|
|
|
|
if len(result) >= headerBytes {
|
|
|
|
|
hasTC := truncatedFlagSet(result)
|
|
|
|
|
if hasTC != tt.wantTCSet {
|
|
|
|
|
t.Errorf("TC flag: got %v, want %v (response size=%d)", hasTC, tt.wantTCSet, len(result))
|
|
|
|
|
}
|
|
|
|
|
} else if tt.responseSize >= headerBytes {
|
|
|
|
|
// If we expected a valid response but got too small, that's unexpected
|
|
|
|
|
t.Errorf("response too small (%d bytes) but expected valid response", len(result))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify response pointer is same (should be in-place modification)
|
|
|
|
|
if &result[0] != &response[0] {
|
|
|
|
|
t.Errorf("function should modify response in place, but got new slice")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestForwarderTCPFallback(t *testing.T) {
|
|
|
|
|
const domain = "large-dns-response.tailscale.com."
|
|
|
|
|
|
|
|
|
|
@ -569,7 +980,10 @@ func TestForwarderTCPFallback(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
resp := mustRunTestQuery(t, request, beVerbose, port)
|
|
|
|
|
resp, err := runTestQueryWithFamily(t, request, "tcp", beVerbose, port)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("error making request: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(resp, largeResponse) {
|
|
|
|
|
t.Errorf("invalid response\ngot: %+v\nwant: %+v", resp, largeResponse)
|
|
|
|
|
}
|
|
|
|
|
@ -636,17 +1050,13 @@ func TestForwarderTCPFallbackDisabled(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
resp := mustRunTestQuery(t, request, func(fwd *forwarder) {
|
|
|
|
|
fwd.verboseFwd = true
|
|
|
|
|
// Disable retries for this test.
|
|
|
|
|
fwd.controlKnobs = &controlknobs.Knobs{}
|
|
|
|
|
fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true)
|
|
|
|
|
setupForwarderWithTCPRetriesDisabled()(fwd)
|
|
|
|
|
}, port)
|
|
|
|
|
|
|
|
|
|
wantResp := append([]byte(nil), largeResponse[:maxResponseBytes]...)
|
|
|
|
|
|
|
|
|
|
// Set the truncated flag on the expected response, since that's what we expect.
|
|
|
|
|
flags := binary.BigEndian.Uint16(wantResp[2:4])
|
|
|
|
|
flags |= dnsFlagTruncated
|
|
|
|
|
binary.BigEndian.PutUint16(wantResp[2:4], flags)
|
|
|
|
|
setTCFlagInPacket(wantResp)
|
|
|
|
|
|
|
|
|
|
if !bytes.Equal(resp, wantResp) {
|
|
|
|
|
t.Errorf("invalid response\ngot (%d): %+v\nwant (%d): %+v", len(resp), resp, len(wantResp), wantResp)
|
|
|
|
|
@ -664,7 +1074,7 @@ func TestForwarderTCPFallbackError(t *testing.T) {
|
|
|
|
|
response := makeTestResponse(t, domain, dns.RCodeServerFailure)
|
|
|
|
|
|
|
|
|
|
// Our request is a single A query for the domain in the answer, above.
|
|
|
|
|
request := makeTestRequest(t, domain)
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypeA, 0)
|
|
|
|
|
|
|
|
|
|
var sawRequest atomic.Bool
|
|
|
|
|
port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) {
|
|
|
|
|
@ -695,7 +1105,7 @@ func TestForwarderTCPFallbackError(t *testing.T) {
|
|
|
|
|
// returns a successful response, we propagate it.
|
|
|
|
|
func TestForwarderWithManyResolvers(t *testing.T) {
|
|
|
|
|
const domain = "example.com."
|
|
|
|
|
request := makeTestRequest(t, domain)
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypeA, 0)
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
@ -837,20 +1247,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) {
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// Our request is a single PTR query for the domain in the answer, above.
|
|
|
|
|
request := func() []byte {
|
|
|
|
|
builder := dns.NewBuilder(nil, dns.Header{})
|
|
|
|
|
builder.StartQuestions()
|
|
|
|
|
builder.Question(dns.Question{
|
|
|
|
|
Name: dns.MustNewName(domain),
|
|
|
|
|
Type: dns.TypePTR,
|
|
|
|
|
Class: dns.ClassINET,
|
|
|
|
|
})
|
|
|
|
|
request, err := builder.Finish()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return request
|
|
|
|
|
}()
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypePTR, 0)
|
|
|
|
|
|
|
|
|
|
port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) {
|
|
|
|
|
})
|
|
|
|
|
@ -868,7 +1265,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) {
|
|
|
|
|
func TestForwarderVerboseLogs(t *testing.T) {
|
|
|
|
|
const domain = "test.tailscale.com."
|
|
|
|
|
response := makeTestResponse(t, domain, dns.RCodeServerFailure)
|
|
|
|
|
request := makeTestRequest(t, domain)
|
|
|
|
|
request := makeTestRequest(t, domain, dns.TypeA, 0)
|
|
|
|
|
|
|
|
|
|
port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) {
|
|
|
|
|
if !bytes.Equal(request, gotRequest) {
|
|
|
|
|
|