🧪
WSL で手を動かして覚える TCP/IP 入門
Windows の WSL2 上で Docker Compose を使い、ping と HTTP の裏側で動く ARP / ICMP / TCP を、実際のパケットを“見ながら”確認します。所要 10〜15 分、初学者向けです。
このハンズオンでやること(ゴールと流れ)
- 同一サブネット上に
client
/server
/sniffer
の3コンテナを立ち上げ、Docker のブリッジlabnet
を作る -
client → server
の通信で、まず ARP(IP→MAC 解決)が起き、その後 ICMP(ping) が流れる様子を観察する -
client
からserver:8000
に HTTP でアクセスし、裏側の TCP 3ウェイハンドシェイク を観察する - 使い終わったら
docker compose down
で後片付けして元の状態に戻す
対象と前提
- Windows 11 + WSL2 + Ubuntu で動作検証をしています。
-
Docker Desktop for Windows をインストールし、
Settings → Resources → WSL Integration
で Ubuntu を ON にします。
- 事前チェック:
PowerShell
PS> wsl -l -v
NAME STATE VERSION
* docker-desktop-data Running 2
docker-desktop Running 2
Ubuntu Running 2
WSL(Ubuntu)ターミナル
$ docker -v
Docker version 24.0.6, build ed223bc
$ docker compose version
Docker Compose version v2.23.0-desktop.1
いずれもバージョンが表示されれば準備完了です。
作るもの(最小の“私設 LAN”)
-
client
とserver
は、同じユーザー定義ブリッジlabnet
に接続されます(同一サブネット上)。 - 同一サブネット間の通信はルータを経由せず、直接イーサネットフレームを投げます。そのため最初に ARP で相手の MAC を解決します。
-
sniffer
はserver
のネットワーク名前空間をそのまま共有し(同じeth0
を見る)、tcpdump
でserver
側を流れるパケットを観察します。
用語解説:
- サブネット: 同じネットワークに属する IP のまとまり。サブネット内どうしはルータなしで直接通信できる。
- MAC アドレス: ネットワーク機器(NIC)の個体識別番号。L2(データリンク層)の「住所」。
- IP アドレス: コンピュータに割り当てられた L3(ネットワーク層)の「住所」。
- ARP: IP アドレスから対応する MAC アドレスを問い合わせる仕組み(IP→MAC の対応表を作る)。
- ネットワーク名前空間: 1 台の OS の中で、仮想的に独立したネットワーク空間を作る仕組み(コンテナごとに分かれる)。
1) ひな形(2ファイル)の用意
本記事で使用する lab 用の内容は、sfx-t/tcpip-labで公開しています。
ホーム直下などに作業用ディレクトリを作ります。
tcpip-lab/
├─ docker-compose.yml
└─ site/
└─ index.html
docker-compose.yml
services:
# 簡単な HTTP サーバー(Python の標準機能)
server:
image: python:3.12-alpine
working_dir: /site
command: sh -c "python -m http.server 8000"
volumes:
- ./site:/site:ro
networks:
- labnet
# ツールが一通り入ったクライアント
client:
image: nicolaka/netshoot:latest
command: ["sh", "-c", "sleep infinity"]
networks:
- labnet
# server と同じネット名前空間で動くスニファ
sniffer:
image: nicolaka/netshoot:latest
network_mode: "service:server" # server の eth0 を共有
cap_add:
- NET_ADMIN
- NET_RAW
command: ["sh", "-c", "sleep infinity"]
depends_on:
- server
networks:
labnet:
driver: bridge
ここでしていること(ざっくり):
-
server: Python の簡易 HTTP サーバを 8000 番で起動。
volumes
でホスト側site/
を読み取り専用で見せています。 -
client: 診断ツールが多数入った
netshoot
を常駐(ping, curl, ip, tcpdump などが使えます)。 -
sniffer:
network_mode: "service:server"
によりserver
と同じネットワーク名前空間(同じeth0
)を共有。cap_add
によりパケットのキャプチャが可能です。 - labnet: Docker のブリッジネットワーク。コンテナ間は同一サブネット上に配置されます。
site/index.html
<!doctype html>
<meta charset="utf-8">
<title>Hello TCP/IP</title>
<h1>Hello TCP/IP</h1>
<p>WSL 上の Docker から配信しています。</p>
ブラウザで見える最小の静的ページです(後で curl
で取得します)。
2) 起動と疎通確認
作業フォルダへ移動して起動します。
$ cd tcpip-lab
$ docker compose up -d
[+] Running 3/3
✔ Network tcpip-lab_labnet Created
✔ Container tcpip-lab-server-1 Started
✔ Container tcpip-lab-client-1 Started
client
と server
が labnet
上で起動します。
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
...
2-1) ARP と ICMP(ping)を観察
client
から server
に ping
を打ち、その様子を sniffer
で観察します。
# 1) client シェルへ
$ docker compose exec client sh
# 2) server の IP を調べる
/ # getent hosts server
172.21.0.2 server
# 3) 別タブで sniffer 側の tcpdump を開始(server が受けるパケットを観測)
$ docker compose exec sniffer sh
/ # tcpdump -n -i eth0 icmp
別のタブで client
のシェルから ping
を実行します。
/ # ping -c 3 server
...
- 最初に ARP が発生し、server の MAC を解決します。
- 続いて ICMP が往復します。
2-2) HTTP と TCP ハンドシェイクを観察
sniffer
で TCP を観察しながら、client
から server:8000
にアクセスします。
# sniffer タブ(server 側通信を観測)
/ # tcpdump -n -i eth0 tcp port 8000
# client タブ
/ # curl -sS http://server:8000/
<html>...
-
SYN → SYN/ACK → ACK
の 3 ウェイハンドシェイクが流れ、HTTP のデータが続きます。 -
FIN
による接続終了も見られます。
3) 後片付け
$ docker compose down
[+] Running 3/3
✔ Container tcpip-lab-client-1 Removed
✔ Container tcpip-lab-server-1 Removed
✔ Network tcpip-lab_labnet Removed
以上で、最小構成での ARP / ICMP / TCP の“見て理解する”ハンズオンは完了です。
Discussion