pingを活用した測定方法

# はじめに
- 意外とpingについて深堀する記事が少ないので書いてみました
- 以前投稿したiperfの記事がそこそこ読んでもらえたので測定シリーズみたいな感じで書こうと思います。

https://qiita.com/Nenshu_agetai/items/743ac5e785b759bd0720

## コンテンツ
- pingを使用して測定する方法 (有効なオプションなどの紹介)
※一部は他のページへのリンクとします。
- 測定結果の解釈方法
- pingを使ってこんなこともできるよ、という紹介 (帯域測定など)

:::note warn
注意
筆者の実務経験に基づき、時に調べて書いていきますが誤りを含む可能性があります。
適宜、指摘いただければ修正します。
:::

## 目次
- pingビギナー：[まずはpingを使ってみよう](#pingの使い方)
- pingを使いこなしたい：[ping測定におけるコマンド使い分け方法](#pingの使い分け)
    - [複数の通信経路を切り替えて測定する(インターフェースの切り替え)](#送信元のインターフェースを切り替える)
    - [測定時間・間隔を調整する](#測定時間と間隔の変更)
- もっと使いこなしたい：[ping応用方法の紹介](#ping応用方法の紹介)
    - [解析のためにタイムスタンプをつける](#解析のためにタイムスタンプをつける)
    - [遅延が大きすぎて結果が図れない状況を打破する](#遅延が大きすぎて結果が図れない状況を打破する)
    - [pingを使った帯域測定方法](#pingで帯域も測定する)
- [おわりに](#おわりに)

# pingに関する基礎知識

最初に簡単にpingについて紹介します。前置きなので、知っている方は飛ばしてください。

## 通信に関する測定指標
通信に関する試験・測定を行うとき、必ず出てくる通信品質指標が3つあります。

- 通信帯域 (スループット)
- ロス率
- 遅延

通信品質を図るとき、通信が間に入るシステムの評価を行うとき、など
様々な場面で上記の測定が求められます。

## pingとはなにか？
上記の3指標のうち、特に**遅延**を測定できるツールが pingです。
ただし、pingで測れるのは往復遅延=RTT(Round Trip Time)のみです。

内部的にはICMPというプロトコルを使用しています。
こまかな話は以下のサイトを見てもらえれば分かると思います。

https://www.infraexpert.com/study/tcpip4.html

端的には**宛先を決めて通信を発生させて、往復遅延(RTT)を測るツール**だと思ってください。

## 往復遅延と片道遅延
ざっくりと、片道遅延の方が精密な測定だと理解してください。
- 往復遅延(RTT)：送信したデータが相手に届いて、相手からの応答が返ってくるまでの時間
- 片道遅延：送信したデータが相手に届くまでの時間

往路(行き)・復路(帰り)の環境が異なる場合、片道遅延を測らないと通信の実態が分かりません。

例えば以下の様なイメージです。
```plantuml
クライアント --[#red]> モバイル区間: Echo Request
note right : 上りは帯域が狭い=遅延が大きい
モバイル区間 -> 有線区間: Echo Request
有線区間 -> サーバ: Echo Request
note right : インターネットを使用する場合は、\n時々刻々と通信環境が変化する
有線区間 <- サーバ: Echo Reply
モバイル区間 <- 有線区間: Echo Reply
クライアント <[#blue]-- モバイル区間: Echo Reply
note right : 下りは帯域が広い=遅延が少ない
```
- モバイル回線は上下で帯域の割合が異なる設定になっていることが多い
- インターネットは上りと下りで影響が読めない

本当に正確な測定をしたい場合は片道遅延で上り・下りを区別した方が良いです。
が、実態としては、研究業務でもない限りそこまでは追究しないことが多いです。

ということで、多くの人は**往復遅延(RTT)を測定できれば十分**かと思います。

# pingの使い方
WindowsもLinuxも共通です。
コマンドラインにて以下を実行します。
```
ping [オプション] 送信先
```
- 送信先：IPアドレス or ホスト名
- オプション(なしでも動く)：pingの動作を変更するオプション

## 使い方の例
疎通確認のためによくやるのが、googleへのping応答の確認です。
```bash
ping 8.8.8.8
```
※8.8.8.8はgoogleのDNSサーバのIPアドレスです。
googleはインターネット上にサーバを構えているので、上記が正しく通ればインターネットに接続できていることになります。

## 結果の見方
**成功した場合**、以下のような結果が表示されます。

```bash
ping 8.8.8.8
""" 応答(Windowsの場合)
8.8.8.8 に ping を送信しています 32 バイトのデータ:
8.8.8.8 からの応答: バイト数 =32 時間 =11ms TTL=117
8.8.8.8 からの応答: バイト数 =32 時間 =12ms TTL=117
8.8.8.8 からの応答: バイト数 =32 時間 =8ms TTL=117
8.8.8.8 からの応答: バイト数 =32 時間 =9ms TTL=117

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


<details><summary>Ubuntuの場合</summary>

```bash
ping 8.8.8.8 -c 3
""" 応答(Ubuntuの場合)
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=13.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=15.1 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=9.90 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 9.901/12.776/15.054/2.145 ms
"""
```
</details>

:::note info
Linuxの人は-cオプションで指定した回数だけpingを送信します。オプションをつけないとCtrl+Cで終了するまで続けます。
:::

**失敗した場合**、以下のような結果が表示されます。

```bash
ping 8.8.8.8
""" 応答(Windowsの場合)
8.8.8.8 に ping を送信しています 32 バイトのデータ:
一般エラー。
一般エラー。
一般エラー。
一般エラー。

8.8.8.8 の ping 統計:
    パケット数: 送信 = 4、受信 = 0、損失 = 4 (100% の損失)、
"""
```

とりあえずエラーという事だけは分かります…。

<details><summary>Ubuntuの場合</summary>

```bash
ping 8.8.8.8 -c 3
""" 応答(Ubuntuの場合)
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
From X.X.X.X icmp_seq=1 Destination Host Unreachable
From X.X.X.X icmp_seq=3 Destination Host Unreachable

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 0 received, +2 errors, 100% packet loss, time 2028ms
"""
```

`Destination Host Unreachable`とは、宛先ホストに到達できなかったことを意味します。

</details>

:::note info
失敗した場合の対処としては、途中の経路までの疎通を確認します。
PC -> Wi-Fiルータ -> ルータ -> インターネット -> サーバ
- まずはWi-Fiルータに疎通が通るかを確認、次にルータ…
- インターネットの入口まで通るなら他のサーバへの疎通も試す
:::

## オプション
全て説明すると車輪の再発明になるので、公式のリンクを張ります。
- [Windowsの場合](https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/ping)
※オプションの \ は - に置き換えてください。
- [Ubuntuの場合](https://manpages.ubuntu.com/manpages/bionic/ja/man8/ping.8.html)
※Linuxについては、公式ページのほかに以下が個人的に見やすかったです。

https://www.server-memo.net/tips/command/ping/ping.html

以降では、測定内容とオプションを紐づけて説明をしていきます。


# pingの使い分け
pingのオプションを使い分けて、どのような測定ができるかを紹介します。
想定する測定のケースと、使用するオプションの紹介、結果の参照方法について記載します。

## 送信元のインターフェースを切り替える
### 想定するケース
- 一つのPCで複数の経路(インターフェース)での通信品質を測定する
- モバイル回線と有線回線の通信品質を比較する

```plantuml
node PC {
  rectangle IF2 as router0_e0
  rectangle IF1 as router0_e1
}

rectangle 経路1 as route_e0
rectangle 経路2 as route_e1

rectangle 事業者網1 as net_e0
rectangle 事業者網2 as net_e1

rectangle インターネット as internet
rectangle サーバ as server

router0_e0 -- route_e0
router0_e1 -- route_e1
route_e0 -- net_e0
route_e1 -- net_e1
net_e0 -- internet
net_e1 -- internet
internet -- server
```

※右のルートと左のルートの遅延を比較したい

### 使用するオプション
WindowsとLinux(Ubuntu)で異なります。

- [Windowsの場合] `-S` オプションで送信元のIPアドレス指定する
```bash
ping -S インターフェースのIPアドレス 送信先
```
- -Sの後にインターフェースのIPアドレスを記載してください
- ipアドレスが分からない場合は `ipconfig` コマンドで確認できます

<details><summary>`ipconfig`コマンドの応答</summary>

```bash
ipconfig
""" 応答
イーサネット アダプター イーサネット:

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: xxxx::xxxx:xxxx:xxxx:xxxx
   IPv4 アドレス . . . . . . . . . . . .: XXX.XXX.XXX.XXX # <- これがIPアドレス
   サブネット マスク . . . . . . . . . .: 255.255.255.0
   デフォルト ゲートウェイ . . . . . . .: XXX.XXX.XXX.XXX
"""
```

</details>

- [Linuxの場合] `-I` ｐオプションで送信元のインターフェースを指定する
```bash
ping -I インターフェース名 送信先
```
- -Iの後にインターフェース名を記載してください
- インターフェース名が分からない場合は `ip a` コマンドで確認できます

<details><summary> `ip a` コマンドの応答</summary>

```bash
ip a
""" 応答
(中略)
#  enp0s3がインターフェース名
#   ↓
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet XXX.XXX.XXX.XXX/24 brd XXX.XXX.XXX.255 scope global noprefixroute enp0s3
       valid_lft forever preferred_lft forever
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx/64 scope global temporary dynamic
       valid_lft 299sec preferred_lft 299sec
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx:718f:86d/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 299sec preferred_lft 299sec
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
"""
```
</details>

### 結果の参照
インターフェースの切り替えの効果を実感するために、以下の2ケースの結果を記載します。

```plantuml
node PC {
  rectangle "IF1(192.168.1.1)" as router0_e0

  rectangle "IF2(192.168.0.1)" as router0_e1
  note right : 通信機器とは接続されていない
}
rectangle "通信機器(192.168.1.1)" as device
router0_e0 -- device
```

**成功するパターン：IF1->通信機器**
```bash
ping 192.168.1.1 -S 192.168.101.30
"""
192.168.1.1 に ping を送信しています 192.168.101.30 から 32 バイトのデータ:
192.168.1.1 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.1.1 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.1.1 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.1.1 からの応答: バイト数 =32 時間 =1ms TTL=64

192.168.1.1 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 1ms、最大 = 1ms、平均 = 1ms
"""
```
192.168.101.1 と 192.168.101.30 は同じネットワークに属しているので、正常に通信できます。

**失敗するパターン：IF2 -> ???**
```bash
ping 192.168.1.1 -S 192.168.0.30
"""
192.168.1.1 に ping を送信しています 192.168.0.30 から 32 バイトのデータ:
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。

192.168.1.1 の ping 統計:
    パケット数: 送信 = 4、受信 = 0、損失 = 4 (100% の損失)、
"""
```
タイムアウトしてパケットが損失してしまいます。

確かにインターフェースが切り替わっています。

## 測定時間と間隔の変更
:::note warn
**Linux限定**のオプションです。
どうやらWindowsでは実現できない様子…(たぶん)
:::

### 想定するケース
- 長時間測定をするために、間隔を長くする
- より精密に細かく品質測定をしたい

### 使用するオプション
- [Linux限定]`-i`オプションで送信間隔を、-wで測定時間を指定する
```bash
ping -i 送信間隔(秒) -w 測定時間(秒) 送信先
```

### 結果の参照
送信間隔を3秒、測定時間を15秒にしてみます。
結果が分かりやすいようにタイムスタンプをつける-Dオプションもつけます。
```bash
ping -i 3 -w 15 8.8.8.8 -D
""" 応答
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
[1704548001.603734] 64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=12.1 ms
[1704548004.604852] 64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=15.2 ms
[1704548007.606764] 64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=12.8 ms
[1704548010.692587] 64 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=59.0 ms
[1704548013.607301] 64 bytes from 8.8.8.8: icmp_seq=5 ttl=116 time=10.4 ms

--- 8.8.8.8 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 12015ms
rtt min/avg/max/mdev = 10.426/21.896/58.987/18.607 ms
"""
```
3秒間隔で15秒継続なので、確かに5回送信しているようです。

# ping応用方法の紹介

## 毎行にタイムスタンプを付与する
### 想定するケース
- pingの結果を解析したい。いつ遅延が大きくなったか分からないと困る。
※起こした現象と遅延を紐づける必要がある(パケット量の遅延への影響測定など)
- ping + iperf で通信結果を解析したい。けど、時間情報がないと同期が取れない
**pingにはデフォルトで時間情報がつかない**ので、非常に頻発するケースです。

### 使用するオプション
WindowsとLinux(Ubuntu)で異なります。Windowsは少し工夫が必要です。

- [Windowsの場合/powershell限定] 無理やり時間情報を付与する
```bash
ping 送信先 | %{(Get-Date).ToString() + " $_"}
```
※(Get-Date).ToString()で時間情報を出力し、その後ろにpingの結果をつけています。

- [Linuxの場合] `-D` オプションを使用する
```bash
ping -D 送信先
```
※epoch秒の形式でタイムスタンプがつきます。

### 結果の参照
- Windowsの場合
```bash
ping 8.8.8.8 | %{(Get-Date).ToString() + " $_"}
""" 応答
2024/01/06 23:05:10
2024/01/06 23:05:10 8.8.8.8 に ping を送信しています 32 バイトのデータ:
2024/01/06 23:05:10 8.8.8.8 からの応答: バイト数 =32 時間 =12ms TTL=117
2024/01/06 23:05:11 8.8.8.8 からの応答: バイト数 =32 時間 =10ms TTL=117
2024/01/06 23:05:12 8.8.8.8 からの応答: バイト数 =32 時間 =16ms TTL=117
2024/01/06 23:05:13 8.8.8.8 からの応答: バイト数 =32 時間 =10ms TTL=117
2024/01/06 23:05:13
2024/01/06 23:05:13 8.8.8.8 の ping 統計:
2024/01/06 23:05:13     パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
2024/01/06 23:05:13 ラウンド トリップの概算時間 (ミリ秒):
2024/01/06 23:05:13     最小 = 10ms、最大 = 16ms、平均 = 12ms
"""
```
無理やりタイムスタンプをつけているので、すべての行にタイムスタンプがついています…。

- Linuxの場合
```bash
ping -D 8.8.8.8 -c 3
""" 応答
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
[1704550109.575467] 64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=9.87 ms
[1704550110.610034] 64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=43.2 ms
[1704550111.591852] 64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=23.3 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 9.873/25.454/43.236/13.709 ms
"""
```
こちらは結果にだけタイムスタンプがつきます。

## 遅延が大きすぎて結果が図れない状況を打破する
### 想定するケース
- 遅延が大きい環境で測定すると、実際の遅延でなくエラーしか取れない
pingの**デフォルトタイムアウトは4秒**です。このため**4秒以上の遅延は測定できません**。

### 使用するオプション
タイムアウトをオプションで変更します。WindowsとLinux(Ubuntu)で異なります。

- [Windowsの場合] `-w` オプションを使用する
```bash
ping -w タイムアウト(ミリ秒) 送信先
```

- [Linuxの場合] `-W` オプションを使用する
```bash
ping -W タイムアウト(秒) 送信先
```

### 結果の参照
とくになし。応答を待つ時間が伸びます。

## pingで帯域も測定する
### 想定するケース
- とにかく簡単に帯域が測りたい
- iperfサーバを立てられない
ちょっとした裏技に近い内容です。ちょっと調べたら出てきますが…。
ただし、用法に注意があります。こちらは本節の最後に記載します。

### 使用するオプション
オプションを使用して送信するデータのバイト数を変更します。
WindowsとLinux(Ubuntu)で異なります。動作はどちらも同じです。

- [Windowsの場合] `-l` オプションを使用する
```bash
ping -l バイト数 送信先
```
※最大65500バイトまで指定できます。

- [Linuxの場合] `-s` オプションを使用する
```bash
ping -s バイト数 送信先
```
※最大65507バイトまで指定できます。

### 結果の参照
Wi-Fiルータへpingを送信してみます。まずは、デフォルトのバイト数で送信します。
```bash
ping 192.168.0.1
""" 応答
192.168.0.1 に ping を送信しています 32 バイトのデータ:
192.168.0.1 からの応答: バイト数 =32 時間 =5ms TTL=64
192.168.0.1 からの応答: バイト数 =32 時間 =8ms TTL=64
192.168.0.1 からの応答: バイト数 =32 時間 =6ms TTL=64
192.168.0.1 からの応答: バイト数 =32 時間 =8ms TTL=64

192.168.0.1 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 5ms、最大 = 8ms、平均 = 6ms
"""
```
32Byteの場合、数msで応答が返ります。ただし、バイト数が32バイト程度の少量だと、帯域の測定では誤差が大きくなる可能性があるので、バイト数を増やしてみます。
Windows最大値の65500Byteで送信してみます。

```bash
ping -l 65500 192.168.0.1
""" 応答
192.168.0.1 に ping を送信しています 65500 バイトのデータ:
192.168.0.1 からの応答: バイト数 =65500 時間 =28ms TTL=64
192.168.0.1 からの応答: バイト数 =65500 時間 =16ms TTL=64
192.168.0.1 からの応答: バイト数 =65500 時間 =20ms TTL=64
192.168.0.1 からの応答: バイト数 =65500 時間 =20ms TTL=64

192.168.0.1 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 16ms、最大 = 28ms、平均 = 21ms
"""
```
平均21msです。32Byteの時と比べて、遅延が大きくなっています。ここで、簡単な計算をしてみます。
通信帯域は 通信量[バイト数] / (通信時間[秒]/2) で求められます。
通信時間を2で割っているのは、往路復路が同じ環境であると想定して片道の通信時間を求めるためです。

```math
通信帯域 = 65500 * 8 / (0.021 / 2) = 49.9Mbps
```

今回は49.9Mbpsという結果が出てきました。
(なんだか思ったより仕事部屋の環境が遅くてびっくり…)

なお、測定した往復遅延(RTT)には単純な帯域由来の遅延のほかに、通信経路上の遅延が含まれます。

```math
測定した遅延 = 通信帯域による遅延 + 通信経路の遅延
```

このため、もう少し厳密にやるなら2回測定した上で差分を取ります。
例えば半分のバイト数32750Byteで往復時間を測定し、その差分から帯域を求めます。

```bash
ping -l 32750 192.168.0.1
""" 応答
192.168.0.1 に ping を送信しています 32750 バイトのデータ:
192.168.0.1 からの応答: バイト数 =32750 時間 =11ms TTL=64
192.168.0.1 からの応答: バイト数 =32750 時間 =12ms TTL=64
192.168.0.1 からの応答: バイト数 =32750 時間 =13ms TTL=64
192.168.0.1 からの応答: バイト数 =32750 時間 =10ms TTL=64

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

ここで、
**65500バイトの遅延** = 帯域が65500バイトを処理する遅延 + 通信経路の遅延
**32750バイトの遅延** = 帯域が32750バイトを処理する遅延 + 通信経路の遅延

なので、
65500バイトの遅延 - 32750バイトの遅延 = **帯域が32750バイトを処理する遅延**

すなわち、往復遅延の差分となる 32750バイト と 10ms で帯域を求めます。

```math
帯域 = (65500 - 32750) * 8 / {(0.021-0.011)/2} = 52.4Mbps
```
ということで、結果としては52.4Mbpsとなります。
ただ、差分を取る方法も時々刻々と状況が変化している場合、あまり精度が出ません。本当に精密に測りたいならiperfを使ったほうが良いです。

**この方法で帯域を測る場合の注意**をいくつか記載します。
- まず、**自分の運用するサーバや機器以外に65500バイトなどの大容量pingを飛ばしてはいけません**。
    - サーバ負荷を上げてしまい、迷惑をかけることになります。(やられた側からするとDos攻撃みたいなものです)
    - そもそも、大きなバイト数のpingは攻撃とみなされ遮断されることがあります。(この場合、応答は返りません)
    - なので、測定時は自分の運用するサーバや機器に対して行ってください。
- この方法では**往路と復路が同等の通信環境であることを仮定します**。
    - **上りと下りを分けての測定はできません**
    - 上りと下りで大きく帯域が異なる場合、平均化されてしまいます。

上記の通り、色々と制約はあります。経路上や終端にpingに応答する自前のサーバや機器があり、帯域を手軽に測りたいときには便利です。

なによりiperfサーバを立てずに済むので、ログインできない通信機器を使用しての測定が可能です。

また、インターネット環境に繋がない閉じたネットワークでは、iperfの導入も難しいことがあります。そのような環境下で、帯域の変化を捉えるために使う、なんてこともできます。

# おわりに
また長くなってしまった。pingは意外と奥が深いですね。
次記事書くなら、測定データをマージするスクリプトなど公開したいな、と思います。

今回もお付き合いいただきありがとうございました。