非対称ルーティングとパケットキャプチャ
同じTCP接続を端末・R1・R2の三地点で確認します。seqとackを対応付け、片方向の記録、フィルター、時刻差が調査結果へ与える影響を確かめます。
説明
往路と復路が別のルーターを通ると、一地点では片方向しか見えないことがあります。表示フィルターや時計のずれも、パケットが消えた、順序が逆転したように見える原因になります。
同じTCP接続を端末・R1・R2の三地点で確認します。seqとackを対応付け、片方向の記録、フィルター、時刻差が調査結果へ与える影響を確かめます。
1往路だけが見えるキャプチャ
端末CからサーバーSの8080へ接続しています。R1のS側出口で採取した記録を解析します。送信元49152と宛先8080、seqとackに着目します。
tcpdump -n -tt -S時刻・絶対seq/ackを表示 -r r1-out.pcapR1の出口での記録R1ではC→Sだけが見えます。ただしSYNの後にACK9001、さらにHELLOの5バイトも送っています。応答パケットがここにないことだけを理由に、サーバー無応答と結論付けると、その後のACKと矛盾します。
2端末側で往復を確認
同じ接続を端末Cのインターフェースで確認します。
tcpdump -n -tt -S同じ形式で解析 -r client.pcap端末で採取した往復端末ではSYN/ACKのseq9000・ack1001が届き、その後にACK9001を送っています。サーバーからACK1006も届くため、HELLOの5バイトまで受信されたことが分かります。一地点で見えない通信が、端点では成立しています。
3戻り側の経路を確認
往路はR1、復路はR2を通る構成です。トポロジーを確認した後、R2の端末側出口を解析します。
cat topology.txt往復の経路
tcpdump -n -tt -S -r r2-out.pcap復路側のパケットR2ではSYN/ACKとACK1006が見え、R1では見えなかった戻りの通信に対応します。この試料はNATを使わず、往復ともIPアドレスとポートの組が保たれています。
4同じパケットを値で対応付ける
capture_rows.pyはPCAPからIPv4/TCPの欄を取り出す補助関数です。R1を出たHELLOと、端末から送られたHELLOの値を比較します。
python3 -c二地点の欄を比較 'from capture_rows import read; a=next(x for x in read("client.pcap") if x["flags"]==24); b=next(x for x in read("r1-out.pcap") if x["flags"]==24); fields=["src","dst","sport","dport","seq","ack","flags"]; print([(k,a[k],b[k]) for k in fields]); print(a["ttl"],b["ttl"],b["ms"]-a["ms"])'接続・seq/ack・TTL・時刻差アドレス・ポート・seq1001・ack9001・flags24は一致し、TTLだけ64→63へ減っています。観測時刻の差は2msです。転送で変わる欄もあるので、フレーム全体の完全一致を要求すると同じパケットを見失います。
5フィルターだけでも片方向になる
往復が入ったclient.pcapに、宛先ポート8080という条件を適用します。
tcpdump -n -tt -S -r client.pcap往復を含む同じ記録 "dst port 8080"宛先ポートだけに限定この条件では戻りパケットの宛先49152が対象外になり、C→Sだけが表示されます。キャプチャ地点の経路だけでなく、採取時と表示時のフィルターも確認します。port 8080なら送信元・宛先の両方を対象にできます。
6時計のずれを加えた記録
別の収集条件を再現するため、R2の時計が30ms進んだ場合の観測時刻を計算します。パケットを運ぶ時間が増えたのではなく、時刻の基準が変わります。
python3 -c時計差の影響を計算 'from capture_rows import read; r=read("r2-out.pcap")[0]; c=next(x for x in read("client.pcap") if x["flags"]==18); recorded=r["ms"]+30; print(recorded,c["ms"],c["ms"]-recorded); print(c["ms"]-(recorded-30))'観測値の差と既知のずれの補正R2側50msと端末側22msをそのまま引くと-28msになります。既知の時計差30msを補正すると2msです。未知の時計差を通信遅延と取り違えず、同期精度や採取位置の時刻誤差も考慮します。
7二つの経路の記録をまとめる
接続の欄をキーにして、R1とR2の記録を合わせた集合が端末の記録と一致するか確認します。この試料では同じseqの再送はありません。
python3 -c二経路と端末の対応を確認 'from capture_rows import read; fields=["src","dst","sport","dport","seq","ack","flags"]; key=lambda x:tuple(x[k] for k in fields); c={key(x) for x in read("client.pcap")}; r={key(x) for n in ("r1-out.pcap","r2-out.pcap") for x in read(n)}; print(len(c),len(r),c==r)'試料内の5種類の対応両方5種類で一致します。実際の再送を含む調査では集合化で回数を失わないようにし、ペイロード、時刻、採取漏れも照合します。非対称経路、表示条件、時計の基準を確認してから損失や遅延を判断します。
まとめ
見えない通信を直ちに損失とせず、往復経路とフィルターを確認します。
同じ通信を複数地点の欄で対応付け、時刻差には時計のずれも含まれることを考慮します。
操作履歴と解説を振り返れます。
シェル / 通信の調査
tcpdump -n -tt -S -r r1-out.pcapreading from file r1-out.pcap, link-type EN10MB (Ethernet) 0.002000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [S], seq 1000, win 8192, length 0 0.032000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [.], ack 9001, win 8192, length 0 0.042000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 1001:1006, ack 9001, win 8192, length 5: HTTP
tcpdump -n -tt -S -r client.pcapreading from file client.pcap, link-type EN10MB (Ethernet) 0.000000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [S], seq 1000, win 8192, length 0 0.022000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [S.], seq 9000, ack 1001, win 8192, length 0 0.030000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [.], ack 9001, win 8192, length 0 0.040000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 1001:1006, ack 9001, win 8192, length 5: HTTP 0.062000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 1006, win 8192, length 0
cat topology.txt
tcpdump -n -tt -S -r r2-out.pcapC 192.0.2.10 -> R1 -> S 198.51.100.20 C 192.0.2.10 <- R2 <- S 198.51.100.20 R1-out: toward S R2-out: toward C client: C eth0, both directions reading from file r2-out.pcap, link-type EN10MB (Ethernet) 0.020000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [S.], seq 9000, ack 1001, win 8192, length 0 0.060000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 1006, win 8192, length 0
python3 -c 'from capture_rows import read; a=next(x for x in read("client.pcap") if x["flags"]==24); b=next(x for x in read("r1-out.pcap") if x["flags"]==24); fields=["src","dst","sport","dport","seq","ack","flags"]; print([(k,a[k],b[k]) for k in fields]); print(a["ttl"],b["ttl"],b["ms"]-a["ms"])'[('src', '192.0.2.10', '192.0.2.10'), ('dst', '198.51.100.20', '198.51.100.20'), ('sport', 49152, 49152), ('dport', 8080, 8080), ('seq', 1001, 1001), ('ack', 9001, 9001), ('flags', 24, 24)]
64 63 2.0tcpdump -n -tt -S -r client.pcap "dst port 8080"reading from file client.pcap, link-type EN10MB (Ethernet) 0.000000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [S], seq 1000, win 8192, length 0 0.030000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [.], ack 9001, win 8192, length 0 0.040000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 1001:1006, ack 9001, win 8192, length 5: HTTP
python3 -c 'from capture_rows import read; r=read("r2-out.pcap")[0]; c=next(x for x in read("client.pcap") if x["flags"]==18); recorded=r["ms"]+30; print(recorded,c["ms"],c["ms"]-recorded); print(c["ms"]-(recorded-30))'50.0 22.0 -28.0 2.0
python3 -c 'from capture_rows import read; fields=["src","dst","sport","dport","seq","ack","flags"]; key=lambda x:tuple(x[k] for k in fields); c={key(x) for x in read("client.pcap")}; r={key(x) for n in ("r1-out.pcap","r2-out.pcap") for x in read(n)}; print(len(c),len(r),c==r)'5 5 True
演習完了