System.Net.NetworkInformation名前空間のPingクラスを使うことで、pingを送信することができる。

PingクラスではSendメソッドSendPingAsyncメソッドを使うことでpingが送信できる。 送信先はホスト名またはIPアドレスを文字列として指定するか、IPアドレスをIPAddressクラスで指定することができる。

ping送信時のタイムアウト時間は引数timeout、その他のオプションは引数optionsで指定できる。

Pingクラスを使ってpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const int timeoutMilliseconds = 5000;
const string targetHost = "example.com";

// 指定したホストに対してpingを送信する
Console.Write($"{targetHost}にpingを送信しています...");

var reply = ping.Send(targetHost, timeoutMilliseconds);

if (reply.Status == IPStatus.Success)
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
else
  Console.WriteLine($"失敗しました ({reply.Status})");
Pingクラスを使ってpingを送信する 
Imports System
Imports System.Net.NetworkInformation

Class Sample
  Shared Sub Main()
    Using ping As New Ping()
      Const timeoutMilliseconds As Integer = 5000
      Const targetHost As String = "example.com"

      ' 指定したホストに対してpingを送信する
      Console.Write($"{targetHost}にpingを送信しています...")

      Dim reply = ping.Send(targetHost, timeoutMilliseconds)

      If reply.Status = IPStatus.Success
        Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
      Else
        Console.WriteLine($"失敗しました ({reply.Status})")
      End If
    End Using
  End Sub
End Class
実行結果例
example.comにpingを送信しています...93.184.216.34から117ミリ秒で応答がありました

localhostにpingを送信しています...127.0.0.1から0ミリ秒で応答がありました

192.168.0.1にpingを送信しています...192.168.0.1から0ミリ秒で応答がありました

192.168.0.99にpingを送信しています...失敗しました (TimedOut)

ping応答に関する詳細は戻り値として返されるPingReplyから参照できる。 応答があった場合、PingReply.Statusプロパティの値はIPStatus.Successとなり、PingReply.RoundtripTimeプロパティには応答を受信するまでにかかった時間がミリ秒単位で設定される。

ホスト名の解決ができなかった場合など、pingが送信できない場合は例外PingExceptionがスローされる。

.NET Coreおよび.NETランタイムではping送信時にPingExceptionが発生するバグがある。 これは、非英語ロケールのUnix系OS、かつ一般ユーザーで実行した場合に発生する。 詳細:§.非英語ロケールでPingExceptionが発生する問題

Pingクラス

Pingクラスを使ったpingの送信、各メソッドの使い方について。

pingの送信 (Ping.Send)

Ping.Sendメソッドを使うことでpingを送信することができる。 非同期的に処理する場合は後述のPing.SendPingAsyncメソッドまたはPing.SendAsyncメソッドを使用する。

ホスト名またはIPアドレスを指定した送信

Sendメソッドでは、引数hostNameOrAddressにホスト名またはIPアドレス(v4またはv6)を文字列形式で指定することにより、その対象に対してpingを送信することができる。

ホストの名前解決に失敗した場合は、例外PingExceptionがスローされる。

ホスト名を指定してpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const string targetHost = "non-existent.invalid"; // 存在しないホスト
//const string targetHost = "192.168.0.1"; // ホスト名だけでなくIPアドレスを直接指定することもできる

// 指定したホストに対してpingを送信する
Console.Write($"{targetHost}にpingを送信しています...");

try {
  var reply = ping.Send(targetHost);

  if (reply.Status == IPStatus.Success)
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
  else
    Console.WriteLine($"失敗しました ({reply.Status})");
}
catch (PingException ex) {
  Console.Error.WriteLine(ex);
}
ホスト名を指定してpingを送信する 
Imports System
Imports System.Net.NetworkInformation

Class Sample
  Shared Sub Main()
    Using ping As New Ping()
      Const targetHost As String = "non-existent.invalid" ' 存在しないホスト
      'Const targetHost As String = "192.168.0.1" ' ホスト名だけでなくIPアドレスを直接指定することもできる

      ' 指定したホストに対してpingを送信する
      Console.Write($"{targetHost}にpingを送信しています...")

      Dim reply = ping.Send(targetHost)

      Try
        If reply.Status = IPStatus.Success
          Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
        Else
          Console.WriteLine($"失敗しました ({reply.Status})")
        End If
      Catch ex As PingException
        Console.Error.WriteLine(ex)
      End Try
    End Using
  End Sub
End Class
実行結果
non-existent.invalidにpingを送信しています...System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
 ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (00000005, 0xFFFDFFFF): Name or service not known
   at System.Net.Dns.GetHostEntryOrAddressesCore(String hostName, Boolean justAddresses)
   at System.Net.Dns.GetHostAddresses(String hostNameOrAddress)
   at System.Net.NetworkInformation.Ping.GetAddressAndSend(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
   --- End of inner exception stack trace ---
   at System.Net.NetworkInformation.Ping.GetAddressAndSend(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 12

IPアドレス(IPAddress)を指定した送信

既知のIPアドレスに対して送信する場合は、送信先をIPAddressクラスとして指定することができる。 IPAddressクラスでは、IPv4/IPv6のどちらも扱うことができる。

文字列形式のIPアドレスからIPAddressインスタンスを作成する場合は、IPAddress.Parseメソッドを使う。

IPアドレスを指定してpingを送信する 
using System;
using System.Net;
using System.Net.NetworkInformation;

using var ping = new Ping();

// 既知のアドレスからIPAddressを作成する
var address = IPAddress.Parse("192.168.0.1"); // IPv4
//var address = IPAddress.Parse("::1"); // IPv6

// 指定したアドレスに対してpingを送信する
Console.Write($"{address}にpingを送信しています...");

var reply = ping.Send(address);

Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
IPアドレスを指定してpingを送信する 
Imports System
Imports System.Net
Imports System.Net.NetworkInformation

Class Sample
  Shared Sub Main()
    Using ping As New Ping()
      ' 既知のアドレスからIPAddressを作成する
      Dim address = IPAddress.Parse("192.168.0.1") ' IPv4
      'Dim address = IPAddress.Parse("::1") ' IPv6

      ' 指定したアドレスに対してpingを送信する
      Console.Write($"{address}にpingを送信しています...")

      Dim reply = ping.Send(address)

      Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
    End Using
  End Sub
End Class
実行結果例
192.168.0.1にpingを送信しています...192.168.0.1から0ミリ秒で応答がありました

タイムアウトの指定

引数timeoutを指定することで、応答に対するタイムアウト時間をミリ秒単位で指定することができる。

タイムアウトした場合、結果として返されるPingReplyのStatusプロパティの値はIPStatus.TimedOutとなる。 (例外はスローされない)

タイムアウト時間を指定してpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const int timeoutMilliseconds = 3000; // タイムアウト時間[ミリ秒]
const string targetHost = "192.168.0.99";

// 指定したホストに対してpingを送信する
Console.Write($"{targetHost}にpingを送信しています...");

var reply = ping.Send(targetHost, timeoutMilliseconds);

if (reply.Status == IPStatus.Success)
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
else if (reply.Status == IPStatus.TimedOut)
  Console.WriteLine($"{timeoutMilliseconds}ミリ秒以内に応答がありませんでした");
else
  Console.WriteLine($"失敗しました ({reply.Status})");
タイムアウト時間を指定してpingを送信する 
Imports System
Imports System.Net.NetworkInformation

Class Sample
  Shared Sub Main()
    Using ping As New Ping()
      Const timeoutMilliseconds As Integer = 3000 ' タイムアウト時間[ミリ秒]
      Const targetHost As String = "192.168.0.99"

      ' 指定したホストに対してpingを送信する
      Console.Write($"{targetHost}にpingを送信しています...")

      Dim reply = ping.Send(targetHost, timeoutMilliseconds)

      If reply.Status = IPStatus.Success
        Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
      Else If reply.Status = IPStatus.TimedOut
        Console.WriteLine($"{timeoutMilliseconds}ミリ秒以内に応答がありませんでした")
      Else
        Console.WriteLine($"失敗しました ({reply.Status})")
      End If
    End Using
  End Sub
End Class
実行結果例
192.168.0.99にpingを送信しています...3000ミリ秒以内に応答がありませんでした

ping送信時のオプション (PingOptions)

引数optionsPingOptionsを指定することで、ping送信時のオプションを指定できる。

PingOptions.Ttl

(この項の内容は検証が不十分です)

PingOptions.Ttlを指定すると、ping送信時のTTL(Time To Live)値を設定できる。

TTLが超過した(0に達した)場合は、Statusの値がIPStatus.TtlExpiredのPingReplyが結果として返されるとされている。

TTLを指定してpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const int timeoutMilliseconds = 5000;
const string targetHost = "example.com";

Console.Write($"{targetHost}にpingを送信しています...");

var reply = ping.Send(
  targetHost,
  timeoutMilliseconds,
  Array.Empty<byte>(), // ICMPパケットにデータを含めない(nullを指定できないため空の配列を渡す)
  new PingOptions() { Ttl = 1 } // TTL=1でpingを送信する
);

switch (reply.Status) {
  case IPStatus.Success:
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
    break;

  case IPStatus.TtlExpired:
    Console.WriteLine($"TTLが超過しました");
    break;

  default:
    Console.WriteLine($"失敗しました ({reply.Status})");
    break;
}

Unix系OS、かつ一般ユーザーで実行した場合、IPStatus.TtlExpiredではなくIPStatus.TimedOutとして扱われる模様。 (関連: §.スーパーユーザーと一般ユーザーでの動作の違い)

PingOptions.DontFragment

(この項の内容は検証が不十分です)

PingOptions.DontFragmenttrueにすると、ping送信時にパケットのフラグメンテーション(複数パケットへの分割)を行わないようになる。

DontFragmentがtrueの場合、かつMTU(Maximum Transmission Unit)値を超えるパケットを送信しようとした場合は、Statusの値がIPStatus.PacketTooBigのPingReplyが結果として返されるとされている。

パケットのフラグメンテーションなし・任意長のデータを含めてpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const int timeoutMilliseconds = 5000;
const string targetHost = "192.168.0.1";

Console.Write($"{targetHost}にpingを送信しています...");

var reply = ping.Send(
  targetHost,
  timeoutMilliseconds,
  new byte[2048], // ICMPパケットに2048バイトのデータを含めて送信する
  new PingOptions() { DontFragment = true } // パケットの分割をさせない
);

switch (reply.Status) {
  case IPStatus.Success:
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
    break;

  case IPStatus.PacketTooBig:
    Console.WriteLine($"MTU値を超えるパケットを送信しようとして失敗しました");
    break;

  default:
    Console.WriteLine($"失敗しました ({reply.Status})");
    break;
}

Unix系OS、かつ一般ユーザーで実行した場合は、IPStatus.TimedOutとして扱われ、スーパーユーザーで実行した場合はSocketException("Message too long")がInnerExceptionにセットされたPingExceptionがスローされる模様。 (関連: §.スーパーユーザーと一般ユーザーでの動作の違い)

pingの非同期送信

Ping.SendPingAsync

async/awaitによる非同期送信を行う場合は、SendPingAsyncメソッドが使用できる。 Ping.Sendメソッドと同様に、引数でタイムアウトオプション(PingOptions)を指定することもできる。

SendPingAsyncメソッドにより非同期でpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

const string targetHost = "example.com";

Console.Write($"{targetHost}にpingを送信しています...");

var reply = await ping.SendPingAsync(targetHost);

if (reply.Status == IPStatus.Success)
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
else
  Console.WriteLine($"失敗しました ({reply.Status})");
実行結果例
example.comにpingを送信しています...93.184.216.34から117ミリ秒で応答がありました

SendPingAsyncの追加以前から既にSendAsyncメソッドが存在していたため、Read/ReadAsyncなど他の同期的/非同期メソッドとは異なる命名となっている。

Ping.SendAsync

SendAsyncメソッドでもpingの非同期送信が行える。 このメソッドはSendPingAsyncメソッドとは異なり、イベントハンドラで非同期処理を行う。

SendAsyncメソッドを呼び出す前にPingCompletedイベントにイベントハンドラを割り当てておくと、SendAsyncメソッドによるping送信が完了した時点でイベントが発生しイベントハンドラが呼び出される。

ping送信の結果はイベントハンドラにてPingCompletedEventArgsとして受け取ることができ、PingCompletedEventArgs.ReplyプロパティからPingReplyを参照できる。

SendAsyncメソッドにより非同期でpingを送信する 
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

var completed = false;

// ping送信が完了したときのイベントハンドラを割り当てる
ping.PingCompleted += (sender, e) => {
  // PingReplyを取得する
  var reply = e.Reply;

  if (reply.Status == IPStatus.Success)
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
  else
    Console.WriteLine($"失敗しました ({reply.Status})");

  // 完了したことを示すフラグを設定
  completed = true;
};

const string targetHost = "example.com";

// SendAsyncによるpingの送信を開始する
Console.Write($"{targetHost}にpingを送信しています");

ping.SendAsync(targetHost, null);

// 送信が完了するまで"."を表示して待機する
while (!completed) {
  Console.Write(".");
  await System.Threading.Tasks.Task.Delay(50);
}
SendAsyncメソッドにより非同期でpingを送信する 
Imports System
Imports System.Net.NetworkInformation

Class Sample
  Shared Sub Main()
    Using ping As New Ping()
      Dim completed = False

      ' ping送信が完了したときのイベントハンドラを割り当てる
      AddHandler ping.PingCompleted, Sub(sender, e)
        ' PingReplyを取得する
        Dim reply = e.Reply

        If reply.Status = IPStatus.Success
          Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
        Else
          Console.WriteLine($"失敗しました ({reply.Status})")
        End If

        ' 完了したことを示すフラグを設定
        completed = True
      End Sub

      Const targetHost As String = "example.com"

      ' SendAsyncによるpingの送信を開始する
      Console.Write($"{targetHost}にpingを送信しています")

      ping.SendAsync(targetHost, Nothing)

      ' 送信が完了するまで"."を表示して待機する
      Do Until completed
        Console.Write(".")
        System.Threading.Thread.Sleep(50)
      Loop
    End Using
  End Sub
End Class
実行結果例
example.comにpingを送信しています....93.184.216.34から115ミリ秒で応答がありました

Pingクラス内部の動作

Unix系OS上での動作

Linux, MacOS, Android等のUnix系OS上でのPingクラスの動作について。

スーパーユーザーと一般ユーザーでの動作の違い

実行ユーザーがroot権限を持つ場合、PingクラスはrawソケットSOCK_RAWを作成してICMPパケットを送信する。 一方、一般ユーザーの場合はrawソケットを作成することができないため、代わりにシステムのpingコマンドを呼び出すことによってpingを送信する。 このため、root権限がなくてもPingクラスでpingを送信することができる。

ただし、一般ユーザーで実行した場合はSendメソッド呼び出しのたびにプロセス生成が行われるため、その分オーバーヘッドは大きくなる。

ローカルループバックアドレスに対して1000回pingを送信した場合を比較すると次のようになる。

スーパーユーザーでのベンチマーク結果
$ LC_ALL=C sudo dotnet run -c Release

| Method |     Mean |    Error |   StdDev |
|------- |---------:|---------:|---------:|
|   Send | 17.87 ms | 0.279 ms | 0.261 ms |
一般ユーザーでのベンチマーク結果
$ LC_ALL=C dotnet run -c Release

| Method |    Mean |    Error |   StdDev |
|------- |--------:|---------:|---------:|
|   Send | 2.228 s | 0.0331 s | 0.0276 s |
検証に使った環境等
BenchmarkDotNet=v0.12.1, OS=ubuntu 20.04
Intel Core i5-6402P CPU 2.80GHz (Skylake), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=5.0.201
  [Host]     : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
  DefaultJob : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
検証に使ったコード
using System;
using System.Net;
using System.Net.NetworkInformation;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class PingSendBenchmark {
  private const int count = 1_000;

  [Benchmark]
  public int Send()
  {
    using var ping = new Ping();
    var ret = 0;

    for (var n = 0; n < count; n++) {
      ping.Send(IPAddress.Loopback);
      ret = n;
    }

    return ret;
  }

  static void Main() => BenchmarkRunner.Run<PingSendBenchmark>();
}

非英語ロケールでPingExceptionが発生する問題

.NET Core/.NET 5ランタイムで発生する問題。 .NETに統合される以前のMonoランタイムでは発生しない。

この問題の修正はmainブランチにマージされている。 また6.0.0のマイルストーンに追加されているため、.NET 6で修正されると見込まれる。

前述のように一般ユーザーで実行されている場合、Pingクラスはシステムのpingコマンドを呼び出す動作となる。

ここで、pingコマンドの出力は、ディストリビューションの言語パックのインストール状況やロケールの設定(環境変数LANG,LC_MESSAGES, LC_ALLなど)によってはローカライズされた状態となる場合がある。 Pingクラスはローカライズされた出力の解析に失敗し、PingExceptionをスローする。

非英語ロケールではPing.SendメソッドでPingExceptionが発生する場合がある  .NET Core 3.1/.NET 5
using System;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;

Console.WriteLine(RuntimeInformation.FrameworkDescription);

foreach (var envvar in new[] {"LANG", "LC_MESSAGES", "LC_ALL"}) {
  Console.WriteLine($"{envvar}={Environment.GetEnvironmentVariable(envvar)}");
}
Console.WriteLine();

using var ping = new Ping();

var reply = ping.Send("192.168.0.1");

Console.WriteLine($"{reply.Status} {reply.Address}, {reply.RoundtripTime}[ms]");
実行結果例1
.NET 5.0.4
LANG=ja_JP.UTF-8
LC_MESSAGES=
LC_ALL=

Unhandled exception. System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
 ---> System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
   at System.Net.NetworkInformation.Ping.SendWithPingUtility(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
   at System.Net.NetworkInformation.Ping.SendPingCore(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
   --- End of inner exception stack trace ---
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 14
実行結果例2
.NET Core 3.1.13
LANG=en_US.UTF8
LC_MESSAGES=ja_JP.UTF8
LC_ALL=

Unhandled exception. System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
 ---> System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
   at System.Net.NetworkInformation.Ping.SendWithPingUtility(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
   at System.Net.NetworkInformation.Ping.SendPingCore(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
   --- End of inner exception stack trace ---
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 14
実行結果例3
Mono 6.12.0.122 (tarball Mon Feb 22 17:33:28 UTC 2021)
LANG=ja_JP.UTF-8
LC_MESSAGES=
LC_ALL=

Success 192.168.0.1, 10[ms]

この問題は、Pingクラスの内部実装が、英語ロケールの出力フォーマットを前提としていることに起因する。


この問題は、以下のいずれかの方法で回避できる。

  1. root権限で実行する
  2. Monoランタイムで実行する
  3. Cロケール(あるいは英語)に設定した上で実行する
  4. pingコマンドの出力に影響する日本語等の言語パックをアンインストールする

Cロケールを使って回避する場合は、次のように実装することでコードの変更のみで回避できる。

一時的にLC_ALLをCにしてPingExceptionの発生を回避する  .NET Core 3.1/.NET 5
using System;
using System.Net.NetworkInformation;

using var ping = new Ping();

// 環境変数LC_ALLの現在の値を保持する
var LC_ALL = Environment.GetEnvironmentVariable("LC_ALL");

try {
  // 一時的に環境変数LC_ALLに"C"を設定する
  Environment.SetEnvironmentVariable("LC_ALL", "C");

  // pingを送信する
  var reply = ping.Send("192.168.0.1");

  Console.WriteLine($"{reply.Status} {reply.Address}, {reply.RoundtripTime}[ms]");
}
finally {
  // 環境変数LC_ALLを以前の値に戻す
  Environment.SetEnvironmentVariable("LC_ALL", LC_ALL);
}