ENECHANGE Developer Blog

ENECHANGE開発者ブログ

アプリケーションの通信エラーをパケットキャプチャで調査する - パケットキャプチャ基盤の構築 -

ENECHANGE所属のエンジニア id:tetsushi_fukabori こと深堀です。
このブログに文章を書いている時点で「ENECHANGE所属」は自明なのでは…と思い始めています。
最初に固めた定型文ってなかなか変えにくいですよね。


前回に引き続き弊社アプリケーションで発生したエラー事象とその調査について記事を書きます。
エラーが発生したアプリケーションはいわゆるwebアプリケーションなのですが、原因の調査手法としてはパケットキャプチャを行うことになりました。
普段webアプリケーションの開発ではあまり出番が無い(と思う)パケットキャプチャなので、方法などを共有できればと思います。

今回はパケットキャプチャ基盤の構築についてです。
実際の解析作業は次回となります。
ちょっと長いので目次を入れておきます。

この記事を届けたい人

  • AWSで構築したアプリケーションでパケットキャプチャをやりたい人
  • キャプチャしたパケットを解析した事例を知りたい人

パケットキャプチャ基盤の概要

前回記載した構成図は以下のとおりです。

パケットキャプチャ基盤概略

大まかには以下の要素から構成されていて、太字が今回新規構築する部分です。
明示的に作成するリソースはすべて列挙しています。

  • 本番アプリケーションが稼働するEC2インスタンス(長いので以降「本番インスタンス」)
  • パケットキャプチャ用EC2インスタンス(長いので以降「pcapインスタンス」)
  • pcapインスタンス用セキュリティグループ
  • ミラーリング宛先用ENI
  • pcapファイル保存用EBS
  • VPC Traffic Mirroringのミラーターゲット/ミラーフィルター/ミラーセッション

以降の手順の前提として「pcapインスタンスはインターネットにアクセス可能でライブラリのダウンロードなどが行えること」「pcapインスタンスはSSMアクセス可能な設定が完了していること」が必要です。
前提を満たすための手順は(一部説明の都合上出てきますが)基本的には記載しません。

また、大事なことですが、本番インスタンスはVPC Traffic Mirroringの要件を満たしたインスタンスタイプである必要があります。

docs.aws.amazon.com

弊社の本番インスタンスは上記を満たしていたためVPC Traffic Mirroringが利用できました。

パケットキャプチャ基盤の構築

AWS上にパケットキャプチャに必要なインフラを構築していきます。

1. pcapインスタンス用セキュリティグループの作成

pcapインスタンス作成に先立ってセキュリティグループを作成しておきます。
基本的にはキャプチャ対象のパケットを通せれば良いのでingressはVPC Traffic Mirroringの要件通り、egressはすべて許可にしています。

VPC Traffic Mirroringの要件を引用すると以下の通りです。

You must configure a security group for the traffic mirror target that allows VXLAN traffic (UDP port 4789) from the traffic mirror source.

以下はAWS CLIでaws ec2 describe-security-groupsを実行した結果の概要です。
は説明のために加筆しています。

{
    "SecurityGroups": [
        {
            ...
            "IpPermissions": [
                {
                    "FromPort": 4789,
                    "IpProtocol": "udp",
                    "IpRanges": [
                        {
                            "CidrIp": "xxx.yyy.0.0/16" ★
                        }
                    ],
                    ...
                }
            ],
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1",
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    ...
                }
            ],
            ...
        }
    ]
}

の箇所は本番インスタンスが所属するVPCサブネットを指定しています。
本番インスタンスについているセキュリティグループを指定しても構いません。
本番インスタンスからのトラフィックのみ受け付けるように設定していればよいかと思います。

2. pcapインスタンスの作成

次にpcapインスタンスを作成します。
併せてミラーリング宛先用ENIとpcapファイル保存用EBSも作ってしまうのが楽かと思います。
ENIやEBSに別途名前やタグを付けて作成したい場合は別手順で作成後アタッチして頂くか、インスタンスと一緒に作成して個別に編集してください。

以下のようなポイントを気にしてインスタンスを作成しました。

  • マシンイメージはパケット解析ツールが稼働しSSMログインに必要なツールが入っているものを使用します
    →x86_64アーキテクチャのAmazon Linux 2023としました。
  • インスタンスサイズはsmallを指定します
    →今回の調査ではt3.smallで問題なく動きましたが、t3.microではパケット解析ツールを起動している際にそれなりの頻度でフリーズ状態になり操作を受け付けなくなりました。small以上が良いかと思います。
  • 本番インスタンスと同じVPCサブネットで起動します
    →別VPCサブネットでも困りません(参考)が、追加の設定が不要なのと本番トラフィックのキャプチャなので同じセキュリティレベルのネットワークを使用したいためです。
  • ミラーリング宛先用としてENIをデフォルトのものとは別に一つ作成・アタッチします
    →アタッチしたENIにVPC Traffic Mirroringでミラーリングしたトラフィックを流し、それをキャプチャします。分けておかないと本番パケット以外がパケットキャプチャに混ざったり、VXLANヘッダを剥がす仮想インターフェイス(後述)でエラーが起こることがありえます。
  • ミラーリング宛先用ENIのセキュリティグループは上述のものを指定します
  • pcapファイル保存用としてEBSをデフォルトのものとは別に一つ作成・アタッチします
    →エラーの発生タイミングが読めないためそれなりの期間のパケットをキャプチャ・保存可能なように大きめの領域を確保します。ルートボリュームと分けておくことで万一溢れた場合にも起動には影響がないようにしておきます。
    →容量は300GBほど、ボリュームタイプは「スループット最適化HHD(st1)」としました。

パケットキャプチャ専用のボリュームとインターフェイスを用意しておくコト以外は特段特殊なことはしていません。

このパケットキャプチャ環境はあくまでエラー調査のための一時的な環境ですのでスケーリングや自動復旧などは考えていません。
冗長化などは必要に応じて設定してください。

3. VPC Traffic Mirroringのミラーターゲット/ミラーフィルターの作成

VPC Traffic Mirroringのリソースを作成します。
依存関係があるのでミラーターゲット・ミラーフィルター→ミラーセッションの順で作成します。
ミラーセッションの内容だけはpcapインスタンス内の設定に影響があったり、後述の理由で何度も作り直すことになるため別立てにします。

まずミラーターゲットを作成します。
AWSマネージドコンソールのVPCトラフィックミラーリングミラーターゲットに進んでください。

VPC Traffic Mirroringのミラーターゲットではミラーリングしたトラフィックの宛先となるリソースを指定します。
今回の場合はミラーリング宛先用ENIが該当します。

ミラーターゲットの作成

ターゲットタイプを「ネットワークインターフェイス」に、ターゲットをミラーリング宛先用ENIに指定します。

次にミラーフィルターを作成します。
AWSマネージドコンソールのVPCトラフィックミラーリングミラーフィルターに進んでください。

VPC Traffic Mirroringのミラーフィルターではミラーリングするトラフィックのフィルタ条件を設定します。
特定の通信だけミラーリングしたい…といった要望に応えるものと思われます。
今回はエラー調査のため最大限取得する情報を増やしたかったことと、すべてのトラフィックをキャプチャしても許容範囲のデータ量に収まると判断して何もしないフィルターを作成しています。
(何もフィルタしなくてもミラーフィルター自体は必要です)

ミラーフィルターは本番インスタンスのインバウンドトラフィック/アウトバウンドトラフィックそれぞれについてルールを指定して作成します。

ミラーフィルターの作成

インバウンド/アウトバウンドともに許可ルールとして「すべてのプロトコル」「すべての送信元(0.0.0.0/0)」「すべての送信先(0.0.0.0/0)」を指定しています。

4. VPC Traffic Mirroringのミラーセッションの作成

VPC Traffic Mirroringのミラーセッションを作成します。
AWSマネージドコンソールのVPCトラフィックミラーリングミラーセッションに進んでください。

VPC Traffic Mirroringのミラーセッションでは「どのミラーソースからどのミラーターゲットに、どんなミラーフィルターを設定してトラフィックをミラーリングするか」を指定します。
ミラーリングの本体ですね。

ミラーセッションでは以下の項目を設定します。

設定項目
設定内容
セッションの設定 > ミラーソース 本番インスタンスのENI
セッションの設定 > ミラーターゲット 作成したミラーターゲット
追加設定 > セッション数 適当な数字
本番インスタンスが複数の場合、複数のミラーセッションを作成するので、念のため重複しない値を設定する
追加設定 > VNI !大事!
任意の数字を設定して良いが、pcapインスタンス上の設定で必要なため控えておく
追加設定 > フィルタ 作成したミラーフィルター

ミラーセッションの作成(追加設定部分のみ)。VNIを控えておくこと。

ここまででAWS上で必要なリソースの作成が完了しました。
セッションを作成した段階でトラフィックのミラーリングが始まりpcapインスタンスにパケットがなだれ込んできますので、ミラーリングはpcapインスタンスの内部的な設定を済ませてからにしたい場合はセッションの作成は後回しでも良いです。

Tips:ミラーセッションの再作成について

作成時に記載の通り、ミラーセッションはソース・ターゲット・フィルターを指定して作成します。
このうちソースは本番インスタンスのENIなので、本番インスタンスがリリースやスケールイン/アウトして入れ替わった場合には対応が必要になります。

具体的には以下のようなパターンがあり得ます。

イベント
本番インスタンス数
対処
スケールイン 減少 対処不要
スケールアウト 増加 セッションの新規作成
インスタンス入れ替わり   セッションの新規作成

インスタンス入れ替わりやスケールインの場合、削除されたインスタンスのENIをソースとしているミラーセッションは自動的に削除されます。
この削除はEventBridgeで捕捉できるイベントを発生させないため、ミラーセッションの削除自体を検知することができません。
ミラーセッションの再作成の必要を検知したい場合は元となるイベントの発生自体をEventBridgeで捕捉するなどの対応が必要そうです。

なお、今回の調査においては上記のイベントは発生頻度が少なく、リリース作業によってインスタンスの入れ替わりがある程度であることがわかっています。
また、何度か発生するエラーのいずれかのパケットをキャプチャできれば良いため、ミラーセッションが削除されてパケットキャプチャが一時的に止まっていても大きな問題にはなりません。
このため、今回は「リリース時にはアプリチームから連絡をもらう」「営業日の朝イチにミラーセッションが消えていないか定期目視確認する」というシンプルな作業で対応しました。

パケットキャプチャ環境のセットアップ

AWS上のインフラは準備できたのでpcapインスタンス上に必要な設定やソフトウェアの導入を行います。
以降の手順は記載がなければpcapインスタンス上にログインして行っています。

大まかな作業内容は以下の通りです。

  • pcapファイル保存用EBSボリュームのマウント
  • ミラーリング宛先用ENIに到着したパケットのVXLANヘッダを剥がす仮想ネットワークインターフェイスの作成
  • tcpdumpの設定
  • termsharkのインストール

1. pcapファイル保存用EBSボリュームのマウント

pcapインスタンスにアタッチしたEBSボリュームのマウントを設定してインスタンス内から使えるようにします。
併せて再起動してもマウントが有効なよう設定を永続化します。

アタッチしたEBSボリュームは何もしないとインスタンス側からは見えません。

ボリュームはアタッチ済み

# 300GBのデバイスxvdbは存在するがマウントされていない
$ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
xvda      202:0    0    8G  0 disk
├─xvda1   202:1    0    8G  0 part /
├─xvda127 259:0    0    1M  0 part
└─xvda128 259:1    0   10M  0 part
xvdb      202:16   0  300G  0 disk

# マウントされていないので使えるディスク領域も増えていない
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        4.0M     0  4.0M   0% /dev
tmpfs           475M     0  475M   0% /dev/shm
tmpfs           190M  2.9M  188M   2% /run
/dev/xvda1      8.0G  1.6G  6.4G  20% /
tmpfs           475M     0  475M   0% /tmp
tmpfs            95M     0   95M   0% /run/user/0

パーティションを作成します。

# ブロックデバイスxvdbを指定してパーティションを作成
# Command (m for help): のところ以外はデフォルト(入力なし)で大丈夫
$ sudo fdisk /dev/xvdb

Welcome to fdisk (util-linux 2.37.4).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x2ab3d11a.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p):

Using default response p.
Partition number (1-4, default 1):
First sector (2048-629145599, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-629145599, default 629145599):

Created a new partition 1 of type 'Linux' and of size 300 GiB.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

# マウントされていないパーティションが作成された
$ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
xvda      202:0    0    8G  0 disk
├─xvda1   202:1    0    8G  0 part /
├─xvda127 259:0    0    1M  0 part
└─xvda128 259:1    0   10M  0 part
xvdb      202:16   0  300G  0 disk
└─xvdb1   202:17   0  300G  0 part

pcapファイルの出力先として/var/log/tcpdumpにマウントします。
余談ですが/varディレクトリのサブディレクトリはLinuxファウンデーションによって決められています。
用途によってディレクトリを選んでください。

# XFSファイルシステムを作成
$ sudo mkfs.xfs /dev/xvdb1
$ sudo file -s /dev/xvdb1
/dev/xvdb1: SGI XFS filesystem data (blksz 4096, inosz 512, v2 dirs)

# /var/log/tcpdumpディレクトリの作成
# tcpdumpユーザー・グループはAmazon Linux 2023にはデフォルトで存在しています
# installコマンドで一括作成します(参考: https://blog.n-z.jp/blog/2014-02-14-install.html )
$ sudo install -o tcpdump -g tcpdump -m 0755 -d /var/log/tcpdump
$ ls -ld /var/log/tcpdump
drwxr-xr-x. 2 tcpdump tcpdump 6 Aug 30 06:43 /var/log/tcpdump

# マウントの実行
# xfsではマウントオプションでuid,gidを取れないのでマウント後はroot:rootになる
# このためもう一度tcpdump:tcpdumpに変更する
# 最初にmkdirした直後ディレクトリをtcpdump:tcpdumpにしているのはマウントが外れたとき対策
$ sudo mount /dev/xvdb1 /var/log/tcpdump
$ sudo install -o tcpdump -g tcpdump -m 0755 -d /var/log/tcpdump
$ ls -ld /var/log/tcpdump
drwxr-xr-x. 2 tcpdump tcpdump 6 Aug 30 06:29 /var/log/tcpdump
$ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
xvda      202:0    0    8G  0 disk
├─xvda1   202:1    0    8G  0 part /
├─xvda127 259:0    0    1M  0 part
└─xvda128 259:1    0   10M  0 part
xvdb      202:16   0  300G  0 disk
└─xvdb1   202:17   0  300G  0 part /var/log/tcpdump
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        4.0M     0  4.0M   0% /dev
tmpfs           475M     0  475M   0% /dev/shm
tmpfs           190M  2.9M  188M   2% /run
/dev/xvda1      8.0G  1.6G  6.4G  20% /
tmpfs           475M     0  475M   0% /tmp
tmpfs            95M     0   95M   0% /run/user/0
/dev/xvdb1      300G  2.2G  298G   1% /var/log/tcpdump

これでEBSボリュームを使用できる状態になりました。
ただしこのマウント設定は永続化されておらず、インスタンスが再起動したらマウントが解除されます。
このため/etc/fstabに設定を追加して起動時にsystemdでマウントするようにします。

# デバイスのUUIDを確認
$ blkid -o list
device                          fs_type      label         mount point                         UUID
------------------------------------------------------------------------------------------------------------------------------------
/dev/xvda128                    vfat                       (not mounted)                       041B-C99D
/dev/xvda1                      xfs          /             /                                   4870ba44-116b-4b5f-b59a-12bad1cba74c
/dev/xvdb1                      xfs                        /var/log/tcpdump                    5e3b970d-7ade-4aba-880e-c5a2bc269efa
/dev/xvda127                                               (not mounted)

# /etc/fstabを確認してxvdb1を追記
$ cat /etc/fstab
#
UUID=4870ba44-116b-4b5f-b59a-12bad1cba74c     /           xfs    defaults,noatime  1   1
UUID=041B-C99D        /boot/efi       vfat    defaults,noatime,uid=0,gid=0,umask=0077,shortname=winnt,x-systemd.automount 0 2
$ sudo vim /etc/fstab
$ cat /etc/fstab
#
UUID=4870ba44-116b-4b5f-b59a-12bad1cba74c     /           xfs    defaults,noatime  1   1
UUID=041B-C99D        /boot/efi       vfat    defaults,noatime,uid=0,gid=0,umask=0077,shortname=winnt,x-systemd.automount 0 2
UUID=5e3b970d-7ade-4aba-880e-c5a2bc269efa /var/log/tcpdump xfs defaults 0 2

/etc/fstabに追記した内容は再起動するか $ sudo systemctl daemon-reload を実行すれば.mountファイルを生成してくれるので、これによりsystemdで自動マウントされます。

$ ls -l /run/systemd/generator/
total 16
-rw-r--r--. 1 root root 366 Aug 30 07:49 -.mount
-rw-r--r--. 1 root root 170 Aug 30 07:49 boot-efi.automount
-rw-r--r--. 1 root root 494 Aug 30 07:49 boot-efi.mount
drwxr-xr-x. 2 root root  60 Aug 30 07:49 getty.target.wants
drwxr-xr-x. 2 root root 100 Aug 30 07:49 local-fs.target.requires
drwxr-xr-x. 2 root root  80 Aug 30 07:49 local-fs.target.wants
-rw-r--r--. 1 root root 551 Aug 30 07:49 var-log-tcpdump.mount

2. ミラーリング宛先用ENIに到着したパケットのVXLANヘッダを剥がす仮想ネットワークインターフェイスの作成

ミラーリング用ENIはアタッチした時点でインスタンスにはネットワークインターフェイスが作成され通信可能な状態になっています。

$ ifconfig
enX0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.25.11.76  netmask 255.255.252.0  broadcast 172.25.11.255
        inet6 fe80::42b:a6ff:fedf:e5f7  prefixlen 64  scopeid 0x20<link>
        ether 06:2b:a6:df:e5:f7  txqueuelen 1000  (Ethernet)
        RX packets 15170  bytes 3294132 (3.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 14339  bytes 2746961 (2.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enX1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.25.9.5  netmask 255.255.252.0  broadcast 172.25.11.255
        inet6 fe80::41c:ff:fe4c:9f59  prefixlen 64  scopeid 0x20<link>
        ether 06:1c:00:4c:9f:59  txqueuelen 1000  (Ethernet)
        RX packets 229  bytes 19468 (19.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 305  bytes 32100 (31.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

どちらのネットワークインターフェイスがSSM接続などに利用されるプライマリかはAWS CLIで確認可能です。

$ aws ec2 describe-instances --instance-ids [pcapインスタンスのID] | jq '[.Reservations[].Instances[].NetworkInterfaces[] | {DeviceIndex: .Attachment.DeviceIndex, PrivateIpAddress: .PrivateIpAddress, MacAddress: .MacAddress}]'
[
  {
    "DeviceIndex": 0,
    "PrivateIpAddress": "172.25.11.76",
    "MacAddress": "06:2b:a6:df:e5:f7"
  },
  {
    "DeviceIndex": 1,
    "PrivateIpAddress": "172.25.9.5",
    "MacAddress": "06:1c:00:4c:9f:59"
  }
]

DeviceIndexが一番小さいものがプライマリになっていますので、ifconfigの結果とあわせてプライマリがenX0、その他がenX1であることがわかります。
つまりenX1に対応するENIをVPC Traffic Mirroringの宛先にしてパケットキャプチャの対象にすることで本番トラフィックだけをキャプチャできます。

一方、VPC Traffic MirroringはVXLANという技術を使用してオリジナルのパケットをカプセル化してターゲットにミラーリングしています。

docs.aws.amazon.com

このためpcapインスタンスのミラーリング宛先用ENIに到達するパケットはオリジナルのパケットにVXLANのためのヘッダが付与されています。
VXLANのヘッダは除去してオリジナルのパケットだけをキャプチャしたほうが解析が楽になりますので、仮想のネットワークインターフェイスを作ることで対処します。

このVXLAN用仮想ネットワークインターフェイスを作成する際、パラメータとして指定する必要があるのがVNI(VXLAN Network Identifier)です。
VXLANは仮想のLANを作る技術ですので、同一のLANに所属していることをVNIで指定する必要があります。
今回はVPC Traffic MirroringのミラーセッションをVNI: 12345で作成したので、そのVNIを指定します。

$ sudo ip link add vxlan0 type vxlan id 12345 dstport 4789 dev enX1
$ sudo ip link set up vxlan0
$ ifconfig
enX0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001

...

enX1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001

...

vxlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 8951
        inet6 fe80::4086:5dff:fe4a:89fb  prefixlen 64  scopeid 0x20<link>
        ether 42:86:5d:4a:89:fb  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 10 overruns 0  carrier 0  collisions 0

vxlan0インターフェイスはtype=vxlanなのでVXLANヘッダを除去してくれます。

こちらもこのままでは再起動した際にインターフェイスが消えますので永続化します。
systemd-networkdで設定するのがキレイなように思いますが、特定の物理インターフェイス(enX1)を対象にして仮想ネットワークインターフェイスを作成する方法がなさそうですのでserviceとして起動時に毎回作成します。

$ sudo vim /etc/systemd/system/vxlan.service
$ cat /etc/systemd/system/vxlan.service
[Unit]
Description=Add persistent vxlan0 interface and start up it
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/ip link add vxlan0 type vxlan id 12345 dstport 4789 dev enX1
ExecStartPost=/usr/sbin/ip link set up vxlan0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

$ sudo systemctl daemon-reload
$ sudo systemctl enable vxlan
$ sudo systemctl start vxlan

以上の設定でvxlan0はホスト起動時に作成・起動されます。

3. tcpdumpの設定

tcpdumpを起動して/var/log/tcpdumpにpcapファイルを出力します。
pcapファイルを一定時間ごとに別ファイルに貯めて、エラーが発生したら当該時間帯のファイルを解析ツールで参照する運用にします。

まずtcpdumpがネットワークインターフェイスからパケットを読み取れるよう権限を与えます。

# pcapグループを作成しログインユーザーをグループに加えます
# ログインユーザーを加えるのはtcpdumpコマンドの試験実行ができるようにするため
$ sudo groupadd pcap
$ sudo usermod -a -G pcap $USER

# tcpdumpの実行ファイルのグループをpcapに変更
$ ls -l $(which tcpdump)
-rwxr-xr-x. 1 root root 1341464 Feb  2  2023 /usr/sbin/tcpdump
$ sudo chgrp pcap /usr/sbin/tcpdump
$ sudo chmod 750 /usr/sbin/tcpdump

# 一度ここでログアウト→ログインした上で以降のコマンドを実行
$ ls -l $(which tcpdump)
-rwxr-x---. 1 root pcap 1341464 Feb  2  2023 /usr/sbin/tcpdump

# tcpdumpの実行ファイルのケーパビリティを設定
$ sudo setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump

起動管理をsystemdに任せたいので起動スクリプトとserviceファイルを作成します。

# 10分ごとに別のファイルにキャプチャしたパケットを出力する起動スクリプトを作成
# ファイル名は10分ごとに異なる必要があるため日時文字列を含む
# 前提としてvxlan0が存在しないといけないので事前に確認する
$ sudo vim /usr/local/bin/run-tcpdump.sh
$ cat /usr/local/bin/run-tcpdump.sh
#!/usr/bin/env bash
if ip link show vxlan0 &>/dev/null ; then
    tcpdump -i vxlan0 -w /var/log/tcpdump/%Y-%m-%d_%H:%M:%S.pcap -G 600
fi

$ sudo chmod +x /usr/local/bin/run-tcpdump.sh

# serviceを設定
$ sudo vim /etc/systemd/system/tcpdump.service
$ cat /etc/systemd/system/tcpdump.service
[Unit]
Description=Start tcpdump for vxlan0
After=vxlan.service
RequiresMountsFor=/var/log/tcpdump

[Service]
ExecStart=/usr/local/bin/run-tcpdump.sh
Restart=always
User=root

[Install]
WantedBy=multi-user.target

$ sudo systemctl daemon-reload
$ sudo systemctl enable tcpdump
$ sudo systemctl start tcpdump
$ sudo systemctl status tcpdump
$ ls -l /var/log/tcpdump
total 0
-rw-r--r--. 1 tcpdump tcpdump 0 Aug 31 02:50 2023-08-31_02:50:40.pcap

これでtcpdumpでpcapが可能になりました。
ただこのままだと無限にpcapファイルが作られ続けていつかディスクが溢れるので、一定期間古いものは削除します。
エラーを検知したら当該のファイルを確認して退避すればいいので、そんなに長い期間保持しなくても良いでしょう。

過去15日分を保持し、(ファイル名のタイムスタンプが)それより前のファイルは削除するようsystemd timerを設定します。
最近のLinuxではcronよりもsystemd timerがおすすめらしいです。

# 削除処理の実体のシェルスクリプトを作成
$ sudo vim /usr/local/bin/remove_old_logs.sh
$ cat /usr/local/bin/remove_old_logs.sh
#!/usr/bin/env bash
DELETE_DATE=$(date -d '15 days ago' +'%Y-%m-%d')
echo "Delete files in /va/log/tcpdump/ which created before ${DELETE_DATE}"
DELETE_DATE_S=$(date -d ${DELETE_DATE} +'%s')
for filename in $(ls -1 /var/log/tcpdump/)
do
    filedate=$(echo ${filename} | cut -c 1-10)
    filedate_s=$(date -d ${filedate} '+%s')
    if [ ${filedate_s} -lt ${DELETE_DATE_S} ]; then
        echo "Delete /var/log/tcpdump/${filename}"
        rm -f /var/log/tcpdump/${filename}
    fi
done

$ sudo chmod +x /usr/local/bin/remove_old_logs.sh

# serviceファイル作成
$ sudo vim /etc/systemd/system/remove_old_logs.service
$ cat /etc/systemd/system/remove_old_logs.service
[Unit]
Description=Remove old tcpdump logs

[Service]
Type=oneshot
ExecStart=/usr/local/bin/remove_old_logs.sh

# timerファイル作成
# 処理の内容的には日次稼働でも良いのですが、稼働確認のために毎時稼働にしています
$ sudo vim /etc/systemd/system/remove_old_logs.timer
$ cat /etc/systemd/system/remove_old_logs.timer
[Unit]
Description=Runs remove_old_logs every hour

[Timer]
OnCalendar=*-*-* *:01:00
Persistent=true

[Install]
WantedBy=timers.target

# service設定
$ sudo systemctl daemon-reload
$ sudo systemctl enable remove_old_logs.timer
$ sudo systemctl start remove_old_logs.timer
$ sudo systemctl status remove_old_logs.timer

以上で/var/log/tcpdump配下にpcapファイルをため続け、一定期間後に削除できるようになりました。

4. termsharkのインストール

パケット解析で最も有名なツールはwiresharkかと思います。
歴史も長くTipsも溜まっていて大変使いやすいです。
ただ今回はターミナルでpcapインスタンスにログインしてパケットを解析する想定なのでターミナルで動くツールを使いたいです。
この用途ではtsharkがありますが(当然)UIがwiresharkとは大きく異なるのでやや学習コストがかかります。
(tsharkを使うならtcpdumpをそのまま解析用途で使う選択で良いかなと思います。)

tsharkでも良いのですが、tsharkをラップしてwiresharkのUIをターミナル上で再現しているtermsharkGitHub)を使ってみます。
私もアプリチームの一部メンバーもwiresharkを使ったことがあるので学習コストが低いことを期待しています。

# termsharkは内部的にtsharkに依存しているのでtsharkをインストール
# yumではtsharkはwireshark-cliというパッケージ名です
$ sudo yum install wireshark-cli
$ which tshark
/usr/bin/tshark

# termsharkのビルドに必要なgoをインストール(~1.17)
# PATHの通し方は各環境に適切なものを選んでください
$ wget https://go.dev/dl/go1.17.13.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.17.13.linux-amd64.tar.gz
$ echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
$ source ~/.bashrc
$ go version
go version go1.17.13 linux/amd64

# termsharkをビルド
$ echo "export GO111MODULE=on" >> ~/.bashrc
$ source ~/.bashrc
$ go install github.com/gcla/termshark/v2/cmd/termshark@v2.4.0
$ go get github.com/gcla/termshark/v2/cmd/termshark
$ echo 'export PATH=$PATH:~/go/bin' >> .bashrc
$ source ~/.bashrc
$ termshark -v
termshark v2.4.0

以上でtermsharkを利用できるようになりました。 pcapファイルを-rオプションの引数にして実行するとpcapファイルの中身を解析できます。

$ termshark -r /var/log/tcpdump/2023-08-17_00\:00\:29.pcap

termsharkの実行画面

termsharkの操作はユーザーガイドに記載があります。

github.com

マウス操作も効いたりします。

まとめ

以上でパケットキャプチャが可能な基盤が構築できました。
この環境をマシンイメージにしておけば以降も使いまわしができて楽かと思います。

ただしvxlanインターフェイスの作成にあたって物理ネットワークインターフェイス名(enX1)やVNI(12345)は固定値になっています。
環境変数に逃して外部から設定可能にしたり、イメージからインスタンスを起動後に修正するなど対応が必要です。

次回はキャプチャしたパケットの解析を行います。