当前位置: 首页 > 工具软件 > dnspython > 使用案例 >

python代码使用dns进行调试_dnspython模块常见用法

邹学民
2023-12-01

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.treeIN SOAapple.tree. apple.apple.tree. (

2016090107 ; serial

28800      ; refresh (8 hours)

7200       ; retry (2 hours)

604800     ; expire (1 week)

86400      ; minimum (1 day)

)

NSns1.apple.tree.

A192.168.183.131

$ORIGIN apple.tree.

aA192.168.183.132

bA192.168.183.133

$TTL 60; 1 minute

cCNAMEb

ns1A192.168.183.131

testA1.1.1.1

testA2.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:', )

('rdtype:', 1)

('address:', u'1.1.1.1')

('address:', u'2.2.2.2')

qname是要查询的域名;rdtype是记录类型,dnspython的数据结构中将各种类型的DNS记录用数字定义,其中“1”表示A记录,具体对应关系如下表:

类型值含义

A1主机地址

NS2经过授权的名称服务器

CNAME5用作别名的规范名称

SOA6标记开始一个授权区域

PTR12域名指针

MX15邮件交换

TXT16文本字符串

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.treeIN SOAapple.tree. apple.apple.tree. (

2016090107 ; serial

28800      ; refresh (8 hours)

7200       ; retry (2 hours)

604800     ; expire (1 week)

86400      ; minimum (1 day)

)

NSns1.apple.tree.

A192.168.183.131

$ORIGIN apple.tree.

aA192.168.183.132

bA192.168.183.133

$TTL 60; 1 minute

cCNAMEb

ns1A192.168.183.131

testA1.1.1.1

testA2.2.2.2

#重启named后

[root@localhost ~]# cat /var/named/apple.tree.zone

$ORIGIN .

$TTL 86400; 1 day

apple.treeIN SOAapple.tree. apple.apple.tree. (

2016090108 ; serial

28800      ; refresh (8 hours)

7200       ; retry (2 hours)

604800     ; expire (1 week)

86400      ; minimum (1 day)

)

NSns1.apple.tree.

A192.168.183.131

$ORIGIN apple.tree.

$TTL 60; 1 minute

aA192.168.183.132

$TTL 86400; 1 day

bA192.168.183.133

$TTL 60; 1 minute

ns1A192.168.183.131

testA1.1.1.1

A2.2.2.2

test2A192.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

 类似资料: