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.