Traceroute
最近割と忙しくて更新できてない。。。
ちょいちょいメモることはあるのでそのあたりはScrapboxに書いていこうと思う。
Tracerouteとは
tracerouteは宛先IPまでの経路を取得するのによく使うツール。
普段割とよく使うけれど、具体的な処理はどうなっているのか確認してみた。
まずはとりあえず、WindowsからTracerouteを試してみる。
宛先はYahoo.co.jp
このように自宅から宛先までに通る経路のIPアドレスを教えてくれる。
具体期にはどのような動作が行われているのか?
まずはWiresharkで確認してみる。
Windowsでtraceroute
Windowsではtracertコマンドを使う。
tracertではICMPのEcho requestが3回送られる(ping)
また、その時TTLを1からスタートし、1ずつインクリメントされて送信される。
このTTLとはルータを経由するときの最大ホップ数を決めるものであり、
パケットの生存期間を表す。
この生存期間が1の状態でルータに到着しそのルータが宛先でない場合、
TTLを1減らす必要があり、0になってしまうため破棄することになる。
このときルータは送信元の機器にTTL不足で破棄した事をICMPのTTL Exceededとして通知する。
動作の流れは
- TTL1でYahoo.co.jpにEcho request送信
- 最初に到着したルータにてTTLが0になるため、PCにTTL Exceededを送信。(3回繰り返す)
- TTL2でYahoo.co.jpにEcho request送信
- 2番目に到着したルータにてTTLが0になるため、PCにTTL Exceededを送信。(3回繰り返す)
- 1~4を繰り返す。
- TTLがYahoo.co.jpに到達するのに十分な数値となり、Yahoo.co.jpからEcho replyが届く
というように処理が行われる。
下のキャプチャ画像で途中Destination unreachableが届いているのは、NetBIOSによる名前解決に対する帰りだと思われる。
Linuxのtraceroute
Linuxで使われるtracerouteコマンドはWindowsとは動作が異なる。
tracerouteコマンドではTTLをインクリメントする点はWindowsと同じだが、
ICMPではなく、UDP通信を送信する。
Windowsと違って、途中からタイムアウトで見えなくなってしまった。
これはICMPとUDPによる違い?
おそらく、UDPがドロップされてしまいタイムアウトしているのでは?
キャプチャした結果は下のようになる。
Linuxの場合は応答を順に待つわけではなく、一気に送信するっぽい?
tracerouteで使われるポート番号は33434あたりのUDPが多いらしい。
Pythonで試してみる。
上のような仕組みであれば、自分でも作れそう。
作ると言ってもライブラリつかうだけだけど。
まず最初に考えたのは標準ライブラリであるsocketを利用する方法。
ネットにあるコードを参考に作ってみたけれども、Linuxでは動作するが、
Windowsで動作しない。。。
def traceroute(dst_node):
dst_address = socket.gethostbyname(dst_node)
print('宛先IP: ' + dst_address)
socket.setdefaulttimeout(5)
ttl = 1
while True:
send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
send_socket.sendto(b"", (dst_address, 33434))
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
recv_socket.bind(("", 33434))
#recv_socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
#recv_socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
curr_addr = None
curr_name = None
try:
_, curr_addr = recv_socket.recvfrom(512)
#print(curr_addr)
curr_addr = curr_addr[0]
try:
curr_name = socket.gethostbyaddr(curr_addr)[0]
except socket.error:
curr_name = curr_addr
except socket.error:
pass
finally:
send_socket.close()
recv_socket.close()
if curr_addr is not None:
curr_host = "%s (%s)" % (curr_name, curr_addr)
else:
curr_host = "*"
print("%d\t%s" % (ttl, curr_host))
ttl += 1
if curr_addr == dst_address:
break
UDPは飛ぶけれども帰りのICMPをプログラムで受信できない。。なぜ?
少し試してもよくわからなかったのでScapyを使うことに。
Scapyはpipでインストールが必要になるが、通信関連を簡単に使える便利なやつ。
今回はこのtracerouteの結果をDBに格納して、図式かできないかなぁなんて考えているので
取得したtracerouteの結果を配列に入れて、DBにInsertしている。
一応ICMPとUDPを共に試すように書けているはず。
※まだ調整中です
def traceroute_win(dst_node):
dst_address = socket.gethostbyname(dst_node)
print('宛先IP: ' + dst_address)
# 疎通チェック
icmp = sr1(IP(dst=dst_address)/ICMP(),timeout=3)
udp = sr1(IP(dst=dst_address)/UDP(sport=RandShort(), dport=33534),timeout=3)
try:
print('ICMP応答あり:' + str(icmp.src))
accessFlag = True
except AttributeError:
print('ICMP応答なし')
accessFlag = False
try:
print('UDP応答あり:' + str(udp.src))
accessFlag = True
except AttributeError:
print('UDP応答なし')
accessFlag = False
# traceroute開始
ttl = 1
unknown = 0
SRC_ARRAY = []
while True:
ans = None
ans = sr1(IP(dst=dst_address,ttl=ttl)/ICMP(),timeout=2)
#result = ans.make_table(lambda s,r: (s.dst, s.ttl, r.src))
try:
print(str(ans.src))
SRC_ARRAY.append(str(ans.src))
unknown = 0
if(dst_address == ans.src):
print('調査完了')
if(debugFlag):
print(SRC_ARRAY)
break
except AttributeError:
ans = sr1(IP(dst=dst_address,ttl=ttl)/UDP(sport=RandShort(), dport=33534),timeout=2)
try:
print(str(ans.src))
SRC_ARRAY.append(str(ans.src))
unknown = 0
if(dst_address == ans.src):
print('調査完了')
if(debugFlag):
print(SRC_ARRAY)
break
except AttributeError:
print('Time out...')
SRC_ARRAY.append('*')
unknown += 1
ttl += 1
if(ttl >= 30):
print('Hop回数が30を超えました。')
break
if((accessFlag == False) and unknown >= 7):
print('宛先に疎通不可のため終了。')
break
if(debugFlag):
print(SRC_ARRAY)
with db.database() as d:
neighbor_src = '127.0.0.1'
for ip in SRC_ARRAY:
result = d.search_node(ip)
if (result == 'none'):
d.register(str(ip), neighbor_src)
else:
print('登録済み')
neighbor_src = ip
このコードを実行すると、下のように配列として表示される。
また、Windowsのtracertと違ってかなり高速。
次はこのデータを図にして自動的にネットワーク図のようなものが作れたらいいなと
考えています。