python网络编程学习笔记(4):域名系统
转载请注明:@小五义 http://www.iyunv.com/xiaowuyi一、什么是域名系统
DNS 计算机域名系统 (DNS) 是由解析器以及域名服务器组成的。当我们在上网的时候,通常输入的是网址,其实这就是一个域名,而我们计算机网络上的计算机彼此之间只能用IP地址才能相互识别。再如,我们去一WEB服务器中请求一WEB页面,我们可以在浏览器中输入网址或者是相应的IP地址,例如我们要上新浪网,我们可以在IE的地址栏中输入网址,也可输入IP地址,但是这样子的IP地址我们记不住或说是很难记住,所以有了域名的说法,这样的域名会让我们容易的记住。
名称
含义
特性
域名服务器
保存有该网络中所有主机的域名和对应IP地址,并具有将域名转换为IP地址功能的服务器。
域名必须对应一个IP地址,而IP地址不一定只对应一个域名,采用类似目录树的等级结构。
域名解析服务器
域名与IP地址之间的转换工作
域名解析过程中的查询顺序为:本地缓存记录、区域记录、转发域名服务器、根域名服务器。
二、访问DNS的方法一:使用socket模块
1、DNS查询
以查询www.external.example.com为例。首先,程序会和操作系统配置文件指定的本地名称服务器通信。这个服务器是一个递归的名称服务器,它收到请求并以适当的方式传递下去。递归服务器做的第一件事情是询问.com域,回答是以一种指向另外一外名称服务器的提名形式给出的。这个名称服务器可以提供名称中包含.com的信息。查询发送到该服务器后,该服务器将以另一个提名回答进行回应,指向另外一台服务器,而这个服务器可以提供example.com的名称信息。这个循环重复多次,直到查询到external.example.com服务的名称服务器。
2、正向查询
最基本的查询是正向查询,即根据一个主机名来查找ip地址。Socket库可以实现这种查询,主要用函数socket.getaddrinfo()。注意,该函数和ipv6不兼容。
Getaddrinfo(host,port[,family[,sockettype[,proto[,flags]]]])
参数host为域名,以字符串形式给出代表一个IPV4/IPV6地址或者None.
参数port如果字符串形式就代表一个服务名,比如“http”"ftp""email"等,或者为数字,或者为None
参数family为地主族,可以为AF_INET,AF_INET6 ,AF_UNIX.
参数socketype可以为SOCK_STREAM(TCP)或者SOCK_DGRAM(UDP)
参数proto通常为0可以直接忽略
参数flags为AI_*的组合,比如AI_NUMERICHOST,它会影响函数的返回值
该函数返回值是一列tuple,每一个tuple如下:
(family,socktype,proto,canonname,sockaddr)
其中sockaddr实际上就是远程机器的地址和端口,也就是查询的数据。
例如:
>>> import socket
>>> print socket.getaddrinfo('www.baidu.com',None)
[(2, 0, 0, '', ('61.135.169.125', 0)), (2, 0, 0, '', ('61.135.169.105', 0))]
>>> print socket.getaddrinfo('www.baidu.com',None)
61.135.169.125
>>> print socket.getaddrinfo('www.baidu.com',None)
0
注意:因为一个网站可能有多个网址,所以两次查询时,结果不同也是很正常的。这里用一段代码将所有查询结果列出:
##@小五义 http://www.iyunv.com/xiaowuyi
import socket
host=raw_input('host:')
result=socket.getaddrinfo(host,None)
counter=0
for i in result:
print "%-2d:%s"%(counter,i)
counter+=1
运行结果如下:
>>>
host:www.baidu.com
0 :('61.135.169.105', 0)
1 :('61.135.169.125', 0)
>>>
host:www.yahoo.com
0 :('106.10.170.118', 0)
>>>
host:www.163.com
0 :('60.210.18.169', 0)
1 :('123.132.254.15', 0)
3、反向查询
反向查询是指通过ip地址查询域名。这里用到gethostbyaddr
gethostbyaddr(addr,len,type)
参数addr可以为IPv4或IPv6的IP地址,参数len为参数addr的长度,参数type为AF_INET。
下面给出的例子,将反向查询ip地址的域名。
##@小五义 http://www.iyunv.com/xiaowuyi
import socket
hostip=raw_input('ip:')
try:
result=socket.gethostbyaddr(hostip)
print "hostname:"+result
print "\n Addresses:"
for i in result:
print " " +i
except socket.herror, e:
print "counld not look up name:",e
运行结果是:
>>>
ip:127.0.0.1
hostname:localhost
Addresses:
127.0.0.1
>>>
ip:216.109.118.73
hostname:gi-2-19.bas1-1-con.ac2.yahoo.com
Addresses:
216.109.118.73
>>>
ip:123.132.254.15
counld not look up name: host not found
>>>
ip:60.210.18.169
counld not look up name: host not found
从运行的结果看,第一次查询到的两个ip放进去,却反向查询不到域名,这里我也没搞明白是什么原因,有待高手解答。
三、访问DNS的方法二:使用PyDNS进行高级查询
pyDNS提供了一个功能更强的访问DNS系统的接口。其下载地址为http://pydns.sourceforge.net。其中py3dns是针对python3.x的,本人的学习环境是python2.6,所以就下载安装了pydns。下载后解压,将DNS文件夹拷贝到Python安装文件夹下的Lib\site-packages\文件夹下即可。
1、简单的pyDNS查询
首先调用DNS.DiscoverNameServers()查找系统中的名称服务器。然后建立一个请求对象DNS.Request()。DNS.Request()的req()方法用来执行实际的查询。通常有两个参数:name给出了实际查询的名称;qtype指定了record类型。当使用请求对象来发出查询请求时,pyDNS会返回一个包含结果的应答对象(answer object),该对象有个属性叫answers,包含所有返回的应答列表。
在给出例子前,首先列出大多数的DNS records列表如下:
A Address. 网址记录(定在右边), 定义於 RFC 1035.
AAAAIPv6 Address. (第 6 代网址表式法). 定义於 RFC 1886.
AFSDBAFS Data Dase location. 定义於 RFC 1183.
CNAMECanonical Name (正式名称), 定义於 RFC 1035. 这是定别名(alias)的指标用法. 别名设定在 LHS, 正式名称设定在 RHS.
GPOSGeographic POSition (地理位置)?, 定义於 RFC 1712. 过时(obsolete)用法, 不建议使用. .
HINFOHost INFOrmation (机器基本资料; OS, 硬体, ...), 定义於 RFC 1035.
ISDNISDN (整合数位网路网址表示法), 定义於 RFC 1183.
KEYpublick key (公开金匙; DNS 资料, 透过编码, 密码通讯), 定义於 RFC 2065.
LOCLOCation (网路所在的地理区域; 表经纬度), 定於 RFC 1876.
MBMailBox. (信箱; 已经很少使用), 定义於 RFC 1035. --> 参考 MX 记录项目.
MD定义於 RFC 1035. 过时(obsolete)用法, 不建议使用. --> 参考 MX 记录项目.
MF定义於 RFC 1035. 过时(obsolete)用法, 不建议使用. --> 参考 MX 记录项目.
MG定义於 RFC 1035.
MINFO定义於 RFC 1035.
MR定义於 RFC 1035.
MXMail eXchanger. (电子邮件, 交寄设定). 定义於 RFC 1035. 基本用法是, 当一个 email address 包含某一笔 MX 记录的 LHS时, 那麽 email 传递系统会, 将该电子邮件, 转交给该笔 MX 的 RHS 所指示的系统, 去进一步处理.
NULL空记录 ( 如空白行等; 无实际用途), 定义於 RFC 1035.
NSName Server (表示 RHS 为一领域名称伺服机器), 定义於 RFC 1035.
NSAPNetwork Services Access Point address. ( ISO-OSI 的网路服务, 网址表示法) 定义於 RFC 1348, 另外又分别经过 RFC 1637, 1706 两次重新定义.
NSAP-PTR定义於 RFC 1348. 过时用法.
NXT定义於 RFC 2065.
PTRPoinTeR. ( 指标 ), 定义於 RFC 1035. 通常用於将 IP addr. 只回到某一个对应 的 domain name.
下面是一个简单的例子:
##@小五义 http://www.iyunv.com/xiaowuyi
# -*- coding: cp936 -*-
import DNS
query=raw_input('输入DNS:')
DNS.DiscoverNameServers()
reqobj=DNS.Request()
answerobj=reqobj.req(name=query,qtype=DNS.Type.ANY)
if not len(answerobj.answers):
print "Not found"
for i in answerobj.answers:
print "%-5s %s"%(i['typename'],i['data'])
运行结果:
输入DNS:163.com
TXT ['v=spf1 include:spf.163.com -all']
A 123.58.180.8
A 123.58.180.5
A 123.58.180.6
A 123.58.180.7
MX (10, '163mx03.mxmail.netease.com')
MX (50, '163mx00.mxmail.netease.com')
MX (10, '163mx01.mxmail.netease.com')
MX (10, '163mx02.mxmail.netease.com')
NS ns2.nease.net
NS ns4.nease.net
NS ns3.nease.net
NS ns1.nease.net
输入DNS:www.yahoo.com
CNAME fd-fp3.wg1.b.yahoo.com
2、查询特殊的名称服务器
前面的例子中,对ANY类型的查询,有种特殊情况,就是如果不事先请求,有时候MX records会丢失。因此,正常情况下,不会使用ANY。解决方法是跳过本地名称服务器,直接向该域中权威的名称服务器发送查询。为了这么做,需要使用系统默认的名称服务器来查找权威名称服务器。这是通过查找接近于当前域的NS records来实现的。下面的例子:
##@小五义 http://www.iyunv.com/xiaowuyi
# -*- coding: cp936 -*-
import DNS
def hierquery(qstring,qtype):
reqobj=DNS.Request()
try:
print query
answerobj=reqobj.req(name=query,qtype=qtype)
answers= for x in answerobj.answers if x['type']==qtype]
print answers
except DNS.Base.DNSError:
answers=[]
if len(answers):
return answers
else:
remainder=qstring.split(".",1)
if len(remainder)==1:
return None
else:
return hierquery(remainder,qtype)
def findnameservers(hostname):
return hierquery(hostname,DNS.Type.NS)
def getrecordsfromnameserver(qstring,qtype,nslist):
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,'找不到服务器'
if verbose:
print "服务器:",",".join(nslist)
return getrecordsfromnameserver(qstring,qtype, nslist)
if__name__=='__main__':
query=raw_input('输入网站:')
DNS.DiscoverNameServers()
answers=nslookup(query,DNS.Type.ANY)
if not len(answers):
print "未找到!"
for i in answers:
print "%-5s %s"%(i['typename'],i['data'])
运行结果如下:
输入网站:163.com
服务器: ns3.nease.net,ns1.nease.net,ns2.nease.net,ns4.nease.net
A 123.58.180.8
A 123.58.180.5
A 123.58.180.6
A 123.58.180.7
MX (10, '163mx02.mxmail.netease.com')
MX (10, '163mx03.mxmail.netease.com')
MX (50, '163mx00.mxmail.netease.com')
MX (10, '163mx01.mxmail.netease.com')
TXT ['v=spf1 include:spf.163.com -all']
NS ns4.nease.net
NS ns1.nease.net
NS ns2.nease.net
NS ns3.nease.net
SOA ('ns4.nease.net', 'admin.nease.net', ('serial', 20014505), ('refresh ', 801, '13 minutes'), ('retry', 3600, '1 hours'), ('expire', 604800, '1 weeks'), ('minimum', 18000, '5 hours'))
输入网站:baidu.com
服务器: dns.baidu.com,ns4.baidu.com,ns2.baidu.com,ns3.baidu.com
SOA ('dns.baidu.com', 'sa.baidu.com', ('serial', 2012081509), ('refresh ', 300, '5 minutes'), ('retry', 300, '5 minutes'), ('expire', 2592000, '4 weeks'), ('minimum', 7200, '2 hours'))
TXT ['v=spf1 ip4:61.135.163.0/24 ip4:220.181.50.0/24 ip4:220.181.18.241 ip4:61.208.132.13 ip4:220.181.27.29 ip4:202.108.22.171 ip4:61.135.162.0/24 ip4:220.181.5.0/24 ip4:123.125.66.0/24 ip4:61.135.168.0/24 a mx ptr ~all']
A 123.125.114.144
A 220.181.111.85
A 220.181.111.86
MX (20, 'jpmx.baidu.com')
MX (20, 'mx50.baidu.com')
MX (10, 'mx.mailcdn.baidu.com')
MX (20, 'mx1.baidu.com')
NS ns4.baidu.com
NS ns2.baidu.com
NS ns3.baidu.com
NS dns.baidu.com
3、分解查询结果
有些records,特别是NS、PTR、CNAME返回的数据中包含另一个主机名。为了得到最终的ip,需要分解返回的信息。这里用下面的代码来完成:
##@小五义 http://www.iyunv.com/xiaowuyi
import sys, DNS, re
def hierquery(qstring,qtype):
reqobj=DNS.Request()
try:
answerobj=reqobj.req(name=query,qtype=qtype)
answers= for x in answerobj.answers if x['type']==qtype]
except DNS.Base.DNSError:
answers=[]
if len(answers):
return answers
else:
remainder=qstring.split(".",1)
if len(remainder)==1:
return None
else:
return hierquery(remainder,qtype)
def findnameservers(hostname):
return hierquery(hostname,DNS.Type.NS)
def getrecordsfromnameserver(qstring,qtype,nslist):
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):
print qstring
nslist=findnameservers(qstring)
print nslist
if nslist==None:
raise RuntimeError,'找不到服务器'
if verbose:
print "服务器:",",".join(nslist)
return getrecordsfromnameserver(qstring,qtype, nslist)
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)
if isinstance(data,list):
return retval
else:
data = data.replace("/n", "/n ")
if descr != None and len(descr):
retval += " %-12s" % (descr + ":")
return retval + " " + data
DNS.DiscoverNameServers()
query1=raw_input('输入网站:')
queries = [(query1, 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))
rev = getreverse(query)
if rev:
print "IP address given; doing reverse lookup using", rev
query = rev
answers = 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'],
answer['data']))
queries.append((answer['data'], DNS.Type.A))
elif answer['typename'] == 'SOA':
data = "/n" + "/n".join(])
##print data
print formatline(count, 'SOA', 'Start of authority', data)
elif answer['typename'] in descriptions:
##print answer['data']
print formatline(count, answer['typename'],
descriptions], 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))
本人在运行时,总是报错,没找到原因,望高手指点。
页:
[1]