域名系統(DNS)是一個分布式的資料庫,它主要用來把主機名轉換成IP位址。
DNS系統之是以存在主要是因為友善使用者記憶和更改IP位址不影響域名。
4.1 進行DNS查詢
DNS提供一系列的提名回答,每個提名給出一個更詳細的答案,直到獲得最終答案。
作為一個例子,讓我們來看一下查詢www.external.example.com。首先,您的程式會和作業系統配置檔案指定的本地名稱伺服器通信。這個伺服器是一個遞歸的名稱伺服器, 它收到請求并以适當的方式傳遞下去。
遞歸伺服器要做的第一件事就是通路.com域。後者有個内置的頂級域名清單,這些伺服器可以分發世界上頂級域名的資訊,例如.com。
對于.com的回答是以一種指向另一個名稱伺服器的提名形式給出的。這個名稱伺服器可以提供名稱中包含.com的資訊。是以,查詢會發送到這個伺服器。該.com伺服器以另一個提名回答作為回應,這個提名回答指向一個可以提供example.com的名稱資訊。
這個循環重複多次,直到最終查詢到達為external.example.com服務的名稱伺服器。這個伺服器知道問題中的IP位址,并傳回它。
4.2 使用作業系統查詢服務
作業系統提供了一套用于DNS查詢的服務。
當您使用作業系統的查找服務時,Unix系統會先去查找本地DNS緩存,緩存儲存在/etc/hosts檔案中。如果未能查找到,就會發送請求給本地DNS伺服器,本地伺服器會傳回給您IP位址。所有這些細節您都不知道,作業系統會自動查詢。
4.2.1 執行基本查詢
最基本的查詢是正向查詢,它根據一個主機名來查找一個IP位址。
您或許回想自己實作DNS查詢的操作,因為查詢DNS确實複雜化了,變得簡單點,程式會運作的更好。這在要多次連接配接某個伺服器的時候,非常有用。在Python中,提供了函數:
getaddrinfo(host, port [, family[, socktype[,proto[, flags]]]])
傳回值是一個tuple:
(family, socktype, proto, canonname, sockaddr)
sockaddr實際上就是遠端機器的位址,是您進行查找的時候要找的資料。
如果您隻是想得到一個簡單的IP位址來連接配接,您可以選擇清單中的第一個tuple。這裡有個:
#!/usr/bin/env python3
#Basic getaddrinfo() basic example - Chapter 4 - getaddrinfo-basic.py
import sys, socket
result = socket.getaddrinfo(sys.argv[1],None)
print(result[0][4])
試着運作一下,得到:
$ ./a.py www.example.com
('93.184.216.34', 0)
還請注意兩次查詢的結果可能不同。因為一個域名可以對應多個IP位址。
通過getaddrinfo()獲得全部的條目也是可以的:
#!/usr/bin/env python3
#Basic getaddrinfo() not quite right list example - Chapter 4 - getaddrinfo-list-broken.py
#Takes a hostname 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 info 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
然而,當運作這個程式的時候,您會看到同一個條目顯示了多次:
$ ./a.py www.yahoo.com
0 : ('124.108.103.104', 0)
1 : ('124.108.103.104', 0)
2 : ('124.108.103.103', 0)
3 : ('124.108.103.103', 0)
這是因為getaddrinfo()會根據每種它所支援的不同協定産生一個結果。
4.2.2 執行反向查詢
方向查詢:由IP位址,查找相應的主機名。
4.2.2.1 反向查找基礎
一個重要的需要明白的問題是:一個IP位址可能并不存在方向的映射。實際上,許多IP位址,并沒有對應的域名。Internet标準有方向DNS,是一個可選的特性。是以,需要確定為每一個反向查找的行為捕獲和處理socket.herror()。下面是一個反向查找的例子。
# -*- coding:utf-8 -*-
# ! /usr/bin/env python3
# Baseic gethostbyaddr() example ---- Chapter 4 - gethostbyaddr-basic.py
# This program performs a reverse lookup on the IP address given on the command line
import socket, sys
try:
# perform the lookup
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 as e:
print("Couldn't look up name:", e)
4.2.2.2 對于反向查找資料真實性的檢查
有時候,您會發現攻擊者會在反向查找記錄中插入僞造的資料。例如:有人可能會在反向查找記錄中插入一個IP位址,宣稱是來自whitehouse.gov。
DNS的組織結構中沒辦法阻止這種欺騙。然而,您可以在程式中加入一些智能來阻止它。為了這麼做,首先您需要正常進行反響查詢來得到域名,然後再根據這個域名進行一次正向查詢。如果是正常的,第一步得到的IP位址應該在正向查詢得到的IP位址上。否則,就是有人在僞造資訊。
# -*- coding:utf-8 -*-
# ! /usr/bin/env python3
# Error-checking gethostbyaddr() example ---- Chapter 4 - gethostbyaddr-paranoid.py
# This program performs a reverse lookup on the IP address given on the command line
# and sanity-checks the result
import socket, sys
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.gaierrror
except socket.herror as e:
print("No host names available for %s; this may be normal." % sys.argv[1])
sys.exit(0)
except socket.gaierror as e:
print("Got hostname %s, but it could not be forward-resolved:%s" % (hostname, 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)
4.2.3 獲得環境資訊
您可以獲得運作程式機器的一些資訊,如域名、IP等。
socket.gethostname():不帶任何參數,傳回一個字元串即主機名。可能是不完整的,如erwein.example.com,您會得到erwin。
socket.getfqdn():它有一個參數----主機名,并試圖獲得完整的資料。也就是說,如果機器名知悉是erwein,并且在example.com的區域内,會傳回erwin.example.com。
# -*- coding:utf-8 -*-
# ! /usr/bin/env python3
# Basic gethostbyaddr() example ---- Chapter 4 - gethostbyaddr-paranoid.py
import socket, sys
def getipaddrs(hostname):
"""Given a host name, perform a standard(forward) lookup and return a list of IP addresses for that host."""
result = socket.getaddrinfo(hostname, None, 0, socket.SOCK_STREAM)
return [x[4][0] for x in result]
# First, do the reverse lookup and get the hostname
hostname = socket.gethostname(sys.argv[1]) # could raise socket.herror
print("Host name:", hostname)
# Try to get the fully qualified name
print("Fully-qualified name:", socket.getfqdn(hostname))
try:
print("IP addresses: ", ",".join(getipaddrs(hostname)))
except socket.gaierror as e:
print("Couldn't get IP address:", e)
為了得到完整的域名,您首先可以使用gethostname()獲得主機名。接着,使用getfqdn()獲得完整的資訊。最後,使用getaddrinfo來獲得該域名對應的IP位址。
4.3 使用PyDNS進行進階查詢
PyDNS提供了一個功能更強的通路DNS系統的接口。一個需要注意的地方是:PyDNS不能提供作業系統自帶檔案的查詢,比如/etc/hosts。
4.3.1 DNS Records
當您執行查詢時,您都會得到來自一個域名伺服器類型的records。下面是您會遇到的大多數records清單:
有作業系統完成的正向查詢隻能得到A、AAAA和CNAME records。反向查詢隻能得到PTR和CNAME records。是以為了得到其他資訊,您必須使用PyDNS或其他類似的DNS庫。
4.3.2 安裝DNS
略