一覧NW-18-3 · 通信品質と切り分け

非対称ルーティングとパケットキャプチャ

同じ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種類で一致します。実際の再送を含む調査では集合化で回数を失わないようにし、ペイロード、時刻、採取漏れも照合します。非対称経路、表示条件、時計の基準を確認してから損失や遅延を判断します。

まとめ

見えない通信を直ちに損失とせず、往復経路とフィルターを確認します。

同じ通信を複数地点の欄で対応付け、時刻差には時計のずれも含まれることを考慮します。

操作履歴と解説を振り返れます。

シェル / 通信の調査

1
tcpdump -n -tt -S -r r1-out.pcap
reading 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
2
tcpdump -n -tt -S -r client.pcap
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.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
3
cat topology.txt tcpdump -n -tt -S -r r2-out.pcap
C 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
4
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.0
5
tcpdump -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
6
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
7
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

演習完了