PythonでUDPトレースルート(traceroute)〜ネットワーク経路調査

目的のホストまでのネットワーク経路を表示するPythonスクリプト

Tracerouteの原理は以下のような感じで、目的のホストまでUDPパケットを出して手前のルーターから一つずつ探っていく感じ。

http://ja.wikipedia.org/wiki/Traceroute

tracerouteはTTLを1ずつ増やしながらパケットを送信することで、経路情報を取得する。 TTLとはパケットの生存期間を表し、ルータを1つ経由することに1ずつ減算される。 ルータはTTLが2以上のパケットが届いた場合、TTLの値を1だけ小さくし次のルータへ転送する。 TTLが1のパケットが届いた場合、届いたパケットを破棄しICMP time exceededパケットを送信者に返す。

tracerouteはまず、TTLを1にセットしたパケットを送信する。最初のルータに届いた時点でTTLがゼロになり、ICMP time exceededメッセージが戻ってくる。このメッセージの送信元アドレスを見れば、最初のルータのIPアドレスがわかる。次にTTLを2にセットして送信すると、今度は2番目のルータからICMP time exceededが戻ってくる。以降、TTLを3、4・・・と増やしていく事で、順にルータのIPアドレスを得る事ができる。

#!/usr/bin/env python
import sys
import socket

def traceroute(dest_name, port, max_hops):
	dest_addr = socket.gethostbyname(dest_name)
	print "target ==> %s (%s)" % (dest_name, dest_addr)

	socket.setdefaulttimeout(10)
	icmp = socket.getprotobyname('icmp')
	udp = socket.getprotobyname('udp')
	ttl = 1

	while True:
		recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
		send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
		send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
		recv_socket.bind(("", port))
		send_socket.sendto("", (dest_addr, port))
		curr_addr = None
		curr_name = None
		try:
			_, curr_addr = recv_socket.recvfrom(512)
			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_name == dest_name or curr_addr == dest_addr or ttl > max_hops:
			break

if __name__ == "__main__":
	traceroute(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))


使い方(実行はroot権限で)

./traceroute.py google.com 13402 30