Ping Part IV: Adventures in Socket programming using System.Net
In this part, we will add some networking code to the code we have thus far. When we get done, we should have a working Ping utility.
Take the program that we wrote in the Ping: Part III and add the following code.
using System;
using System.Text;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
namespace ping
{
public class ICMP_PACKET
{
public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)
{
...
}
public ICMP_PACKET(byte[] data, int offset, int count)
{
this.i_type = data[offset++];
this.i_code = data[offset++];
this.i_cksum = ByteToUint(data, offset);
offset += 2;
this.i_id = ByteToUint(data, offset);
offset += 2;
this.i_seq = ByteToUint(data, offset);
offset += 2;
this.data = new byte[count - headerLength];
Array.Copy(data, offset, this.data, 0, this.data.Length);
}
public static UInt16 ByteToUint(byte [] networkBytes, int offset)
{
return BitConverter.ToUInt16(new byte[] { networkBytes[offset+1], networkBytes[offset] }, 0);
}
}
class Program
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Usage();
}
if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)
|| 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)
|| 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))
{
Usage();
}
string target = args[0];
IPAddress [] heTarget = Dns.GetHostAddresses(target);
IPAddress ipTarget = null;
foreach (IPAddress ip in heTarget)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipTarget = ip;
break;
}
}
IPAddress[] heSource = Dns.GetHostAddresses(Dns.GetHostName());
IPAddress ipSource = null;
foreach (IPAddress ip in heSource)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipSource = ip;
break;
}
}
IPEndPoint epLocal = new IPEndPoint(ipSource, 0);
Socket pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
pingSocket.Bind(epLocal);
byte [] data = Encoding.ASCII.GetBytes("1234567890abcdef");
Console.WriteLine("Ping {0}({1}) with {2} bytes of data...",target, ipTarget.ToString(), data.Length);
Console.WriteLine();
for (int i = 0; i < 3; i++)
{
ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(111, 222, data);
IPEndPoint epRemote = new IPEndPoint(ipTarget, 0);
pingSocket.SendTo(packet.Serialize(), epRemote);
byte[] receiveData = new byte[1024];
EndPoint epResponse = (EndPoint)new IPEndPoint(0, 0);
int read = pingSocket.ReceiveFrom(receiveData, ref epResponse);
ICMP_PACKET recvPacket = new ICMP_PACKET(receiveData, 20, read);
Console.WriteLine("Reply from:{0} bytes = {1}", ((IPEndPoint)epResponse).Address.ToString(), recvPacket.PayloadLength);
}
}
...
}
A couple of important points that I wanted to bring to your attention:
1) I have uncommented the code which checks the validity of command-line parameters in Program::Main.
2) We have added a new constructor ICMP_PACKET(byte [], int, int). This is to parse the response returned by the server.
3) The ByteToUInt(byte [], int) method is used to convert from Network Order to Host Order. This is needed because the bytes on the wire are in Network Order, which is BigEndian
4) In Main(), when we resolve the IPAddress of the source and destination, we need to make sure that we work with the correct IP address. Since this program is limited to IPv4, we need to make sure that we do not accidently work with IPv6 addresses. The IPv6 address may be returned when the host that you are trying to resolve is IPv6 enabled.
5) When we read the response from the socket, we get back more bytes that the size of the ICMP packet sent by the server. This is because we opened the Socket with SocketType.Raw. In this mode, we will also get back the IPv4 header that is encapsulating the ICMP packet. So, when we process the response packet, we need to make sure that we skip past the IP header. That is why we skip the first 20 bytes of the packet when we try to create an ICMP_PACKET with the returned data.
If you compile and run this program, you will be able to ping a host without any problems.
As utilities go, this is a very barebones utility. It does not handle errors from the network very well. For the interested, here are some things that you can do to this to make it more robust:
Be ready to handle exceptions from Socket.SendTo() and Socket.ReceiveFrom().
The utility does not handle timeouts. It would be a simple matter to add code and set a timeout on the Receive() call. That way, if the response doesnt come back within a certain time interval, you can print an error ( or an asterisk like the Ping utility that comes with the OS).
Give a command line option to change the #bytes sent in the payload.
Well, that is it then. Now we have a working Ping utility. Hopefully you have learned how to convert a protocol specification into a working program.