コンテナ環境でデバッグのためにパケットキャプチャし、WireSharkで確認する

# はじめに

本ドキュメントでは、tcpdumpを用いてパケットキャプチャしたファイルをWireSharkで読む方法を案内します。Linux環境で直接実行、dockerコンテナ環境でコンテナに変更を加えない形で実行、kubernetes環境でpodに変更を加えない形で実行、と様々な環境でパケットキャプチャを実行したいシーンがあると思いますので、整理してみました。

まずパケットキャプチャを取得する前に、構成図を書くことをオススメします。取得したいトラフィックと通信経路を図示化しましょう。そうすると、パケットキャプチャを仕掛けることができるポイント(仕掛けても良いポイント、という方が正しいかも)を明確にすることが目的です。


# 注意事項

パケットキャプチャはシステムに大きな影響を与えます。キャプチャする通信量が多ければ、その分負荷は高くなり、出力するファイルサイズも大きくなります。本番環境での実施は避けるべきです。どうしてもパケットキャプチャを取るしかない、という場合は、パケットキャプチャをするポイントを最小限に絞る、そもそも通信の途中経路に仕掛けるなど、熟慮して実施するべきです。


# 条件

## 確認環境

- WireShark version 4.0.3
- macOS Ventura 13.2.1
- ubuntu 22.04.1 LTS
- docker version 20.10.17


# tcpdumpでパケットキャプチャ

Linux環境で直接実行する場合は、大きな問題にはならないかと思います。一方、コンテナ環境(dockerやdocker composeの場合は、コンテナに変更を加えない形で実行したい、というシーンが多いと思います。また、kubernetes環境でpodに変更を加えない形で実行する必要があるシーンもあります。様々な環境でパケットキャプチャを実行する手順を整理してみました。

## Linux環境

tcpdumpがインストールされている環境であれば、以下のコマンドでパケットキャプチャしたファイルを作成可能です。下記の実行例はいい加減なファイル名になっています。適宜指定して下さい。

```sh
sudo tcpdump -w efg.pcap
[sudo] password for ubuntu: 
tcpdump: listening on enp2s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
```

`Control + C`で停止します。

```sh
^C794 packets captured
796 packets received by filter
0 packets dropped by kernel
```

ファイルを確認します。

```sh
ls -l efg.pcap
-rw-r--r-- 1 tcpdump tcpdump 4233116 Mar 13 13:05 efg.pcap
```

macOSからファイルを取得します。ファイル名とパスは適宜指定して下さい。

```sh
scp ubuntu@192.168.10.100:~/efg.pcap ~/Download/.
```

## dockerコンテナ環境

dockerコンテナ環境において、コンテナにはtcpdumpが入っていない、という条件での作業を想定しています。tcpdumpが入っているコンテナを使うことができるのであれば、上記のLinux環境を参照して下さい。

下記コマンドは、いずれもホストOS上で実行します。まずパケットキャプチャを実行したいコンテナのCONTAINER IDを確認します。

```sh
docker ps -a
CONTAINER ID   IMAGE                                COMMAND                  CREATED       STATUS       PORTS                                       NAMES
d2b8179a5cad   bitnami/mongodb:4.2.5                "/opt/bitnami/script…"   7 hours ago   Up 7 hours   27017/tcp                                   mongo-1
```

以下のコマンドでパケットキャプチャしたファイルを作成可能です。ファイルはホストOSのコマンド実行時のディレクトリに作成されます。下記の実行例はいい加減なファイル名になっています。適宜指定して下さい。

```sh
sudo nsenter -n -t $(docker inspect --format {{.State.Pid}} <確認したCONTAINER ID>) -- tcpdump -w abc.pcap
[sudo] password for ubuntu: 
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
```

`Control + C`で停止します。

```sh
^C3208 packets captured
3211 packets received by filter
0 packets dropped by kernel
```

## kubernetes環境

テスト環境や開発環境での利用を想定している手順であり、本番環境での利用は想定していません。ご理解の上、読み進めて下さい。

ここでは、worker nodeにtcpdumpをインストールし、nsenterで特定のコンテナのプロセスを指定してキャプチャを実行する手順を案内します。これ以外にも[Sidecarでデバッグコンテナを起動する方法](https://blog.mosuke.tech/entry/2021/01/31/network-debug-on-containers/)や[tcpdumpを含むデバッグ用podを立ち上げ](https://qiita.com/dg4101/items/09133b5090ad08ce2219)があります。それぞれわかりやすい記事を見つけたので、そちらをご参照下さい。管理者権限など、セキュリティ面でのデメリットも理解した上で検討すべきかと思います。

kubernetes環境において、podにはtcpdumpが入っていない、という条件での作業を想定しています。パブリッククラウドのkubernetesサービスを利用する場合、worker nodeの起動時のマシンイメージにはtcpdumpが含まれていないケースが大半です。
まずパケットキャプチャの対象podがスケジュールされているworker nodeにログインし、tcpdumpをインストールします。

```sh
sudo apt update
sudo apt install tcpdump
```

上記が実施できれば、基本的な手順はdockerコンテナ環境の手順と同じです。下記コマンドはいずれもWorker node上で実行します。パブリッククラウドのkubernetesサービスでは、containerdへの移行に伴い、docker cliが使えないケースが増えています。`crictl ps`を用いてコンテナIDを確認し、`crictl inspect`を用いてプロセスIDを確認する、もしくは`ps aux`でプロセスIDを直接確認するなどご対応下さい。

以下のコマンドでパケットキャプチャしたファイルを作成可能です。ファイルはホストOSのコマンド実行時のディレクトリに作成されます。下記の実行例はいい加減なファイル名になっています。適宜指定して下さい。

```sh
sudo nsenter -n -t <確認したプロセスID> -- tcpdump -w abc.pcap
[sudo] password for ubuntu: 
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
```

`Control + C`で停止します。

```sh
^C3208 packets captured
3211 packets received by filter
0 packets dropped by kernel
```


# WireSharkでキャプチャを確認

## WireSharkのダウンロード

[こちら](https://www.wireshark.org/download.html)からWireSharkはダウンロードできます。ご自分の環境に合わせて選択し、インストールして下さい。


## 準備作業

WireSharkにてキャプチャを追いかけるのためにオススメの表示設定に変更します。一つは時刻表示、もう一つは表示項目の追加です。こだわりポイントはそれぞれ色々あるかと思いますが、私はこの2点をすぐに変更しています。

### 時刻表示設定の変更

インストールしたばかりだと下記の様に時刻がパケットキャプチャし始めてからの経過時間で表示されているはずです。

![packet_cap_read_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/9dc99690-7670-c018-8318-f465a15d74a8.png)

メニューのView -> Time Display Format -> Date and Time of Day (1970-01-01 01:02:03:123456)を選ぶと分かりやすいかと。ログファイルと照らし合わせる都合などUTCで表示したい場合は、UTC Date and Time of Day (1970-01-01 01:02:03:123456)を選ぶと良いかと。ここではDate and Time of Dayにします。

![packet_cap_read_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/4d076008-b662-8ae7-ff23-ab88d2c8a057.png)

下記の様に表示が変更されました。

![packet_cap_read_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/e33ea551-ba48-1208-bfaa-b18fde91d617.png)


### 表示項目の追加

インストール後のデフォルト設定状態だと、No.(キャプチャのパケット順)、Time(時刻)、Source (送信元IPアドレス)、Destination (送信先IPアドレス)、Protocol(プロトコル種別)が左から順番に表示されます。

TCP/IP通信のパケットキャプチャを読み解く上で、少なくとも送信元IPアドレス、送信元ポート、送信先IPアドレス、送信先ポートの4つが必要だと思うので、送信元ポートと送信先ポートを表示するよう設定します。

ウインドウのカラム(Column)のどこか(下記図の赤枠あたり)を右クリックし、Column Preferenceをクリックします。

![packet_cap_read_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/4851bb6e-18af-0770-e22e-9181f8487580.png)


+をクリックし、表示項目を二つ追加します。次にTitleにSrc Port、Dest Portと入力します。この表示は自由に設定できるので、分かりやすい項目にして下さい。長すぎると横幅を無駄に使いますのでご注意を。

次にドラッグアンドドロップで表示順番を調整します。送信元IPアドレス、送信元ポート、送信先IPアドレス、送信先ポートの順番が分かりやすいかと思います。

![packet_cap_read_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/34cfca18-bb2a-12e4-04b9-a7c37a6323e0.png)

そして、表示内容を指定します。表内容の変更を行いたいTypeの箇所をダブルクリックすると変更できます(macOSでは)。Src PortにはSource portを、Dest Portには、Dest port (unresolved)を指定します。デフォルトでProtocolにプロトコル情報が表示されるので、ポート番号は名前解決しない表示の方が使い勝手が良いかと思います。

![packet_cap_read_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/b2fba268-cd71-5d89-2d2b-40c99438e96b.png)

設定変更の結果、この様な表示になりました。

![packet_cap_read_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/f3e7ca28-b1af-8466-eaa4-033e1e74104f.png)

## ファイルの読み込み

WireSharkを起動し、フォルダアイコンをクリックします。

![packet_cap_read_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/241a450d-a138-e967-f22a-5eaa28ffa345.png)

読み込みたいファイルを選択すると、デフォルトでは自動識別になっており、ファイルフォーマット、ファイルサイズ、レコード数、キャプチャ開始時間、キャプチャにかけた時間などが表示されます。キャプチャファイルが大量になることはよくあるので、ファイルを開く前に確認できるのは便利です。

![packet_cap_read_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/8549f7b9-292c-34bb-9ebc-5fa70a485d4c.png)

ファイルを開くと下記の様になります。SSHにて、192.168.1.2のMacから192.168.1.3にSSHした状態で取得したキャプチャとなります。また、Macから192.168.1.3に8034で起動しているwebアプリに対してアクセスしている状態です。ここから不要な通信をフィルタを使って落としていく方が作業効率がいいかと思います。

![packet_cap_read_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557529/1b46d2f3-8a6a-5406-c889-7c40aeb5f70d.png)


## フィルタ

フィルタを使って、必要十分な情報に絞ることが、パケットキャプチャを読み解く上での重要なポイントとなります。様々なフィルタがありますが、目的別にまとまっているサイトを見つけたので、[こちら](https://hirotanoblog.com/wireshark-filter/8191/)をご参照下さい。下記には、とりあえずほぼ毎回設定する例を挙げておきます。

sshにてログインした状態でtcpdumpによるパケットキャプチャを行なった場合などは、自分自身のssh通信以外を確認したい、というシーンが多いかと思います。

`!(tcp.port == 22)`

あわせて、arpも表示したくない、という場合は下記の様に指定すると通信内容が絞れます。

`!(tcp.port == 22) and not arp`

特定のポートと送信先を指定して表示したい場合は下記の様に指定すると絞れます。

`tcp.port==1883 and ip.dst_host==192.168.1.100`

上記で使ったip.dst_hostは送信先に特定のIPアドレスがある場合となるため、返信の通信などもフィルタアウトされます。送信元もしくは送信先のいずれかのIPアドレスを指定したい場合は下記のように指定すると期待するフィルタとなります。

`tcp.port==1883 and ip.addr==192.168.1.100`


# 最後に
この手順に記載しているように、パケットキャプチャを取得すること自体は難しいことではありません。しかし、パケットキャプチャは、最後の手段だと個人的には考えています。そもそもシステムに対して大きな負荷を与える処理であることが理由です。また、キャプチャデータを読むにしても、時間がかかります。これらのことを考慮した上でパケットキャプチャの是非をご検討下さい。