8-27 3,566PVs
域名系统(DNS)是一个分布式数据库,用来把主机名编程IP地址,DNS以及想关系统之所以存在,有以下两个原因:域名例如www.jevylee.com比IP地址例如222.11.111.22更好记。此外,一个域名在不同的时间段可以使不同的IP,使一个网站可以有多个服务器。当前,对python来说,有几种不同的DNS模块,PyDNS就是其中一种。其他的有dnspython,可以从www.dnspython.org得到。还可以从http://pilcrow.madison.wi.us/得到dnslook。还有adns,一个异步DNS库,可以从http://dustman.net/andy/python/adns-python得到。
DNS是一个庞大的、全球分布式数据库。运作过程如下:首先,您的程序会和操作系统配置文件指定的本地DNS服务器通信。这个服务器是一个递归的名称服务器,它收到请求并以适当的方式传递下去,它会完成大量工作。距离查询external.example.com,递归服务器做的第一件事情是询问.com域。后者有个内置的顶级域名列表,这些服务器可以分发世界上顶级域名的信息,例如.com。对于.com的回答以一种指向另外一个名称服务器的提名形式给出的。这个民称服务器可以提供名称中包含.com的信息。查询会发送到这个服务器,然后改.com服务器以另外一个提名进行回答,这个提名回答指向另外一台服务器,而这个服务器可以提供example。com的名称信息。这个循环重复多次,直到最终查询到达为external.example.com服务的名称服务器。这个服务器知道问题中的IP地址,并返回它。操作系统提供执行基本DNS查找的服务,这些服务对大多数程序来说是足够的,直接节约了花费在DNS查询上的时间。Python在它的socket模块中,提供了访问这些基本操作系统服务的接口。第三方模块还提供了很多更高级的功能。
操作系统本身带有一些用于DNS查找的功能(经常被称为resolver库),这些功能可以满足大部分应用程序的需求。有些程序,有其是邮件服务器和DNS查询程序,则需要一些更先进的工具。很多UNIX系统包含一个名为/etc/hosts的文件,其中定义了主机名和IP地址。操作系统对于查询通常会先查看/etc/hosts,如果没有找到答案,则会去DNS查询。一般来说,windows机器是不是用主机文件的。同样,每种操作系统都会为管理员提供一个办法来为DNS服务器指定IP地址,这样才能解决查询问题。在UNIX系统上是通过/etc/resolv.conf来实现的,而在windows系统上,则是通过注册表,并且是通过控制面板中的网络连接的TCP/IP设置来维护的。在这两种机器上,提供了默认的名称服务器和默认的域名。
最基本的查询是正向查询,它根据一个主机名来查找IP地址。例如,如果您想从www.example.com上下载一个web页面,首先,您需要找到IP地址。某些时候,python的Socket库会实现这种正向查询,在Python中使用的是socket.getaddrinfo()。python的定义如下:
getaddrinfo(host,port[,family[,socktype[,proto[,flags]]]])
C程序员会很熟悉gethostbyname()函数。python确实也提供了一个类似的socket.gethostbyname()函数,但是这个函数和IPv6不兼容。getaddrinfo函数host参数就是寻找的域名,其他参数只有当你想把结果传给socket.socket()或socket.connect()的时候才用到。它们会在输出中限制显示什么协议,以及为了建立Socket而填写根据默认值得到的结果。可以设置port值为none,然后省略其他参数来进行一个基本查询。python定义socket.getaddrinfo()的返回值是一列tuple,每一个tuple看上去如下:(family,socktype,proto,canonname,sockaddr)sockaddr实际上就是机器的地址,是执行查找时要找的数据,因为python tuple中的元素以0开始,他是结果中的第四个元素。socket.getaddrinfo()函数返回一个tuple的列表是因为对于一个查询,可能有多个答案。例如,一个网站有几个服务器,就会对一个域名返回多个IP地址。可以使用其中的任何一个,这样可以解决负载的问题。
1 2 3 4 5 6 7 |
#!/usr/bin/env python # Basic getaddrinfo() basic example - Chapter 4 - getaddrinfo-basic.py import sys, socket result = socket.getaddrinfo(sys.argv[1], None) print result[0][4] |
也可以通过getaddrinfo()获得全部的条目也可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/env python # Basic getaddrinfo() not quite right list example - Chapter 4 # getaddrinfo-list-broken.py # Takes a host name on the command line and prints all resulting # matches for it. Broken; a given name may occur multiple times. import sys, socket # Put the list of results into the "result" variable. result = socket.getaddrinfo(sys.argv[1], None) counter = 0 for item in result: # Print out the address tuple for each item print "%-2d: %s" % (counter, item[4]) counter += 1 |
反向查找对于一个IP地址,完全有可能不存在反向映射。很多IP地址都没有对应的域名。下面是一个用python实现反向擦好找的例子,它会把命令行中给出的IP地址作为参数,并返回相应的域名,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/usr/bin/env python # Basic gethostbyaddr() example - Chapter 4 - gethostbyaddr-basic.py # This program performs a reverse lookup on the IP address given # on the command line import sys, socket try: # Perform the lokoup result = socket.gethostbyaddr(sys.argv[1]) # Display the looked-up hostname print "Primary hostname:" print " " + result[0] # Display the list of available addresses that is also returned print "nAddresses:" for item in result[2]: print " " + item except socket.herror, e: print "Couldn't look up name:", e |
有时候会有一些客户端使用伪造的hostname来欺骗服务器,从而连接到服务器。服务器可以使用下面的程序来发现这个现象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#!/usr/bin/env python # Error-checking gethostbyaddr() example - Chapter 4 # gethostbyaddr-paranoid.py # Performs a reverse lookup on the IP address given on the command line # and sanity-checks the result. import sys, socket def getipaddrs(hostname): """Get a list of IP addresses from a given hostname. This is a standard (forward) lookup.""" result = socket.getaddrinfo(hostname, None, 0, socket.SOCK_STREAM) return [x[4][0] for x in result] def gethostname(ipaddr): """Get the hostname from a given IP address. This is a reverse lookup.""" return socket.gethostbyaddr(ipaddr)[0] try: # First, do the reverse lookup and get the hostname. hostname = gethostname(sys.argv[1]) # could raise socket.herror # Now, do a forward lookup on the result from the earlier reverse # lookup. ipaddrs = getipaddrs(hostname) # could raise socket.gaierror except socket.herror, e: print "No host names available for %s; this may be normal." % sys.argv[1] sys.exit(0) except socket.gaierror, e: print "Got hostname %s, but it could not be forward-resolved: %s" % (hostname, str(e)) sys.exit(1) # If the forward lookup did not yield the original IP address anywhere, # someone is playing tricks. Explain the situation and exit. if not sys.argv[1] in ipaddrs: print "Got hostname %s, but on forward lookup," % hostname print "original IP %s did not appear in IP address list." % sys.argv[1] sys.exit(1) # Otherwise, show the validated hostname. print "Validated hostname:", hostname |
这个例子,IP地址不是非常有用,因为只是用来回查接口地址。大多数机器都有回路和至少一个配置的网络设备,所以IP地址可能有多个。这个例子中最有用的是本地主机名。很多系统是在私有网络上的,在公共的Internet上既得不到主机名,也得不到完整的名称。
下面讲述一些DNS Records的知识,熟悉DNS系统的人都知道记录值的概念,A记录值给出一个主机名的IP地址,AAAA记录值给出一个主机的IPv6地址,CNAME记录值给出主机别名,CNAME也会指向一条A记录的主机名,后者给出了最终的IP地址。有时候,CNAME也指向PTR记录,而不是A记录。MX是指定有限选择的邮件交换服务器,本身有一个顺序,会从具有最小顺序数字的服务器开始尝试。PTR记录为一个IP地址提供主机名,用在反向查询中。NS记录为一个域名定义DNS服务器。TXT记录存储一个关于主机的专门信息,例如描述。SOA存有起始授权机构信息,它含有一些如何在DNS上保存record的信息。
由操作系统查询只能得到A,AAAA,和CNAME records。方向只能得到PTR和CNAME records。所以,为了获得其他信息,必须使用PyDNS或者其他DNS库。在pydns.sourceforge.net上下载PyDNS库后,打开Python shell。运行DNS.DiscoverNameServers()。如果有显示,则说明可以找到DNS服务器。如果没有反应,则说明找不到DNS服务器,可以手动设置,DNS.defaults[‘servers’] = [‘8.8.8.8′,’…’]你可以设置多个。初始化完成DNS服务器后,下一步需要建立一个请求对象,调用DNS.Request(),这个函数有两个参数,一个是name,一个是qtype,前者指明需要查询的名称,后者指定了前面列表中的某条record类型。
下面是一个利用PyDNS编写的简单的查询程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/env python # Basic DNS library example - Chapter 4 - DNS-basic.py import sys, DNS DNS.defaults['server'] = ['202.112.144.236','8.8.8.8'] query = sys.argv[1] #DNS.DiscoverNameServers() reqobj = DNS.Request() answerobj = reqobj.req(name = query, qtype = DNS.Type.ANY) if not len(answerobj.answers): print "Not found." for item in answerobj.answers: print "%-5s %s" % (item['typename'], item['data']) |
上面的程序查询的是DNS服务器上的缓存,并不能获得完整的DNS信息。需要查询名称完整的DNS信息,需要使用下面的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#!/usr/bin/env python # Expanded DNS library example - Chapter 4 - DNSany.py import sys, DNS def hierquery(qstring, qtype): """Given a query type qtype, returns answers of that type for lookup qstring. If no answers are found, removes the most specific component (the part before the leftmost period) and retries the query with the result. If the topmost query fails, returns None.""" reqobj = DNS.Request() try: answerobj = reqobj.req(name = qstring, qtype = qtype) answers = [x['data'] for x in answerobj.answers if x['type'] == qtype] except DNS.Base.DNSError: answers = [] # Fake an empty return if len(answers): return answers else: remainder = qstring.split(".", 1) if len(remainder) == 1: return None else: return hierquery(remainder[1], qtype) def findnameservers(hostname): """Attempts to determine the authoritative nameservers for a given :rtype : object hostname. Returns None on failure.""" return hierquery(hostname, DNS.Type.NS) def getrecordsfromnameserver(qstring, qtype, nslist): """Given a list of nameservers in nslist, executes the query requested by qstring and qtype on each in order, returning the data from the first server that returned 1 or more answers. If no server returned any answers, returns [].""" for ns in nslist: reqobj = DNS.Request(server = ns) try: answers = reqobj.req(name = qstring, qtype = qtype).answers if len(answers): return answers except DNS.Base.DNSError: pass return [] def nslookup(qstring, qtype, verbose = 1): nslist = findnameservers(qstring) if nslist == None: raise RuntimeError, "Could not find nameserver to use." if verbose: print "Using nameservers:", ", ".join(nslist) return getrecordsfromnameserver(qstring, qtype, nslist) if __name__ == '__main__': query = sys.argv[1] #DNS.DiscoverNameServers() DNS.defaults['server'] = ['202.112.144.236','8.8.8.8'] answers = nslookup(query, DNS.Type.ANY) if not len(answers): print "Not found." for item in answers: print "%-5s %s" % (item['typename'], item['data']) |
此外还有反向DNS的程序,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#!/usr/bin/env python # DNS query program - Example 4 - DNSquery.py import sys, DNS, DNSany, re def getreverse(query): """Given the query, returns an appropriate reverse lookup string under IN-ADDR.ARPA if query is an IP address; otherwise, returns None. This function is not IPv6-compatible.""" if re.search('^d+.d+.d+.d+$', query): octets = query.split('.') octets.reverse() return '.'.join(octets) + '.IN-ADDR.ARPA' return None def formatline(index, typename, descr, data): retval = "%-2s %-5s" % (index, typename) data = data.replace("n", "n ") if descr != None and len(descr): retval += " %-12s" % (descr + ":") return retval + " " + data DNS.DiscoverNameServers() queries = [(sys.argv[1], DNS.Type.ANY)] donequeries = [] descriptions = {'A': 'IP address', 'TXT': 'Data', 'PTR': 'Host name', 'CNAME': 'Alias for', 'NS': 'Name server'} while len(queries): (query, qtype) = queries.pop(0) if query in donequeries: # Don't look up the same thing twice continue donequeries.append(query) print "-" * 77 print "Results for %s (lookup type %s)" % (query, DNS.Type.typestr(qtype)) print rev = getreverse(query) if rev: print "IP address given; doing reverse lookup using", rev query = rev answers = DNSany.nslookup(query, qtype, verbose = 0) if not len(answers): print "Not found." count = 0 for answer in answers: count += 1 if answer['typename'] == 'MX': print formatline(count, answer['typename'], 'Mail server', "%s, priority %d" % (answer['data'][1], answer['data'][0])) queries.append((answer['data'][1], DNS.Type.A)) elif answer['typename'] == 'SOA': data = "n" + "n".join([str(x) for x in answer['data']]) print formatline(count, 'SOA', 'Start of authority', data) elif answer['typename'] in descriptions: print formatline(count, answer['typename'], descriptions[answer['typename']], answer['data']) else: print formatline(count, answer['typename'], None, str(answer['data'])) if answer['typename'] in ['CNAME', 'PTR']: queries.append((answer['data'], DNS.Type.ANY)) if answer['typename'] == 'NS': queries.append((answer['data'], DNS.Type.A)) |
如果使用PyDNS来完成反向查询。必须颠倒IP地址的内容,把IN-ADDR.ARPA家在后面。这是DNS协议使用的基本格式,并在反向查询中必须使用。
总结:
Domain Name System(DNS)用于在文字名称和底层通信的IP地址之间转换,Python通过Socket模块提供了访问操作系统本身DNS的接口。可以通过gethostname()得到程序运行机器上的信息,可以得到机器本身的主机名。