Pingコマンドのパケットサイズについて調べてみた

2023.12.26

しばたです。  

先日リリースされたAmazon CloudWatch Network Monitorについて動作検証をしています。

その中でICMPエコー要求による監視パラメーターの一つに「パケットサイズ」があります。

ここで「そういえばICMPエコー要求のパケットサイズについて意識したことが無いな。」と思い、代表的なPingコマンドについてパケットサイズ関連の情報を調べてみました。

なお、今回はIPv4のみ対象としています。

ICMPメッセージフォーマットとPingコマンドの基本

IPv4におけるICMPエコー要求およびエコー応答におけるメッセージフォーマットはRFC792(とそのアップデート)で定義されており以下のフォーマットになっています。

Echo or Echo Reply Message

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Type      |     Code      |          Checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Identifier          |        Sequence Number        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Data ...
   +-+-+-+-+-

最初の4ByteはICMP共通のヘッダで、TypeについてはType 8 = エコー要求Type 0 = エコー応答、Codeは常に0となります。
次の4ByteはIDとシーケンス番号で設定する内容は実装依存となります。
最後に可変長のデータ領域(ペイロード)となり、パケットサイズを変える際はここのサイズが変わることになります。

そしてPingコマンドはOS毎で実装が異なり送出されるパケットの内容はそれぞれ独自の内容となります。

LinuxにおけるPingコマンド

今回はAmazon Linux 2023環境を調査対象にしました。
LinuxにおけるPing実装は大きくiputilsInetutilsに分かれる様ですが、Amazon Linuxはiputils実装が採用されています。

LinuxにおけるPingコマンドでは-sパラメーターでパケットサイズを調整可能です。
デフォルト値は56Byteで先頭8Byteのヘッダ(Typeからシーケンス番号まで)と合わせてICMP全体で64Byteになる様にされています。

実行例はこんな感じです。

# -s パラメーターでパケットサイズを調整可能。デフォルト 56
$ ping -c 4 -s 56 10.0.11.15
PING 10.0.11.15 (10.0.11.15) 56(84) bytes of data.
64 bytes from 10.0.11.15: icmp_seq=1 ttl=127 time=0.751 ms
64 bytes from 10.0.11.15: icmp_seq=2 ttl=127 time=0.479 ms
64 bytes from 10.0.11.15: icmp_seq=3 ttl=127 time=0.621 ms
64 bytes from 10.0.11.15: icmp_seq=4 ttl=127 time=0.529 ms

--- 10.0.11.15 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3083ms
rtt min/avg/max/mdev = 0.479/0.595/0.751/0.103 ms

出力メッセージのうち

PING 10.0.11.15 (10.0.11.15) 56(84) bytes of data.

の部分の56が指定サイズ、(84)がIPヘッダを含めたサイズ(56 (ペイロード) + 8 (ICMPヘッダ) + 20 (IPヘッダ))となります。

そしてパケットキャプチャした結果はこんな感じでした。

IDとシーケンス番号にはそれぞれ固有の値がセットされていました。
データ領域の内、最初の16Byteにはタイムスタンプがセットされ残りは0x10から始まるバイト列が埋められる形になっています。

このバイト列は0x10を起点に0x10, 0x11, 0x12, ~, 0xff, 0x00, 0x01, ~の順でループする構造となっていました。
そして-sの値が16より少ない場合はタイムスタンプは記録されず0x00 ~ 0x0eまでのバイト列のみ埋められます。

ここまでをまとめると以下の様になります。

  • -s < 16の場合
    • 0x00から始まるバイト列 : -sの値 Byte
  • -s >= 16の場合
    • タイムスタンプ : 16Byte
    • 0x11から始まるバイト列 : (-sの値 - 16) Byte

macOSにおけるPingコマンド

Mac環境は私物のMac Mini(macOS Sonoma 14.2.1)を調査対象にしました。
macOSにおけるPing実装はBSD派生の独自の様 *1ですが、パケットサイズについてはLinux同様-sパラメーターで設定可能であり、デフォルト値も同じ56となります。

実行例はこんな感じ。

# -s パラメーターでパケットサイズを調整可能。デフォルト 56
% ping -c 4 -s 56 192.168.11.70
PING 192.168.11.70 (192.168.11.70): 56 data bytes
64 bytes from 192.168.11.70: icmp_seq=0 ttl=128 time=4.028 ms
64 bytes from 192.168.11.70: icmp_seq=1 ttl=128 time=5.500 ms
64 bytes from 192.168.11.70: icmp_seq=2 ttl=128 time=7.003 ms
64 bytes from 192.168.11.70: icmp_seq=3 ttl=128 time=4.568 ms

--- 192.168.11.70 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 4.028/5.275/7.003/1.128 ms

こちらの出力メッセージは

PING 192.168.11.70 (192.168.11.70): 56 data bytes

と、指定サイズの56だけ表示されていますが、ICMP全体のサイズは追加で8Byte、IP全体ではさらにIPヘッダのサイズを足す必要がありますのでご注意ください。
パケットキャプチャした結果はこんな感じです。

こちらもIDとシーケンス番号両方に固有の値が設定されますが、IDについては最初のパケットは常に0になっていました。
データ領域の内、最初の8Byteにはタイムスタンプがセットされ残りは0x08から始まり0xffまでを繰り返すバイト列が埋められる形になっています。
-sの値が8より少ない場合はタイムスタンプは記録されず全てのバイトが0x00で埋められました。

まとめると以下の通りです。

  • -s < 8の場合
    • 全て0x00のバイト列 : -sの値 Byte
  • -s >= 8の場合
    • タイムスタンプ : 8Byte
    • 0x08から始まるバイト列 : (-sの値 - 8) Byte

WindowsにおけるPingコマンド

Windows環境は日本語版のWindows Server 2022を調査対象にしています。

WindowsにおけるPing実装は非公開でありコマンド体系もLinuxやmacOSとかなり異なります。
パケットサイズについては-lパラメーターで設定可能であり、デフォルト値は32となります。

実行例はこんな感じ。

# -l パラメーターでパケットサイズを調整可能。デフォルト 32
PS C:\> ping -l 32 10.0.11.15

10.0.11.15 に ping を送信しています 32 バイトのデータ:
10.0.11.15 からの応答: バイト数 =32 時間 <1ms TTL=127
10.0.11.15 からの応答: バイト数 =32 時間 <1ms TTL=127
10.0.11.15 からの応答: バイト数 =32 時間 <1ms TTL=127
10.0.11.15 からの応答: バイト数 =32 時間 <1ms TTL=127

10.0.11.15 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 0ms、最大 = 0ms、平均 = 0ms

こちらの出力メッセージは

10.0.11.15 に ping を送信しています 32 バイトのデータ:

とmacOSと同じパターンになっており、ICMP全体のサイズは追加で8Byte、IP全体ではさらにIPヘッダのサイズを足す必要があります。
パケットキャプチャした結果はこんな感じです。

IDとシーケンス番号にはそれぞれ固有の値がセットされていました。
データ領域にタイムスタンプは設定されず、0x61 (a)から0x77 (w)を延々と繰り返すバイト列が設定されていました。

まとめると以下の通りです。

  • 全て条件において
    • 0x61 (a) ~ 0x77 (w)を繰り返すバイト列 : -lの値 Byte

Windowsだけパケットサイズのデフォルト値が異なるのでLinuxやmacOSと合わせるには-l 56にする必要があります。

# LinuxやmacOSとパケットサイズを合わせるには -l 56 にする
ping -l 56 10.0.11.15

余談 : PowerShellの場合

Windows PowerShellおよびPowerShell 7(PowerShell Core)ではTest-ConnectionコマンドレットでPingと同等の行為が可能です。
どちらも.NETのネットワーク機能を使ってICMPパケットを送出しているのですが、.NET Core以降はクロスプラットフォーム化により内部的な実装+挙動が若干異なっています。

Windows PowerShellの場合

Test-Connectionコマンドでは-BufferSizeパラメーターでパケットサイズを調整可能で、デフォルト値は32です。
送出されるパケットの内容もWindowsのPingコマンドと同じになる様に調整されていました。

# Test-Connection 実行例
PS C:\> Test-Connection -ComputerName 10.0.11.15 -BufferSize 32

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
EC2AMAZ-RQ... 10.0.11.15      10.0.11.15                                                32       0
EC2AMAZ-RQ... 10.0.11.15      10.0.11.15                                                32       0
EC2AMAZ-RQ... 10.0.11.15      10.0.11.15                                                32       0
EC2AMAZ-RQ... 10.0.11.15      10.0.11.15                                                32       2

パケットキャプチャの結果は以下。

(WindowsのPing同様にデータ領域は0x61 (a)から0x77 (w)で埋められる)

PowerShell 7の場合

PowerShell 7のTest-Connectionでも同様のパケットを送出する仕組みになっているのですが、.NET 7からの破壊的変更に対応する改修でバグを仕込んでしまっており怪しい挙動をします。

本日時点で最新のPowerShell 7.4.0においてもバグにより-BufferSizeパラメーターが未指定または-BufferSize 32の場合はデータ領域に一切のデータが設定されません。
このためICMPパケットサイズは常に8Byteのままです。
(ただ、これでも疎通確認自体は問題無く可能です)

# -BufferSize 未指定および -BufferSize 32は無視されるバグがある
# ※32以外のサイズ指定なら期待した動作をする
PowerShell 7.4.0
PS C:\> Test-Connection -ComputerName 10.0.11.15 -BufferSize 32

   Destination: 10.0.11.15

Ping Source           Address                   Latency BufferSize Status
                                                   (ms)        (B)
---- ------           -------                   ------- ---------- ------
   1 EC2AMAZ-RQEKLVQ  10.0.11.15                      0         32 Success
   2 EC2AMAZ-RQEKLVQ  10.0.11.15                      0         32 Success
   3 EC2AMAZ-RQEKLVQ  10.0.11.15                      0         32 Success
   4 EC2AMAZ-RQEKLVQ  10.0.11.15                      0         32 Success

(バグによりデータ領域に何も設定されていない)

また、Linux環境においては非rootユーザーでは-BufferSizeパラメーターをデフォルト以外の値にすると権限エラーとなります。

# 非ルートユーザーの場合 -BufferSize を指定すると権限エラー
# ※ルートユーザーなら -BufferSizeを指定可能だが、-BufferSize 32は無視されるバグがあるので注意
PowerShell 7.4.0
PS /> Test-Connection 10.0.11.15 -BufferSize 56
Test-Connection: Unable to send custom ping payload. Run program under privileged user account or grant cap_net_raw capability using setcap(8).

これ自体は.NET側の仕様変更によるものであるため仕方ないのですが、エラーを出さない様にするために-BufferSize 32の場合が無視されるバグが発生しているので要注意です。

こちらについては時間があればIssueを上げておこうと思います。
取り急ぎはPowerShell 7のTest-Connectionは使わない方が良いでしょう。
正直Pingコマンドで事足りるので私は一切使っていません... *2

まとめ

最後に全体を通した内容を簡単にまとめておきます。

  • PingコマンドはOSにより実装が異なり送出されるICMPパケットの内容も異なる
  • OSにより実装は異なるがパケットサイズを調整するパラメーターは存在する
  • パラメーターで調整可能なサイズはICMPデータ領域(ペイロード)のサイズ
  • ICMPパケットの全体のサイズを出すにはヘッダの8Byteを追加する必要がある

おわりに

以上となります。

軽い気持ちで調べ始めたのですがなかなかのボリュームになりました。
通常の疎通確認においてはそこまでパケットサイズに気を配ることは無いと思います。
本記事の内容はちょっとしたトリビアくらいに捉えてもらうのが良いでしょう。

脚注

  1. どうもこれっぽい https://opensource.apple.com/source/network_cmds/network_cmds-433/ping.tproj/
  2. 本記事を書いてはじめてこのバグに気が付いたくらいですし