def resolve_query(header_id, initial_domain_name, domain_name, current_ip, initial_ip, is_cname): print("iteration") ''' This is the main "powerhouse" function that serves a client request. Upon receiveing a domain name, it first checks if it is contained in tha cache. If the cache contains information about the requested domain name, the function returns it. The function will recursively search for the domain name asked for as specified in the lecture notes. Cache also helps reducing queries by providing sub-domains for a domain name. :param header_id: ID of the initial client query :param initial_domain_name: the domain name the client was requesting :param domain_name: the domain name this function is currently trying to resolve :param current_ip: the current IP address we want to send a follow-up query to :param initial_ip: the first IP address that our LNS sends a query to (always the Root Server in our case) :param is_cname: :return: a dictionary of lists containing RRs categorized by the section they belong to in the packet ''' global acache global cnamecache global nscache global global_cname rr_return = {'answers': [], 'authorities': [], 'additional': [], 'additional_A': []} # Only for the very first iteration (where domain_name == initial_domain_name) check if the requested domain name # is inside the cache if acache.contains(domain_name) and domain_name == initial_domain_name and not global_cname: print("containsx") # Get the IP and ttl from the cache and construct an RR_A object for answer section ip = acache.getIpAddresses(domain_name) ttl = acache.getExpiration(domain_name, ip[0]) rr_answer = RR_A(domain_name, ttl, ip[0]) #ip[0].toNetwork() if ip[0] is not in byte form # Append the RR_A into the dictionary's answer section rr_return['answers'].append(rr_answer) # Constructing the authority section by looping through all possible sub-domains of domain_name and get the # highest-qualified one that exists in the nscache lowest_domain = get_subdomain_order(domain_name) for i in range(len(lowest_domain)): this_domain = DomainName(lowest_domain[i]) if nscache.contains(this_domain): # Get the list of name servers associated with this domain name something = nscache.get(this_domain) for j in range(len(something)): # For each name server associated with this domain name, retrieve the ttl and construct an # RR_NS object and add it to the authority section of the dictionary ttl = something[j][1] nsdn = nscache.get_nsdn(this_domain) rr_authorities = RR_NS(this_domain, ttl, nsdn[j]) rr_return['authorities'].append(rr_authorities) # Check the IP address of all the name servers in the authority section, create an RR_A object and # add it to the additional section of the dictionary if acache.contains(nsdn[j]): this_ip = acache.getIpAddresses(nsdn[j]) this_ttl = acache.getExpiration(nsdn[j], this_ip[0]) rr_additional = RR_A(nsdn[j], this_ttl, this_ip[0]) rr_return['additional'].append((rr_additional)) # If we found an existing sub-domain, look no further break return rr_return # During the first iteration check what is the highest-qualified sub-domain that exists in the cache. If we find such # sub-domain, jump straight to that sub-domain's IP address instead of doing more queries and causing traffic if current_ip == initial_ip and not is_cname and not global_cname: lowest_domain = get_subdomain_order(domain_name) for i in range(len(lowest_domain)): this_domain = DomainName(lowest_domain[i]) if nscache.contains(this_domain): # Once we find that sub-domain, retrieve its IP address from the cache and recursively call # serve_request() using the new IP address as target nsdn = nscache.get_nsdn(this_domain) next_ip = acache.getIpAddresses(nsdn[0]) return resolve_query(header_id, initial_domain_name, domain_name, next_ip, initial_ip, is_cname) # Construct the packet send_packet_header = Header(header_id, 0, 0, 1) send_packet_question = QE(1, domain_name) send_packet = send_packet_header.pack() + send_packet_question.pack() # Send the packet to the target IP address and wait for an answer cs.sendto(send_packet, (current_ip, 53)) reply, a = cs.recvfrom(512) # Parse the reply and construct the RRs header_record = parse_header(reply) rr = get_records(reply, header_record['ancount'], header_record['nscount']) # Cache all the RRs; this stage is only reached when these RRs are not duplicate, otherwise we would have reached # them before, without needing to cause traffic if header_record['ancount'] != 0: #len(rr['answers']) != 0: for i in rr['answers']: if i._type == 1: acache.put(domain_name, i._addr, i._ttl) elif i._type == 5: cnamecache.put(domain_name, i._cname, i._ttl) if header_record['nscount'] != 0: for i in rr['authorities']: if i._type == 2: nscache.put(i._dn, i._nsdn, i._ttl, True) if len(rr['additional']) != 0: for i in rr['additional']: if i._type == 1: acache.put(i._dn, i._addr, i._ttl) '''print("acache is:") print(acache.__str__()) print("nscache is:") print(nscache.__str__()) print("cnamecache is:") print(cnamecache.__str__())''' # Main recursive part try: # If there is no answer section and there is an additional section, make the same query targetting the first # RR_A in additional section if len(rr['additional']) != 0: next_ip = bin_to_str(rr['additional_A'].pop(0)._addr) return resolve_query(header_id, initial_domain_name, domain_name, next_ip, initial_ip, is_cname) # If we have an answer section elif header_record['ancount'] != 0: # IF we found the domain name the client originally requested, return thr records if initial_domain_name == domain_name: return rr # If we found a domain name from an authority section, we target this new domain's IP address with our query else: next_ip = bin_to_str(rr['answers'][0]._addr) return resolve_query(header_id, initial_domain_name, initial_domain_name, next_ip, initial_ip, is_cname) # If there is no answer and no RR_A additional section, we need to search for a domain name of the first RR_NS # from the authority section recursively else: next_domain_name = rr['authorities'][0]._nsdn return resolve_query(header_id, initial_domain_name, next_domain_name, ROOTNS_IN_ADDR, initial_ip, is_cname) # If the server we send a question to does not respond, go ask the next server in the additional section except timeout: next_ip = bin_to_str(rr['additional_A'].pop(0)._addr) return resolve_query(header_id, initial_domain_name, initial_domain_name, next_ip, initial_ip, is_cname)
def recurser(question, ipQuerried): queryHeader = Header.fromData(question) queryQE = QE.fromData(question, queryHeader.__len__()) if acache.contains(queryQE._dn): ips = acache.getIpAddresses(queryQE._dn) foundRRA = 0 for ip in ips: ttl = acache.getExpiration(queryQE._dn, ip) - int(time()) if ttl < 0: # too late for this record acache.cache.pop(queryQE._dn) else: answers.append(RR_A(queryQE._dn, ttl, inet_aton(ip))) foundRRA = 1 if foundRRA is 1: newHeader = Header(queryHeader._id, 0, 0, 1, ancount=len(answers)) newQE = QE(dn=queryQE._dn) return newHeader.pack() + newQE.pack() elif cnamecache.contains(queryQE._dn): cn = cnamecache.getCanonicalName(queryQE._dn) ttl = cnamecache.getCanonicalNameExpiration(queryQE._dn) - int(time()) if ttl < 0: cnamecache.cache.pop(queryQE._dn) else: newHeader = Header(queryHeader._id, 0, 0, 1) newQE = QE(dn=cn) reply = recurser(newHeader.pack() + newQE.pack(), ROOTNS_IN_ADDR) if reply != None: answers.append(RR_CNAME(queryQE._dn, ttl, cn)) return reply try: cs.sendto(question, (ipQuerried, 53)) ( nsreply, server_address, ) = cs.recvfrom(2048) # some queries require more space except timeout: return None if len(nsreply) < 43: return None # handle case where there is an empty response # Store these for later use when we want to solve CNAMEs or NSs queryHeader = Header.fromData(nsreply) queryQE = QE.fromData(nsreply, queryHeader.__len__()) originalQ = str(queryQE).split("IN")[0].strip() offset = queryHeader.__len__() + queryQE.__len__() # We'll need these for parsing, trust me minRRLineLen = len(nsreply) - offset - 1 rrCounter = 0 nsAuthorities = [] rra = [] cnames = [] queryRRTuples = [] # Parsing all returned RRs while minRRLineLen < len(nsreply) - offset: # Get next glue line auxRRline = RR.fromData(nsreply, offset) # Append to RR list, update offset queryRRTuples.append(auxRRline) offset += queryRRTuples[rrCounter][1] queryRR = queryRRTuples[rrCounter][0] if queryRR.__class__ == RR_NS: nsAuthorities.append(queryRR) elif queryRR.__class__ == RR_A: rra.append(queryRR) elif queryRR.__class__ == RR_CNAME: cnames.append(queryRR) # Update minimum line length for safety stop if minRRLineLen > auxRRline[1]: minRRLineLen = auxRRline[1] rrCounter += 1 # Start the handling of RRs # Case where we only got NS back if len(rra) == 0 and len(cnames) == 0: for auth in nsAuthorities: reply = recurser(question, str(auth._nsdn)) return reply # Cache NS for later if len(nsAuthorities) > 0: for ns in nsAuthorities: nscache.put(ns._dn, ns._nsdn, ns._ttl + int(time()), authoritative=True) # Cache CNAMEs for later and querry them if len(cnames) > 0: for queryRR in cnames: if cnamecache.contains(queryRR._dn) == False: cnamecache.put(queryRR._dn, queryRR._cname, queryRR._ttl + int(time())) answers.append(queryRR) newHeader = Header(randint(1, 65000), Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) newQE = QE(dn=queryRR._cname) newQuery = newHeader.pack() + newQE.pack() reply = recurser(newQuery, ROOTNS_IN_ADDR) return reply # Cache all RR_As for later, look if we got the one we are looking for if len(rra) > 0: for queryRR in rra: if acache.contains(queryRR._dn) == False: acache.put(queryRR._dn, inet_ntoa(queryRR._addr), queryRR._ttl + int(time()), authoritative=True) parts = queryRR.__str__().split("A") ip = parts[len(parts) - 1].strip() # Found required answer if queryRR._dn == originalQ: # Add all answers counter = 0 while counter < len(rra): if rra[counter]._dn == originalQ: answers.append(rra[counter]) rra.pop(counter) counter += 1 return nsreply else: reply = recurser(question, ip) return reply
append_packet = resolve_query(header_id, dn, dn, ROOTNS_IN_ADDR, ROOTNS_IN_ADDR, False) # Append the answer sections to the final response packet for j in append_packet['answers']: new_packet['additional'].append(j) # Construct the answer, authority and additional sections from 'packet' dictionary response_packet_RR_answer = bytes() response_packet_RR_authorities = bytes() response_packet_RR_additional = bytes() for i in new_packet['answers']: response_packet_RR_answer += i.pack() for i in new_packet['authorities']: response_packet_RR_authorities += i.pack() for i in new_packet['additional']: response_packet_RR_additional += i.pack() # From the received dictionary, construct the header and the question section response_packet_header = Header(header_id, 0, 0, 1, len(new_packet['answers']), len(new_packet['authorities']), len(new_packet['additional']), 1, 1, 0, 1, 1) response_packet_question = QE(1, initial_qe_dn) # Construct the final packet and send it back to the client response_packet_RR = bytes() response_packet_RR = response_packet_RR_answer + response_packet_RR_authorities + response_packet_RR_additional response_packet = response_packet_header.pack() + response_packet_question.pack() + response_packet_RR ss.sendto(response_packet, client_address)
else: # Saving all this data for response reconstrucion later question = data ip = ROOTNS_IN_ADDR initialHeader = Header.fromData(data) initialQE = QE.fromData(data, initialHeader.__len__()) initialId = initialHeader._id # The call to solve the main query nsreply = recurser(question, ip) if nsreply == None: # Respond with name error newHeader = Header(initialId, 0, Header.RCODE_NAMEERR, 1) response = newHeader.pack() + initialQE.pack() else: # Save these because we were using globals (never a good practice - too late now, sue me) finalAns = [] finalAns.extend(answers) responseRRA = "" for ans in finalAns: responseRRA += ans.pack() foundParent = 0 parent = ans._dn # Finding the NS most highly-qualified domain parent for returned DomainNames # These are definitely found in cache as we already parsed them in the main query while foundParent == 0: parent = parent.parent()