4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pythonでネームサーバを指定してdigするためのライブラリ

Last updated at Posted at 2020-06-14

きっかけ

複数ゾーンの複数レコードをネームサーバごとに一致することをチェックするというタスクがあり、そこそこ数があったので python でスクリプトにした。

その時に、python で NS指定 で dig するライブラリって何かいいのがないかなと思って調べたら、202006上旬現在時点で有用そうな情報がまとまったページが見当たらなかったのでここに書き残しておこうと思った。

python なのは、単純に自分が python を勉強してるだけなので簡単にやりたかったらシェルスクリプトで書いたほうが早かったと思う。

環境

この記事を書く上での動作確認は、
Windows10/WSL/Ubuntu-18.04 上にインストールした

# pyenv --version
pyenv 1.2.17-2-ga8ca63fc

# pipenv --version
pipenv, version 2018.11.26

# python --version
Python 3.8.2

で確認している。

上記とは別に、CentOS8上の同バージョン pyenv/pipenv/python でも実施している。

digコマンドで確認する場合

試したかったことのサンプル。

# dig blog.watarinohibi.tokyo @8.8.8.8

;; ANSWER SECTION:
blog.watarinohibi.tokyo. 19     IN      A       157.230.43.191

# dig blog.watarinohibi.tokyo @dns1.p06.nsone.net 

;; ANSWER SECTION:
blog.watarinohibi.tokyo. 20     IN      A       157.230.45.115

dns1.p06.nsone.net は Netlify のNSサーバ
検索してるURLはNetlifyにデプロイしてる自分のブログサイト
上記は例で、実際は特定の1レコードが返ってくるAレコード、SOAレコード、TXTレコード、MXレコード等に対して実施した。

使えそうなライブラリ

  • socket
  • python-dns
  • pydig
  • pynslookup

socket

socket.gethostbyname_ex(hostname)
ホスト名から、IPv4形式の各種アドレス情報を取得します。戻り値は (hostname, aliaslist, ipaddrlist) のタプルで、 hostname は ip_address で指定したホストの正式名、 aliaslist は同じアドレスの別名のリスト(空の場合もある)、 ipaddrlist は同じホスト上の同一インターフェイスのIPv4アドレスのリスト(ほとんどの場合は単一のアドレスのみ) を示します。

使い方

test1.py
#!/usr/bin/env python3
import socket

domain = 'blog.watarinohibi.tokyo'
ns1 = '8.8.8.8' # google DNS
ns2 = 'dns1.p06.nsone.net' # Netlify DNS

# socket
print ("=== socket.getaddinfo")
addrs = socket.getaddrinfo(domain, 80)
for addr in addrs:
    print(addr)

print ("=== socket.gethostbyname")
addrs = socket.gethostbyname(domain)
print (addrs)
 # ./test.py
=== socket.getaddinfo
(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('157.230.45.115', 80))
(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('157.230.45.115', 80))
(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('157.230.45.115', 80))
(<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2400:6180:0:d1::575:a001', 80, 0, 0))
(<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2400:6180:0:d1::575:a001', 80, 0, 0))
(<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('2400:6180:0:d1::575:a001', 80, 0, 0))
=== socket.gethostbyname
157.230.45.115

NSを指定するオプションが(たぶん、見落としてなければ)ない。
今回の用途には不適だが、標準ライブラリで名前解決を試したいときはこれで十分そう。

dnspython

http://www.dnspython.org/
https://github.com/rthalley/dnspython
https://pypi.org/project/dnspython/

使い方

http://www.dnspython.org/examples.html 
あたりを参考に。今回は、NSを指定してのAレコードのルックアップを試したい。

test.py
import dns.resolver

print ("=== dns.resolver @", ns1)
resolver = dns.resolver.Resolver()
resolver.nameservers = [ns1]
answers = resolver.query(domain, 'A')
for rdata in answers:
    print (rdata)

print ("=== dns.resolver @", ns2)
resolver = dns.resolver.Resolver()
resolver.nameservers = [ns2]
answers = resolver.query(domain, 'A')
for rdata in answers:
    print (rdata)
 # ./test.py
=== dns.resolver @ 8.8.8.8
134.209.106.40
=== dns.resolver @ dns1.p06.nsone.net
Traceback (most recent call last):
  ~~~ stack trace

という感じで、IPv4アドレスでのNS指定では動いたが、NetlifyDNSなどのCNAMEレコードでのNS指定ではエラーになってしまった。
https://github.com/rthalley/dnspython/issues/235
にこのエラーと思わしきやりとりがあるが、RFCにないので対応してないみたい。
とはいえ、NSレコードって全然CNAMEで使うものなイメージがあるので、ここらへんはよくわからない。。

pynslookup

https://github.com/wesinator/pynslookup
https://pypi.org/project/nslookup/

使い方

test.py
from nslookup import Nslookup

print ("=== nslookup @", ns1)
dns_query = Nslookup(dns_servers=[ns1])
ips_record = dns_query.dns_lookup(domain)
print(ips_record.response_full, ips_record.answer)

print ("=== nslookup @", ns2)
dns_query = Nslookup(dns_servers=[ns2])
ips_record = dns_query.dns_lookup(domain)
print(ips_record.response_full, ips_record.answer)
 # ./test.py
=== nslookup @ 8.8.8.8
['blog.watarinohibi.tokyo. 19 IN A 178.128.17.49'] ['178.128.17.49']
=== nslookup @ dns1.p06.nsone.net
Traceback (most recent call last):
~~~ stack trace

というわけでこちらもエラー。
コードを確認したところ、
https://github.com/wesinator/pynslookup/blob/master/nslookup/nslookup.py

nslookup.py
import dns.resolver, dns.exception

しており、内部的に dnspython をそのまま使ってるぽかった。

pydig

https://github.com/leonsmith/pydig
https://pypi.org/project/pydig/

使い方

test.py
import pydig

print ("=== nslookup @", ns1)
resolver = pydig.Resolver(
    nameservers = [ns1]
)
answer = resolver.query(domain, 'A')
print (answer)

print ("=== nslookup @", ns2)
resolver = pydig.Resolver(
    nameservers = [ns2]
)
answer = resolver.query(domain, 'A')
print (answer)
 # ./test.py
=== nslookup @ 8.8.8.8
['157.230.35.153']
=== nslookup @ dns1.p06.nsone.net
['206.189.89.118']

お、動いた。こいつはCNAMEのNS指定でも動きそう。
コードを確認すると、
https://github.com/leonsmith/pydig/blob/master/pydig/resolver.py

import subprocess
--
Builds up the final arguments to pass into subprocess

と書いてあり、 subprocess でローカルの dig が動いてるだけだった。なるほど。
digのパスが違う、指定したい場合は、オプションで指定できる。

>>> resolver = pydig.Resolver(
...     executable='/usr/bin/dig',
...     nameservers=[
...         '1.1.1.1',
...         '1.0.0.1',
...     ],
...     additional_args=[
...         '+time=10',
...     ]
... )

結論

Python で NS を指定しての dig を使いたい場合は、digコマンドのラッパーである pydig を使えばよさそうだけど、単なるラッパーでしかないようなのでこれだったら自分で subprocess で書いてしまってもよいかもしれない。

DNSムズい。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?