1. 程式人生 > >dnspython模塊常見用法

dnspython模塊常見用法

python dns

dnspython是一個處理DNS的Python工具模塊,支持查詢、DNS動態更新、操作ZONE配置文件等功能。由於網上文檔較少且不詳細,官方文檔還不完善,這個模塊使用起來比較困難,所以我決定把我自己學到的東西做個記錄總結。

學習環境部署

? 操作系統: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模塊常見用法