pcap (packet capture) というパケットキャプチャ用APIの実装には、UNIX系のlibpcapとWindowsのWinPcapがあります。これらのライブラリを利用したアプリケーションとしては、UNIX系のtcpdumpとWindowsのWiresharkが有名です。また、スクリプト言語にはlibpcap/WinPcapのラッパーが存在しており、パケットキャプチャアプリケーションの開発時に利用できます。例えばPerlにはNet::Pcapというラッパーがあります。
以下にtcpdumpコマンドを用いたパケットキャプチャのチートシートを記載します。キャプチャしたものの解析に必要となるヘッダ情報の意味自体は「TCPDUMPの出力を見てみよう」などを参照してください。
tcpdumpを実行するPCに複数のネットワークインターフェースが存在する場合に、それらのうちどれでパケットキャプチャするかを指定します。-i オプションを指定しないと up 状態のインタフェースの中で番号が一番小さいものが自動的に選択されることに注意します。システムによっては lo (ループバックインタフェース) を含むすべてのインタフェースに関してキャプチャを行う -i any がサポートされています。
$ tcpdump -i eth0
あるネットワークインターフェースには様々なTCP/UDPパケットが到着します。それらのうち、TCP/UDPパケットのヘッダにおける「送信元ポート番号」または「宛先ポート番号」が特定の番号のもののみに絞り込むことができます。
$ tcpdump -i eth0 port 80
$ tcpdump -i eth0 src port 80 ← 「送信元ポート番号」が80番のもののみ
$ tcpdump -i eth0 dst port 80 ← 「宛先ポート番号」が80番のもののみ
$ tcpdump -i eth0 dst port 80 or dst port 443 ← 条件は 'or' で緩められます
あるネットワークインターフェースには様々なIPパケットが到着します。それらのうち、IPパケットのヘッダにおける「送信元IPアドレス」または「宛先IPアドレス」が特定の番号のもののみに絞り込むことができます。
$ tcpdump -i eth0 host 10.0.2.15
$ tcpdump -i eth0 src host 10.0.2.15 ← 「送信元IPアドレス」で絞り込み
$ tcpdump -i eth0 dst host 10.0.2.15 ← 「宛先IPアドレス」で絞り込み
サブネットマスクを使用して、複数のIPアドレスを指定することもできます。
$ tcpdump -i eth0 net 10.0.2.15 mask 255.255.255.255 ← "host 10.0.2.15" と同義
$ tcpdump -i eth0 net 10.0.2.0 mask 255.255.255.0 ← "10.0.2.0",...,"10.0.2.255"
tcpdumpコマンドを実行するマシンの、指定したネットワークインターフェース宛てではないパケットを無視するためには、ネットワークカードのプロミスキャスモードを '-p' オプションを付与して無効にします。
$ tcpdump -i eth0 port 80 -p
パケットのペイロードのサイズがある既定値を越えると、パケットキャプチャ時に超過した部分が切り捨てられ、tcpdumpの処理上無視されます。この既定値を無限にするためには '-s0' オプションを付与します。
$ tcpdump -i eth0 port 80 -s0
'-vvv' オプションを付与すると詳細な情報が出力されます。
$ tcpdump -i eth0 port 80 -vvv
$ tcpdump -i eth0 port 80 -t
'http' ではなく '80' と表示されるようにするには '-nn' オプションを付与します。
$ tcpdump -i eth0 port 80 -nn
通常実行時はヘッダ情報だけが表示されます。それに加えて、ペイロード情報をアスキー表示するためには '-A' または '-X' オプションを付与します。前述の '-s0' オプションと併用するとよいです。
$ tcpdump -i eth0 port 80 -s0 -A ← アスキー表示
$ tcpdump -i eth0 port 80 -s0 -X ← アスキーだけでなく16進数でも表示
'-w' オプションを付与すると標準出力ではなくファイル出力ができます。pcapの生データでありエディタで開いてもその意味はよく分かりませんが、tcpdumpやWiresharkで読み込むことができます。
$ tcpdump -i eth0 port 80 -w http.cap
$ tcpdump -r http.cap
$ tcpdump -r http.cap -t ← オプション指定が可能
一つの巨大なファイルが生成されることを避け、指定秒数毎にファイル分割を行うためには '-G' オプションを付与します。なお、パケットが発生しなかった場合はファイル作成自体がなされません。
$ tcpdump -i eth0 port 80 -s0 -G 3600 -w http_%Y%m%d_%H%M.cap
バックグラウンドでパケットキャプチャ用にtcpdumpコマンドを実行しておきます。
$ tcpdump -i eth0 port 80 -p -s0 -w http.cap &
Yahooサイトへアクセスします。
$ curl http://www.yahoo.co.jp/
tcpdumpコマンドをフォアグラウンドに戻して終了させます。
$ fg
(Ctrl-C)
ファイルから読み込んで一連のやりとりをアスキー表示してみます。
$ tcpdump -r http.cap -Atvvv | less
表示された内容を見ると、TCPの教科書に記載されている通りの通信がなされていることが確認できます。
ping と併用するとネットワークの疎通確認時などに便利です。icmp と指定することで TCP および UDP パケットを無視できます。また、前述の通り -i オプションを指定しないと up 状態のインタフェースの中で番号が一番小さいものが自動的に選択されることに注意します。
$ ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.051 ms
...
$ sudo tcpdump -i lo icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
14:41:15.458338 IP localhost > localhost: ICMP echo request, id 47689, seq 1, length 64
14:41:15.458360 IP localhost > localhost: ICMP echo reply, id 47689, seq 1, length 64
14:41:16.457353 IP localhost > localhost: ICMP echo request, id 47689, seq 2, length 64
14:41:16.457365 IP localhost > localhost: ICMP echo reply, id 47689, seq 2, length 64
...
インタフェースの確認
$ traceroute www.example.com
traceroute to www.example.com (93.184.216.34), 30 hops max, 60 byte packets
1 gateway (10.0.2.2) 0.195 ms 0.093 ms 0.135 ms
...
$ ip a | grep -B2 10.0.2
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:08:17:39 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
パケットキャプチャ
sudo tcpdump -ienp0s3 -p -s0 -X -t -vvv -nn port 80
curl www.example.com
ローカルホストから外部サーバへの HTTP リクエストが含まれる IP パケット
IP (tos 0x0, ttl 64, id 3530, offset 0, flags [DF], proto TCP (6), length 119)
10.0.2.15.44370 > 93.184.216.34.80: Flags [P.], cksum 0x4253 (incorrect -> 0x2a00), seq 1:80, ack 1, win 29200, length 79: HTTP, length: 79
GET / HTTP/1.1
Host: www.example.com
User-Agent: curl/7.52.1
Accept: */*
0x0000: 4500 0077 0dca 4000 4006 eacd 0a00 020f E..w..@.@.......
0x0010: 5db8 d822 ad52 0050 6834 dd99 2c93 2202 ]..".R.Ph4..,.".
0x0020: 5018 7210 4253 0000 4745 5420 2f20 4854 P.r.BS..GET./.HT
0x0030: 5450 2f31 2e31 0d0a 486f 7374 3a20 7777 TP/1.1..Host:.ww
0x0040: 772e 6578 616d 706c 652e 636f 6d0d 0a55 w.example.com..U
0x0050: 7365 722d 4167 656e 743a 2063 7572 6c2f ser-Agent:.curl/
0x0060: 372e 3532 2e31 0d0a 4163 6365 7074 3a20 7.52.1..Accept:.
0x0070: 2a2f 2a0d 0a0d 0a */*....
上記は TCP セグメントを含む IP パケットです。
TCP データ部は 41 バイト目 4745 5420 2f20 4854
から始まります。今回は以下のコマンドで数値を ASCII コードに変換して確認できます。マルチバイト文字やバイナリデータの場合は適宜処理します。
$ echo -e '\x47\x45\x54\x20\x2f\x20\x48\x54'
GET / HT
$ echo 'print chr(0x47)' | python
G
$ echo 'print hex(ord("G"))' | python
0x47
送信元IPアドレス 32bit 0a00 020f
、宛先IPアドレス 32bit 5450 2f31
$ echo '
> import socket, struct
> print socket.inet_ntoa(struct.pack("!I", 0x0a00020f))' | python
10.0.2.15
$ mysql -uroot -e "select inet_ntoa(`echo 'obase=10; ibase=16; 0A00020F' | bc`)"
+----------------------+
| inet_ntoa(167772687) |
+----------------------+
| 10.0.2.15 |
+----------------------+
送信元ポート番号 16bit + 宛先ポート番号 16bit ad52 0050
$ echo 'print 0xad52' | python
44370
$ echo 'print 0x0050' | python
80
TCPヘッダ長 4bit + 将来の機能拡張用の予約 6bit + コードビット 6bit 5018
`5` * 1ワード(32ビット(4バイト)) = 20 バイト
インタフェースの確認 (Google Public DNS)
traceroute 8.8.8.8
ip r
パケットキャプチャ
sudo tcpdump -ienp0s3 -p -s0 -X -t -vvv -nn udp and port 53
DNS クエリ (id: 43521)
$ dig @8.8.8.8 www.example.com
; <<>> DiG 9.10.3-P4-Debian <<>> @8.8.8.8 www.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43521 <-- ID は 43521 です。
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.example.com. IN A
;; ANSWER SECTION:
www.example.com. 6354 IN A 93.184.216.34
;; Query time: 87 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Sep 02 16:05:08 JST 2018
;; MSG SIZE rcvd: 60
DNS からの応答 IP パケット
IP (tos 0x0, ttl 64, id 24556, offset 0, flags [none], proto UDP (17), length 88)
8.8.8.8.53 > 10.0.2.15.36621: [udp sum ok] 43521$ q: A? www.example.com. 1/0/1 www.example.com. [1h45m54s] A 93.184.216.34 ar: . OPT UDPsize=512 (60)
0x0000: 4500 0058 5fec 0000 4011 fe8a 0808 0808 E..X_...@.......
0x0010: 0a00 020f 0035 8f0d 0044 1ccd aa01 81a0 .....5...D......
0x0020: 0001 0001 0000 0001 0377 7777 0765 7861 .........www.exa
0x0030: 6d70 6c65 0363 6f6d 0000 0100 01c0 0c00 mple.com........
0x0040: 0100 0100 0018 d200 045d b8d8 2200 0029 .........].."..)
0x0050: 0200 0000 0000 0000 ........
送信元ポート番号 16bit + 宛先ポート番号 16bit 0035 8f0d
$ echo 'print 0x0035' | python
53
$ echo 'print 0x8f0d' | python
36621
UDP データの開始 aa01 81a0
$ echo 'print 0xaa01' | python
43521
ID が取得できました。