python網絡編程學習筆記(4):域名系統


轉載請注明:@小五義 http://www.cnblogs.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可以直接忽略   

參數flagsAI_*的組合,比如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)[0][4][0] 

61.135.169.125 

>>> print socket.getaddrinfo('www.baidu.com',None)[0][4][1] 

0

注意:因為一個網站可能有多個網址,所以兩次查詢時,結果不同也是很正常的。這里用一段代碼將所有查詢結果列出:

##@小五義 http://www.cnblogs.com/xiaowuyi
import socket
host=raw_input('host:')
result=socket.getaddrinfo(host,None)
counter=0
for i in result:
   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可以為IPv4IPv6IP地址,參數len為參數addr的長度,參數typeAF_INET 

下面給出的例子,將反向查詢ip地址的域名。

 

##@小五義 http://www.cnblogs.com/xiaowuyi
import socket
hostip=raw_input('ip:')
try:
    result=socket.gethostbyaddr(hostip)
    print "hostname:"+result[0]
    print "\n Addresses:"
    for i in result[2]:
        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: [Errno 11004] host not found

>>> 

ip:60.210.18.169

counld not look up name: [Errno 11004] 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.   

AAAA  IPv6 Address. (第 代網址表式法). 定義於 RFC 1886.   

AFSDB  AFS Data Dase location. 定義於 RFC 1183.   

CNAME  Canonical Name (正式名稱), 定義於 RFC 1035. 這是定別名(alias)的指標用法別名設定在 LHS, 正式名稱設定在 RHS.   

GPOS  Geographic POSition (地理位置)?, 定義於 RFC 1712. 過時(obsolete)用法不建議使用 

HINFO  Host INFOrmation (機器基本資料; OS, 硬體, ...), 定義於 RFC 1035.   

ISDN  ISDN (整合數位網路網址表示法), 定義於 RFC 1183.   

KEY  publick key (公開金匙; DNS 資料透過編碼密碼通訊), 定義於 RFC 2065.   

LOC  LOCation (網路所在的地理區域表經緯度), 定於 RFC 1876.   

MB  MailBox. (信箱已經很少使用), 定義於 RFC 1035. --> 參考 MX 記錄項目.   

MD  定義於 RFC 1035. 過時(obsolete)用法不建議使用. --> 參考 MX 記錄項目.   

MF  定義於 RFC 1035. 過時(obsolete)用法不建議使用. --> 參考 MX 記錄項目.   

MG  定義於 RFC 1035.   

MINFO  定義於 RFC 1035.   

MR  定義於 RFC 1035.   

MX  Mail eXchanger. (電子郵件交寄設定). 定義於 RFC 1035. 基本用法是當一個 email address 包含某一筆 MX 記錄的 LHS那麽 email 傳遞系統會將該電子郵件轉交給該筆 MX 的 RHS 所指示的系統去進一步處理.   

NULL  空記錄 如空白行等無實際用途), 定義於 RFC 1035.   

NS  Name Server (表示 RHS 為一領域名稱伺服機器), 定義於 RFC 1035.   

NSAP  Network Services Access Point address. ( ISO-OSI 的網路服務網址表示法定義於 RFC 1348, 另外又分別經過 RFC 1637, 1706 兩次重新定義 

NSAP-PTR  定義於 RFC 1348. 過時用法.   

NXT  定義於 RFC 2065.   

PTR  PoinTeR. ( 指標 ), 定義於 RFC 1035. 通常用於將 IP addr. 只回到某一個對應 的 domain name.  

下面是一個簡單的例子:

##@小五義 http://www.cnblogs.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.cnblogs.com/xiaowuyi
# -*- coding: cp936 -*-
import DNS
def hierquery(qstring,qtype):
    reqobj=DNS.Request()
    try:
        print query
        answerobj=reqobj.req(name=query,qtype=qtype)
        answers=[x['data'] 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[1],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,特別是NSPTRCNAME返回的數據中包含另一個主機名。為了得到最終的ip,需要分解返回的信息。這里用下面的代碼來完成:

 

##@小五義 http://www.cnblogs.com/xiaowuyi
import sys, DNS, re
def hierquery(qstring,qtype):
    reqobj=DNS.Request()
    try:
        answerobj=reqobj.req(name=query,qtype=qtype)
        answers=[x['data'] 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[1],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))
    print
    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'][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 data
            print formatline(count, 'SOA', 'Start of authority', data)
        elif answer['typename'] in descriptions:
            ##print answer['data']
            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))

 

本人在運行時,總是報錯,沒找到原因,望高手指點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM