Pythonではsocket
モジュールを用いることでTCP/IPの低レベルな処理を呼び出すことができるのだが、その中のsocket.getaddrinfo()
を用いると、ドメイン名をDNSサーバに問い合わせて対応するIPアドレスを得る(名前解決を行う)ことができる。以前扱ったsocket.gethostbyname()
はIPv4のみの対応だが、こちらはIPv6にも対応している。名前解決に失敗した場合は例外socket.gaierror
が発生する。
Pythonの対話モード上でのテスト
下はPython 3の対話モードでexample.com
に対してsocket.getaddrinfo()
を呼び出しているところ。本記事全体においてIPアドレス部分は伏せている。
(モジュールをインポート) >>> import socket (example.comに対してgetaddrinfo()を呼び出す) >>> i = socket.getaddrinfo ('example.com', None) (戻り値を確認) >>> i [(<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx', 0, 0, 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('xx.xxx.xxx.xx', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('xx.xxx.xxx.xx', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('xx.xxx.xxx.xx', 0))] (存在しない名前を指定した場合) >>> i = socket.getaddrinfo ('examplee.co', None) Traceback (most recent call last): ... socket.gaierror: [Errno -2] Name or service not known
名前解決処理におけるsocket.getaddrinfo()の詳細
socket.getaddrinfo()
の2番目の引数は、名前解決のみを行うのであればNone
でよい- 戻り値は5要素のタプルが並んだリストの形となる
- タプルの1番目の要素が
AF_INET6
ならIPv6アドレスでAF_INET
ならIPv4アドレスとなり、これらの定数はsocket
のすぐ下にあるが、Python 3ではsocket.AddressFamily
の下にもある - リスト内にはタプルの2番目の要素の値以外が同一の複数の項目が存在する
- この要素の定数は
socket
のすぐ下にあるが、Python 3ではsocket.SocketKind
の下にもある
- この要素の定数は
- タプルの5番目の要素はタプル型となっており、この最初(0番)の要素がIPアドレスの文字列となっている
socket.getaddrinfo()
の6番目の引数にsocket.AI_CANONNAME
を指定すると、指定したドメイン名にCNAME(別名)が存在する場合にリストの1番目の要素のタプルの4番目の要素にその名前が入り、存在しない場合は元の名前(1番目の引数に渡した文字列)が入る- リストの2番目以降の要素のタプルの4番目の要素は全て空文字列となる
- タプルの1番目の要素が
サンプルとその実行例
下のコードは引数に指定したドメイン名(複数可)全てについて名前解決を試みて結果を表示する。別名が存在する場合にはその名前を表示した後で名前解決を行う。
nslookup.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
# 引数に指定(複数可)した全ドメイン文字列に対してDNSによる名前解決を行う
# IPv4/IPv6両対応,CNAME対応
from __future__ import print_function
from socket import AI_CANONNAME, getaddrinfo, gaierror
from socket import AF_INET6, AF_INET, SOCK_STREAM
import sys
def nslookup (hostlist):
"""
名前解決を行う
"""
for host in hostlist:
try:
# 別名の処理を行うためにAI_CANONNAMEを指定する
addrinfolist = getaddrinfo (host, None, 0, 0, 0, AI_CANONNAME)
except gaierror:
print ('{0} -> *** NOT FOUND ***'.format (host))
continue
# getaddrinfo()ではタプルの *リスト* が返されるため
# リストの各要素ごとにループしつつタプルの要素も展開する
# IPアドレスの文字列はタプルsockaddrの最初(0番)の要素となる
# このプログラムではprotoは使用していない
for family, kind, proto, canonical, sockaddr in addrinfolist:
# IPv4かIPv6かを判定して出力に含めることにする
ipver = ''
if family == AF_INET6:
ipver = ' [IPv6]'
elif family == AF_INET:
ipver = ' [IPv4]'
# kindの値ごとに重複した項目が存在し
# また、canonicalは一番最初の項目以外は空文字列になるため
# SOCK_STREAMの項目でのみ表示処理を行う
if kind == SOCK_STREAM:
if canonical == host or canonical == '':
# 別名が設定されていない場合はIPアドレスを表示
print ('{0} = {1}{2}'.format (host, sockaddr[0], ipver))
else:
# 別名が設定されている場合はそれを表示してから
# 名前解決処理を行い、ループを抜ける
print ('{0} -> {1}'.format (host, canonical))
nslookup ([canonical,])
break
if __name__ == '__main__':
if len (sys.argv) < 2:
sys.exit ('USAGE: {0} [HOSTNAME...]'.format (__file__))
# 重複する引数があれば取り除く
hosts = []
for host in sys.argv[1:]:
if not host in hosts:
hosts.append (host)
# 処理対象のリストを関数に渡す
nslookup (hosts)
実行例$ /path/to/nslookup.py example.com example.jp example.net example.org example.com = xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx [IPv6] example.com = xx.xxx.xxx.xx [IPv4] example.jp -> *** NOT FOUND *** example.net = xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx [IPv6] example.net = xx.xxx.xxx.xx [IPv4] example.org = xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx [IPv6] example.org = xx.xxx.xxx.xx [IPv4]
このコードは名前解決に成功したものも失敗したものも表示するが、これを改造することで名前解決に失敗したドメインか成功したドメインのいずれかのみを表示させることもできる。
関連:コマンドでドメイン名の名前解決を行う
コマンドで名前解決を行う場合、host
コマンドを用いるのが最も簡単で、IPv4とIPv6のアドレスの両方に対応しているドメインの場合、両方のアドレスが表示される。出力もシンプルで見やすい。
(example.comを試して成功) $ host example.com example.com has address xx.xxx.xxx.xx example.com has IPv6 address xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx (example.jpを試して失敗) $ host example.jp Host example.jp not found: 3(NXDOMAIN) $ echo ${?} 1
上のhost
はBINDのもので、Debian/Ubuntuではbind9-host
というパッケージとなっており、別の実装であるknot-host
という別のパッケージでは少しだけ出力が異なる。
BINDのツールであるnslookup
やdig
といったコマンド(Debian/Ubuntuではdnsutils
というパッケージ)ではany
を問い合わせの種類に指定することで、IPv4とIPv6のアドレスの両方に対応しているドメインで両方のアドレスが表示され、AAAA
のところに表示されるのがIPv6のアドレスでA
のところに表示されるのがIPv4のアドレスとなる。ただし他の種類の情報も表示されるため、IPアドレス部分が見つけづらい。
(nslookupコマンドを使用する場合) $ nslookup -type=any example.com ... example.com has AAAA address xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx Name: example.com Address: xx.xxx.xxx.xx (digコマンドを使用する場合) $ dig example.com any ... ;; ANSWER SECTION: example.com. xxxxx IN AAAA xxxx:xxxx:xxx:x:xxx:xxxx:xxxx:xxxx ... example.com. xxxxx IN A xx.xxx.xxx.xx
なお、この any
の部分を aaaa
にして実行するとIPv6のアドレスのみが表示される。
- Python 2.7.12, 3.5.2
- BIND 9.10.3-P4