dnspythonでDNSサーバのzone fileを扱う

BINDやNSDといったDNSサーバのゾーンファイルをpythonで扱うライブラリは幾つかある.その中でもdnspythonはある程度メンテされていて,かつ使いやすそう.

インストール

pipでインストール可能.ただしPython 2.xと3.xとでパッケージが分かれているので注意が必要.

Python 2.xの場合

$ pip install dnspython

Python 3.xの場合

$ pip install dnspython3

Zone fileの読み込み & レコードの参照

下のようなzone fileが/path/to/example.com.zoneに置いてある場合を考える.

$ORIGIN example.com.
$TTL 3600
@       IN  SOA ns1.example.com. root.example.com. (
                    2014060801  ; Serial
                    28800       ; Refresh
                    14400       ; Retry
                    3600000     ; Expire
                    3600        ; Minimum TTL
                )

        IN  NS  ns1.example.com.

ns1     IN  A   192.168.1.2

;;; Network
gateway     IN  A       192.168.1.1
            IN  AAAA    2001:db8::1

;;; Server
svr1        IN  A       192.168.1.101
svr2        IN  A       192.168.1.102
svr3        IN  A       192.168.2.201
wiki        IN  CNAME   svr3

これを読み込んだ上で,全てのレコードを表示する.

import dns
from dns import (zone, rdataclass, rdatatype)

zone_example = zone.from_file("/path/to/example.com.zone")

print("Origin: {}".format(zone_example.origin))

for name, node in zone_example.nodes.items():
    for rdataset in node.rdatasets:
        for rdata in rdataset:
            print("{}: {} {} {}".format(name,
                                        rdataclass.to_text(rdataset.rdclass),
                                        rdatatype.to_text(rdataset.rdtype),
                                        rdataset.ttl))

            rdata_property_names = set(dir(rdata)) - set(dir(dns.rdata.Rdata))

            for property_name in rdata_property_names:
                if not property_name.startswith("_"):
                    print("    {}: {}".format(property_name,
                                              getattr(rdata, property_name)))

この場合の出力はこのような感じ:

Origin: example.com.
@: IN SOA 3600
    mname: ns1
    minimum: 3600
    serial: 2014060800
    rname: root
    expire: 3600000
    refresh: 28800
    retry: 14400
@: IN NS 3600
    target: ns1
svr1: IN A 3600
    address: 192.168.1.101
svr2: IN A 3600
    address: 192.168.1.102
svr3: IN A 3600
    address: 192.168.2.201
wiki: IN CNAME 3600
    target: svr3
ns1: IN A 3600
    address: 192.168.1.2
gateway: IN A 3600
    address: 192.168.1.1
gateway: IN AAAA 3600
    address: 2001:db8::1

取得したいレコードのドメイン名が既知である場合,find_node()/find_rdataset()を使う.似た名前のメソッドfind_rrset()があるが,こちらは返ってくるオブジェクトにドメイン名の情報が含まれているかの違いがある.

node_gateway = zone_example.find_node("gateway")
rdataset_gateway_a = zone_example.find_rdataset("gateway", "A")
rrset_gateway_a = zone_example.find_rrset("gateway", "A")

print(node_gateway)
print(node_gateway.rdatasets)
print(rdataset_gateway_a.__repr__(),
      rdataset_gateway_a,
      hasattr(rdataset_gateway_a, "name"))
print(rrset_gateway_a.__repr__(),
      rrset_gateway_a,
      hasattr(rrset_gateway_a, "name"))

この場合の出力はこうなる:

<DNS node 140259498981032>
[<DNS IN A rdataset>, <DNS IN AAAA rdataset>]
<DNS IN A rdataset> 3600 IN A 192.168.1.1 False
<DNS gateway IN A RRset> gateway 3600 IN A 192.168.1.1 True

指定した名前のレコードが存在しない場合,KeyErrorが投げられる.Noneを返してほしい場合,get_node()/get_rdataset()/get_rrset()を使う. 読み込んだzone fileはレコードの新規追加や編集・削除もできる.このあたりは参考元のページにサンプルコード付きでかなり詳しく書かれているので割愛.

Zone fileの編集の半自動化に使ったり,テストのようなものを書くのに使えそう.

参考