ICMPの応答とエラー通知
応答の種類と引用された通信から、確認できた配送状況を整理します。
説明
ICMPはIP通信の状態やエラーを伝えるプロトコルです。Echoへの応答、途中でTTLが尽きた通知、宛先ポートを使えない通知は、それぞれ意味が違います。
Echoの識別子と往復時間を確認し、TTL超過とUDPポート不在の記録を比較します。エラー中に引用された元のパケットを取り出し、無応答から言える範囲と、アプリケーションの確認が別に必要なことを調べます。
1Echo要求と応答
echo.pcapは192.0.2.10と198.51.100.20のEcho往復を表す試料です。
tcpdump -n -tt -vvv時刻とICMPの識別情報 -r echo.pcapEchoの往復要求type8と応答type0の組です。ID4660、sequence1が対応し、時刻は0と0.020000なので往復20msです。応答のTTL63は受信時に残っていた値で、初期TTLが分からなければ通過台数を一意に決められません。
2要求と応答の識別子を復号
ICMP本体をtype、code、checksum、identifier、sequenceへ分け、データ部分も比較します。
python3 -cICMPの各欄を解析 'from pathlib import Path; import struct; a=Path("echo-request.bin").read_bytes(); b=Path("echo-reply.bin").read_bytes(); print(struct.unpack("!BBHHH",a[:8]),a[8:]); print(struct.unpack("!BBHHH",b[:8]),b[8:]); print(a[4:]==b[4:])'識別子とデータの一致typeとchecksumは変わりますが、identifier、sequence、データは一致します。複数の要求を送るときは、相手のIPやこれらの識別情報も使って応答を対応させます。ICMPにはTCPやUDPのポート番号はありません。
3TTLが途中で尽きた通知
TTL1で送ったUDPの経路診断用パケットに対するICMPを読みます。
tcpdump -n -tt -vvv通知と引用されたパケット -r ttl-error.pcapTTL超過の記録192.0.2.1からTime Exceededが返っています。通知の中に元の192.0.2.10→198.51.100.20とUDPポートが引用されています。通知を送ったルーターのIPと、元のパケットの最終宛先は別です。
4エラー通知の中に引用された元の通信
ICMP本体先頭のtype/codeと、8バイト後に始まる引用IPv4ヘッダーを解析します。
python3 -c引用された通信を取り出す 'from pathlib import Path; import ipaddress,struct; m=Path("ttl-error.bin").read_bytes(); q=m[8:]; ihl=(q[0]&15)*4; print(m[0],m[1],ipaddress.ip_address(q[12:16]),ipaddress.ip_address(q[16:20]),struct.unpack("!HH",q[ihl:ihl+4]))'外側の通知と内側のIP/UDPtype11・code0、元の送信元と宛先、UDP40000→33434が得られます。経路診断ではこうした引用を送ったプローブへ結び付けます。通知を受けたことだけで、別のプローブや別アプリケーションの経路まで同じと決められません。
5宛先まで届いてもポートが開いていない場合
今度は十分なTTLで送った別のプローブに対し、同じ宛先から返るUDP port unreachableを読みます。
tcpdump -n -tt -vvv宛先からの到達不能通知 -r port-error.pcapUDPポート不在の記録type3・code3に相当するport unreachableです。パケットが宛先へ届いた結果として、指定UDPポートを受け付けられないと通知しています。途中のルーターでTTLが尽きた例とは、通知元も意味も異なります。
6MTUを超えたパケットの通知
DFが付いた2028バイトのIPv4パケットが、MTU600の経路へ進もうとした記録です。
tcpdump -n -tt -vvv通知されたMTUと引用パケット -r mtu-error.pcapDF付きパケットへの通知fragmentation neededはtype3・code4で、port unreachableのcode3とは異なります。通知されたMTU600に収めるには、オプションなしのIPv4ヘッダー20バイトとUDPヘッダー8バイトを引き、UDPデータを572バイト以下にします。これは転送先のMTUによる制限で、UDPポートの受付状況を示す通知ではありません。
7無応答から分かる範囲
三つのプローブに対し、二つ目だけ時間内に応答がなかった記録を整理します。
python3 -c応答がない記録を表示 'probes=[(1,"192.0.2.1"),(2,None),(3,"198.51.100.20")]; print([(ttl,reply if reply is not None else "timeout") for ttl,reply in probes])'応答とタイムアウトを区別途中がtimeoutでも、TTL3の応答は得られています。制御メッセージの抑制、フィルター、戻り経路等で応答だけが来ない場合もあるため、星印一つをその位置の転送断と断定しません。経路が時間やプローブで変わる場合もあります。
8pingの成功とサービスの応答の違い
同じ宛先へのICMP、TCP、HTTPの確認記録を読みます。
cat checks.csv層ごとの確認結果Echoは成功、TCP443はRSTで拒否、TCP80は接続し、HTTPは503です。IPの応答、ポートの受付、アプリケーションの正常動作は段階が違います。「pingが通る」だけでWebサービスが利用可能とは判定できません。
まとめ
ICMPのtype/codeと、識別子や引用パケットで元の通信へ対応させます。
途中のTTL超過、宛先のポート不在、無応答では分かることが異なります。
ICMPで応答することと、TCP接続やアプリケーションの正常動作を分けて確認します。
操作履歴と解説を振り返れます。
シェル / PCAPと通信の記録
tcpdump -n -tt -vvv -r echo.pcapreading from file echo.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 64, id 1, offset 0, flags [none], proto ICMP (1), length 36)
192.0.2.10 > 198.51.100.20: ICMP echo request, id 4660, seq 1, length 16
0.020000 IP (tos 0x0, ttl 63, id 1, offset 0, flags [none], proto ICMP (1), length 36)
198.51.100.20 > 192.0.2.10: ICMP echo reply, id 4660, seq 1, length 16python3 -c 'from pathlib import Path; import struct; a=Path("echo-request.bin").read_bytes(); b=Path("echo-reply.bin").read_bytes(); print(struct.unpack("!BBHHH",a[:8]),a[8:]); print(struct.unpack("!BBHHH",b[:8]),b[8:]); print(a[4:]==b[4:])'(8, 0, 21557, 4660, 1) b'abcdefgh' (0, 0, 23605, 4660, 1) b'abcdefgh' True
tcpdump -n -tt -vvv -r ttl-error.pcapreading from file ttl-error.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 64, id 1, offset 0, flags [none], proto ICMP (1), length 56)
192.0.2.1 > 192.0.2.10: ICMP time exceeded in-transit, length 36
IP (tos 0x0, ttl 1, id 20, offset 0, flags [none], proto UDP (17), length 32)
192.0.2.10.40000 > 198.51.100.20.33434: [no cksum] UDP, length 4python3 -c 'from pathlib import Path; import ipaddress,struct; m=Path("ttl-error.bin").read_bytes(); q=m[8:]; ihl=(q[0]&15)*4; print(m[0],m[1],ipaddress.ip_address(q[12:16]),ipaddress.ip_address(q[16:20]),struct.unpack("!HH",q[ihl:ihl+4]))'11 0 192.0.2.10 198.51.100.20 (40000, 33434)
tcpdump -n -tt -vvv -r port-error.pcapreading from file port-error.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 63, id 1, offset 0, flags [none], proto ICMP (1), length 56)
198.51.100.20 > 192.0.2.10: ICMP 198.51.100.20 udp port 33434 unreachable, length 36
IP (tos 0x0, ttl 63, id 21, offset 0, flags [none], proto UDP (17), length 32)
192.0.2.10.40000 > 198.51.100.20.33434: [no cksum] UDP, length 4tcpdump -n -tt -vvv -r mtu-error.pcapreading from file mtu-error.pcap, link-type EN10MB (Ethernet)
0.000000 IP (tos 0x0, ttl 64, id 17185, offset 0, flags [none], proto ICMP (1), length 56)
192.0.2.1 > 192.0.2.10: ICMP 198.51.100.20 unreachable - need to frag (mtu 600), length 36
IP (tos 0x0, ttl 64, id 17185, offset 0, flags [DF], proto UDP (17), length 2028)
192.0.2.10.40000 > 198.51.100.20.9000: UDP, length 2000python3 -c 'probes=[(1,"192.0.2.1"),(2,None),(3,"198.51.100.20")]; print([(ttl,reply if reply is not None else "timeout") for ttl,reply in probes])'[(1, '192.0.2.1'), (2, 'timeout'), (3, '198.51.100.20')]
cat checks.csvCHECK,RESULT,DETAIL ICMP_echo,reply,20 ms TCP_443,refused,RST received TCP_80,connected,SYN-ACK received HTTP_status,503,Service Unavailable
演習完了