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と違ってかなり高速。

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