def constructQuestion(id, dn): newHeader = Header(id, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1, ancount=0, nscount=0, arcount=0, qr=0, aa=0, tc=0, rd=0, ra=0) neQE = QE(type=QE.TYPE_A, dn=dn) return "{0}{1}".format(newHeader.pack(), neQE.pack())
def parseDNSPacket(data): """ Extracts useful DNS information from a binary format """ newData = { 'header': None, 'question': None, 'answers': None, 'authority': None, 'additional': None } offset = 0 newData['header'] = Header.fromData(data, offset) offset += newData['header'].__len__() newData['question'] = QE.fromData(data, offset) offset += newData['question'].__len__() (newData['answers'], offset) = parseSectionList(data, newData['header']._ancount, offset) (newData['authority'], offset) = parseSectionList(data, newData['header']._nscount, offset) (newData['additional'], offset) = parseSectionList(data, newData['header']._arcount, offset) return newData
def parse_response_payload(payload): """ for parsing responses the program receives from nameservers """ header = Header.fromData(payload) byte_ptr = len(header) config = OrderedDict(zip(["question" , "answer", "authority", "additional"], ["_qdcount", "_ancount", "_nscount", "_arcount"])) parsed = {"header" : header} for key, val in config.items(): #the question section isn't parsed as a RR, needs special treatment if key is "question": #assumes only ever receive one question entry if getattr(header, val) > 1: raise Exception("Uh oh!") question = QE.fromData(payload, byte_ptr) parsed[key] = [question,] byte_ptr += len(question) else: num_entries = getattr(header, val) rrs, byte_ptr = ([], byte_ptr) if num_entries is 0 else parse_rrs(payload, byte_ptr, num_entries) parsed[key] = rrs return parsed
def construct_A_query(domain_name): """ Constructs a query for an A record for a given domain name domain name is the domain name we're querying about """ #generate a 16 bit random number - Header class ensures this is packed correctly h_id = randint(0, 65535) header = Header(h_id, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount = 1, ancount=0, nscount=0, arcount=0, qr=False, aa = False, tc=False, rd=False, ra=False) if not isinstance(domain_name, DomainName): raise Exception("construct_A_query didnt receive a domain name of type DomainName") question = QE(type=QE.TYPE_A, dn=domain_name) return ("{0}{1}".format(header.pack(),question.pack()), question)
def insertRRStoCache( reply, offset, header ): #This function takes a payload and inserts them into the CACHE ans = {} ns = {} addit = {} nsarray = [] ararray = [] anarray = [] header = Header.fromData(reply) qe = QE.fromData(reply, len(header)) for x in range(header._ancount): rr = RR.fromData(reply, offset) cache = CacheEntry(expiration=int(time()) + rr[0]._ttl, authoritative=False) ans[x] = rr, cache offset = offset + rr[1] anarray.append(ans) for x in range(header._nscount): rr = RR.fromData(reply, offset) cache = CacheEntry(expiration=int(time()) + rr[0]._ttl, authoritative=True) ns[x] = rr, cache offset = offset + rr[1] nsarray.append(ns) for x in range(header._arcount): rr = RR.fromData(reply, offset) cache = CacheEntry(expiration=int(time()) + rr[0]._ttl, authoritative=False) addit[x] = rr, cache offset = offset + rr[1] ararray.append(addit) return ans, ns, addit
def storeInCache(reply): header = Header.fromData(reply) qe = QE.fromData(reply, len(header)) count = header._ancount + header._nscount + header._arcount offset = len(header) + len(qe) (ans, ns, addit) = insertRRStoCache(reply, offset, header) cache[qe._dn] = ans, ns, addit
def parseDNS( data, reply ): #This function parses the DNS payload and converts them into a nameserver, ip address dictionary and nameserver array header = Header.fromData(data) qe = QE.fromData(data, len(header)) (nsarray, offset) = getNS(reply, len(header) + len(qe)) (ararray, offset) = getAR(reply, offset) nsIPdict = mapValues(nsarray, ararray) return nsIPdict, nsarray
def print_dns_payload(data): """ Used for debugging """ logger.log(DEBUG1, "payload (length:{0} bytes) received is:\n{1}\n".format(len(data), hexdump(data))) logger.log(DEBUG1, "Header received is:\n{0}\n".format(Header.fromData(data))) header_len = len(Header.fromData(data)) question = QE.fromData(data, header_len) logger.log(DEBUG1, "Question received is:\n{0}\n".format(question))
def queryCNAME(id, question, destination, data): addToCNameCache(data._dn, data._cname, data._ttl) newQuestion = QE(dn=data._cname) result = recursiveQuery(id, newQuestion, ROOTNS_IN_ADDR, True) cnameAnswer = result['answer'] result['answer'] = RR_A(question._dn, cnameAnswer._ttl, cnameAnswer._addr) return result
def queryServer(domain, serverAddress, cnames): timeNow = int(time()) if (timeNow - requestReceived) > 60 - TIMEOUT: #check that you haven't been querying for more than 60 seconds raise timeout _id = randint(0, 65535) #random 16 bit int queryHeader = Header(_id, Header.OPCODE_QUERY, Header.RCODE_NOERR, 1).pack() question = QE(dn=domain).pack() packet = queryHeader + question cs.sendto(queryHeader + question, (serverAddress, 53)) (data, address) = cs.recvfrom(512) return handleResponse(data, _id, cnames)
def sendDNSQuery( data, address): #This function takes a payload and an address and sends them printQuestion(data, address) cs.sendto(data, (address, 53)) ( reply, temp_address, ) = cs.recvfrom(512) header = Header.fromData(reply) if (header._ancount > 0): #Checks if the response has answers qe = QE.fromData(data, len(header)) rr = RR.fromData(reply, len(header) + len(qe)) if rr[0]._type == 5: return queryCNAME( data, reply, rr[0]._cname ) #If CNAME is in the answer then it is converted into IP address else: return reply else: try: (nsIPdict, nsarray) = parseDNS(data, reply) except AttributeError: qe = QE.fromData(reply, len(header)) rr = RR.fromData(reply, len(header) + len(qe)) if rr[0]._type == 6: return reply if bool( nsIPdict ) == False: #If a nameserver didnt respond with any IP we create a new question and send in to the root server for x in range(len(nsarray)): #print nsarray[x][0]._nsdn newData = constructQuestion(header._id, nsarray[x][0]._nsdn) newReply = sendDNSQuery(newData, ROOTNS_IN_ADDR) anarray = getAnswers(newReply) for y in range(len(anarray)): ip = inet_ntoa(anarray[y][0]._inaddr) return sendDNSQuery(data, ip) return performRecursiveQuery( data, nsIPdict, nsarray) #If no answers in response then a recursive query is made
def addAdditionalRecords(responseRRList): ''' For a given set of query answers, search cache and append corresponding AUTHORITY and ADDITIONAL sections Resent query of an authority record if its glue record does not exist ''' # Identify corresponding domain for AUTHORITY and ADDITIONAL sections targetDomain = responseRRList[-1]._dn.parent() if responseRRList[-1]._dn.parent() != None else "." while True: # If this domain exists in nscache if targetDomain in nscache: # Search in nscache and append matched results for currentNSCache in nscache[targetDomain].keys(): responseRRAuthorityList.append(RR_NS(targetDomain, nscache[targetDomain][currentNSCache]._expiration, currentNSCache)) responseHeader._nscount += 1 # If corresponding glue record exists, append it if currentNSCache in acache: for currentACache in acache[currentNSCache]._dict.keys(): responseRRGlueList.append(RR_A(currentNSCache, acache[currentNSCache]._dict[currentACache]._expiration, currentACache.toNetwork())) responseHeader._arcount += 1 # If corresponding glue record does not exist, send query of it else: # Only query valid authoritative records, discard SOA type if currentNSCache not in invalidRRAuthority: print currentNSCache print "\nOne or more additional glue record is missing, searching...." (processHeader, processRR) = resolveQuery(clientQueryHeader, QE(dn=currentNSCache), glueMode = True) for currentOne in processRR: if currentOne._type == RR.TYPE_A: responseRRGlueList.append(currentOne) responseHeader._arcount += 1 else: pass break # If this domain does not exist in nscache, try its parent domain else: targetDomain = targetDomain.parent() if targetDomain.parent() != None else "." # Search and save authoritative records with invalid glue records (no A type) # Saved invalid authoritative records will not be queried again for currentGlue in responseRRGlueList: responseRRGlueDN.append(currentGlue._dn) for currentAuthority in responseRRAuthorityList: if currentAuthority._nsdn not in responseRRGlueDN: invalidRRAuthority.append(currentAuthority._nsdn) return responseRRAuthorityList, responseRRGlueList
def packReply( data, reply ): #This function packs the response payload by first change the header variables, question variables and individulaly packing the RR replyheader = Header.fromData(reply) replyqe = QE.fromData(reply, len(replyheader)) count = replyheader._ancount + replyheader._nscount + replyheader._arcount offset = len(replyheader) + len(replyqe) rr_entries = [] for x in range(count): rr = RR.fromData(reply, offset) rr_entries.append(rr) offset = offset + rr[1] dataHeader = Header.fromData(data) replyheader._id = dataHeader._id dataqe = QE.fromData(data, len(dataHeader)) replyqe._type = dataqe._type replyqe._dn = dataqe._dn #check this rr_entries[0][0]._dn = dataqe._dn packed_rr = "" for x in range(0, len(rr_entries)): packed_rr = packed_rr + rr_entries[x][0].pack() return "{0}{1}{2}".format(replyheader.pack(), replyqe.pack(), packed_rr)
def getAnswers( reply ): #This function takes a payload and returns an array that has the answer records header = Header.fromData(reply) qe = QE.fromData(reply, len(header)) offset = len(header) + len(qe) answerCount = header._ancount answers = [] #print answerCount for x in range(answerCount): rr = RR.fromData(reply, offset) answers.append(rr) offset = offset + rr[1] return answers
def constructErrorHeader(data): #Constructs a server fail response header = Header.fromData(data) id = header._id qe = QE.fromData(data, len(header)) newHeader = Header(id, Header.OPCODE_QUERY, Header.RCODE_SRVFAIL, qdcount=1, ancount=0, nscount=0, arcount=0, qr=0, aa=0, tc=0, rd=0, ra=0) return "{0}{1}".format(newHeader.pack(), qe.pack())
def checkAuthorityRecords(id, question, data, seenCNAME): """ If no additional record is found, check the authority records """ filterAuthorityRecords(data) for authority in data['authority']: if authority._type != RR.TYPE_NS: continue newQuestion = QE(dn=authority._nsdn) result = recursiveQuery(id, newQuestion, ROOTNS_IN_ADDR, seenCNAME) if 'rcode' in result and result['rcode'] == Header.RCODE_NOERR: result1 = recursiveQuery(id, question, inet_ntoa(result['answer']._addr), seenCNAME) if result1['rcode'] == Header.RCODE_NOERR: return result1 return {'rcode': Heade.RCODE_SRVFAIL}
def parseRequest(data): requestHeader = Header.fromData(data) try: assert(requestHeader._opcode == Header.OPCODE_QUERY) assert(requestHeader._rcode == Header.RCODE_NOERR) assert(requestHeader._qr == False) except AssertionError: raise ValueError("Incorrectly formatted request") noOfQueries = requestHeader._qdcount offset = requestHeader.__len__() queries = [] for i in range(noOfQueries): queries.append(QE.fromData(data, offset)) offset += queries[i].__len__() if str(queries[i]) == "NIMPL": raise NotImplementedError return (requestHeader._id, queries)
def get_ip_addr(qe, dns_server_to_send=ROOTNS_IN_ADDR): if qe._dn in acache: print "\nIP address of question entry found in cache: question =", qe._dn return_header = Header(randint(0, 65536), Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1, qr=True, aa=True) return_rrs = [] for key in acache[qe._dn]._dict.keys(): return_rrs.append(RR_A(qe._dn, acache[qe._dn]._dict[key]._expiration, key.toNetwork())) return_header._ancount += 1 return return_header, return_rrs if qe._dn in cnamecache: print "\nCNAME found in cache - starting search for IP address of canonical name ", cnamecache[qe._dn]._cname cname_qe = QE(dn=cnamecache[qe._dn]._cname) (return_header, return_rrs) = get_ip_addr(cname_qe) return_header._ancount += 1 return_rrs.insert(0, RR_CNAME(qe._dn, cnamecache[qe._dn]._expiration, cnamecache[qe._dn]._cname)) return return_header, return_rrs # if dns server to send is root, check whether parent domain of query exists in cache if dns_server_to_send == ROOTNS_IN_ADDR and qe._dn.parent() is not None: dn_runner = qe._dn.parent() while dn_runner.parent() is not None: if dn_runner in nscache: print "\nName server for parent domain found in cache: parent domain =", dn_runner for key in nscache[dn_runner].keys(): if key in acache: for ip in acache[key]._dict.keys(): print "Next authoritative DNS name server domain is:", key print "Next authoritative DNS name server IP is:", ip try: return get_ip_addr(qe, dns_server_to_send=str(ip)) except Exception, e: if e.message != "authoritative DNS name server down": print "Unhandled Exception:", e print "" raise e print "\nauthoritative DNS name server down, trying next one" break dn_runner = dn_runner.parent()
def resolve_iterative(question_domain, nameserver=DomainName(ROOTNS_DN)): logger.info("SEP: ITERATIVE START: '{}' => '{}'".format( question_domain, nameserver)) # Nameserver itself should be resolved if nameserver not in acache: resolve_recursive(nameserver) # If it still isn't resolved, ignore it if nameserver not in acache: logger.info("SEP3: ITERATIVE ERROR: '{}' => '{}'".format( question_domain, nameserver)) return [], [], [] nameserver_ip = acache[nameserver]._dict.keys()[0] # Check A cache if question_domain in acache: logger.info("Domain is in A-Cache") return [acache[question_domain]], [], [] # Check CNAME cache if question_domain in cnamecache: logger.info("Domain is in CNAME-Cache") return [cnamecache[question_domain]], [], [] # At this point the domain is definitely not in the cache, so we'll ask the nameserver # Build the request request_hdr = Header(id=randint(0, 65536), opcode=Header.OPCODE_QUERY, rcode=Header.RCODE_NOERR, qdcount=1) request_question = QE(dn=question_domain) request_data = request_hdr.pack() + request_question.pack() response = None response_hdr = None # ------------------------------------------------------------------------------------------ # We'll send the UDP data to ask the nameserver about the question domain for _ in range(2): logger.info("Requesting '{}' to nameserver: '{}' ({})".format( question_domain, nameserver, nameserver_ip)) # Send data to server cs.sendto(request_data, (str(nameserver_ip), 53)) # Wait for reply try: cs.settimeout(2) response, _ = cs.recvfrom(512) # Build response header response_hdr = Header.fromData(response) # If a proper response is received, break if response_hdr._id == request_hdr._id: break except timeout: logger.info("Nameserver '{}' timed out".format(nameserver)) if response is not None: break if response is None: logger.info("SEP3: ITERATIVE ERROR: '{}' => '{}'".format( question_domain, nameserver)) return [], [], [] # ------------------------------------------------------------------------------------------ # Process the returned resource records # Skip header and question entry resource_record_head = len(request_data) resource_record_quantity = response_hdr._ancount + response_hdr._nscount + response_hdr._arcount rr_answers = [] rr_authoritative = [] rr_additional = [] # Go over each resource record for curr_resource_record_index in range(resource_record_quantity): # Fetch the current resource record curr_resource_record, rr_size = RR.fromData(response, resource_record_head) # Move the reading head resource_record_head += rr_size # Record will be authoritative if it's in the AUTHORITY SECTION (hence after the ANSWERS section) is_answer = curr_resource_record_index < response_hdr._ancount is_authoritative = not is_answer and curr_resource_record_index < response_hdr._ancount + response_hdr._nscount is_additional = not (is_answer or is_authoritative) # Store the current resource record if curr_resource_record._type in [ RR.TYPE_A, RR.TYPE_NS, RR.TYPE_CNAME ]: if is_answer: rr_answers.append(curr_resource_record) elif is_authoritative: rr_authoritative.append(curr_resource_record) else: rr_additional.append(curr_resource_record) # Record type A if curr_resource_record._type == RR.TYPE_A: record_address = InetAddr.fromNetwork(curr_resource_record._inaddr) # logger.info("Adding 'A' record for {}: {}".format(curr_resource_record._dn, record_address)) logger.info(curr_resource_record) # Add to A-Cache if curr_resource_record._dn not in acache: acache[curr_resource_record._dn] = ACacheEntry({ record_address: CacheEntry(expiration=curr_resource_record._ttl, authoritative=is_authoritative) }) # Update A-Cache else: acache[curr_resource_record. _dn]._dict[record_address] = CacheEntry( expiration=curr_resource_record._ttl, authoritative=is_authoritative) # Record type NS elif curr_resource_record._type == RR.TYPE_NS: # logger.info("Adding 'NS' record for {}: {}".format(curr_resource_record._dn, curr_resource_record._nsdn)) logger.info(curr_resource_record) # Add to NS-Cache if curr_resource_record._dn not in nscache: nscache[curr_resource_record._dn] = OrderedDict({ curr_resource_record._nsdn: CacheEntry(expiration=curr_resource_record._ttl, authoritative=True) }) # Update A-Cache else: nscache[curr_resource_record._dn][ curr_resource_record._nsdn] = CacheEntry( expiration=curr_resource_record._ttl, authoritative=True) # Record type CNAME elif curr_resource_record._type == RR.TYPE_CNAME: # logger.info("Adding 'CNAME' record for {}".format(curr_resource_record._dn)) logger.info(curr_resource_record) cnamecache[curr_resource_record._dn] = CnameCacheEntry( curr_resource_record._cname, expiration=curr_resource_record._ttl) else: logger.info(curr_resource_record) logger.info( "Received {} resource records ({} answers, {} authoritative, {} additional)" .format(resource_record_quantity, len(rr_answers), len(rr_authoritative), len(rr_additional))) # if len(rr_answers): # logger.info(rr_answers[0]) # elif len(rr_authoritative): # logger.info(rr_authoritative[0]) logger.info("SEP3: ITERATIVE END : '{}' => '{}'".format( question_domain, nameserver)) return rr_answers, rr_authoritative, rr_additional
# ------------------------------------------------------------------ # Parse the request header = Header.fromData(data) # Ignore anything other than requests if header._qr != 0: logger.info("Ignoring non-request...") continue logger.info("Received request ({} bytes). Header:".format(len(data))) logger.info(header) # Parse current question entry question = QE.fromData(data, 12) logger.info("Received question:") logger.info(question) # Only respond to A and CNAME queries if question._type != QE.TYPE_A: logger.info("Ignoring request of type other than A...") continue answers = [] authorities = [] additionals = [] # Resolve question entry, with a time limit of 60 seconds try:
if rrec._type == RR.TYPE_CNAME: cnamecache[rrec._dn] = CnameCacheEntry(rrec._cname, expiration=rrec._ttl) if rrec._type == RR.TYPE_NS: if rrec._dn not in nscache: nscache[rrec._dn] = OrderedDict([(rrec._nsdn, CacheEntry(expiration=rrec._ttl, authoritative=True))]) else: nscache[rrec._dn][rrec._nsdn] = CacheEntry(expiration=rrec._ttl, authoritative=True) offset += offset_inc # If answer exist, send answer (and all RRs from last response received) to client. # Else, check authority & additional section to determine next DNS name server to send query. if response_header._ancount > 0: if response_rrs[0]._type == RR.TYPE_CNAME: print "CNAME found - starting search for IP address of canonical name ", response_rrs[0]._cname cname_qe = QE(dn=response_rrs[0]._cname) (return_header, return_rrs) = get_ip_addr(cname_qe) return_header._ancount += 1 return_rrs.insert(0, response_rrs[0]) return return_header, return_rrs else: return response_header, response_rrs authority_rrs = response_rrs[:response_header._nscount] additional_rrs = response_rrs[-response_header._arcount:] tried = [] for ns in authority_rrs: if ns._type == RR.TYPE_NS: for add in additional_rrs:
def check_cache( data ): #This function checks if the question from the client is in the cache header = Header.fromData(data) id = header._id qe = QE.fromData(data, len(header)) domain = qe._dn if domain in cache: #Checks if the question is in the cache dictionary else queries the root server as normal ans = cache[qe._dn][0] ns = cache[qe._dn][1] addit = cache[qe._dn][2] keys = ans.keys() values = ans.values() anarray = [] nsarray = [] ararray = [] packed_rr = "" for x in range( len(ans) ): #These for loops gets the RR from cache and updates the expiry time rr = ans.values()[x][0][0] cac = ans.values()[x][1] expiry = cac._expiration - int(time()) rr._ttl = expiry if expiry > 0: anarray.append(rr) packed_rr = packed_rr + rr.pack() #print len(anarray) if len(anarray) > 0: for x in range(len(ns)): rr = ns.values()[x][0][0] cac = ns.values()[x][1] expiry = cac._expiration - int(time()) rr._ttl = expiry if expiry > 0: nsarray.append(rr) packed_rr = packed_rr + rr.pack() for x in range(len(addit)): rr = addit.values()[x][0][0] cac = addit.values()[x][1] expiry = cac._expiration - int(time()) rr._ttl = expiry if expiry > 0: ararray.append(rr) packed_rr = packed_rr + rr.pack() newHeader = Header(id, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1, ancount=len(anarray), nscount=len(nsarray), arcount=len(ararray), qr=0, aa=0, tc=0, rd=0, ra=0) return "{0}{1}{2}".format(newHeader.pack(), qe.pack(), packed_rr) else: return sendDNSQuery(data, ROOTNS_IN_ADDR) else: return sendDNSQuery(data, ROOTNS_IN_ADDR)
signal.signal(signal.SIGALRM, handler) signal.alarm(60) ##clean out expired records before we handle this query update_caches() response = construct_response(*resolve(0, qid, question, qname, qname, get_best_ns(nscache, qname), [], [], [])) signal.alarm(0) except (OutOfTimeException) as e: # Server Failure logger.error(e) signal.alarm(0) response = construct_response(qid, question, [], [], [], RCODE=Header.RCODE_SRVFAIL) return response # This is a simple, single-threaded server that takes successive # connections with each iteration of the following loop: while 1: logger.log(DEBUG1, "\n"*40) logger.log(DEBUG1, "="*400) (data, address,) = ss.recvfrom(512) # DNS limits UDP msgs to 512 bytes if not data: log.error("client provided no data") continue header = Header.fromData(data) qid = header._id header_len = len(header) question = QE.fromData(data, header_len) qname = DomainName.fromData(data, header_len) response = exc(qid, qname, question) ss.sendto(response, address)
def resolveQuery(clientQueryHeader, clientQueryQE, RaiseException = False, glueMode = False): ''' Resolve a query for a given QE The function can also be called resursivly with RaiseException = True, see later The function can additionally be called when querying the address of a glue record with glueMode = True, see later ''' # Initialise reponses resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) resolverReplyRRAuthority = [] resolverReplyRRGlue = [] resolverReplyRRCNAME = [] usedNameServer = [] # Copy client's query QE resolverQueryQE = QE(clientQueryQE._type, clientQueryQE._dn) # Set root nameserver as default queryNameServer = ROOTNS_IN_ADDR # Keep querying until return while True: print "\n------------------------------------\n" print "sending query of:",resolverQueryQE print "nameserver:", queryNameServer # Check if current query is cached (cacheType, cacheHeader, cacheRR) = checkCache(resolverQueryHeader, resolverQueryQE) # If A type cache exists, append its CNAME and return results if cacheType == 'a': print "Cache hit (A) for query: ", resolverQueryQE._dn cacheRR = resolverReplyRRCNAME + cacheRR cacheHeader._ancount = len(cacheRR) return cacheHeader, cacheRR # If CNAME cache exists, keep searching all its CNAME in cache elif cacheType == 'cname': print "Cache hit (CNAME) for query: ", resolverQueryQE._dn resolverReplyRRCNAME.append(cacheRR) cacheQueryQE = QE(clientQueryQE._type, cacheRR._cname) while cacheType != 'none': (cacheType, cacheHeader, cacheRR) = checkCache(Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1), cacheQueryQE) # If A record is found in cache, return if cacheType == 'a': print "Cache hit (A) for query: ", cacheQueryQE._dn cacheRR = resolverReplyRRCNAME + cacheRR cacheHeader._ancount = len(cacheRR) return cacheHeader, cacheRR # If another CNAME is found, append it and keep searching next one in cache elif cacheType == 'cname': print "Cache hit (A) for query: ", cacheQueryQE._dn resolverReplyRRCNAME.append(cacheRR) cacheQueryQE._dn = cacheRR._cname # If no A record found in cache, break loop and start a query else: print "Cache miss for query: ", cacheQueryQE._dn break # Initialise query header, QE and name server resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) resolverQueryQE._dn = cacheRR._cname queryNameServer = ROOTNS_IN_ADDR # No cached answer else: print "Cache miss for query: ", resolverQueryQE._dn pass # Keep querying a name server for twice before giving up and catch timeout for each query cs.sendto(resolverQueryHeader.pack()+resolverQueryQE.pack(), (queryNameServer, 53)) try: resolverReplyID = 0 while resolverQueryID != resolverReplyID: exceptionFlag = False cs.settimeout(1) (resolverReply, resolverReplyAddress, ) = cs.recvfrom(512) cs.settimeout(None) resolverReplyID = Header.fromData(resolverReply)._id except timeout: cs.settimeout(None) print "Target Name Server is not responding, attempt: 1/2" print "\nDouble timeout period and retrying..." # If last attempt failed, double the timeout period and try again cs.sendto(resolverQueryHeader.pack()+resolverQueryQE.pack(), (queryNameServer, 53)) try: resolverReplyID = 0 while resolverQueryID != resolverReplyID: exceptionFlag = False cs.settimeout(2) (resolverReply, resolverReplyAddress, ) = cs.recvfrom(512) cs.settimeout(None) resolverReplyID = Header.fromData(resolverReply)._id break except timeout: cs.settimeout(None) print "Target Name Server is not responding, attempt: 2/2" exceptionFlag = True # If query finally failed, change queryNameServer accordingly if exceptionFlag == True: # If root name server is not reponsive, give up query if queryNameServer == ROOTNS_IN_ADDR: print "\nRoot name server is not responding, abandoning query" return Header(clientQueryHeader._id, clientQueryHeader._opcode, Header.RCODE_SRVFAIL, qdcount= clientQueryHeader._qdcount, qr=True, rd=clientQueryHeader._rd, ra=True), [] # If other NS records exist, select another name server for the same zone elif len(resolverReplyRRAuthority) > 1: print "\nTLD or authoritative name server is not responding, finding alternatives" # Save the failed name server to prevent further use usedNameServer.append(queryNameServer) # Find an alternative name server for currentRRAuthority in resolverReplyRRAuthority: if currentRRAuthority._type == RR.TYPE_NS: for currentRRGlue in resolverReplyRRGlue: if currentRRAuthority._nsdn == currentRRGlue._dn and inet_ntoa(currentRRGlue._inaddr) not in usedNameServer: queryNameServer = inet_ntoa(currentRRGlue._inaddr) resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) break break # Otherwise, give up query else: print "\nTLD or authoritative name server is not responding, no alternatives found" return Header(clientQueryHeader._id, clientQueryHeader._opcode, Header.RCODE_SRVFAIL, qdcount= clientQueryHeader._qdcount, qr=True, rd=clientQueryHeader._rd, ra=True), [] # If query is successful, process resource records else: resolverReplyHeader = Header.fromData(resolverReply) print "\nResponse received:" print resolverReplyHeader resolverReplyRR = [] offset = len(resolverQueryHeader.pack()+resolverQueryQE.pack()) for currentRecordIndex in range(resolverReplyHeader._ancount + resolverReplyHeader._nscount + resolverReplyHeader._arcount): (currentRecord, currentRecordOffset) = RR.fromData(resolverReply, offset) resolverReplyRR.append(currentRecord) print currentRecord authoritativeFlag = True if resolverReplyHeader._aa == 1 else False # Save current record in cache saveToCache(currentRecord, authoritativeFlag) offset += currentRecordOffset # If answer exists, classify the answer section if resolverReplyHeader._ancount > 0: # If type A answer is found, return it with all its CNAME records if resolverReplyRR[0]._type == RR.TYPE_A: print "\nAddress answer found" resolverReplyRR = resolverReplyRR[0:resolverReplyHeader._ancount] resolverReplyRR = resolverReplyRRCNAME + resolverReplyRR resolverReplyHeader._ancount = len(resolverReplyRR) return resolverReplyHeader, resolverReplyRR # If type CNAME answer is found, save to cache and send query for the CNAME elif resolverReplyRR[0]._type == RR.TYPE_CNAME: print "\nCNAME found" resolverReplyRRCNAME.append(resolverReplyRR[0]) resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) resolverQueryQE._dn = resolverReplyRR[0]._cname queryNameServer = ROOTNS_IN_ADDR # Discard other types of answers else: print "Unknown answer type, please verify your query\n" return Header(clientQueryHeader._id, clientQueryHeader._opcode, Header.RCODE_SRVFAIL, qdcount= clientQueryHeader._qdcount, qr=True, rd=clientQueryHeader._rd, ra=True), [] # If no answer is found, keep querying with NS type records elif resolverReplyHeader._ancount == 0: print "\nNo answer received, processing AUTHORITY and ADDITIONAL sections" resolverReplyRRAuthority = resolverReplyRR[:resolverReplyHeader._nscount] # Filter glue records resolverReplyRRGlue = [] for currentRRGlue in resolverReplyRR[-resolverReplyHeader._arcount:]: if currentRRGlue._type == RR.TYPE_A: resolverReplyRRGlue.append(currentRRGlue) # Determine whether correct glue records are available glueRecordFlag = False for currentRRAuthority in resolverReplyRRAuthority: if currentRRAuthority._type == RR.TYPE_NS: for currentRRGlue in resolverReplyRRGlue: # Correct glue record found, use the glue record for next query if currentRRAuthority._nsdn == currentRRGlue._dn: glueRecordFlag = True queryNameServer = inet_ntoa(currentRRGlue._inaddr) resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) # If no glue record is found, determine whether correct authoritative records available if glueRecordFlag == False: matchFlag = False for currentRRAuthority in resolverReplyRRAuthority: if currentRRAuthority._type == RR.TYPE_NS: matchFlag = True # If correct authoritative records available, send query of its address if matchFlag: for currentRRAuthority in resolverReplyRRAuthority: if currentRRAuthority._type == RR.TYPE_NS: try: (missingHeader, missingRR) = resolveQuery(clientQueryHeader, QE(dn = currentRRAuthority._nsdn), True) queryNameServer = inet_ntoa(missingRR[0]._inaddr) resolverQueryID = randint(1, 65535) resolverQueryHeader = Header(resolverQueryID, Header.OPCODE_QUERY, Header.RCODE_NOERR, qdcount=1) break except Exception: pass # If no correct authoritative records available, query/answer is invalid (Most likely SOA type). else: # This flag is set only when this function is called recursively (i.e.querying for address of authoritative records) if RaiseException: print "\nInvalid query/answer type: Re-trying" raise Exception("Invalid query/answer type: Re-trying") # This flag is set only when this function is called for appending the ADDITIONAL section of given ANSWER and AUTHORITY sections if glueMode: print "\nInvalid answer type to the glue record query, cannot be cached or returned" return resolverReplyHeader, [] # Client query is invalid, re-try until timeout else: print "\nInvalid query/answer type: please verify your query" sleep (1) continue # DNS packet received from name server is corrupted, re-try else: print "\nDNS packet corrupted: Answer number is negative\nRe-trying...\n" continue
def printQuestion(data, address): header = Header.fromData(data) qe = QE.fromData(data, len(header)) print "Looking up " print qe._dn print address
def parseQuestions(data, offset, noOfQuestions): questions = [] for i in range(noOfQuestions): questions.append(QE.fromData(data, offset)) offset += len(questions[i])#increase offset by length of previous question return (offset, questions)
def parseQuery(data): ''' Parse a DNS query with fromData() provided in Header and QE classes ''' return Header.fromData(data), QE.fromData(data, 12)