def testCache(self): cac = RecordCache(0) self.assertEqual(cac.lookup("abcdefghqqqq.com", Type.A, Class.IN), []) test = ResourceRecord("blabla.com", Type.A, Class.IN, 0, ARecordData("111.111.111.111")) cac.add_record(test) self.assertEqual(cac.lookup("blabla.com", Type.A, Class.IN), test)
def test_cache_lookup(self): """ Add a record to the cache and look it up """ rr = ResourceRecord("wiki.nl", Type.A, Class.IN, self.ttl, RecordData.create(Type.A, "192.168.123.456")) cache = RecordCache() cache.add_record(rr) lookup_vals = cache.lookup("wiki.nl", Type.A, Class.IN) self.assertEqual([rr], lookup_vals)
def test_TTL_expiration(self): """ cache a record, wait till ttl expires, see if record is removed from cache """ rr = ResourceRecord("wiki.nl", Type.A, Class.IN, self.ttl, RecordData.create(Type.A, "192.168.123.456")) cache = RecordCache() cache.add_record(rr) time.sleep(rr.ttl) lookup_vals = cache.lookup("wiki.nl", Type.A, Class.IN) self.assertFalse(lookup_vals)
def test_expired_cache_entry(self): cache = RecordCache(0) record = ResourceRecord( name=Name("bonobo.putin"), type_=Type.A, class_=Class.IN, ttl=0, rdata=ARecordData("1.0.0.1"), ) cache.add_record(record) self.assertEqual(cache.lookup(Name("bonobo.putin"), Type.A, Class.IN), None)
def test_invalid_domain_from_cache(self): cache = RecordCache(0) record = ResourceRecord( name=Name("bonobo.putin"), type_=Type.A, class_=Class.IN, ttl=60, rdata=ARecordData("1.0.0.1"), ) cache.add_record(record) self.assertEqual(cache.lookup(Name("bonobo.putin"), Type.A, Class.IN), record)
def test_ttl_overwrite(self): cache = RecordCache(60) record = ResourceRecord( name=Name("bonobo.putin"), type_=Type.A, class_=Class.IN, ttl=0, rdata=ARecordData("1.0.0.1"), ) cache.add_record(record) cache_entry = cache.lookup(Name("bonobo.putin"), Type.A, Class.IN) self.assertEqual(cache_entry, record) self.assertEqual(cache_entry.ttl, 60)
def test_cache_disk_io(self): """ Add a record to the cache, write to disk, read from disk, do a lookup """ rr = ResourceRecord("wiki.nl", Type.A, Class.IN, self.ttl, RecordData.create(Type.A, "192.168.123.456")) cache = RecordCache() cache.write_cache_file() # overwrite the current cache file # add rr to cache and write to disk cache.add_record(rr) cache.write_cache_file() # read from disk again new_cache = RecordCache() new_cache.read_cache_file() lookup_vals = new_cache.lookup("wiki.nl", Type.A, Class.IN) self.assertEqual([rr], lookup_vals)
class Resolver: """DNS resolver""" def __init__(self, timeout, caching, ttl): """Initialize the resolver Args: caching (bool): caching is enabled if True ttl (int): ttl of cache entries (if > 0) """ self.cache = RecordCache(10000000) self.timeout = timeout self.caching = caching self.ttl = ttl self.hints = { "a.root-servers.net": "198.41.0.4", "b.root-servers.net": "192.228.79.201", "c.root-servers.net": "192.33.4.12", "d.root-servers.net": "199.7.91.13", "e.root-servers.net": "192.203.230.10", "f.root-servers.net": "192.5.5.241", "g.root-servers.net": "192.112.36.4", "h.root-servers.net": "128.63.2.53", "i.root-servers.net": "192.36.148.17", "j.root-servers.net": "192.58.128.30", "k.root-servers.net": "193.0.14.129", "l.root-servers.net": "199.7.83.42", "m.root-servers.net": "202.12.27.33" } def gethostbyname(self, hostname): """Translate a host name to IPv4 address. Currently this method contains an example. You will have to replace this example with the algorithm described in section 5.3.3 in RFC 1034. Args: hostname (str): the hostname to resolve Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ #start with empty lists aliaslist = [] ipaddrlist = [] #initiate with the list of hints to use. servers = self.hints.copy() #so hostname is not lost when we resolve a CNAME at some point. domain = hostname finished = False #check cache of hostname is in the cache. cache_entries = self.cache.lookup(hostname, Type.A, Class.IN) + self.cache.lookup( hostname, Type.CNAME, Class.IN) if (len(cache_entries) > 0): #TODO pass while (servers and not finished): #take a server from the list of servers to ask for the domain we're looking for. name, server = servers.popitem() #if the server doesn't have an address, resolve this address first. if (server == None): a, b, c = self.gethostbyname(name) if (len(c) > 0): servers[name] = c[0] server = c[0] else: continue #check whether the name is in the cache. cache_entries = self.cache.lookup(name, Type.A, Class.IN) + self.cache.lookup( name, Type.CNAME, Class.IN) if (len(cache_entries) > 0): for ce in cache_entries: if ce.type_ == 1: address = ans.rdata.to_dict()['address'] if (address not in ipaddrlist): ipaddrlist.append(address) if ce.type_ == 5: domain = ans.to_dict()['rdata']['cname'] if domain not in aliaslist: aliaslist.append(str(domain)) #send question to the server to see if it knows where we can find domain try: response = self.sendQuestion(domain, server) except: continue #if the location is in the answers we're done. for ans in response.answers: if ans.type_ == 1: address = ans.rdata.to_dict()['address'] if (address not in ipaddrlist): ipaddrlist.append(address) if (self.caching): self.cache.add_record(ans) if ans.type_ == 5: domain = ans.to_dict()['rdata']['cname'] if domain not in aliaslist: aliaslist.append(str(domain)) #add new servers we can ask for the location to our list of servers. new_servers = self.getNewServers(response.authorities, response.additionals) #if we found a (list of) IP-address(es) we're done. if (len(ipaddrlist) > 0): finished = True #combine old list of servers with new one. servers = {**servers, **new_servers} return hostname, aliaslist, ipaddrlist '''Used to combine the suggested servers to check. ''' def getNewServers(self, authorities, additionals): serverlist = {} for b in authorities: if (b.type_ == 6): continue serverlist.update({str(b.rdata.to_dict()['nsdname']): None}) for a in additionals: if (str(a.name) == str(b.rdata.to_dict()['nsdname'])): if (a.type_ == 1): serverlist.update({ str(b.rdata.to_dict()['nsdname']): a.rdata.to_dict()['address'] }) return serverlist '''Used to handle the sending and receiving of requests/responses.''' def sendQuestion(self, hostname, server): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) # Create and send query identifier = 9001 # placeholder question = Question(Name(hostname), Type.A, Class.IN) header = Header(identifier, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = 1 query = Message(header, [question]) sock.sendto(query.to_bytes(), (server, 53)) # Receive response data = sock.recv(512) response = Message.from_bytes(data) sock.close() return response
def gethostbyname(self, hostname, dnsserv='192.112.36.4'): """Translate a host name to IPv4 address. Currently this method contains an example. You will have to replace this example with the algorithm described in section 5.3.3 in RFC 1034. Args: hostname (str): the hostname to resolve Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ ipaddrlist = [] cnames = [] temp = [] if (self.caching): rcache = RecordCache(self.ttl) rcord = rcache.lookup(hostname, Type.ANY, Class.IN) if (rcord): for rec in rcord: if rec.type_ == Type.A: arec = rec.rdata ipaddrlist.append(arec.address) elif rec.type_ == Type.CNAME: crec = rec.rdata cnames.append(crec.cname) if ipaddrlist: return hostname, cnames, ipaddrlist elif cnames: return self.gethostbyname(cnames[0], dnsserv) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) # Create and send query question = Question(Name(str(hostname)), Type.A, Class.IN) header = Header(9001, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = 1 query = Message(header, [question]) sock.sendto(query.to_bytes(), (str(dnsserv), 53)) # Receive response data = sock.recv(2048) response = Message.from_bytes(data) print("Number of answers: " + str(len(response.answers))) print("Number of authorities: " + str(len(response.authorities))) print("Number of additionals: " + str(len(response.additionals))) # Get data aliaslist = cnames ipaddrlist = [] dnslist = [] while response.answers: for answer in response.answers: if answer.type_ == Type.A: print("found A RR") if (self.caching): rcache.add_record(answer) ipaddrlist.append(answer.rdata.address) if answer.type_ == Type.CNAME: aliaslist.append(answer.rdata.cname) if answer.type_ == Type.NS: dnslist.append(answer.rdata.nsdname) if ipaddrlist: return hostname, aliaslist, ipaddrlist elif aliaslist: question = Question(Name(aliaslist[0]), Type.A, Class.IN) query = Message(header, [question]) sock.sendto(query.to_bytes(), (dnsserv, 53)) data = sock.recv(2048) response = Message.from_bytes(data) elif dnslist: nsname = dnslist.pop() maybe_dnsserv = self.getnsaddr(nsname, response.additionals) if maybe_dnsserv: dnsserv = maybe_dnsserv else: pass sock.sendto(query.to_bytes(), (dnsserv, 53)) data = sock.recv(2048) response = Message.from_bytes(data) else: break if response.authorities: for authority in response.authorities: if authority.type_ != Type.NS: pass dnslist.append(authority.rdata.nsdname) while dnslist: nsname = dnslist.pop() maybe_next_dnsserv = self.getnsaddr(nsname, response.additionals) if maybe_next_dnsserv: next_dns_serv = maybe_next_dnsserv else: pass (hname, aliasl, ipaddrl) = self.gethostbyname(hostname, nsname) if ipaddrl: return hname, aliasl, ipaddrl
class Resolver: """DNS resolver""" def __init__(self, timeout, caching, ttl, sock=None, cache=None): """Initialize the resolver Args: caching (bool): caching is enabled if True ttl (int): ttl of cache entries (if > 0) """ self.timeout = timeout self.caching = caching self.rc = cache self.ttl = ttl if self.rc == None: self.rc = RecordCache(self.ttl) self.zone = Zone() self.sock = sock if self.sock is None: self.sock = SocketWrapper(53) self.sock.start() def _make_id(self): gm = time.gmtime() mss = str(time.time()).split(".")[1][0:3] gms = str(gm.tm_sec) id = int(gms + mss) return id def gethostbyname(self, hostname): """Translate a host name to IPv4 address. Currently this method contains an example. You will have to replace this example with the algorithm described in section 5.3.3 in RFC 1034. Args: hostname (str): the hostname to resolve Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ alias_list = [] a_list = [] slist = [] found = False acs = self.getRecordsFromCache(hostname, Type.A, Class.IN) if acs: a_list += acs return hostname, alias_list, a_list nscs = self.matchByLabel(hostname, Type.NS, Class.IN) for ns in nscs: glue = self.getRecordsFromCache(str(ns.rdata.nsdname)) if glue: slist += glue else: slist += [ns] id = self._make_id() # Create and send query question = Question(Name(hostname), Type.A, Class.IN) header = Header(id, 0, 1, 0, 0, 0) header.qr = 0 # 0 for query header.opcode = 0 # standad query header.rd = 0 # not recursive query = Message(header, [question]) self.zone.read_master_file('dns/root.zone') sbelt = [] for root in list(self.zone.records.values()): sbelt += [r for r in root if r.type_ == Type.A] while not found: if slist: rr = slist.pop() if rr.type_ == Type.A: addr = rr.rdata.address self.sock.send((query, addr, 53)) elif rr.type_ == Type.NS: fqdn = str(rr.rdata.nsdname) _, _, a_rrs = self.gethostbyname(fqdn) slist += a_rrs continue elif rr.type_ == Type.CNAME: fqdn = str(rr.rdata.cname) _, cname_rrs, a_rrs = self.gethostbyname(fqdn) a_list += a_rrs alias_list += cname_rrs break elif sbelt: rr = sbelt.pop() addr = rr.rdata.address self.sock.send((query, addr, 53)) else: break # Receive response data = None while not data: data = self.sock.msgThere(id) response, _ = data[0] #response = Message.from_bytes(data) for answer in response.answers: if answer.type_ == Type.A: self.addRecordToCache(answer) a_list.append(answer) found = True if answer.type_ == Type.CNAME: self.addRecordToCache(answer) alias_list.append(answer) slist += [answer] continue nss = [] for auth in response.authorities: if auth.type_ == Type.NS: nss.append(auth) self.addRecordToCache(auth) a_add = {} for add in response.additionals: if add.type_ == Type.A: name = str(add.name) a_add[name] = add self.addRecordToCache(add) for ns in nss: name = str(ns.rdata.nsdname) if name in a_add: slist += [a_add[name]] else: slist += [ns] return hostname, alias_list, a_list def addRecordToCache(self, record): if self.caching: self.rc.add_record(record) def getRecordsFromCache(self, dname, t=Type.A, c=Class.IN): if self.caching: return self.rc.lookup(dname, t, c) else: return [] def matchByLabel(self, dname, type_, class_): if self.caching: return self.rc.matchByLabel(dname, type_, class_) else: return [] def shutdown(self): """ to be used only when initialized for testing :return: """ self.sock.shutdown()
class Server: """A recursive DNS server""" def __init__(self, port, caching, ttl): """Initialize the server Args: port (int): port that server is listening on caching (bool): server uses resolver with caching if true ttl (int): ttl for records (if > 0) of cache """ self.caching = caching self.ttl = ttl self.port = port self.done = False self.zone = Zone() self.zone.read_master_file('zone') self.cache = RecordCache(ttl) if self.caching: self.cache.read_cache_file() self.doLogging = True def log(self, *args, end="\n"): if self.doLogging: print(*args, end=end) def search_zone(self, question): tests = str(question).split('.') for i in range(len(tests)): test = '.'.join(tests[i:]) if test == '': test = '.' r = self.zone.records.get(test, None) if not r == None: if test == str(question): return True, r return False, r return False, [] def zone_resolution(self, questions): for q in questions: self.log("\tRESOLVING:", q.qname) res, rlist = self.search_zone(q.qname) answers = [] authorities = [] additionals = [] for r in rlist: if r.type_ == Type.A and res: answers.append(r) if r.type_ == Type.NS: res, a = self.search_zone(r.rdata.nsdname) authorities.append(r) if res: additionals.append(a[0]) return answers, authorities, additionals def check_cache(self, hostname): iplist = self.cache.lookup(hostname, Type.A, Class.IN) namelist = self.cache.lookup(hostname, Type.CNAME, Class.IN) if not (iplist == [] and namelist == []): iplist = [x.rdata.address for x in iplist] namelist = [x.rdata.cname for x in namelist] return True, iplist, namelist return False, [], [] def consult_cache(self, questions): answers = [] for q in questions: res, iplist, namelist = self.check_cache(q.qname) if res: for ip in iplist: answers.append(ResourceRecord(q.qname, Type.A, Class.IN, self.ttl, ARecordData(ip))) for n in namelist: answers.append(ResourceRecord(q.qname, Type.CNAME, Class.IN, self.ttl, CNAMERecordData(n))) return answers def build_message(self,id, rd, aa, rcode, questions, answers, authorities, additionals): header = Header(id, 0, len(questions), len(answers), len(authorities), len(additionals)) header.qr = 1 header.opcode = 0 header.rd = rd header.ra = 1 header.aa = aa header.rcode = rcode return Message(header, questions=questions, answers=answers, authorities=authorities, additionals=additionals) def serve(self): """Start serving requests""" sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("", self.port)) while not self.done: data, address = sock.recvfrom(65565) message = Message.from_bytes(data) rd = message.header.rd rcode = 0 aa = 1 self.log("REQUEST RECIEVED:", address) answers, authorities, additionals = self.zone_resolution(message.questions) if answers == []: if authorities == [] and additionals == []: self.log("\tZONE RESOLUTION FAILED") answers = self.consult_cache(message.questions) if answers == []: self.log("\tCACHE LOOKUP FAILED") rcode = 3 else: aa = 0 if rcode == 3 and rd == 1: rcode = 0 self.log("\tCALLING RESOLVER") resolver = Resolver(5, True, 0) resolver.rd = 0 resolver.rootip = "198.41.0.4" for q in message.questions: self.log("\t\tRESOLVING:", q.qname) hostname, namelist, iplist = resolver.gethostbyname(str(q.qname)) if hostname == str(q.qname): for ip in iplist: answers.append(ResourceRecord(q.qname, Type.A, Class.IN, self.ttl, ARecordData(ip))) for n in namelist: answers.append(ResourceRecord(q.qname, Type.CNAME, Class.IN, self.ttl, CNAMERecordData(n))) self.log("SENDING RESPONSE:", rcode, "\n") mess = self.build_message(message.header.ident, rd, aa, rcode, message.questions, answers, authorities, additionals) sock.sendto(mess.to_bytes(), address) def shutdown(self): """Shut the server down""" self.done = True
class Resolver: """DNS resolver""" def __init__(self, timeout, caching, ttl, rootip="198.41.0.4"): """Initialize the resolver Args: caching (bool): caching is enabled if True ttl (int): ttl of cache entries (if > 0) """ self.timeout = timeout self.caching = caching self.ttl = ttl self.doLogging = False self.rootip = rootip self.rd = 0 if self.caching: self.cache = RecordCache(ttl) self.cache.read_cache_file() def log(self, *args, end="\n"): if self.doLogging: print(*args, end=end) def logHeader(self, header): self.log("\tFLAGS", end="") self.log(" QR", header.qr, end=";") self.log(" OPCODE", header.opcode, end=";") self.log(" AA", header.aa, end=";") self.log(" TC", header.tc, end=";") self.log(" RD", header.rd, end=";") self.log(" RA", header.ra, end=";") self.log(" Z", header.z, end=";") self.log(" RCODE", header.rcode, end=";\n") def check_cache(self, hostname): iplist = self.cache.lookup(Name(hostname), Type.A, Class.IN) namelist = self.cache.lookup(Name(hostname), Type.CNAME, Class.IN) if not (iplist == [] and namelist == []): iplist = [x.rdata.address for x in iplist] namelist = [x.rdata.cname for x in namelist] return True, iplist, namelist hostname = hostname.split('.') for i in range(len(hostname)): test = '.'.join(hostname[i:]) namelist = self.cache.lookup(Name(test), Type.NS, Class.IN) namelist = [x.rdata.nsdname for x in namelist] if not namelist == []: for x in namelist: newips = self.cache.lookup(x, Type.A, Class.IN) iplist.extend(newips) iplist = [x.rdata.address for x in iplist] return False, iplist, namelist return False, [], [] def send_request(self, ip, name): #create socket and request sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) question = Question(name, Type.A, Class.IN) header = Header(9001, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = self.rd query = Message(header, [question]) sock.sendto(query.to_bytes(), (ip, 53)) # Receive response data = sock.recv(512) sock.close() response = Message.from_bytes(data) self.logHeader(response.header) if self.caching: for r in response.resources: if r.type_ == Type.A or r.type_ == Type.CNAME or r.type_ == Type.NS: self.cache.add_record(r) return response.answers, response.authorities, response.additionals def resolve_request(self, ip, hostname): self.log("\nRESOLVING REQUEST", hostname, "at:", ip) answers, authorities, additionals = self.send_request(ip, hostname) namelist = [] ipaddrlist = [] if len(answers) != 0: self.log("\n\tGOT RESPONSE") for answer in answers: if answer.type_ == Type.A: ipaddrlist.append(answer.rdata.address) self.log("\t\t", answer.type_, answer.rdata.address) if answer.type_ == Type.CNAME: namelist.append(hostname) self.log("\t\t", answer.type_, answer.rdata.cname) return True, ipaddrlist, namelist else: if len(additionals) != 0: self.log("\n\tGOT ADDITIONALS") for answer in additionals: if answer.type_ == Type.A: ipaddrlist.append(answer.rdata.address) self.log('\t\t', answer.type_, answer.rdata.address) if len(authorities) != 0: self.log("\n\tGOT AUTHORITIES") for answer in authorities: if answer.type_ == Type.NS: namelist.append(answer.rdata.nsdname) self.log('\t\t', answer.type_, answer.rdata.nsdname) return False, ipaddrlist, namelist def gethostbyname(self, hostname): """Translate a host name to IPv4 address. Currently this method contains an example. You will have to replace this example with the algorithm described in section 5.3.3 in RFC 1034. Args: hostname (str): the hostname to resolve Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ self.log("NEW QUERY:", hostname) serveriplist = [self.rootip] if self.caching: res, iplist, namelist = self.check_cache(hostname) if not (iplist == [] and namelist == []): self.log("CACHE HIT") if res: return hostname, namelist, iplist else: serveriplist = iplist while len(serveriplist) != 0: res, iplist, namelist = self.resolve_request(serveriplist[0], Name(hostname)) if res: self.log("END OF QUERY:", hostname) if self.caching: self.cache.write_cache_file() return hostname, namelist, iplist elif len(iplist) == 0 and not len(namelist) == 0: newlist = [] for x in namelist: newhostname, newaliases, newips= self.gethostbyname(str(x)) newlist.extend(newips) newlist.extend(serveriplist) serveriplist = newlist else: iplist.extend(serveriplist[1:]) serveriplist = iplist self.log("FAILURE") return hostname, [], [] return hostname, [], []
class Resolver(object): """ DNS resolver """ def __init__(self, timeout, caching, ttl, nameservers=[], use_rs=True): """ Initialize the resolver Args: caching (bool): caching is enabled if True ttl (int): ttl of cache entries (if > 0) """ self.timeout = timeout self.caching = caching self.ttl = ttl if ttl > 0 else 0 #Deze check is niet nodig voor de resolver gemaakt via de server, maar wel voor de resolver gemaakt door de client if caching: self.cache = RecordCache() self.nameservers = nameservers if use_rs: self.nameservers += dns.consts.ROOT_SERVERS def is_valid_hostname(self, hostname): """ Check if hostname could be a valid hostname Args: hostname (str): the hostname that is to be checked Returns: boolean indiciting if hostname could be valid """ valid_hostnames = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$" return re.match(valid_hostnames, hostname) def save_cache(self): """ Save the cache if appropriate """ if self.caching: if self.cache is not None: self.cache.write_cache_file() def ask_server(self, query, server): """ Send query to a server Args: query (Message): the query that is to be sent server (str): IP address of the server that the query must be sent to Returns: responses ([Message]): the responses received converted to Messages """ response = None sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) try: sock.sendto(query.to_bytes(), (server, 53)) data = sock.recv(1024) response = dns.message.Message.from_bytes(data) if response.header.ident != query.header.ident: return None if self.caching: for record in response.additionals + response.answers + response.authorities: if record.type_ == Type.A or record.type_ == Type.CNAME: record.ttl = self.ttl record.timestamp = int(time.time()) self.cache.add_record(record) except socket.timeout: pass return response def gethostbyname(self, hostname): """ Resolve hostname to an IP address Args: hostname (str): the FQDN that we want to resolve Returns: hostname (str): the FQDN that we want to resolve, aliaslist ([str]): list of aliases of the hostname, ipaddrlist ([str]): list of IP addresses of the hostname """ print("==GETHOSTNAME START=================") aliaslist = [] ipaddrlist = [] #Check if the hostname is valid valid = self.is_valid_hostname(hostname) if not valid: return hostname, [], [] #Check if the information is in the cache if self.caching: for alias in self.cache.lookup(hostname, Type.CNAME, Class.IN): aliaslist.append(alias.rdata.data) for address in self.cache.lookup(hostname, Type.A, Class.IN): ipaddrlist.append(address.rdata.data) if ipaddrlist: print("We found an address in the cache!") return hostname, aliaslist, ipaddrlist #Do the recursive algorithm hints = self.nameservers while hints: #Get the server to ask hint = hints[0] hints = hints[1:] #Build the query to send to that server identifier = randint(0, 65535) question = dns.message.Question(hostname, Type.A, Class.IN) header = dns.message.Header(identifier, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = 0 query = dns.message.Message(header, [question]) #Try to get a response response = self.ask_server(query, hint) if response == None:#We didn't get a response for this server, so check the next one print("Server at " + hint + " did not respond.") continue #Analyze the response for answer in response.answers + response.additionals:#First get the aliases if answer.type_ == Type.CNAME and answer.rdata.data not in aliases: aliaslist.append(answer.rdata.data) for answer in response.answers:#Then try to get an address if answer.type_ == Type.A and (answer.name == hostname or answer.name in aliaslist): ipaddrlist.append(answer.rdata.data) if ipaddrlist != []: print("We found an address using the recursive search!") return hostname, aliaslist, ipaddrlist else: for nameserver in response.authorities: if nameserver.type_ == Type.NS:#Do a lookup for that ns? #print(nameserver.rdata.data) if self.caching: self.cache.add_record(nameserver) hints = [nameserver.rdata.data] + hints print("Recursive search for " + hostname + " was a total failure") return hostname, [], []