@ -23,12 +23,25 @@ import itertools
def dictappend ( seq ) :
"""
Append the [ ( k , v ) ] list to a defaultdict .
"""
s = defaultdict ( list )
# update doesn't work
# .update does not work as expected for this.
for ( k , v ) in seq :
s [ k ] . append ( v )
return s
def ipport ( name ) :
"""
Split a name into an ( ip , port ) pair , defaulting the port to 53
"""
x = name . split ( ' : ' )
if len ( x ) == 1 :
return x [ 0 ] , 53
else :
return x [ 0 ] , int ( x [ 1 ] )
class Resolver ( object ) :
""" DNS resolver """
@ -50,6 +63,9 @@ class Resolver(object):
@classmethod
def query ( cls , query , dest ) :
"""
Send a ` query ` to the destination address at ` dest ` and get a response back
"""
timeout = 2
sock = socket . socket ( socket . AF_INET , socket . SOCK_DGRAM )
sock . settimeout ( timeout )
@ -62,25 +78,39 @@ class Resolver(object):
sock . close ( )
return response
@classmethod
def makepacket ( cls , flags , qs = [ ] , ans = [ ] , ns = [ ] , ar = [ ] , ident = 9001 ) :
header = dns . message . Header ( ident , 0 , len ( qs ) , len ( ans ) , len ( ns ) , len ( ar ) )
for ( k , v ) in flags . items ( ) :
setattr ( header , k , v )
return dns . message . Message ( header , qs , ans , ns , ar )
def gethostbyname ( self , hostname , additionals = [ ] ) :
""" Translate a host name to IPv4 address.
Currently this method contains an example . You will have to replace
this example with example with the algorithm described in section
5.3 .3 in RFC 1034.
def query_dns ( self , hostname , type_ , server , recursive = False ) :
"""
Query a nameserver directly with the arguments as specified
"""
question = dns . message . Question ( hostname , type_ , Class . IN )
query = dns . message . makepacket ( { " qr " : 0 , " opcode " : 0 , " rd " : recursive } , [ question ] )
print " asking " , server
response = Resolver . query ( query , server )
if response . header . rcode == RCode . ServFail :
raise Exception ( " server failure " )
return response
Args :
hostname ( str ) : the hostname to resolve
def best_ns_from_cache ( self , hostname ) :
"""
This function finds the nearest nameserver for this hostname in the cache .
"""
hs = hostname
a_list = [ ]
while hs : # look up NS servers in cache with decreasing fqdn
lookedup = self . cache . lookup ( hs , Type . NS , Class . IN )
for n in lookedup :
a_list + = self . cache . lookup ( n . rdata . data , Type . A , Class . IN )
if lookedup :
return ( a_list , lookedup )
hs = ' . ' . join ( hs . split ( ' . ' ) [ 1 : ] )
return [ ] , None
Returns :
( str , [ str ] , [ str ] ) : ( hostname , aliaslist , ipaddrlist )
def lookup ( self , type_ , hostname , additionals = [ ] ) :
"""
Look up all of the relevant resource records , either from cache
or by going over the network .
This is used by the server , and also by gethostbyname .
The algorithm is specified in RFC 1034 , but copied into the comments here .
"""
SNAME = hostname
@ -89,42 +119,41 @@ class Resolver(object):
# 1. See if the answer is in local information, and if so return
# it to the client.
while True :
while type_ != Type . CNAME :
# follow cnames
lookedupcnames = self . cache . lookup ( SNAME , Type . CNAME , Class . IN )
if lookedupcnames :
#print "found CNAMES", SNAME, lookedupcnames
aliases . append ( lookedupcnames [ 0 ] )
#aliases.append(SNAME)
SNAME = lookedupcnames [ 0 ] . rdata . data
aliases . append ( SNAME )
continue
break
lookedup = self . cache . lookup ( SNAME , Type. A , Class . IN )
lookedup = self . cache . lookup ( SNAME , type_ , Class . IN )
if lookedup :
#print "got result from cache", SNAME
return SNAME , aliases , [ d . rdata . data for d in lookedup ]
return SNAME , aliases , lookedup
# 2. Find the best servers to ask.
hs = hostname
SLIST = [ ]
while hs : # look up NS servers in cache with decreasing fqdn
lookedup = self . cache . lookup ( hs , Type . NS , Class . IN )
ns_a_list , lookedup = self . best_ns_from_cache ( hostname )
if lookedup :
for n in lookedup :
# TODO: use ns_a_list
SLIST . append ( ( False , n . rdata . data ) )
if lookedup :
#print "got NS from cache", hs
break
hs = ' . ' . join ( hs . split ( ' . ' ) [ 1 : ] )
else :
nameservers = [ ns . rdata . data for ns in self . SBELT . records[ ' . ' ] ]
nameservers = [ ns . rdata . data for ns in self . SBELT . lookup ( ' . ' ) [ 1 ] ]
SLIST = [ ]
for ns in nameservers :
try :
SLIST . append ( ( True , self . SBELT . records [ ns ] [ 0 ] . rdata . data ) )
ips = [ x . rdata . data for x in self . SBELT . lookup ( ns ) [ 1 ] if x . type_ == Type . A ]
SLIST . append ( ( True , ips [ 0 ] ) )
except KeyError :
SLIST . append ( ( False , ns ) )
while True :
# do the looked up ones first
SLIST . sort ( key = lambda a : a [ 0 ] , reverse = True )
if len ( SLIST ) == 0 :
return [ [ ] , [ ] , [ ] ]
@ -135,7 +164,9 @@ class Resolver(object):
if not isLookedUp :
# XXX: this can get into an infinite loop
#print "looking up name server"
ip , port = ipport ( ip )
ns_hostname , ns_aliases , ns_ips = self . gethostbyname ( ip )
ns_ips = [ i + " : " + str ( port ) for i in ns_ips ]
if len ( ns_ips ) :
ip = ns_ips [ 0 ]
SLIST = ns_ips [ 1 : ] + SLIST
@ -146,15 +177,12 @@ class Resolver(object):
# state 2
# 3. Send them queries until one returns a response.
#print "querying", ip, "with", hostname
question = dns . message . Question ( hostname , Type. A , Class . IN )
query = Resolver . makepacket ( { " qr " : 0 , " opcode " : 0 , " rd " : 0 } , [ question ] )
question = dns . message . Question ( hostname , type_ , Class . IN )
query = dns. message . makepacket ( { " qr " : 0 , " opcode " : 0 , " rd " : 0 } , [ question ] )
# state 3
try :
response = Resolver . query ( query , ( ip , 53 ) )
# temp
if response . header . rcode == RCode . ServFail :
raise Exception ( " server failure " )
except Exception as e :
response = self . query_dns ( hostname , type_ , ipport ( ip ) , False )
except RuntimeError as e :
print e
# d. if the response shows a servers failure or other
# bizarre contents, delete the server from the SLIST and
@ -167,7 +195,7 @@ class Resolver(object):
# error, cache the data as well as returning it back to
# the client.
if response . header . rcode == dns . rcodes . RCode . NXDomain :
print " NXDOMAIN "
print " NXDOMAIN " , SNAME
return ( [ ] , [ ] , [ ] ) # edge 3 -> 6
for ans in ( response . answers + response . additionals + response . authorities ) :
@ -175,26 +203,28 @@ class Resolver(object):
if response . header . aa :
# state 5
answers = dictappend ( [ ( ans . name , ans . rdata . data ) for ans in response . answers if ans . type_ == Type . A ] )
cnames = dictappend ( [ ( ans . name , ans . rdata . data ) for ans in ( response . answers + response . additionals ) if ans . type_ == Type . CNAME ] )
answers = dictappend ( [ ( ans . name , ans ) for ans in response . answers if ans . type_ == type_ ] )
cnames = dictappend ( [ ( ans . name , ans ) for ans in ( response . answers + response . additionals ) if ans . type_ == Type . CNAME ] )
if len ( answers ) + len ( cnames ) == 0 :
return SNAME , aliases , [ ]
while True :
if SNAME in cnames :
if SNAME in cnames and type_ != Type . CNAME :
# state 7
# c. if the response shows a CNAME and that is not the
# answer itself, cache the CNAME, change the SNAME to the
# canonical name in the CNAME RR and go to step 1.
if cnames [ SNAME ] [ 0 ] in aliases :
#if cnames[SNAME][0] in aliases: # XXX Fix this
#print "not following cname loop"
return SNAME , aliases , [ ]
# return SNAME, aliases, []
aliases . append ( cnames [ SNAME ] [ 0 ] )
SNAME = cnames [ SNAME ] [ 0 ]
SNAME = cnames [ SNAME ] [ 0 ] . rdata . data
#print "following CNAME", SNAME
if SNAME in answers :
return SNAME , aliases , answers [ SNAME ]
break # 5 -> 5'
# recursing
# 5 -> 0
return self . gethostbyname( SNAME , aliases )
return self . lookup( type_ , SNAME , aliases )
# b. if the response contains a better delegation to other
@ -212,3 +242,17 @@ class Resolver(object):
continue # 4 -> 1
# NOT REACHED
#print "NOT REACHED"
def gethostbyname ( self , hostname , additionals = [ ] ) :
""" Translate a host name to IPv4 address.
Args :
hostname ( str ) : the hostname to resolve
Returns :
( str , [ str ] , [ str ] ) : ( hostname , aliaslist , ipaddrlist )
"""
hostname , aliases , responses = self . lookup ( Type . A , hostname , additionals )
aliases = [ a . name for a in aliases ]
responses = [ r . rdata . data for r in responses ]
return hostname , aliases , responses