diff --git a/integration_tests.py b/integration_tests.py index 8d2173f2..9a15e4bb 100755 --- a/integration_tests.py +++ b/integration_tests.py @@ -229,6 +229,120 @@ def run_zdns_multiline(self, flags, names, executable=ZDNS_EXECUTABLE): for server in NS_LOOKUP_IPV6_WWW_ZDNS_TESTING["data"]["servers"]: del server["ipv4_addresses"] + NSA_LOOKUP_WWW_ZDNS_TESTING = { + "name": "www.zdns-testing.com", + "status": "NOERROR", + "data": { + "ip_records": [ + { + "ipv4_addresses": [ + "2.3.4.5", + "3.4.5.6", + "1.2.3.4" + ], + "ipv6_addresses": [ + "fd5a:3bce:8713::1", + "fde6:9bb3:dbd6::2", + "fdb3:ac76:a577::3" + ], + "name_server": { + "ip": "216.239.38.108", + "name": "ns-cloud-c4.googledomains.com" + }, + "status": "NOERROR" + }, + { + "name_server": { + "ip": "2001:4860:4802:38::6c", + "name": "ns-cloud-c4.googledomains.com" + }, + "status": "ERROR" + }, + { + "ipv4_addresses": [ + "3.4.5.6", + "1.2.3.4", + "2.3.4.5" + ], + "ipv6_addresses": [ + "fd5a:3bce:8713::1", + "fde6:9bb3:dbd6::2", + "fdb3:ac76:a577::3" + ], + "name_server": { + "ip": "216.239.36.108", + "name": "ns-cloud-c3.googledomains.com" + }, + "status": "NOERROR" + }, + { + "name_server": { + "ip": "2001:4860:4802:36::6c", + "name": "ns-cloud-c3.googledomains.com" + }, + "status": "ERROR" + }, + { + "ipv4_addresses": [ + "2.3.4.5", + "3.4.5.6", + "1.2.3.4" + ], + "ipv6_addresses": [ + "fdb3:ac76:a577::3", + "fd5a:3bce:8713::1", + "fde6:9bb3:dbd6::2" + ], + "name_server": { + "ip": "216.239.32.108", + "name": "ns-cloud-c1.googledomains.com" + }, + "status": "NOERROR" + }, + { + "name_server": { + "ip": "2001:4860:4802:32::6c", + "name": "ns-cloud-c1.googledomains.com" + }, + "status": "ERROR" + }, + { + "ipv4_addresses": [ + "3.4.5.6", + "2.3.4.5", + "1.2.3.4" + ], + "ipv6_addresses": [ + "fdb3:ac76:a577::3", + "fd5a:3bce:8713::1", + "fde6:9bb3:dbd6::2" + ], + "name_server": { + "ip": "216.239.34.108", + "name": "ns-cloud-c2.googledomains.com" + }, + "status": "NOERROR" + }, + { + "name_server": { + "ip": "2001:4860:4802:34::6c", + "name": "ns-cloud-c2.googledomains.com" + }, + "status": "ERROR" + } + ] + }, + } + + NSA_LOOKUP_IPV4_WWW_ZDNS_TESTING = copy.deepcopy(NSA_LOOKUP_WWW_ZDNS_TESTING) + for record in NSA_LOOKUP_IPV4_WWW_ZDNS_TESTING["data"]["ip_records"]: + if "ipv6_addresses" in record: + del record["ipv6_addresses"] + NSA_LOOKUP_IPV6_WWW_ZDNS_TESTING = copy.deepcopy(NSA_LOOKUP_WWW_ZDNS_TESTING) + for record in NSA_LOOKUP_IPV6_WWW_ZDNS_TESTING["data"]["ip_records"]: + if "ipv4_addresses" in record: + del record["ipv4_addresses"] + PTR_LOOKUP_GOOGLE_PUB = [ { "type":"PTR", @@ -450,6 +564,26 @@ def assertEqualNSLookup(self, res, correct): del server["ttl"] self.assertEqual(recursiveSort(res["data"]["servers"]), recursiveSort(correct["data"]["servers"])) + def sort_ips(self, records): + for record in records: + if "ipv4_addresses" in record: + record["ipv4_addresses"] = sorted(record["ipv4_addresses"]) + if "ipv6_addresses" in record: + record["ipv6_addresses"] = sorted(record["ipv6_addresses"]) + return records + + def assertEqualNSALookup(self, res, correct): + self.assertEqual(res["name"], correct["name"]) + self.assertEqual(res["status"], correct["status"]) + self.assertEqual(len(res["data"]["ip_records"]), len(correct["data"]["ip_records"])) + + # Sort the IPs before checking + res_records = self.sort_ips(res["data"]["ip_records"]) + correct_records = self.sort_ips(correct["data"]["ip_records"]) + + for record in res_records: + self.assertIn(record, correct_records) + def test_a(self): c = "A" name = "zdns-testing.com" @@ -638,6 +772,33 @@ def test_ns_lookup_ipv6(self): self.assertSuccess(res, cmd) self.assertEqualNSLookup(res, self.NS_LOOKUP_IPV6_WWW_ZDNS_TESTING) + def test_nsa_lookup_default(self): + c = "nsalookup" + name = "www.zdns-testing.com" + cmd, res = self.run_zdns(c, name) + self.assertSuccess(res, cmd) + self.assertEqualNSALookup(res, self.NSA_LOOKUP_IPV4_WWW_ZDNS_TESTING) + + def test_nsa_lookup_iterative_failure(self): + c = "nsalookup --iterative" + name = "www.zdns-testing.com" + prompt = "NSA module does not support iterative resolution" + self.run_zdns_check_failure(c, name, prompt) + + def test_nsa_lookup_ipv4_ipv6(self): + c = "nsalookup --ipv4-lookup --ipv6-lookup" + name = "www.zdns-testing.com" + cmd, res = self.run_zdns(c, name) + self.assertSuccess(res, cmd) + self.assertEqualNSALookup(res, self.NSA_LOOKUP_WWW_ZDNS_TESTING) + + def test_nsa_lookup_ipv6(self): + c = "nsalookup --ipv6-lookup" + name = "www.zdns-testing.com" + cmd, res = self.run_zdns(c, name) + self.assertSuccess(res, cmd) + self.assertEqualNSALookup(res, self.NSA_LOOKUP_IPV6_WWW_ZDNS_TESTING) + def test_spf_lookup(self): c = "spf" name = "zdns-testing.com" diff --git a/main.go b/main.go index 4d21c4a9..841d71c0 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( _ "github.com/zmap/zdns/pkg/dmarc" _ "github.com/zmap/zdns/pkg/miekg" _ "github.com/zmap/zdns/pkg/mxlookup" + _ "github.com/zmap/zdns/pkg/nsalookup" _ "github.com/zmap/zdns/pkg/nslookup" _ "github.com/zmap/zdns/pkg/spf" ) diff --git a/pkg/nsalookup/nsalookup.go b/pkg/nsalookup/nsalookup.go new file mode 100644 index 00000000..bfbb9777 --- /dev/null +++ b/pkg/nsalookup/nsalookup.go @@ -0,0 +1,178 @@ +/* + * ZDNS Copyright 2022 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 nsalookup + +import ( + "log" + "net" + + "github.com/spf13/pflag" + + "github.com/zmap/dns" + "github.com/zmap/zdns/pkg/miekg" + "github.com/zmap/zdns/pkg/nslookup" + "github.com/zmap/zdns/pkg/zdns" +) + +// Per Connection Lookup ====================================================== +// +type Lookup struct { + Factory *RoutineLookupFactory + nslookup.Lookup +} + +// This LookupClient is created to call the actual implementation of DoMiekgLookup +type LookupClient struct{} + +func (lc LookupClient) ProtocolLookup(s *miekg.Lookup, q miekg.Question, nameServer string) (interface{}, zdns.Trace, zdns.Status, error) { + return s.DoMiekgLookup(q, nameServer) +} + +// For the nameservers, return the IP and name +type NameServer struct { + Name string `json:"name" groups:"short,normal,long,trace"` + IP string `json:"ip" groups:"short,normal,long,trace"` +} + +// Each of the records in the final result +type ARecord struct { + NameServer NameServer `json:"name_server" groups:"short,normal,long,trace"` + Status zdns.Status `json:"status" groups:"short,normal,long,trace"` + IPv4Addresses []string `json:"ipv4_addresses,omitempty" groups:"short,normal,long,trace"` + IPv6Addresses []string `json:"ipv6_addresses,omitempty" groups:"short,normal,long,trace"` +} + +// Final result to be returned by DoLookup +type Result struct { + ARecords []ARecord `json:"ip_records,omitempty" groups:"short,normal,long,trace"` +} + +func (s *Lookup) DoLookup(name, nameServer string) (interface{}, zdns.Trace, zdns.Status, error) { + var retv Result + l := LookupClient{} + var curServer string + + if nameServer == "" { + nameServer = s.Factory.Factory.RandomNameServer() + } + + // Lookup both ipv4 and ipv6 addresses of nameservers. + nsResults, nsTrace, nsStatus, nsError := s.DoNSLookup(name, true, true, nameServer) + + if nsStatus != zdns.STATUS_NOERROR { + return nil, nsTrace, nsStatus, nsError + } + + // IPv4Lookup and IPv6Lookup determine whether to lookup IPv4 or IPv6 addresses for the domain + lookupIpv4 := s.Factory.Factory.IPv4Lookup || !s.Factory.Factory.IPv6Lookup + lookupIpv6 := s.Factory.Factory.IPv6Lookup + + var fullTrace zdns.Trace = nsTrace + + // Iterate over all the namesevers + for _, nserver := range nsResults.Servers { + // Use all the ipv4 and ipv6 addresses of each nameserver + ips := append(nserver.IPv4Addresses, nserver.IPv6Addresses...) + for _, ip := range ips { + curServer = net.JoinHostPort(ip, "53") + // Do ipv4 or ipv6 lookup or both depending on the flags set. + aResult, aTrace, aStatus, _ := s.DoTargetedLookup(l, name, curServer, lookupIpv4, lookupIpv6) + + var ipv4s []string + var ipv6s []string + + if aResult != nil { + ipv4s = aResult.(miekg.IpResult).IPv4Addresses + ipv6s = aResult.(miekg.IpResult).IPv6Addresses + } + + fullTrace = append(fullTrace, aTrace) + aRecord := ARecord{ + NameServer: NameServer{Name: nserver.Name, IP: ip}, + Status: aStatus, + IPv4Addresses: ipv4s, + IPv6Addresses: ipv6s, + } + + retv.ARecords = append(retv.ARecords, aRecord) + } + } + return retv, nil, zdns.STATUS_NOERROR, nil +} + +// Per GoRoutine Factory ====================================================== +// +type RoutineLookupFactory struct { + miekg.RoutineLookupFactory + Factory *GlobalLookupFactory +} + +func (s *RoutineLookupFactory) MakeLookup() (zdns.Lookup, error) { + a := Lookup{Factory: s} + nameServer := s.Factory.RandomNameServer() + a.Initialize(nameServer, dns.TypeA, dns.ClassINET, &s.RoutineLookupFactory) + return &a, nil +} + +// Global Factory ============================================================= +// +type GlobalLookupFactory struct { + miekg.GlobalLookupFactory + IPv4Lookup bool + IPv6Lookup bool +} + +func (s *GlobalLookupFactory) SetFlags(f *pflag.FlagSet) { + // If there's an error, panic is appropriate since we should at least be getting the default here. + var err error + s.IPv4Lookup, err = f.GetBool("ipv4-lookup") + if err != nil { + panic(err) + } + s.IPv6Lookup, err = f.GetBool("ipv6-lookup") + if err != nil { + panic(err) + } +} + +// Command-line Help Documentation. This is the descriptive text what is +// returned when you run zdns module --help +func (s *GlobalLookupFactory) Help() string { + return "" +} + +func (s *GlobalLookupFactory) MakeRoutineFactory(threadID int) (zdns.RoutineLookupFactory, error) { + r := new(RoutineLookupFactory) + r.Factory = s + r.RoutineLookupFactory.Factory = &s.GlobalLookupFactory + r.Initialize(s.GlobalConf) + r.ThreadID = threadID + return r, nil +} + +func (s *GlobalLookupFactory) Initialize(c *zdns.GlobalConf) error { + s.GlobalConf = c + if c.IterativeResolution { + log.Fatal("NSA module does not support iterative resolution") + } + return nil +} + +// Global Registration ======================================================== +// +func init() { + s := new(GlobalLookupFactory) + zdns.RegisterLookup("NSALOOKUP", s) +} diff --git a/pkg/nsalookup/nsalookup_test.go b/pkg/nsalookup/nsalookup_test.go new file mode 100644 index 00000000..4a2c1e4a --- /dev/null +++ b/pkg/nsalookup/nsalookup_test.go @@ -0,0 +1,480 @@ +/* + * ZDNS Copyright 2022 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 nsalookup + +import ( + "net" + "reflect" + "testing" + + "github.com/zmap/dns" + "github.com/zmap/zdns/pkg/miekg" + "github.com/zmap/zdns/pkg/nslookup" + "github.com/zmap/zdns/pkg/zdns" + "gotest.tools/v3/assert" +) + +// Global variables used for the nslookup mock +var nsRecords = make(map[string]nslookup.Result) +var nsStatus = zdns.STATUS_NOERROR + +// Mock the NS lookup. +func (s *Lookup) DoNSLookup(name string, lookupIpv4 bool, lookupIpv6 bool, nameServer string) (nslookup.Result, zdns.Trace, zdns.Status, error) { + if res, ok := nsRecords[name]; ok { + return res, nil, nsStatus, nil + } else { + return nslookup.Result{}, nil, zdns.STATUS_NXDOMAIN, nil + } +} + +type domain_ns struct { + domain string + ns string +} + +// Global variables used for the targeted lookup +var mockResults = make(map[domain_ns]miekg.IpResult) +var protocolStatus = make(map[domain_ns]zdns.Status) + +func (s *Lookup) DoTargetedLookup(l LookupClient, name, nameServer string, lookupIpv4 bool, lookupIpv6 bool) (interface{}, []interface{}, zdns.Status, error) { + retv := miekg.IpResult{} + cur_domain_ns := domain_ns{domain: name, ns: nameServer} + default_status := zdns.STATUS_NOERROR + + if res, ok := mockResults[cur_domain_ns]; ok { + if lookupIpv4 { + retv.IPv4Addresses = res.IPv4Addresses + } + if lookupIpv6 { + retv.IPv6Addresses = res.IPv6Addresses + } + + if status, ok := protocolStatus[cur_domain_ns]; ok { + return retv, nil, status, nil + } else { + return retv, nil, default_status, nil + } + } else { + return nil, nil, zdns.STATUS_ERROR, nil + } +} + +type minimalRecords struct { + Status zdns.Status + IPv4Addresses []string + IPv6Addresses []string +} + +func InitTest(t *testing.T) (*GlobalLookupFactory, zdns.Lookup) { + nsRecords = make(map[string]nslookup.Result) + mockResults = make(map[domain_ns]miekg.IpResult) + nsStatus = zdns.STATUS_NOERROR + protocolStatus = make(map[domain_ns]zdns.Status) + + gc := new(zdns.GlobalConf) + gc.NameServers = []string{"127.0.0.1"} + + glf := new(GlobalLookupFactory) + glf.GlobalConf = gc + glf.IPv4Lookup = false + glf.IPv6Lookup = false + + rlf := new(RoutineLookupFactory) + rlf.Factory = glf + rlf.Client = new(dns.Client) + + l, err := rlf.MakeLookup() + if l == nil || err != nil { + t.Error("Failed to initialize lookup") + } + + if err := glf.Initialize(gc); err != nil { + t.Errorf("Factory was unable to initialize: %v", err.Error()) + } + return glf, l +} + +// Test One NS with one IP with only ipv4-lookup +func TestOneNsIpv4(t *testing.T) { + _, l := InitTest(t) + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ipv6_1 := "2001:db8::3" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1}, + IPv6Addresses: []string{ipv6_1}, + TTL: 3600, + }, + }, + } + + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + expectedrecordsMap[ipv6_1] = minimalRecords{ + Status: zdns.STATUS_ERROR, + IPv4Addresses: nil, + IPv6Addresses: nil, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test One NS with two IPs with only ipv4-lookup +func TestOneNsMultipleIps(t *testing.T) { + _, l := InitTest(t) + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ipv4_2 := "192.0.2.4" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1, ipv4_2}, + IPv6Addresses: nil, + TTL: 3600, + }, + }, + } + + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_2, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + expectedrecordsMap[ipv4_2] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test One NS with one IP with ipv4-lookup and ipv6-lookup +func TestOneNsIpv4AndIpv6(t *testing.T) { + glf, l := InitTest(t) + glf.IPv4Lookup = true + glf.IPv6Lookup = true + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ipv6_1 := "2001:db8::3" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1}, + IPv6Addresses: []string{ipv6_1}, + TTL: 3600, + }, + }, + } + + // This test assumes only IPv4 address returns records for the domain. + // IPv6 doesn't return anything + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + expectedrecordsMap[ipv6_1] = minimalRecords{ + Status: zdns.STATUS_ERROR, + IPv4Addresses: nil, + IPv6Addresses: nil, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test One NS with IPv4, IPv6 addresses and with ipv4-lookup and ipv6-lookup +func TestOneNsAllIpsWork(t *testing.T) { + glf, l := InitTest(t) + glf.IPv4Lookup = true + glf.IPv6Lookup = true + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ipv6_1 := "2001:db8::3" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1}, + IPv6Addresses: []string{ipv6_1}, + TTL: 3600, + }, + }, + } + + // This test assumes only both IPv4 and IPv6 NS addresses work + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv6_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + expectedrecordsMap[ipv6_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test two NS with one IP each with only ipv4-lookup +func TestTwoNsIpv4(t *testing.T) { + _, l := InitTest(t) + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ns2 := "ns1.example.com" + ipv4_2 := "192.0.2.4" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1}, + IPv6Addresses: nil, + TTL: 3600, + }, + { + Name: ns2, + Type: "NS", + IPv4Addresses: []string{ipv4_2}, + IPv6Addresses: nil, + TTL: 3600, + }, + }, + } + + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_2, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + expectedrecordsMap[ipv4_2] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test two NS with one IP each with ipv4-lookup but giving different IPs for the A lookup +func TestTwoNsMismatchIpv4(t *testing.T) { + _, l := InitTest(t) + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ns2 := "ns1.example.com" + ipv4_2 := "192.0.2.4" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1}, + IPv6Addresses: nil, + TTL: 3600, + }, + { + Name: ns2, + Type: "NS", + IPv4Addresses: []string{ipv4_2}, + IPv6Addresses: nil, + TTL: 3600, + }, + }, + } + + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: []string{"2001:db8::1"}, + } + mockResults[domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_2, "53")}] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.2"}, + IPv6Addresses: []string{"2001:db8::2"}, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + expectedrecordsMap[ipv4_2] = minimalRecords{ + Status: zdns.STATUS_NOERROR, + IPv4Addresses: []string{"192.0.2.2"}, + IPv6Addresses: nil, + } + res, _, _, _ := l.DoLookup("example.com", "") + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +// Test error in A lookup via targeted lookup records +func TestErrorInOneTargetedLookup(t *testing.T) { + _, l := InitTest(t) + + ns1 := "ns1.example.com" + ipv4_1 := "192.0.2.3" + ipv4_2 := "192.0.2.4" + + nsRecords["example.com"] = nslookup.Result{ + Servers: []nslookup.NSRecord{ + { + Name: ns1, + Type: "NS", + IPv4Addresses: []string{ipv4_1, ipv4_2}, + IPv6Addresses: nil, + TTL: 3600, + }, + }, + } + + domain_ns_1 := domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_1, "53")} + protocolStatus[domain_ns_1] = zdns.STATUS_SERVFAIL + mockResults[domain_ns_1] = miekg.IpResult{ + IPv4Addresses: nil, + IPv6Addresses: nil, + } + // The default protocol status is NOERROR, but we explicitly set it + // in this test for sake of clarity + domain_ns_2 := domain_ns{domain: "example.com", ns: net.JoinHostPort(ipv4_2, "53")} + protocolStatus[domain_ns_2] = zdns.STATUS_NOERROR + mockResults[domain_ns_2] = miekg.IpResult{ + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + + expectedrecordsMap := make(map[string]minimalRecords) + expectedrecordsMap[ipv4_1] = minimalRecords{ + Status: protocolStatus[domain_ns_1], + IPv4Addresses: nil, + IPv6Addresses: nil, + } + expectedrecordsMap[ipv4_2] = minimalRecords{ + Status: protocolStatus[domain_ns_2], + IPv4Addresses: []string{"192.0.2.1"}, + IPv6Addresses: nil, + } + res, _, status, _ := l.DoLookup("example.com", "") + assert.Equal(t, status, zdns.STATUS_NOERROR) + verifyResult(t, res.(Result).ARecords, expectedrecordsMap) +} + +func TestNXDomain(t *testing.T) { + _, l := InitTest(t) + + res, _, status, _ := l.DoLookup("nonexistent.example.com", "") + + assert.Equal(t, status, zdns.STATUS_NXDOMAIN) + assert.Equal(t, res, nil) +} + +func TestServFail(t *testing.T) { + _, l := InitTest(t) + nsStatus = zdns.STATUS_SERVFAIL + nsRecords["example.com"] = nslookup.Result{} + res, _, status, _ := l.DoLookup("example.com", "") + + assert.Equal(t, status, nsStatus) + assert.Equal(t, res, nil) +} + +func verifyResult(t *testing.T, records []ARecord, expectedrecordsMap map[string]minimalRecords) { + recordsLength := len(records) + expectedrecordsLength := len(expectedrecordsMap) + + if recordsLength != expectedrecordsLength { + t.Errorf("Expected %v records, found %v", expectedrecordsLength, recordsLength) + } + + for _, record := range records { + ip := record.NameServer.IP + name := record.NameServer.Name + expectedRecords, ok := expectedrecordsMap[ip] + if !ok { + t.Errorf("Did not find NS server %v in expected records.", ip) + } + assert.Equal(t, record.Status, expectedRecords.Status) + if !reflect.DeepEqual(record.IPv4Addresses, expectedRecords.IPv4Addresses) { + t.Errorf("IPv4 addresses not matching for NS %v with IP %v, expected %v, found %v", name, ip, expectedRecords.IPv4Addresses, record.IPv4Addresses) + } + if !reflect.DeepEqual(record.IPv6Addresses, expectedRecords.IPv6Addresses) { + t.Errorf("IPv6 addresses not matching for NS %v with IP %v, expected %v, found %v", name, ip, expectedRecords.IPv6Addresses, record.IPv6Addresses) + } + } +}