tracerouteとは? (Pythonで試す)

ネットワーク

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として通知する。

動作の流れは

  1. TTL1でYahoo.co.jpにEcho request送信
  2. 最初に到着したルータにてTTLが0になるため、PCにTTL Exceededを送信。(3回繰り返す)
  3. TTL2でYahoo.co.jpにEcho request送信
  4. 2番目に到着したルータにてTTLが0になるため、PCにTTL Exceededを送信。(3回繰り返す)
  5. 1~4を繰り返す。
  6. 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と違ってかなり高速。

次はこのデータを図にして自動的にネットワーク図のようなものが作れたらいいなと
考えています。

タイトルとURLをコピーしました