一、什麼是網域名稱系統
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)[0][4][0]
61.135.169.125
>>> print socket.getaddrinfo('www.baidu.com',None)[0][4][1]
0
注意:因為一個網站可能有多個網址,所以兩次查詢時,結果不同也是很正常的。這裡用一段代碼將所有查詢結果列出:
複製代碼 代碼如下:
##@小五義
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可以為IPv4或IPv6的IP地址,參數len為參數addr的長度,參數type為AF_INET。
下面給出的例子,將反向查詢ip地址的網域名稱。
複製代碼 代碼如下:
##@小五義
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. (第 6 代網址表式法). 定義於 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.
下面是一個簡單的例子:
複製代碼 代碼如下:
##@小五義
# -*- 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來實現的。下面的例子:
複製代碼 代碼如下:
##@小五義
# -*- 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,特別是NS、PTR、CNAME返回的資料中包含另一個主機名稱。為了得到最終的ip,需要分解返回的資訊。這裡用下面的代碼來完成:
複製代碼 代碼如下:
##@小五義
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))
本人在運行時,總是報錯,沒找到原因,望高手指點。