PING UTILITY and WEBSERVICE in C# .NET

By Peter A. Bromberg, Ph.D.

Peter Bromberg  

Some time ago Lance Olson put some code together in an MSDN Magazine article about Peer- to-Peer in .NET at http://msdn.microsoft.com/msdnmag/issues/01/02/netpeers/netpeers.asp. This is from February 2001 so the code is obviously from BETA 1, and won't work in BETA 2 or Visual Studio.NET RTM without some serious revisions.



However, recently I realized I needed to get up to speed with socket programming in C#, and so I revisited the particular code portion in the article about doing a PING. First of all, what is PING? Actually it has taken on several distinct meanings. One is that Ping is actually an acronym for the words 'Packet INternet Groper'. Another is that it is in fact not an acronym at all, but a noun that was adopted from a verb the US Navy uses to describe what its submarines do when looking for objects under the sea. Their subs send out sonar waves and then wait for a return wave when it bounces off something, such as another sub, whale, ocean floor etc. This, in turn, was adopted from bats and dolphins, who navigate in roughly the same way. This is what a system administrator does when Ping is used. So Ping has evolved into a verb in the computer industry, and it is used in somewhat the same manner of the Navy. A Ping utility is essentially a system administrator's tool that is used to see if a computer is operating and also to see if network connections are intact. Ping uses the Internet Control Message Protocol (ICMP) Echo function which is detailed in RFC 792. A small packet is sent through the network to a particular IP address. This packet contains 64 bytes - 56 data bytes and 8 bytes of protocol reader information. The computer that sent the packet then waits (or 'listens') for a return packet. If the connections are good and the target computer is up, a good return packet will be received.

But PING is also a useful utility for programmers. Often it is critical to see if an endpoint is "up" before attemping to send a message. Also, it can be very useful to estimate the response time from the host. That's why I decided to sit down with Lance's excellent code sample, revamp it for the Release version of Visual Studio.NET, and turn it into a class library that can be used, among many other useful things, in a webservice. It turned out to be an excellent exercise in understanding socket programming in C# as I hope it will be for you, too.

If you search the web for examples of PING in C# you will find some examples by a someone (whose name I won't mention) who basically just "lifted" Olson's code and changed the namespace and function names, and turned the BETA 1 code into a console application. Well, we already have a console application called PING. My code comes from the source, and I am more than happy to give the original author credit for it. A number of classes and methods have changed considerably since the original BETA 1 code was written, and I've updated everything to be 100% Release version compliant.

I'm reproducing my code here inline, and also making the project available to download at the bottom of this article.

using System;
namespace .CBS.Util // change this to yourcompany.yourdivision.whatever
{
using System;
using System.Net;
using System.Net.Sockets;
/// <summary>
/// The Main Ping Class
/// </summary>
public class Ping
{
//Declare some Constant Variables
const int SOCKET_ERROR = -1;
const int ICMP_ECHO = 8;
/// <summary>

/// </summary>
public static void Main()
{

}

/// <summary>
/// This public method takes the "hostname" of the server
/// and then it pings it and shows the response time
/// Data is returned as a string. IP addresses are resolved.
/// Example usage:
/// using .CBS.Util
/// Ping p = new Ping();
/// textBox2.Text = p.PingHost(textBox1.Text);
/// </summary>
public string PingHost(string host)
{
//Declare the IPHostEntry
IPHostEntry serverHE, fromHE;
int nBytes = 0;
int dwStart = 0, dwStop = 0;
//Initialize a Socket of Type ICMP

Socket socket =
new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 1000);
// Get the server endpoint
try
{
serverHE = Dns.GetHostByName(host);
}
catch(Exception)
{

return "Host not found"; // uh-oh!
}

// Convert the server IP_EndPoint to an EndPoint
IPEndPoint ipepServer = new IPEndPoint(serverHE.AddressList[0], 0);
EndPoint epServer = (ipepServer);

// Set the receiving endpoint to the client machine
fromHE = Dns.GetHostByName(Dns.GetHostName());
IPEndPoint ipEndPointFrom = new IPEndPoint(fromHE.AddressList[0], 0);
EndPoint EndPointFrom = (ipEndPointFrom);

int PacketSize = 0;
IcmpPacket packet = new IcmpPacket(); // didn't know we had this in .Net, eh?
// (script kiddies: GET LOST!)
// Construct the packet to send
packet.Type = ICMP_ECHO; //8
packet.SubCode = 0;
packet.CheckSum = UInt16.Parse("0");
packet.Identifier = UInt16.Parse("45");
packet.SequenceNumber = UInt16.Parse("0");
int PingData = 32; // sizeof(IcmpPacket) - 8;
packet.Data = new Byte[PingData];
//Initialize the Packet.Data
for (int i = 0; i < PingData; i++)
{
packet.Data[i] = (byte)'#';
}

// Variable to hold the total Packet size
PacketSize = PingData + 8;
Byte [] icmp_pkt_buffer = new Byte[ PacketSize ];
Int32 Index = 0;
// Call Method Serialize which counts
// The total number of Bytes in the Packet

Index = Serialize(
packet,
icmp_pkt_buffer,
PacketSize,
PingData );
//Error in Packet Size
if( Index == -1 )
{
return "Error Creating Packet";

}

// convert into a UInt16 array
//Get the Half size of the Packet

Double double_length = Convert.ToDouble(Index);
Double dtemp = Math.Ceiling( double_length / 2);
int cksum_buffer_length = Convert.ToInt32(dtemp);
//Create a Byte Array
UInt16 [] cksum_buffer = new UInt16[cksum_buffer_length];
//Code to initialize the Uint16 array
int icmp_header_buffer_index = 0;
for( int i = 0; i < cksum_buffer_length; i++ )
{
cksum_buffer[i] =
BitConverter.ToUInt16(icmp_pkt_buffer,icmp_header_buffer_index);
icmp_header_buffer_index += 2;
}
//Call method to return a checksum
UInt16 u_cksum = checksum(cksum_buffer, cksum_buffer_length);
//Save checksum to Packet
packet.CheckSum = u_cksum;

// Now that we have the checksum, serialize the packet again
Byte [] sendbuf = new Byte[ PacketSize ];
//again check the packet size
Index = Serialize(
packet,
sendbuf,
PacketSize,
PingData );
//if there is an error return it
if( Index == -1 )
{
return "Error Creating Packet";

}

dwStart = System.Environment.TickCount; // Start timing
//send the Packet over the socket
if ((nBytes = socket.SendTo(sendbuf, PacketSize, 0, epServer)) == SOCKET_ERROR)
{
return "Socket Error: cannot send Packet";
}
// Initialize the buffers. The receive buffer is the size of the
// ICMP header plus the IP header (20 bytes)

Byte [] ReceiveBuffer = new Byte[256];
nBytes = 0;
// Receive the bytes
bool recd =false ;
int timeout=0 ;

// loop for checking the time of the server response
while(!recd)
{
nBytes = socket.ReceiveFrom(ReceiveBuffer, 256, 0, ref EndPointFrom);
if (nBytes == SOCKET_ERROR)
{
return "Host not Responding" ;


}
else if(nBytes>0)
{
dwStop = System.Environment.TickCount - dwStart; // stop timing
return "Reply from "+epServer.ToString()+" in "
+dwStop+"ms. Received: "+nBytes+ " Bytes.";


}
timeout=System.Environment.TickCount - dwStart;
if(timeout>1000)
{
return "Time Out" ;

}
}

// close the socket
socket.Close();
return "";
}
/// <summary>
/// This method get the Packet and calculates the total size
/// of the Pack by converting it to byte array
/// </summary>
public static Int32 Serialize(IcmpPacket packet, Byte[] Buffer,
Int32 PacketSize, Int32 PingData )
{
Int32 cbReturn = 0;
// serialize the struct into the array
int Index=0;

Byte [] b_type = new Byte[1];
b_type[0] = (packet.Type);

Byte [] b_code = new Byte[1];
b_code[0] = (packet.SubCode);

Byte [] b_cksum = BitConverter.GetBytes(packet.CheckSum);
Byte [] b_id = BitConverter.GetBytes(packet.Identifier);
Byte [] b_seq = BitConverter.GetBytes(packet.SequenceNumber);


Array.Copy( b_type, 0, Buffer, Index, b_type.Length );
Index += b_type.Length;


Array.Copy( b_code, 0, Buffer, Index, b_code.Length );
Index += b_code.Length;


Array.Copy( b_cksum, 0, Buffer, Index, b_cksum.Length );
Index += b_cksum.Length;


Array.Copy( b_id, 0, Buffer, Index, b_id.Length );
Index += b_id.Length;

Array.Copy( b_seq, 0, Buffer, Index, b_seq.Length );
Index += b_seq.Length;

// copy the data
Array.Copy( packet.Data, 0, Buffer, Index, PingData );
Index += PingData;
if( Index != PacketSize/* sizeof(IcmpPacket) */)
{
cbReturn = -1;
return cbReturn;
}

cbReturn = Index;
return cbReturn;
}
/// <summary>
/// This Method has the algorithm to make a checksum
/// </summary>
public static UInt16 checksum( UInt16[] buffer, int size )
{
Int32 cksum = 0;
int counter;
counter = 0;

while ( size > 0 )
{
UInt16 val = buffer[counter];

cksum += Convert.ToInt32( buffer[counter] );
counter += 1;
size -= 1;
}

cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (UInt16)(~cksum);
}
} // class ping
/// <summary>
/// Class that holds the Packet information
/// </summary>
public class IcmpPacket
{
public Byte Type; // type of message
public Byte SubCode; // type of sub code
public UInt16 CheckSum; // ones complement checksum of struct
public UInt16 Identifier; // identifier
public UInt16 SequenceNumber; // sequence number
public Byte [] Data;

} // class IcmpPacket
}

I'm not going into a treatise on socket programming in C# as that's beyond the scope of this article. But what this code does is illustrate the creation of sockets, socket types, protocol types, creating a Packet and checksum, using the SocketOptions class, and much more. Compile this into a class library, give it a strong name with the SN utility, set the proper assemblyinfo directives, install in the GAC, and you can include the library in any of your work that needs a simple PING utility class.

 

And just to show how easy it is to use this, I've gone ahead and created a PING webservice out of it. The following is from my Service1.asmx.cs file (key WEBMETHOD code is highlighted):

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using .CBS.Util;
namespace Ping
{
/// <summary>
/// Summary description for Service1.
/// </summary>

[ WebService(Description="Ping",Namespace="http://www.nullskull.com/webservices")]

public class Service1 : System.Web.Services.WebService
{
public Service1()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}

#region Component Designer generated code

//Required by the Web Services Designer
private IContainer components = null;

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}

#endregion
[WebMethod]
public string Ping( string ipOrHostName)
{

.CBS.Util.Ping p = new .CBS.Util.Ping();
return p.PingHost(ipOrHostName);

}
}
}

If you'd like to try out the actual live webservice , GO HERE.

 

Dowload the code that accompanies this article


 




 

 

Peter Bromberg is an independent consultant specializing in distributed .NET solutions in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com