dnspython模塊常見用法
學習環境部署
? 操作系統:Centos7.4
? 安裝模塊:pip install dnspython –upgrade
註:默認安裝的模塊版本是1.12.0,根據我的測試結果這個模塊有些問題,因此加上參數upgrade升級到最新的1.15.0
為了方便測試dnspython模塊的功能,我們用bind搭建一個最小化配置的DNS服務器:
1. 安裝bind軟件包
yum install bind -y
2. 生成TSIG key
TSIG key用於保護DNS主從更新、動態更新等操作,只有通過了認證的同步或更新請求才會被接受。首先用dnssec-keygen命令生成這個key:
[root@localhost dns]# dnssec-keygen -a HMAC-MD5 -b 128 -n HOST "test_key"
Ktest_key.+157+52058
這條命令生成了2個文件Ktest_key.+157+52058.key和Ktest_key.+157+52058.private,
查看Ktest_key.+157+52058.private內容如下:
[root@localhost dns]# cat Ktest_key.+157+52058.private
Private-key-format: v1.3
Algorithm: 157 (HMAC_MD5)
Key: B6kQalChhELcQKgCwD+UQw==
Bits: AAA=
Created: 20180126091256
Publish: 20180126091256
Activate: 20180126091256
根據Algorithm和Key字段的內容,在/etc/named.conf內添加內容:
key "test_key" {
algorithm hmac-md5;
secret "epYaIl5VMJGRSG4WMeFW5g==";
};
3. 定義ZONE文件
創建ZONE文件/var/named/apple.tree.zone,定義一個我們自己的測試域apple.tree,內容如下,192.168.183.131是我的測試機的ip,請替換成你們實際環境的ip:
$ORIGIN .
$TTL 86400 ; 1 day
apple.tree IN SOA apple.tree. apple.apple.tree. (
2016090107 ; serial
28800 ; refresh (8 hours)
7200 ; retry (2 hours)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns1.apple.tree.
A 192.168.183.131
$ORIGIN apple.tree.
a A 192.168.183.132
b A 192.168.183.133
$TTL 60 ; 1 minute
c CNAME b
ns1 A 192.168.183.131
test A 1.1.1.1
test A 2.2.2.2
在/etc/named.conf中添加這個ZONE,並且只允許通過上面定義的TSIG key驗證的客戶端動態更新此ZONE:
key "test_key" {
algorithm hmac-md5;
secret "epYaIl5VMJGRSG4WMeFW5g==";
};
4. /etc/named.conf最終內容如下:
options {
listen-on port 53 { 127.0.0.1; 192.168.183.131; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
allow-query { any; };
recursion yes;
dnssec-enable yes;
dnssec-validation yes;
bindkeys-file "/etc/named.iscdlv.key";
managed-keys-directory "/var/named/dynamic";
pid-file "/run/named/named.pid";
session-keyfile "/run/named/session.key";
};
key "test_key" {
algorithm hmac-md5;
secret " B6kQalChhELcQKgCwD+UQw== ";
};
logging {
channel default_debug {
file "data/named.run";
severity dynamic;
};
};
zone "." IN {
type hint;
file "named.ca";
};
zone "apple.tree" IN {
type master;
file "/var/named/apple.tree.zone";
allow-update { key test_key; };
};
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
5. 重啟named服務
systemctl restart named
6. 驗證
把DNS服務器改成本機,解析上面添加的ZONE中的記錄,如果得到下面的結果,說明DNS服務搭建成功:
[root@localhost dns]# host test.apple.tree
test.apple.tree has address 1.1.1.1
test.apple.tree has address 2.2.2.2
DNS查詢
dns.resolver實現了DNS查詢功能,類似nslookup命令。具體使用方法:
import dns.resolver
r = dns.resolver.query("test.apple.tree", "A")
print ("qname:",r.qname)
print ("rdtype:",r.rdtype)
for i in r.response.answer:
for j in i.items:
print ("address:",j.address)
上面的腳本中我們使用dns.resolver.query方法查詢之前添加的關於test.apple.tree的A記錄,腳本執行結果如下:
[root@localhost dns]# python query.py
('qname:', <DNS name test.apple.tree.>)
('rdtype:', 1)
('address:', u'1.1.1.1')
('address:', u'2.2.2.2')
qname是要查詢的域名;rdtype是記錄類型,dnspython的數據結構中將各種類型的DNS記錄用數字定義,其中“1”表示A記錄,具體對應關系如下表:
類型 值 含義
A 1 主機地址
NS 2 經過授權的名稱服務器
CNAME 5 用作別名的規範名稱
SOA 6 標記開始一個授權區域
PTR 12 域名指針
MX 15 郵件交換
TXT 16 文本字符串
address是被查詢的域名對應的ip地址,可能有多個。
DNS動態更新
dns動態更新是一種在不reload和重啟DNS服務的情況下更新ZONE內容的機制。dns.update實現了這種功能。具體用法如下:
import dns.tsigkeyring
import dns.update
import dns.query
keyring = dns.tsigkeyring.from_text({'test_key': 'B6kQalChhELcQKgCwD+UQw=='})
update = dns.update.Update("apple.tree", keyring=keyring)
update.add("test2", 60, 'a', "192.168.183.131")
update.replace("a", 60, 'a', "192.168.183.132")
update.delete("c")
response = dns.query.tcp(update, '127.0.0.1')
print response
在搭建bind的過程中我們生成過一個用於驗證的key,動態更新需要用到它,上面腳本中填寫的key名字和內容需要跟/etc/named.conf中配置的一樣。dns.update.Update類的的方法add、replace和delete分別實現了添加、修改和刪除記錄的功能。dns.query.tcp向DNS服務器(本例中是127.0.0.1)發送更新請求。執行腳本後查看結果:
[root@localhost dns]# python update.py
id 58485
opcode UPDATE
rcode NOERROR
flags QR RA
;ZONE
apple.tree. IN SOA
;PREREQ
;UPDATE
;ADDITIONAL
[root@localhost dns]# host test2.apple.tree
test2.apple.tree has address 192.168.183.131
[root@localhost dns]# host a.apple.tree
a.apple.tree has address 192.168.183.132
[root@localhost dns]# host c.apple.tree
Host c.apple.tree not found: 3(NXDOMAIN)
查詢結果顯示我們腳本中對服務器的操作已經生效了。如果這時候我們去查看ZONE文件,會發現我們的更新沒有體現在/var/named/apple.tree.zone中,但是/var/named目錄下多出了一個apple.tree.zone.jnl文件,動態更新的內容保存在這個jnl文件中,並被直接加載進內存,當named服務被重啟(reload不行)時,更改會被寫進/var/named/apple.tree.zone中。
#重啟named前
[root@localhost ~]# cat /var/named/apple.tree.zone
$ORIGIN .
$TTL 86400 ; 1 day
apple.tree IN SOA apple.tree. apple.apple.tree. (
2016090107 ; serial
28800 ; refresh (8 hours)
7200 ; retry (2 hours)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns1.apple.tree.
A 192.168.183.131
$ORIGIN apple.tree.
a A 192.168.183.132
b A 192.168.183.133
$TTL 60 ; 1 minute
c CNAME b
ns1 A 192.168.183.131
test A 1.1.1.1
test A 2.2.2.2
#重啟named後
[root@localhost ~]# cat /var/named/apple.tree.zone
$ORIGIN .
$TTL 86400 ; 1 day
apple.tree IN SOA apple.tree. apple.apple.tree. (
2016090108 ; serial
28800 ; refresh (8 hours)
7200 ; retry (2 hours)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns1.apple.tree.
A 192.168.183.131
$ORIGIN apple.tree.
$TTL 60 ; 1 minute
a A 192.168.183.132
$TTL 86400 ; 1 day
b A 192.168.183.133
$TTL 60 ; 1 minute
ns1 A 192.168.183.131
test A 1.1.1.1
A 2.2.2.2
test2 A 192.168.183.131
使用dnspython直接操作ZONE文件
要修改ZONE文件,首先要了解三個概念zone、node和rdataset之間的關系。在一個zone可能包括一個或多個node,一個node包括一個或多個rdataset,一個rdataset包括一組class和type相同的記錄數據(rdata)。為了更直觀,我們用腳本分析一下zone文件/var/named/apple.tree.zone。
import dns.zone
from dns.exception import DNSException
from dns.rdataclass import *
from dns.rdatatype import *
domain = "apple.tree"
print "Getting zone object for domain", domain
zone_file = "/var/named/apple.tree.zone"
try:
zone = dns.zone.from_file(zone_file, domain)
print "Zone origin:", zone.origin
for name, node in zone.nodes.items():
rdatasets = node.rdatasets
print "\n**** BEGIN NODE ****"
print "node name:", name
for rdataset in rdatasets:
print " --- BEGIN RDATASET ---"
print " rdataset string representation:", rdataset
print " rdataset rdclass:", rdataset.rdclass
print " rdataset rdtype:", rdataset.rdtype
print " rdataset ttl:", rdataset.ttl
print " rdataset has following rdata:"
for rdata in rdataset:
print " -- BEGIN RDATA --"
print " rdata string representation:", rdata
if rdataset.rdtype == SOA:
print " ** SOA-specific rdata **"
print " expire:", rdata.expire
print " minimum:", rdata.minimum
print " mname:", rdata.mname
print " refresh:", rdata.refresh
print " retry:", rdata.retry
print " rname:", rdata.rname
print " serial:", rdata.serial
if rdataset.rdtype == MX:
print " ** MX-specific rdata **"
print " exchange:", rdata.exchange
print " preference:", rdata.preference
if rdataset.rdtype == NS:
print " ** NS-specific rdata **"
print " target:", rdata.target
if rdataset.rdtype == CNAME:
print " ** CNAME-specific rdata **"
print " target:", rdata.target
if rdataset.rdtype == A:
print " ** A-specific rdata **"
print " address:", rdata.address
except DNSException, e:
print e.__class__, e
腳本首先通過dns.zone.from_file方法從文件中讀取ZONE信息,把zone文件轉化為Python認識的數據結構,然後分層次打印出zone的所有node名稱以及node包含的內容,執行結果如下:
[root@localhost dns]# python zone.py
Getting zone object for domain apple.tree
Zone origin: apple.tree.
**** BEGIN NODE ****
node name: @
--- BEGIN RDATASET ---
rdataset string representation: 86400 IN SOA @ apple 2016090108 28800 7200 604800 86400
rdataset rdclass: 1
rdataset rdtype: 6
rdataset ttl: 86400
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: @ apple 2016090108 28800 7200 604800 86400
** SOA-specific rdata **
expire: 604800
minimum: 86400
mname: @
refresh: 28800
retry: 7200
rname: apple
serial: 2016090108
--- BEGIN RDATASET ---
rdataset string representation: 86400 IN NS ns1
rdataset rdclass: 1
rdataset rdtype: 2
rdataset ttl: 86400
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: ns1
** NS-specific rdata **
target: ns1
--- BEGIN RDATASET ---
rdataset string representation: 86400 IN A 192.168.183.131
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 86400
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 192.168.183.131
** A-specific rdata **
address: 192.168.183.131
**** BEGIN NODE ****
node name: a
--- BEGIN RDATASET ---
rdataset string representation: 60 IN A 192.168.183.132
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 60
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 192.168.183.132
** A-specific rdata **
address: 192.168.183.132
**** BEGIN NODE ****
node name: b
--- BEGIN RDATASET ---
rdataset string representation: 86400 IN A 192.168.183.133
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 86400
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 192.168.183.133
** A-specific rdata **
address: 192.168.183.133
**** BEGIN NODE ****
node name: test
--- BEGIN RDATASET ---
rdataset string representation: 60 IN A 1.1.1.1
60 IN A 2.2.2.2
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 60
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 1.1.1.1
** A-specific rdata **
address: 1.1.1.1
-- BEGIN RDATA --
rdata string representation: 2.2.2.2
** A-specific rdata **
address: 2.2.2.2
**** BEGIN NODE ****
node name: ns1
--- BEGIN RDATASET ---
rdataset string representation: 60 IN A 192.168.183.131
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 60
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 192.168.183.131
** A-specific rdata **
address: 192.168.183.131
**** BEGIN NODE ****
node name: test2
--- BEGIN RDATASET ---
rdataset string representation: 60 IN A 192.168.183.131
rdataset rdclass: 1
rdataset rdtype: 1
rdataset ttl: 60
rdataset has following rdata:
-- BEGIN RDATA --
rdata string representation: 192.168.183.131
** A-specific rdata **
address: 192.168.183.131
從腳本的輸出可以看出zone的結構層次。我們的測試域有@、a、b、test、ns1和test2等6個node;其中test2這個node包括1個rdataset,這個rdataset中有一條A記錄,指向地址192.168.183.131。
修改zone文件的腳本示例代碼如下,在這個腳本中:
import dns.zone,dns.name,dns.rdata
originName = dns.name.from_text('apple.tree')
zone = dns.zone.from_file('/var/named/apple.tree.zone',originName,relativize=False)
print "before modify:"
print zone.to_text()
#增加一條新的A記錄
newRdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "127.0.0.1")
newNode = zone.find_node(dns.name.from_text("mail", originName), create=True)
newRdataset = newNode.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A, create=True)
newRdataset.add(newRdata)
#刪除一條已有的A記錄
oldRdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "192.168.183.131")
oldNode = zone.find_node(dns.name.from_text("test2", originName), create=False)
oldRdataset = oldNode.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A, create=False)
oldRdataset.remove(oldRdata)
#修改一條已有的A記錄
new_ip = "192.168.183.155"
node = zone.find_node(dns.name.from_text("test", originName), create=False)
Rdataset = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A, create=False)
for rdata in Rdataset:
rdata.address = new_ip
print "after modify:"
print zone.to_text()
腳本增加一條A記錄“mail IN A 127.0.0.1”,刪除一條A記錄“test2 IN A 192.168.183.131”,還把test對應的地址修改為192.168.183.155。dns.zone.to_text方法把zone的信息從Python數據結構轉化為字符串,dns.zone.to_text輸出的格式跟/var/named/apple.tree.zone不太一樣,但也是合法的。腳本執行結果:
[root@localhost dns]# python modify.py
before modify:
@ 86400 IN SOA @ apple 2016090108 28800 7200 604800 86400
@ 86400 IN NS ns1
@ 86400 IN A 192.168.183.131
a 60 IN A 192.168.183.132
b 86400 IN A 192.168.183.133
ns1 60 IN A 192.168.183.131
test 60 IN A 1.1.1.1
test 60 IN A 2.2.2.2
test2 60 IN A 192.168.183.131
after modify:
@ 86400 IN SOA @ apple 2016090108 28800 7200 604800 86400
@ 86400 IN NS ns1
@ 86400 IN A 192.168.183.131
a 60 IN A 192.168.183.132
b 86400 IN A 192.168.183.133
mail 0 IN A 127.0.0.1
ns1 60 IN A 192.168.183.131
test 60 IN A 192.168.183.155
test 60 IN A 192.168.183.155
把修改後的內容寫進/var/named/apple.tree.zone,重啟named,測試解析效果:
[root@localhost named]# host mail.apple.tree
mail.apple.tree has address 127.0.0.1
[root@localhost named]# host test.apple.tree
test.apple.tree has address 192.168.183.155
[root@localhost named]# host test2.apple.tree
Host test2.apple.tree not found: 3(NXDOMAIN)
只能修改記錄還不算完事,實際環境中的DNS通常都是主從兩臺,當一個ZONE在主服務器上的數據更新時,會發生一次到從服務器的同步。判斷主服務器上的數據是不是更新的依據就是SOA記錄中的serial大小,當主的serial大於從時,就會進行同步。因此我們每次編輯完ZONE信息後,都不要忘了增加serial的值,否則就會發生DNS主從數據不一致的情況。在上面的腳本最後增加下面這段代碼:
#增加serial
for (name,ttl,rdata) in zone.iterate_rdatas('SOA'):
rdata.serial += 1
dnspython模塊常見用法