一覧NW-06-2 · ARPとICMP

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/UDP

type11・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と通信の記録

1
tcpdump -n -tt -vvv -r echo.pcap
reading 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 16
2
python3 -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
3
tcpdump -n -tt -vvv -r ttl-error.pcap
reading 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 4
4
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]))'
11 0 192.0.2.10 198.51.100.20 (40000, 33434)
5
tcpdump -n -tt -vvv -r port-error.pcap
reading 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 4
6
tcpdump -n -tt -vvv -r mtu-error.pcap
reading 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 2000
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])'
[(1, '192.0.2.1'), (2, 'timeout'), (3, '198.51.100.20')]
8
cat checks.csv
CHECK,RESULT,DETAIL
ICMP_echo,reply,20 ms
TCP_443,refused,RST received
TCP_80,connected,SYN-ACK received
HTTP_status,503,Service Unavailable

演習完了