Nov. 19, 2009, 9 a.m.
posted by vendetta
An Advanced Ping Program
SimplePing was designed to demonstrate the basics of using the ICMP Echo Request/Reply sequence to ping a remote host. If you have used commercial ping products, you know that several features of such programs were not implemented here. The unsophisticated SimplePing has the following limitations:
-
The destination address had to be entered as an IP address.
-
The size of the ping data was not configurable.
-
The elapsed time for the ping to process was not recorded.
This section shows how to create a more advanced version of the ping program that implements features to remove these limitations.
AdvPing.cs is a Windows-based ping program that implements some of the features of more sophisticated ping utilities.
To allow the customer to enter either IP addresses or hostnames in the query text box, the Dns.Resolve() method is used. You learned in Chapter 4, "DNS and C#," that the Dns.Resolve() method will resolve either a text hostname or a text IP address into an IPHostEntry array. The IPHostEntry array contains all of the IP addresses associated with the hostname or IP address. Because the ping program needs to worry about only one address, it takes the first one in the list:
IPHostEntry iphe = Dns.Resolve(hostbox.Text); IPEndPoint iep = new IPEndPoint(iphe.AddressList[0], 0);
You want to allow multiple ping packets, so AdvPing uses a separate thread to handle the actual ping function. When the customer clicks the Start button, a new thread is started. The new thread contains a loop that continually pings the remote host until the thread is aborted. To do this, the customer interface includes a button that the customer can click to stop the pinging.
Each separate pinging ICMP packet sent from the program is assigned a sequential Sequence number, to help identify the individual pings. Because the Sequence number changes for each ping packet, the ICMP checksum value needs to be recomputed for each packet.
Listing 11.4 is the AdvPing.cs program.
using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
class AdvPingForm
{
private static int pingstart, pingstop, elapsedtime;
private static TextBox hostbox, databox;
private static ListBox results;
private static Thread pinger;
private static Socket sock;
public AdvPing()
{
Text = "Advanced Ping Program";
Size = new Size(400, 380);
Label label1 = new Label();
label1.Parent = this;
label1.Text = "Enter host to ping:";
label1.AutoSize = true;
label1.Location = new Point(10, 30);
hostbox = new TextBox();
hostbox.Parent = this;
hostbox.Size = new Size(200, 2 * Font.Height);
hostbox.Location = new Point(10, 55);
results = new ListBox();
results.Parent = this;
results.Location = new Point(10, 85);
results.Size = new Size(360, 18 * Font.Height);
Label label2 = new Label();
label2.Parent = this;
label2.Text = "Packet data:";
label2.AutoSize = true;
label2.Location = new Point(10, 330);
databox = new TextBox();
databox.Parent = this;
databox.Text = "test packet";
databox.Size = new Size(200, 2 * Font.Height);
databox.Location = new Point(80, 325);
Button sendit = new Button();
sendit.Parent = this;
sendit.Text = "Start";
sendit.Location = new Point(220,52);
sendit.Size = new Size(5 * Font.Height, 2 * Font.Height);
sendit.Click += new EventHandler(ButtonSendOnClick);
Button stopit = new Button();
stopit.Parent = this;
stopit.Text = "Stop";
stopit.Location = new Point(295,52);
stopit.Size = new Size(5 * Font.Height, 2 * Font.Height);
stopit.Click += new EventHandler(ButtonStopOnClick);
Button closeit = new Button();
closeit.Parent = this;
closeit.Text = "Close";
closeit.Location = new Point(300, 320);
closeit.Size = new Size(5 * Font.Height, 2 * Font.Height);
closeit.Click += new EventHandler(ButtonCloseOnClick);
sock = new Socket(AddressFamily.InterNetwork,
SocketType.Raw, ProtocolType.Icmp);
sock.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, 3000);
}
void ButtonSendOnClick(object obj, EventArgs ea)
{
pinger = new Thread(new ThreadStart(sendPing));
pinger.IsBackground = true;
pinger.Start();
}
void ButtonStopOnClick(object obj, EventArgs ea)
{
pinger.Abort();
results.Items.Add("Ping stopped");
}
void ButtonCloseOnClick(object obj, EventArgs ea)
{
sock.Close();
Close();
}
void sendPing()
{
IPHostEntry iphe = Dns.Resolve(hostbox.Text);
IPEndPoint iep = new IPEndPoint(iphe.AddressList[0], 0);
EndPoint ep = (EndPoint)iep;
ICMP packet = new ICMP();
int recv, i = 1;
packet.Type = 0x08;
packet.Code = 0x00;
Buffer.BlockCopy(BitConverter.GetBytes(1), 0, packet.Message, 0, 2);
byte[] data = Encoding.ASCII.GetBytes(databox.Text);
Buffer.BlockCopy(data, 0, packet.Message, 4, data.Length);
packet.MessageSize = data.Length + 4;
int packetsize = packet.MessageSize + 4;
results.Items.Add("Pinging " + hostbox.Text);
while(true)
{
packet.Checksum = 0;
Buffer.BlockCopy(BitConverter.GetBytes(i), 0, packet.Message, 2, 2
UInt16 chcksum = packet.getChecksum();
packet.Checksum = chcksum;
pingstart = Environment.TickCount;
sock.SendTo(packet.getBytes(), packetsize, SocketFlags.None, iep);
try
{
data = new byte[1024];
recv = sock.ReceiveFrom(data, ref ep);
pingstop = Environment.TickCount;
elapsedtime = pingstop - pingstart;
results.Items.Add("reply from: " + ep.ToString() + ", seq: " + i +
", time = " + elapsedtime + "ms");
} catch (SocketException)
{
results.Items.Add("no reply from host");
}
i++;
Thread.Sleep(3000);
}
}
public static void Main()
{
Application.Run(new AdvPing());
}
}
In the AdvPing class constructor, the Windows form is created. It presents the customer with an interface to enter the hostname or IP address to ping, a ListBox object to display ping results, and two buttons to stop and start the ping thread. The Socket object used for the ICMP connection is also created, and a ReceiveTimeout value of 3 seconds is set:
sock = new Socket(AddressFamily.InterNetwork,
SocketType.Raw, ProtocolType.Icmp);
sock.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, 3000);
After the customer enters a hostname or IP address in the text box and clicks the Start button, a separate thread is created to handle the ping process. The sendPing() method is similar to the SimplePing program, except that sendPing() performs an endless loop of pinging the remote destination.
The Environment.TickCount property tracks the amount of time it takes to send and receive the ping packets. Each TickCount value represents the length of time (in milliseconds) the system has been operating. By subtracting the value taken before the first packet is sent and the value when the return packet is received, the program determines the time it takes for the ping process to run.
When the customer clicks the Stop button, the ping thread is aborted, but the socket is left open. This enables the customer to choose another hostname or IP address to ping and click the Start button. When the Close button is clicked, the Socket object is closed and the program is terminated.
Similar to the SimplePing program, the AdvPing program must be compiled with the ICMP.cs class file:
csc /t:winexe AdvPing.cs ICMP.cs
The /t:winexe parameter is also used on the compiler command line to create a windows executable program.
Testing AdvPing
You can test the AdvPing program by starting it from Windows Explorer, or by opening a command-prompt window and running the program there. When the program starts, you’ll see the customer interface (Figure). After entering a hostname or IP address, along with the data text you would like sent in the message, click the Start button to start the ping. Each ping attempt is recorded in the central list box.
