package dnsmsg_test import ( "net/url" "strings" "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) // newTXTExtra is a helper constructor of the expected extra data. func newTXTExtra(ttl uint32, strs ...string) (extra []dns.RR) { return []dns.RR{&dns.TXT{ Hdr: dns.RR_Header{ Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: ttl, }, Txt: strs, }} } func TestNewConstructor(t *testing.T) { t.Parallel() cloner := agdtest.NewCloner() badContactURL := errors.Must(url.Parse("invalid-scheme://devteam@adguard.com")) testCases := []struct { name string conf *dnsmsg.ConstructorConfig wantErrMsg string }{{ name: "good", conf: &dnsmsg.ConstructorConfig{ Cloner: cloner, StructuredErrors: agdtest.NewSDEConfig(true), BlockingMode: &dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL: agdtest.FilteredResponseTTL, EDEEnabled: true, }, wantErrMsg: "", }, { name: "all_bad", conf: &dnsmsg.ConstructorConfig{ FilteredResponseTTL: -1, }, wantErrMsg: "configuration: " + "cloner: no value\n" + "structured errors: no value\n" + "blocking mode: no value\n" + "filtered response ttl: negative value", }, { name: "sde_enabled", conf: &dnsmsg.ConstructorConfig{ Cloner: cloner, StructuredErrors: agdtest.NewSDEConfig(true), BlockingMode: &dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL: agdtest.FilteredResponseTTL, EDEEnabled: false, }, wantErrMsg: "configuration: structured errors: " + "ede must be enabled to enable sde", }, { name: "sde_empty", conf: &dnsmsg.ConstructorConfig{ Cloner: cloner, StructuredErrors: &dnsmsg.StructuredDNSErrorsConfig{ Enabled: true, }, BlockingMode: &dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL: agdtest.FilteredResponseTTL, EDEEnabled: true, }, wantErrMsg: "configuration: structured errors: " + "contact data: empty value\n" + "justification: empty value", }, { name: "sde_bad", conf: &dnsmsg.ConstructorConfig{ Cloner: cloner, StructuredErrors: &dnsmsg.StructuredDNSErrorsConfig{ Enabled: true, Contact: []*url.URL{badContactURL, nil}, Justification: "\uFFFE", Organization: "\uFFFE", }, BlockingMode: &dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL: agdtest.FilteredResponseTTL, EDEEnabled: true, }, wantErrMsg: "configuration: structured errors: " + `contact data: at index 0: scheme: bad enum value: "invalid-scheme"` + "\n" + "contact data: at index 1: no value\n" + "justification: bad code point at index 0\n" + "organization: bad code point at index 0", }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() _, err := dnsmsg.NewConstructor(tc.conf) testutil.AssertErrorMsg(t, tc.wantErrMsg, err) }) } } func TestConstructor_AppendDebugExtra(t *testing.T) { t.Parallel() msgs := agdtest.NewConstructor(t) shortText := "This is a short test text" longText := strings.Repeat("a", 2*dnsmsg.MaxTXTStringLen) testCases := []struct { name string text string wantErrMsg string wantExtra []dns.RR qt uint16 }{{ name: "short_text", text: shortText, qt: dns.TypeTXT, wantExtra: newTXTExtra(agdtest.FilteredResponseTTLSec, shortText), wantErrMsg: "", }, { name: "long_text", text: longText, qt: dns.TypeTXT, wantExtra: newTXTExtra( agdtest.FilteredResponseTTLSec, longText[:dnsmsg.MaxTXTStringLen], longText[dnsmsg.MaxTXTStringLen:], ), wantErrMsg: "", }, { name: "error_type", text: "Type A", qt: dns.TypeA, wantExtra: nil, wantErrMsg: "bad qtype for txt resp: A", }, { name: "empty_text", text: "", qt: dns.TypeTXT, wantExtra: newTXTExtra(agdtest.FilteredResponseTTLSec, ""), wantErrMsg: "", }} const fqdn = testFQDN for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() req := &dns.Msg{ MsgHdr: dns.MsgHdr{ Id: dns.Id(), }, Question: []dns.Question{{ Name: fqdn, Qtype: tc.qt, Qclass: dns.ClassCHAOS, }}, } resp := &dns.Msg{} resp = resp.SetReply(req) appendErr := msgs.AppendDebugExtra(req, resp, tc.text) testutil.AssertErrorMsg(t, tc.wantErrMsg, appendErr) wantExtra := tc.wantExtra if len(wantExtra) > 0 { wantExtra[0].Header().Name = fqdn } assert.Equal(t, tc.wantExtra, resp.Extra) }) } } func BenchmarkConstructor_AppendDebugExtra(b *testing.B) { msgs := agdtest.NewConstructor(b) longText := strings.Repeat("abc", 2*dnsmsg.MaxTXTStringLen) req := &dns.Msg{ MsgHdr: dns.MsgHdr{ Id: dns.Id(), }, Question: []dns.Question{{ Name: testFQDN, Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS, }}, } resp := (&dns.Msg{}).SetReply(req) var err error b.ReportAllocs() for b.Loop() { err = msgs.AppendDebugExtra(req, resp, longText) } assert.NoError(b, err) // Most recent results: // // goos: darwin // goarch: arm64 // pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg // cpu: Apple M1 Pro // BenchmarkConstructor_AppendDebugExtra-8 8809124 137.3 ns/op 253 B/op 2 allocs/op }