zl程序教程

您现在的位置是:首页 >  后端

当前栏目

python网络编程学习笔记(四):域名系统

2023-06-13 09:15:27 时间

一、什么是域名系统

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实际上就是远程机器的地址和端口,也就是查询的数据。

例如:

>>>importsocket
>>>printsocket.getaddrinfo("www.baidu.com",None)
[(2,0,0,"",("61.135.169.125",0)),(2,0,0,"",("61.135.169.105",0))]
>>>printsocket.getaddrinfo("www.baidu.com",None)[0][4][0]
61.135.169.125
>>>printsocket.getaddrinfo("www.baidu.com",None)[0][4][1]
0

注意:因为一个网站可能有多个网址,所以两次查询时,结果不同也是很正常的。这里用一段代码将所有查询结果列出:

复制代码代码如下:


##@小五义
importsocket
host=raw_input("host:")
result=socket.getaddrinfo(host,None)
counter=0
foriinresult:
  print"%-2d:%s"%(counter,i[4])
  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地址的域名。

复制代码代码如下:


##@小五义
importsocket
hostip=raw_input("ip:")
try:
   result=socket.gethostbyaddr(hostip)
   print"hostname:"+result[0]
   print"\nAddresses:"
   foriinresult[2]:
       print""+i
exceptsocket.herror,e:
   print"counldnotlookupname:",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

counldnotlookupname:[Errno11004]hostnotfound

>>>

ip:60.210.18.169

counldnotlookupname:[Errno11004]hostnotfound

从运行的结果看,第一次查询到的两个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会返回一个包含结果的应答对象(answerobject),该对象有个属性叫answers,包含所有返回的应答列表。

在给出例子前,首先列出大多数的DNSrecords列表如下:
AAddress.网址记录(定在右边),定义於RFC1035.  
AAAA IPv6Address.(第6代网址表式法).定义於RFC1886.  
AFSDB AFSDataDaselocation.定义於RFC1183.  
CNAME CanonicalName(正式名称),定义於RFC1035.这是定别名(alias)的指标用法.别名设定在LHS,正式名称设定在RHS.  
GPOS GeographicPOSition(地理位置)?,定义於RFC1712.过时(obsolete)用法,不建议使用.. 
HINFO HostINFOrmation(机器基本资料;OS,硬体,...),定义於RFC1035.  
ISDN ISDN(整合数位网路网址表示法),定义於RFC1183.  
KEY publickkey(公开金匙;DNS资料,透过编码,密码通讯),定义於RFC2065.  
LOC LOCation(网路所在的地理区域;表经纬度),定於RFC1876.  
MB MailBox.(信箱;已经很少使用),定义於RFC1035.-->参考MX记录项目.  
MD 定义於RFC1035.过时(obsolete)用法,不建议使用.-->参考MX记录项目.  
MF 定义於RFC1035.过时(obsolete)用法,不建议使用.-->参考MX记录项目.  
MG 定义於RFC1035.  
MINFO 定义於RFC1035.  
MR 定义於RFC1035.  
MX MaileXchanger.(电子邮件,交寄设定).定义於RFC1035.基本用法是,当一个emailaddress包含某一笔MX记录的LHS时,那麽email传递系统会,将该电子邮件,转交给该笔MX的RHS所指示的系统,去进一步处理.  
NULL 空记录(如空白行等;无实际用途),定义於RFC1035.  
NS NameServer(表示RHS为一领域名称伺服机器),定义於RFC1035.  
NSAP NetworkServicesAccessPointaddress.(ISO-OSI的网路服务,网址表示法)定义於RFC1348,另外又分别经过RFC1637,1706两次重新定义. 
NSAP-PTR 定义於RFC1348.过时用法.  
NXT 定义於RFC2065.  
PTR PoinTeR.(指标),定义於RFC1035.通常用於将IPaddr.只回到某一个对应的domainname. 

下面是一个简单的例子:

复制代码代码如下:
##@小五义
#-*-coding:cp936-*-
importDNS
query=raw_input("输入DNS:")
DNS.DiscoverNameServers()

reqobj=DNS.Request()
answerobj=reqobj.req(name=query,qtype=DNS.Type.ANY)
ifnotlen(answerobj.answers):
   print"Notfound"
foriinanswerobj.answers:
  print"%-5s%s"%(i["typename"],i["data"])

运行结果:

输入DNS:163.com

TXT  ["v=spf1include: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
CNAMEfd-fp3.wg1.b.yahoo.com

2、查询特殊的名称服务器

前面的例子中,对ANY类型的查询,有种特殊情况,就是如果不事先请求,有时候MXrecords会丢失。因此,正常情况下,不会使用ANY。解决方法是跳过本地名称服务器,直接向该域中权威的名称服务器发送查询。为了这么做,需要使用系统默认的名称服务器来查找权威名称服务器。这是通过查找接近于当前域的NSrecords来实现的。下面的例子:

复制代码代码如下:
##@小五义
#-*-coding:cp936-*-
importDNS
defhierquery(qstring,qtype):
   reqobj=DNS.Request()
   try:
       printquery
       answerobj=reqobj.req(name=query,qtype=qtype)
       answers=[x["data"]forxinanswerobj.answersifx["type"]==qtype]
       printanswers
   exceptDNS.Base.DNSError:
       answers=[]
   iflen(answers):
       returnanswers
   else:
       remainder=qstring.split(".",1)
       iflen(remainder)==1:
           returnNone
       else:
           returnhierquery(remainder[1],qtype)
deffindnameservers(hostname):
   returnhierquery(hostname,DNS.Type.NS)
defgetrecordsfromnameserver(qstring,qtype,nslist):
   fornsinnslist:
       reqobj=DNS.Request(server=ns)
       try:
           answers=reqobj.req(name=qstring,qtype=qtype).answers
           iflen(answers):
               returnanswers
       exceptDNS.Base.DNSError:
           pass
   return[]

defnslookup(qstring,qtype,verbose=1):
   nslist=findnameservers(qstring)
   ifnslist==None:
       raiseRuntimeError,"找不到服务器"
   ifverbose:
       print"服务器:",",".join(nslist)
   returngetrecordsfromnameserver(qstring,qtype,nslist)
if __name__=="__main__":
   query=raw_input("输入网站:")
   DNS.DiscoverNameServers()
   answers=nslookup(query,DNS.Type.ANY)
   ifnotlen(answers):
       print"未找到!"
   foriinanswers:
       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=spf1include: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,"13minutes"),("retry",3600,"1hours"),("expire",604800,"1weeks"),("minimum",18000,"5hours"))

输入网站: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,"5minutes"),("retry",300,"5minutes"),("expire",2592000,"4weeks"),("minimum",7200,"2hours"))

TXT  ["v=spf1ip4:61.135.163.0/24ip4:220.181.50.0/24ip4:220.181.18.241ip4:61.208.132.13ip4:220.181.27.29ip4:202.108.22.171ip4:61.135.162.0/24ip4:220.181.5.0/24ip4:123.125.66.0/24ip4:61.135.168.0/24amxptr~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,需要分解返回的信息。这里用下面的代码来完成:

复制代码代码如下:
##@小五义
importsys,DNS,re
defhierquery(qstring,qtype):
   reqobj=DNS.Request()
   try:
       answerobj=reqobj.req(name=query,qtype=qtype)
       answers=[x["data"]forxinanswerobj.answersifx["type"]==qtype]
   exceptDNS.Base.DNSError:
       answers=[]
   iflen(answers):
       returnanswers
   else:
       remainder=qstring.split(".",1)
       iflen(remainder)==1:
           returnNone
       else:
           returnhierquery(remainder[1],qtype)
deffindnameservers(hostname):
   returnhierquery(hostname,DNS.Type.NS)
defgetrecordsfromnameserver(qstring,qtype,nslist):
   fornsinnslist:
       reqobj=DNS.Request(server=ns)
       try:
           answers=reqobj.req(name=qstring,qtype=qtype).answers
           iflen(answers):
               returnanswers
       exceptDNS.Base.DNSError:
           pass
   return[]

defnslookup(qstring,qtype,verbose=1):
   printqstring
   nslist=findnameservers(qstring)
   printnslist
   ifnslist==None:
       raiseRuntimeError,"找不到服务器"
   ifverbose:
       print"服务器:",",".join(nslist)
   returngetrecordsfromnameserver(qstring,qtype,nslist)

defgetreverse(query):
   """Giventhequery,returnsanappropriatereverselookupstring
   underIN-ADDR.ARPAifqueryisanIPaddress;otherwise,returnsNone.
   ThisfunctionisnotIPv6-compatible."""
   ifre.search("^/d+/./d+/./d+/./d+$",query):
       octets=query.split(".")
       octets.reverse()
       return".".join(octets)+".IN-ADDR.ARPA"
   returnNone

defformatline(index,typename,descr,data):
   retval="%-2s%-5s"%(index,typename)
   ifisinstance(data,list):
       returnretval
   else:

       data=data.replace("/n","/n        ")
       ifdescr!=Noneandlen(descr):
           retval+="%-12s"%(descr+":")
       returnretval+""+data

DNS.DiscoverNameServers()
query1=raw_input("输入网站:")
queries=[(query1,DNS.Type.ANY)]
donequeries=[]
descriptions={"A":"IPaddress",
               "TXT":"Data",
               "PTR":"Hostname",
               "CNAME":"Aliasfor",
               "NS":"Nameserver"}

whilelen(queries):
   (query,qtype)=queries.pop(0)
   ifqueryindonequeries:
       #Don"tlookupthesamethingtwice
       continue
   donequeries.append(query)
   print"-"*77
   print"Resultsfor%s(lookuptype%s)"%(query,DNS.Type.typestr(qtype))
   print
   rev=getreverse(query)
   ifrev:
       print"IPaddressgiven;doingreverselookupusing",rev
       query=rev

   answers=nslookup(query,qtype,verbose=0)
   ifnotlen(answers):
       print"Notfound."

   count=0
   foranswerinanswers:
       count+=1
       ifanswer["typename"]=="MX":
           printformatline(count,answer["typename"],
                            "Mailserver",
                            "%s,priority%d"%(answer["data"][1],
                                                 answer["data"][0]))
           queries.append((answer["data"][1],DNS.Type.A))
       elifanswer["typename"]=="SOA":
           data="/n"+"/n".join([str(x)forxinanswer["data"]])
           ##printdata
           printformatline(count,"SOA","Startofauthority",data)
       elifanswer["typename"]indescriptions:
           ##printanswer["data"]
           printformatline(count,answer["typename"],
                            descriptions[answer["typename"]],answer["data"])
       else:
           printformatline(count,answer["typename"],None,
                            str(answer["data"]))
       ifanswer["typename"]in["CNAME","PTR"]:
           queries.append((answer["data"],DNS.Type.ANY))
       ifanswer["typename"]=="NS":
           queries.append((answer["data"],DNS.Type.A))

本人在运行时,总是报错,没找到原因,望高手指点。