Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NS followed by A lookup module #294

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
178 changes: 178 additions & 0 deletions pkg/nsalookup/nsalookup.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading