The internet has fundamentally changed the majority of the applications we develop. Rarely is an application designed to exist in isolation. Instead, most apps rely on other networked apps to retrieve and store information. In this post, we’ll explore the network utility ping and how we can utilize a C# library to test the reachability of any network host.

What Is Ping?

Written in 1983 by Mike Muuss, the design of the ping utility is one that allows network administrators to measure, diagnose, and troubleshoot network issues. Being developed in the military, ping naturally gets its name after the sound sonar makes and mimics the behavior of sonar itself.

Ping is a computer network administration software utility used to test the reachability of a host on an Internet Protocol (IP) network. It is available for virtually all operating systems that have networking capability, including most embedded network administration software. Wikipedia

When invoked, ping transmits a set of bytes, and the output shows information including the host’s IP address, the Internet Control Message Protocol (ICMP) sequence, the Time To Live (TTL), and the total response time in milliseconds.

$ ping -c 5 www.example.com
PING www.example.com (93.184.216.34): 56 data bytes
64 bytes from 93.184.216.34: icmp_seq=0 ttl=59 time=8.053 ms
64 bytes from 93.184.216.34: icmp_seq=1 ttl=59 time=8.907 ms
64 bytes from 93.184.216.34: icmp_seq=2 ttl=59 time=8.543 ms
64 bytes from 93.184.216.34: icmp_seq=3 ttl=59 time=6.793 ms
64 bytes from 93.184.216.34: icmp_seq=4 ttl=59 time=8.523 ms

--- www.example.com ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 6.793/8.164/8.907/0.737 ms

As we can see, the output can help us diagnose whether our target host is up and responding expectedly.

Ping with .NET

While being a command-line utility on most operating systems, ping is also implementable. The .NET Framework has an implementation of Ping under the System.Net.NetworkInformation namespace. We can use this class to perform the same ping actions from our C# applications.

using System;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

namespace Mothership
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var pingSender = new Ping();
            var hostNameOrAddress = "example.com";
            
            Console.Clear();
            Console.WriteLine($"PING {hostNameOrAddress}");

            for (int i = 0; i < 5; i++)
            {
                var reply = await pingSender.SendPingAsync(hostNameOrAddress);
                Console.WriteLine($"{reply.Buffer.Length} bytes from {reply.Address}:" +
                                  $" icmp_seq={i} status={reply.Status} time={reply.RoundtripTime}ms");
            }
        }
    }
}

We get the following results from running our console application.

PING example.com
0 bytes from 93.184.216.34: icmp_seq=0 status=Success time=7ms
0 bytes from 93.184.216.34: icmp_seq=1 status=Success time=7ms
0 bytes from 93.184.216.34: icmp_seq=2 status=Success time=7ms
0 bytes from 93.184.216.34: icmp_seq=3 status=Success time=7ms
0 bytes from 93.184.216.34: icmp_seq=4 status=Success time=7ms

One strange behavior we notice is the reply buffer length is always 0. The odd behavior is because the Ping class behaves differently based on our operating system. On Unix systems (Linux and macOS), the .NET Framework delegates to the UnixCommandLinePing class, which does not map PingOptions and does not read the response byte array. The difference in behavior across operating systems should be accounted for, as it could cause null reference exceptions`.

// from Ping.Unix.cs
private PingReply ParsePingUtilityOutput(IPAddress address, string output)
{
    long rtt = UnixCommandLinePing.ParseRoundTripTime(output);
    return new PingReply(
        address,
        null, // Ping utility cannot accommodate these, return null to indicate they were ignored.
        IPStatus.Success,
        rtt,
        Array.Empty<byte>()); // Ping utility doesn't deliver this info.
}

The cool thing about the Ping implementation in .NET is the presence of the IPStatus enum, which makes it clear what kind of response we are dealing with.

public enum IPStatus
{
  Unknown = -1, // 0xFFFFFFFF
  Success = 0,
  DestinationNetworkUnreachable = 11002, // 0x00002AFA
  DestinationHostUnreachable = 11003, // 0x00002AFB
  DestinationProhibited = 11004, // 0x00002AFC
  DestinationProtocolUnreachable = 11004, // 0x00002AFC
  DestinationPortUnreachable = 11005, // 0x00002AFD
  NoResources = 11006, // 0x00002AFE
  BadOption = 11007, // 0x00002AFF
  HardwareError = 11008, // 0x00002B00
  PacketTooBig = 11009, // 0x00002B01
  TimedOut = 11010, // 0x00002B02
  BadRoute = 11012, // 0x00002B04
  TtlExpired = 11013, // 0x00002B05
  TtlReassemblyTimeExceeded = 11014, // 0x00002B06
  ParameterProblem = 11015, // 0x00002B07
  SourceQuench = 11016, // 0x00002B08
  BadDestination = 11018, // 0x00002B0A
  DestinationUnreachable = 11040, // 0x00002B20
  TimeExceeded = 11041, // 0x00002B21
  BadHeader = 11042, // 0x00002B22
  UnrecognizedNextHeader = 11043, // 0x00002B23
  IcmpError = 11044, // 0x00002B24
  DestinationScopeMismatch = 11045, // 0x00002B25
}

There we have it! We can ping a network host and wait for a response right from our .NET code. Give it a try and leave a comment below.