diff --git a/cmd/alookup.go b/cmd/alookup.go index f3db9aa8..a95fa703 100644 --- a/cmd/alookup.go +++ b/cmd/alookup.go @@ -36,7 +36,7 @@ Specifically, alookup acts similar to nslookup and will follow CNAME records.`, &Timeout, &IterationTimeout, &Class_string, &Servers_string, &Config_file, &Localaddr_string, - &Localif_string, &NanoSeconds, &ClientSubnet_string) + &Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID) }, } diff --git a/cmd/mxlookup.go b/cmd/mxlookup.go index 6d28f8ec..1935116d 100644 --- a/cmd/mxlookup.go +++ b/cmd/mxlookup.go @@ -34,7 +34,7 @@ correspond with an exchange record.`, &Timeout, &IterationTimeout, &Class_string, &Servers_string, &Config_file, &Localaddr_string, - &Localif_string, &NanoSeconds, &ClientSubnet_string) + &Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID) }, } diff --git a/cmd/root.go b/cmd/root.go index 5d2700a6..3be3a670 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,6 +38,7 @@ var ( Class_string string NanoSeconds bool ClientSubnet_string string + NSID bool ) // rootCmd represents the base command when called without any subcommands @@ -57,7 +58,7 @@ ZDNS also includes its own recursive resolution and a cache to further optimize &Timeout, &IterationTimeout, &Class_string, &Servers_string, &Config_file, &Localaddr_string, - &Localif_string, &NanoSeconds, &ClientSubnet_string) + &Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID) }, } @@ -117,6 +118,7 @@ func init() { rootCmd.PersistentFlags().BoolVar(&NanoSeconds, "nanoseconds", false, "Use nanosecond resolution timestamps") rootCmd.PersistentFlags().StringVar(&ClientSubnet_string, "client-subnet", "", "Client subnet in CIDR format for EDNS0.") rootCmd.PersistentFlags().BoolVar(&GC.Dnssec, "dnssec", false, "Requests DNSSEC records by setting the DNSSEC OK (DO) bit") + rootCmd.PersistentFlags().BoolVar(&NSID, "nsid", false, "Request NSID.") rootCmd.PersistentFlags().Bool("ipv4-lookup", false, "Perform an IPv4 Lookup in modules") rootCmd.PersistentFlags().Bool("ipv6-lookup", false, "Perform an IPv6 Lookup in modules") diff --git a/integration_tests.py b/integration_tests.py index fa8d8398..61bed8ae 100755 --- a/integration_tests.py +++ b/integration_tests.py @@ -5,6 +5,7 @@ import json import unittest import tempfile +from ipaddress import ip_address def recursiveSort(obj): @@ -282,7 +283,7 @@ def run_zdns_multiline(self, flags, names, executable=ZDNS_EXECUTABLE): } } } - + TCP_LARGE_TXT_ANSWERS = [ { "type": "TXT", @@ -890,15 +891,55 @@ def test_edns0_client_subnet(self): c = f"A --client-subnet {subnet} --name-servers=8.8.8.8:53" cmd, res = self.run_zdns(c, name) self.assertSuccess(res, cmd) + address, netmask = tuple(subnet.split("/")) + family = 1 if ip_address(address).version == 4 else 2 + self.assertEqual(address, res["data"]['additionals'][0]['csubnet']['address']) + self.assertEqual(int(netmask), res["data"]['additionals'][0]['csubnet']["source_netmask"]) + self.assertEqual(family, res["data"]['additionals'][0]['csubnet']['family']) + self.assertTrue("source_scope" in res["data"]['additionals'][0]['csubnet']) correct = [{"type":"A", "class":"IN", "answer": ip_addr, "name":"ecs-geo.zdns-testing.com"}] self.assertEqualAnswers(res, correct, cmd) + def test_edns0_nsid(self): + name = "google.com" + # using Google Public DNS for testing as its NSID always follows format 'gpdns-' + c = f"A --nsid --name-servers=8.8.8.8:53" + cmd, res = self.run_zdns(c, name) + self.assertSuccess(res, cmd) + self.assertTrue("nsid" in res["data"]['additionals'][0]) + self.assertTrue(res["data"]['additionals'][0]['nsid']['nsid'].startswith("gpdns-")) + + def test_edns0_ede_1(self): + name = "dnssec.fail" + # using Cloudflare Public DNS (1.1.1.1) that implements EDE + c = f"A --name-servers=1.1.1.1:53" + cmd, res = self.run_zdns(c, name) + self.assertServFail(res, cmd) + self.assertTrue("ede" in res["data"]['additionals'][0]) + ede_obj = res["data"]['additionals'][0]["ede"][0] + self.assertEqual("DNSKEY Missing", ede_obj["error_text"]) + self.assertEqual("no SEP matching the DS found for dnssec.fail.", ede_obj["extra_text"]) + self.assertEqual(9, ede_obj["info_code"]) + + def test_edns0_ede_2_cd(self): + name = "dnssec.fail" + # using Cloudflare Public DNS (1.1.1.1) that implements EDE, checking disabled resulting in NOERROR + c = f"A --name-servers=1.1.1.1:53 --checking-disabled" + cmd, res = self.run_zdns(c, name) + self.assertSuccess(res, cmd) + self.assertTrue("ede" in res["data"]['additionals'][0]) + ede_obj = res["data"]['additionals'][0]["ede"][0] + self.assertEqual("DNSKEY Missing", ede_obj["error_text"]) + self.assertEqual("no SEP matching the DS found for dnssec.fail.", ede_obj["extra_text"]) + self.assertEqual(9, ede_obj["info_code"]) + def test_dnssec_response(self): # checks if dnssec records are returned c = f"SOA --dnssec --name-servers=8.8.8.8:53" name = "." cmd, res = self.run_zdns(c, name) self.assertSuccess(res, cmd) + self.assertEqual('do', res["data"]['additionals'][0]['flags']) self.assertEqualTypes(res, ["SOA", "RRSIG"]) def test_cd_bit_not_set(self): diff --git a/pkg/miekg/answers.go b/pkg/miekg/answers.go index 5ef1c0b5..091334b8 100644 --- a/pkg/miekg/answers.go +++ b/pkg/miekg/answers.go @@ -15,6 +15,7 @@ package miekg import ( + "encoding/hex" "fmt" "net" "strconv" @@ -72,13 +73,6 @@ type DSAnswer struct { Digest string `json:"digest" groups:"short,normal,long,trace"` } -type EDNSAnswer struct { - Type string `json:"type" groups:"short,normal,long,trace"` - Version uint8 `json:"version" groups:"short,normal,long,trace"` - Flags string `json:"flags" groups:"short,normal,long,trace"` - UDPSize uint16 `json:"udpsize" groups:"short,normal,long,trace"` -} - type GPOSAnswer struct { Answer Longitude string `json:"preference" groups:"short,normal,long,trace"` @@ -466,15 +460,85 @@ func makeEDNSAnswer(cAns *dns.OPT) EDNSAnswer { if cAns.Do() { flags = "do" } - return EDNSAnswer{ - Type: opttype + strconv.Itoa(int(cAns.Version())), - Version: cAns.Version(), + optRes := EDNSAnswer{ + Type: opttype + strconv.Itoa(int(cAns.Version())), + Version: cAns.Version(), // RCODE omitted for now as no EDNS0 extension is supported in // lookups for which an RCODE is defined. //Rcode: dns.RcodeToString[cAns.ExtendedRcode()], - Flags: flags, - UDPSize: cAns.UDPSize(), + Flags: flags, + UDPSize: cAns.UDPSize(), + } + + for _, o := range cAns.Option { + switch o.(type) { + case *dns.EDNS0_LLQ: //OPT 1 + optRes.LLQ = &Edns0LLQ{ + Code: o.(*dns.EDNS0_LLQ).Code, + Version: o.(*dns.EDNS0_LLQ).Version, + Opcode: o.(*dns.EDNS0_LLQ).Opcode, + Error: o.(*dns.EDNS0_LLQ).Error, + Id: o.(*dns.EDNS0_LLQ).Id, + LeaseLife: o.(*dns.EDNS0_LLQ).LeaseLife, + } + case *dns.EDNS0_UL: // OPT 2 + optRes.UL = &Edns0UL{ + Code: o.(*dns.EDNS0_UL).Code, + Lease: o.(*dns.EDNS0_UL).Lease, + KeyLease: o.(*dns.EDNS0_UL).KeyLease, + } + case *dns.EDNS0_NSID: //OPT 3 + hexDecoded, err := hex.DecodeString(o.(*dns.EDNS0_NSID).Nsid) + if err != nil { + continue + } + optRes.NSID = &Edns0NSID{Nsid: string(hexDecoded)} + case *dns.EDNS0_DAU: //OPT 5 + optRes.DAU = &Edns0DAU{ + Code: o.(*dns.EDNS0_DAU).Code, + AlgCode: o.(*dns.EDNS0_DAU).String(), + } + case *dns.EDNS0_DHU: //OPT 6 + optRes.DHU = &Edns0DHU{ + Code: o.(*dns.EDNS0_DHU).Code, + AlgCode: o.(*dns.EDNS0_DHU).String(), + } + case *dns.EDNS0_N3U: //OPT 7 + optRes.N3U = &Edns0N3U{ + Code: o.(*dns.EDNS0_N3U).Code, + AlgCode: o.(*dns.EDNS0_N3U).String(), + } + case *dns.EDNS0_SUBNET: //OPT 8 + optRes.ClientSubnet = &Edns0ClientSubnet{ + SourceScope: o.(*dns.EDNS0_SUBNET).SourceScope, + Family: o.(*dns.EDNS0_SUBNET).Family, + Address: o.(*dns.EDNS0_SUBNET).Address.String(), + SourceNetmask: o.(*dns.EDNS0_SUBNET).SourceNetmask, + } + case *dns.EDNS0_EXPIRE: //OPT 9 + optRes.Expire = &Edns0Expire{ + Code: o.(*dns.EDNS0_EXPIRE).Code, + Expire: o.(*dns.EDNS0_EXPIRE).Expire, + } + case *dns.EDNS0_COOKIE: //OPT 11 + optRes.Cookie = &Edns0Cookie{Cookie: o.(*dns.EDNS0_COOKIE).Cookie} + case *dns.EDNS0_TCP_KEEPALIVE: //OPT 11 + optRes.TcpKeepalive = &Edns0TCPKeepalive{ + Code: o.(*dns.EDNS0_TCP_KEEPALIVE).Code, + Timeout: o.(*dns.EDNS0_TCP_KEEPALIVE).Timeout, + Length: o.(*dns.EDNS0_TCP_KEEPALIVE).Length, // deprecated, always equal to 0, keeping it here for a better readability + } + case *dns.EDNS0_PADDING: //OPT 12 + optRes.Padding = &Edns0Padding{Padding: o.(*dns.EDNS0_PADDING).String()} + case *dns.EDNS0_EDE: //OPT 15 + optRes.EDE = append(optRes.EDE, &Edns0Ede{ + InfoCode: o.(*dns.EDNS0_EDE).InfoCode, + ErrorCodeText: dns.ExtendedErrorCodeToString[o.(*dns.EDNS0_EDE).InfoCode], + ExtraText: o.(*dns.EDNS0_EDE).ExtraText, + }) + } } + return optRes } func ParseAnswer(ans dns.RR) interface{} { diff --git a/pkg/miekg/edns.go b/pkg/miekg/edns.go new file mode 100644 index 00000000..1cfd209d --- /dev/null +++ b/pkg/miekg/edns.go @@ -0,0 +1,114 @@ +/* + * ZDNS Copyright 2023 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package miekg + +// Structures covering DNS EDNS0 Option Codes (https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11) + +// Edns0LLQ OPT 1 +type Edns0LLQ struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + Version uint16 `json:"version" groups:"short,normal,long,trace"` + Opcode uint16 `json:"opcode" groups:"short,normal,long,trace"` + Error uint16 `json:"error" groups:"short,normal,long,trace"` + Id uint64 `json:"id" groups:"short,normal,long,trace"` + LeaseLife uint32 `json:"lease_life" groups:"short,normal,long,trace"` +} + +// Edns0UL OPT 2 +type Edns0UL struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + Lease uint32 `json:"lease" groups:"short,normal,long,trace"` + KeyLease uint32 `json:"key_lease" groups:"short,normal,long,trace"` +} + +// Edns0NSID OPT 3 +type Edns0NSID struct { + Nsid string `json:"nsid" groups:"short,normal,long,trace"` +} + +// Edns0DAU OPT 5 +type Edns0DAU struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + AlgCode string `json:"alg_code" groups:"short,normal,long,trace"` +} + +// Edns0DHU OPT 6 +type Edns0DHU struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + AlgCode string `json:"alg_code" groups:"short,normal,long,trace"` +} + +// Edns0N3U OPT 7 +type Edns0N3U struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + AlgCode string `json:"alg_code" groups:"short,normal,long,trace"` +} + +// Edns0ClientSubnet OPT 8 +type Edns0ClientSubnet struct { + Family uint16 `json:"family" groups:"short,normal,long,trace"` + SourceNetmask uint8 `json:"source_netmask" groups:"short,normal,long,trace"` + SourceScope uint8 `json:"source_scope" groups:"short,normal,long,trace"` + Address string `json:"address" groups:"short,normal,long,trace"` +} + +// Edns0Expire OPT 9 +type Edns0Expire struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + Expire uint32 `json:"expire" groups:"short,normal,long,trace"` +} + +// Edns0Cookie OPT 10 +type Edns0Cookie struct { + Cookie string `json:"cookie" groups:"short,normal,long,trace"` +} + +// Edns0TCPKeepalive OPT 11 +type Edns0TCPKeepalive struct { + Code uint16 `json:"code" groups:"short,normal,long,trace"` + Timeout uint16 `json:"timeout" groups:"short,normal,long,trace"` + Length uint16 `json:"length" groups:"short,normal,long,trace"` +} + +// Edns0Padding OPT 12 +type Edns0Padding struct { + Padding string `json:"padding" groups:"short,normal,long,trace"` +} + +// Edns0Ede OPT15 +type Edns0Ede struct { + InfoCode uint16 `json:"info_code" groups:"short,normal,long,trace"` + ErrorCodeText string `json:"error_text" groups:"short,normal,long,trace"` + ExtraText string `json:"extra_text" groups:"short,normal,long,trace"` +} + +type EDNSAnswer struct { + Type string `json:"type" groups:"short,normal,long,trace"` + Version uint8 `json:"version" groups:"short,normal,long,trace"` + Flags string `json:"flags" groups:"short,normal,long,trace"` + UDPSize uint16 `json:"udpsize" groups:"short,normal,long,trace"` + LLQ *Edns0LLQ `json:"llq,omitempty" groups:"short,normal,long,trace"` //not implemented + UL *Edns0UL `json:"ul,omitempty" groups:"short,normal,long,trace"` //not implemented + NSID *Edns0NSID `json:"nsid,omitempty" groups:"short,normal,long,trace"` + DAU *Edns0DAU `json:"dau,omitempty" groups:"short,normal,long,trace"` //not implemented + DHU *Edns0DHU `json:"dhu,omitempty" groups:"short,normal,long,trace"` //not implemented + N3U *Edns0N3U `json:"n3u,omitempty" groups:"short,normal,long,trace"` //not implemented + ClientSubnet *Edns0ClientSubnet `json:"csubnet,omitempty" groups:"short,normal,long,trace"` + Expire *Edns0Expire `json:"expire,omitempty" groups:"short,normal,long,trace"` //not implemented + Cookie *Edns0Cookie `json:"cookie,omitempty" groups:"short,normal,long,trace"` //not implemented + TcpKeepalive *Edns0TCPKeepalive `json:"tcp_keepalive,omitempty" groups:"short,normal,long,trace"` //not implemented + Padding *Edns0Padding `json:"padding,omitempty" groups:"short,normal,long,trace"` //not implemented + EDE []*Edns0Ede `json:"ede,omitempty" groups:"short,normal,long,trace"` +} diff --git a/pkg/miekg/miekg.go b/pkg/miekg/miekg.go index 9e909213..8a6c7372 100644 --- a/pkg/miekg/miekg.go +++ b/pkg/miekg/miekg.go @@ -193,6 +193,7 @@ type RoutineLookupFactory struct { ThreadID int PrefixRegexp *regexp.Regexp Dnssec bool + EdnsOptions []dns.EDNS0 } func (s *RoutineLookupFactory) Initialize(c *zdns.GlobalConf) { @@ -246,6 +247,13 @@ func (s *RoutineLookupFactory) Initialize(c *zdns.GlobalConf) { s.DNSClass = c.Class s.Dnssec = c.Dnssec + + if c.ClientSubnet != nil { + s.EdnsOptions = append(s.EdnsOptions, c.ClientSubnet) + } + if c.NSID != nil { + s.EdnsOptions = append(s.EdnsOptions, c.NSID) + } } func (s *RoutineLookupFactory) MakeLookup() (zdns.Lookup, error) { @@ -263,6 +271,7 @@ type Lookup struct { DNSClass uint16 NameServer string IterativeStop time.Time + EdnsOptions []dns.EDNS0 Conn *dns.Conn } @@ -278,7 +287,7 @@ func (s *Lookup) Initialize(nameServer string, dnsType uint16, dnsClass uint16, } func (s *Lookup) doLookup(q Question, nameServer string, recursive bool) (Result, zdns.Status, error) { - return DoLookupWorker(s.Factory.Client, s.Factory.TCPClient, s.Conn, q, nameServer, recursive, s.Factory.Factory.GlobalConf.ClientSubnet, s.Factory.Dnssec, s.Factory.Factory.GlobalConf.CheckingDisabled) + return DoLookupWorker(s.Factory.Client, s.Factory.TCPClient, s.Conn, q, nameServer, recursive, s.Factory.EdnsOptions, s.Factory.Dnssec, s.Factory.Factory.GlobalConf.CheckingDisabled) } // CheckTxtRecords common function for all modules based on search in TXT record @@ -308,7 +317,7 @@ func (s *Lookup) FindTxtRecord(res Result) (string, error) { } // Expose the inner logic so other tools can use it -func DoLookupWorker(udp *dns.Client, tcp *dns.Client, conn *dns.Conn, q Question, nameServer string, recursive bool, edns0subnet *dns.EDNS0_SUBNET, dnssec bool, checkingDisabled bool) (Result, zdns.Status, error) { +func DoLookupWorker(udp *dns.Client, tcp *dns.Client, conn *dns.Conn, q Question, nameServer string, recursive bool, ednsOptions []dns.EDNS0, dnssec bool, checkingDisabled bool) (Result, zdns.Status, error) { res := Result{Answers: []interface{}{}, Authorities: []interface{}{}, Additional: []interface{}{}} res.Resolver = nameServer @@ -319,9 +328,9 @@ func DoLookupWorker(udp *dns.Client, tcp *dns.Client, conn *dns.Conn, q Question m.CheckingDisabled = checkingDisabled m.SetEdns0(1232, dnssec) - if edns0subnet != nil { - opt := m.Extra[0].(*dns.OPT) - opt.Option = append(opt.Option, edns0subnet) + ednsOpt := m.IsEdns0() + if ednsOpt != nil { + ednsOpt.Option = append(ednsOpt.Option, ednsOptions...) } var r *dns.Msg @@ -337,7 +346,7 @@ func DoLookupWorker(udp *dns.Client, tcp *dns.Client, conn *dns.Conn, q Question // if record comes back truncated, but we have a TCP connection, try again with that if r != nil && (r.Truncated || r.Rcode == dns.RcodeBadTrunc) { if tcp != nil { - return DoLookupWorker(nil, tcp, conn, q, nameServer, recursive, edns0subnet, dnssec, checkingDisabled) + return DoLookupWorker(nil, tcp, conn, q, nameServer, recursive, ednsOptions, dnssec, checkingDisabled) } else { return res, zdns.STATUS_TRUNCATED, err } @@ -358,6 +367,12 @@ func DoLookupWorker(udp *dns.Client, tcp *dns.Client, conn *dns.Conn, q Question } if r.Rcode != dns.RcodeSuccess { + for _, ans := range r.Extra { + inner := ParseAnswer(ans) + if inner != nil { + res.Additional = append(res.Additional, inner) + } + } return res, TranslateMiekgErrorCode(r.Rcode), nil } diff --git a/pkg/miekg/miekg_test.go b/pkg/miekg/miekg_test.go index 12dedcf5..901142cd 100644 --- a/pkg/miekg/miekg_test.go +++ b/pkg/miekg/miekg_test.go @@ -14,6 +14,7 @@ package miekg import ( + "encoding/hex" "net" "reflect" "regexp" @@ -1725,10 +1726,10 @@ func TestParseAnswer(t *testing.T) { Ttl: 3600, Rdlength: 0, }, - Hash: 1, - Flags: 0, + Hash: 1, + Flags: 0, Iterations: 0, - Salt: "", + Salt: "", NextDomain: "MIFDNDT3NFF3OD53O7TLA1HRFF95JKUK", // www.example.com TypeBitMap: []uint16{dns.TypeA, dns.TypeRRSIG}, } @@ -1760,9 +1761,9 @@ func TestParseAnswer(t *testing.T) { // OPT record rr = &dns.OPT{ Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeOPT, - Class: 1232, + Name: ".", + Rrtype: dns.TypeOPT, + Class: 1232, }, } res = ParseAnswer(rr) @@ -1782,6 +1783,110 @@ func TestParseAnswer(t *testing.T) { } } +func TestParseEdnsAnswerNsid1(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{&dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: hex.EncodeToString([]byte("test_nsid"))}}, + } + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, ednsAnswer.NSID.Nsid, "test_nsid", "Unexpected NSID string. Expected %v, got %v", "test_nsid", ednsAnswer.NSID.Nsid) +} + +func TestParseEdnsAnswerNsid2(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{&dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: "not_a_hex_string"}}, + } + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, ednsAnswer.NSID, (*Edns0NSID)(nil), "Unexpected NSID string. Expected %v, got %v", nil, ednsAnswer.NSID) +} + +func TestParseEdnsAnswerNoEdns(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{}, + } + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, ednsAnswer.NSID, (*Edns0NSID)(nil), "Unexpected NSID string. Expected %v, got %v", nil, ednsAnswer.NSID) + assert.Equal(t, len(ednsAnswer.EDE), 0, "Expected no EDE error code, got %v", len(ednsAnswer.EDE)) + +} + +func TestParseEdnsAnswerEDE1(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{&dns.EDNS0_EDE{InfoCode: 65535, ExtraText: "testing"}}, + } + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, len(ednsAnswer.EDE), 1, "Expected only one EDE error code, got %v", len(ednsAnswer.EDE)) + assert.Equal(t, ednsAnswer.EDE[0].InfoCode, uint16(65535), "Unexpected EDE info code. Expected %v, got %v", 65535, ednsAnswer.EDE[0].InfoCode) + assert.Equal(t, ednsAnswer.EDE[0].ExtraText, "testing", "Unexpected EDE extra text. Expected %v, got %v", "testing", ednsAnswer.EDE[0].ExtraText) +} + +func TestParseEdnsAnswerEDE2(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{ + &dns.EDNS0_EDE{InfoCode: 65535, ExtraText: "testing1"}, + &dns.EDNS0_EDE{InfoCode: 65534, ExtraText: "testing2"}}, + } + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, len(ednsAnswer.EDE), 2, "Expected only one EDE error code, got %v", len(ednsAnswer.EDE)) + assert.Equal(t, ednsAnswer.EDE[0].InfoCode, uint16(65535), "Unexpected EDE info code. Expected %v, got %v", 65535, ednsAnswer.EDE[0].InfoCode) + assert.Equal(t, ednsAnswer.EDE[0].ExtraText, "testing1", "Unexpected EDE extra text. Expected %v, got %v", "testing1", ednsAnswer.EDE[1].ExtraText) + assert.Equal(t, ednsAnswer.EDE[1].InfoCode, uint16(65534), "Unexpected EDE info code. Expected %v, got %v", 655354, ednsAnswer.EDE[0].InfoCode) + assert.Equal(t, ednsAnswer.EDE[01].ExtraText, "testing2", "Unexpected EDE extra text. Expected %v, got %v", "testing2", ednsAnswer.EDE[1].ExtraText) +} + +func TestParseEdnsAnswerClientSubnet1(t *testing.T) { + rr := &dns.OPT{ + Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 1232}, + Option: []dns.EDNS0{ + &dns.EDNS0_SUBNET{ + Code: dns.EDNS0SUBNET, + Family: uint16(1), + SourceNetmask: uint8(24), + SourceScope: uint8(20), + Address: net.ParseIP("1.2.3.4"), + }, + }} + res := ParseAnswer(rr) + ednsAnswer, ok := res.(EDNSAnswer) + assert.Equal(t, ok, true, "Failed to parse OPT record") + assert.Equal(t, ednsAnswer.Version, uint8(0), "Unexpected EDNS Version. Expected %v, got %v", 0, ednsAnswer.Version) + assert.Equal(t, ednsAnswer.UDPSize, uint16(1232), "Unexpected EDNS UDP Size. Expected %v, got %v", 0, ednsAnswer.UDPSize) + assert.Equal(t, ednsAnswer.Flags, "", "Unexpected EDNS Flags. Expected %v, got %v", 0, ednsAnswer.Flags) + assert.Equal(t, ednsAnswer.ClientSubnet.SourceScope, uint8(20), "Unexpected source scope. Expected %v, got %v", 20, ednsAnswer.ClientSubnet.SourceScope) + assert.Equal(t, ednsAnswer.ClientSubnet.SourceNetmask, uint8(24), "Unexpected source netmask. Expected %v, got %v", 24, ednsAnswer.ClientSubnet.SourceNetmask) + assert.Equal(t, ednsAnswer.ClientSubnet.Family, uint16(1), "Unexpected family. Expected %v, got %v", 1, ednsAnswer.ClientSubnet.Family) + assert.Equal(t, ednsAnswer.ClientSubnet.Address, "1.2.3.4", "Unexpected address. Expected %v, got %v", "1.2.3.4", ednsAnswer.ClientSubnet.Address) +} + func verifyAnswer(t *testing.T, answer interface{}, original dns.RR, expectedAnswer interface{}) { ans, ok := answer.(Answer) if !ok { diff --git a/pkg/zdns/conf.go b/pkg/zdns/conf.go index b09b6d55..6f47e242 100644 --- a/pkg/zdns/conf.go +++ b/pkg/zdns/conf.go @@ -50,6 +50,7 @@ type GlobalConf struct { LocalAddrSpecified bool LocalAddrs []net.IP ClientSubnet *dns.EDNS0_SUBNET + NSID *dns.EDNS0_NSID Dnssec bool CheckingDisabled bool diff --git a/pkg/zdns/zdns.go b/pkg/zdns/zdns.go index baa319fe..70a7eccf 100644 --- a/pkg/zdns/zdns.go +++ b/pkg/zdns/zdns.go @@ -35,7 +35,8 @@ func Run(gc GlobalConf, flags *pflag.FlagSet, timeout *int, iterationTimeout *int, class_string *string, servers_string *string, config_file *string, localaddr_string *string, - localif_string *string, nanoSeconds *bool, clientsubnet_string *string) { + localif_string *string, nanoSeconds *bool, + clientsubnet_string *string, nsid *bool) { factory := GetLookup(gc.Module) @@ -138,6 +139,10 @@ func Run(gc GlobalConf, flags *pflag.FlagSet, gc.NameServersSpecified = true } + if *nsid { + gc.NSID = new(dns.EDNS0_NSID) + } + if *clientsubnet_string != "" { parts := strings.Split(*clientsubnet_string, "/") if len(parts) != 2 {