TT Lab
はじめる
学ぶ 学習パス コース

ネットワーク基礎 — Linux VM で手を動かす

TCP ハンドシェイクを3行で捕まえる

TT Labで続きを見る

このラボはVM上で動きます

ループバック(lo)上でncを使ってサーバーとクライアントを作り、tcpdumpでその間を捕まえます。外部のネットワークが不要なので、捕まえるパケットはすべて自分のものです。

目標

接続が開いて閉じる過程をSYN・SYN-ACK・ACK・FINのフラグで読み取り、閉じたポートと開いたポートの違い、TIME-WAIT、エフェメラルポート、そしてポートを掴んでいるプロセスの探し方を身につけます。

なぜ重要なのか

「接続できない」という1つの文に、異なる3つのことが混ざっています。SYNを送ったのにRSTが返ってくるなら、相手は生きていてポートだけが閉じており、何も返ってこないなら、ファイアウォールが捨てているか経路がなく、SYN-ACKまで来たのにデータが来ないなら、アプリケーションの問題です。この3つはユーザーには同じく「つながらない」と見えますが、直す人はまったく違います。フラグを読めるようになれば、最初の30秒でその3つが分かれます。

ステップ

  1. nc -lk 127.0.0.1 9000のサーバーをバックグラウンドで起動し(受け取ったデータは /root/tcp/recv.txt へ)、ss -ltnの出力を /root/tcp/listen.txt に保存してください。
  2. loのポート9000をtcpdumpで捕まえながらhelloを1行送って、ハンドシェイクと終了を含むキャプチャを /root/tcp/handshake.txt に保存してください。
  3. 誰も待ち受けていないポート9001への接続を試み、エラーメッセージとそのときのキャプチャ(RSTが見える)を /root/tcp/refused.txt にまとめて保存してください。
  4. ポート9000にもう一度接続して閉じた直後に、ss -tanのTIME-WAITの行を /root/tcp/timewait.txt に保存してください。
  5. エフェメラルポートの範囲と、ステップ2のキャプチャでクライアントが使ったポートを、/root/tcp/ports.txt にrange=・client_port=の2行で保存してください。
  6. Pythonで127.0.0.1:9000にもう一度bindを試み、そのエラーを /root/tcp/inuse.txt に保存してください。
  7. ss -ltnpでポート9000を掴んでいるプロセスを探し、その行を /root/tcp/owner.txt に保存してください。
  8. /root/tcp/report.md に、client_port=・server_port=の2行と、3段階のハンドシェイクとTIME-WAITがなぜあるのかを書いてください。

参考

待ち受けソケット

nc -lk 127.0.0.1 9000のサーバーをバックグラウンドで起動し(受け取ったデータは /root/tcp/recv.txt へ)、ss -ltnの出力を /root/tcp/listen.txt に保存してください。

setsid nc -lk 127.0.0.1 9000 > /root/tcp/recv.txt 2>&1 < /dev/null &。ss -ltnは、TCP(-t)の待ち受けソケット(-l)を数値(-n)で表示します。127.0.0.1:9000がLISTENと表示されている必要があります。

3行のハンドシェイク

loのポート9000をtcpdumpで捕まえながらhelloを1行送って、ハンドシェイクと終了を含むキャプチャを /root/tcp/handshake.txt に保存してください。

先にキャプチャを起動します: timeout 6 tcpdump -i lo -nn -l 'tcp port 9000' > /root/tcp/handshake.txt 2>&1 &。1秒後にecho hello | nc -q 1 127.0.0.1 9000を実行します。ファイルに、[S]、[S.]、[.]の順で始まり[F.]で終わる行がある必要があり、recv.txtにはhelloが入ります。

閉じたポートはRSTで答える

誰も待ち受けていないポート9001への接続を試み、エラーメッセージとそのときのキャプチャ(RSTが見える)を /root/tcp/refused.txt にまとめて保存してください。

キャプチャtimeout 5 tcpdump -i lo -nn -l 'tcp port 9001' >> /root/tcp/refused.txt 2>&1 &を起動し、nc -zv -w 1 127.0.0.1 9001 >> /root/tcp/refused.txt 2>&1を実行します。ファイルにConnection refusedとFlags [R.]の両方がある必要があります。

TIME-WAIT

ポート9000にもう一度接続して閉じた直後に、ss -tanのTIME-WAITの行を /root/tcp/timewait.txt に保存してください。

echo again | nc -q 1 127.0.0.1 9000の直後にss -tan | grep TIME-WAIT > /root/tcp/timewait.txtを実行します。先に閉じた側(クライアント)に残ります。60秒後に消えるので、すぐに確認してください。

エフェメラルポート

エフェメラルポートの範囲と、ステップ2のキャプチャでクライアントが使ったポートを、/root/tcp/ports.txt にrange=・client_port=の2行で保存してください。

範囲はsysctl -n net.ipv4.ip_local_port_range(2つの数字)です。クライアントのポートは、handshake.txtの[S]の行にある127.0.0.1.<포트> >のポートです(プレースホルダーはポート番号です)。その値が範囲の中にある必要があります。

ポートは1つしか掴めない

Pythonで127.0.0.1:9000にもう一度bindを試み、どこにbindしたかの1行とそのエラーを /root/tcp/inuse.txt にまとめて保存してください。

{ echo 'bind 127.0.0.1:9000'; python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 9000))' 2>&1; } > /root/tcp/inuse.txt。Address already in useが出る必要があります。同じアドレス・ポートは、1つのソケットしか持てません。どのポートで起きた衝突かがファイルに残っている必要があります。別のポートの衝突メッセージは、このステップの答えではありません。

誰が掴んでいるか

ss -ltnpでポート9000を掴んでいるプロセスを探し、その行を /root/tcp/owner.txt に保存してください。

ss -ltnp | grep ':9000 '。-pがusers:(("nc",pid=…))を付け足してくれます。rootでなければ、他のユーザーのプロセスは見えません。

学んだこと

/root/tcp/report.md に、client_port=・server_port=の2行と、3段階のハンドシェイクとTIME-WAITがなぜあるのかを書いてください。

client_portはステップ5の値、server_portは9000です。本文にSYNとTIME-WAITという語を含める必要があります。