< C# Raw UDP Socket Program Example | Main | C# Raw Socket Ping Program Example (Cont...) >


 

Chapter 8 Part 20:

Client (and Server) Sockets Communication

 

 

What do we have in this chapter 8 Part 20?

  1. C# Raw Socket Ping Program Example

 

C# Raw Socket Ping Program Example

 

Create a new console application project and you might use RawSocketPingCS as the name.

 

C# Raw Socket Ping Program Example -creating a new console application in Visual Studio 2008

 

Next, rename the class to RawSocketPing by renaming the source file just to reflect the application to be developed.

 

C# Raw Socket Ping Program Example - renaming the source file automatically rename the class name

 

Add/edit the following using directives at the top of the file.

 

using System;

using System.Threading;

using System.Net;

using System.Net.Sockets;

using System.Diagnostics;

using System.Collections;

// Reference the protocol header classes

using ProtocolHeaderDefinition;

 

Add the public modifier to the class.

 

C# Raw Socket Ping Program Example - adding the class public access modifier

 

Add the following codes for the RawSocketPing class.

 

 

    public Socket           pingSocket;                    // Raw socket handle

    public AddressFamily    pingFamily;             // Indicates IPv4 or IPv6 ping

    public int              pingTtl;                                 // Time-to-live value to set on ping

    public ushort           pingId;                              // ID value to set in ping packet

    public ushort           pingSequence;               // Current sending sequence number

    public int              pingPayloadLength;          // Size of the payload in ping packet

    public int              pingCount;                          // Number of times to send ping request

    public int              pingOutstandingReceives;    // Number of outstanding receive operations

    public int              pingReceiveTimeout;             // Timeout value to wait for ping response

    private IPEndPoint      destEndPoint;                // Destination being pinged

    public IPEndPoint       responseEndPoint;        // Contains the source address of the ping response

    public EndPoint         castResponseEndPoint;   // Simple cast time used for the responseEndPoint

    private byte [ ]         pingPacket;                         // Byte array of ping packet built

    private byte [ ]         pingPayload;                       // Payload in the ping packet

    private byte [ ]         receiveBuffer;                      // Buffer used to receive ping response

    private IcmpHeader      icmpHeader;                // ICMP header built (for IPv4)

    private Icmpv6Header    icmpv6Header;           // ICMP header built (for IPv6)

    private Icmpv6EchoRequest   icmpv6EchoRequestHeader;    // ICMPv6 echo request header (for IPv6)

    private ArrayList           protocolHeaderList;      // List of protocol headers to assemble into a packet

    private AsyncCallback       receiveCallback;     // Async callback function called when a receive completes

    private DateTime            pingSentTime;            // Timestamp of when ping request was sent

    public  ManualResetEvent    pingReceiveEvent;   // Event to signal main thread that receive completed

    public  ManualResetEvent    pingDoneEvent;        // Event to indicate all outstanding receives are done

                                                                                        //    this ping class can be disposed

 

    /// <summary>

    /// Base constructor that initializes the member variables to default values. It also

    /// creates the events used and initializes the async callback function.

    /// </summary>

    public RawSocketPing()

    {

        pingSocket = null;

        pingFamily = AddressFamily.InterNetwork;

        pingTtl = 8;

        pingPayloadLength = 8;

        pingSequence = 0;

        pingReceiveTimeout = 4000;

        pingOutstandingReceives = 0;

        destEndPoint = new IPEndPoint( IPAddress.Loopback, 0 );

        protocolHeaderList = new ArrayList();

        pingReceiveEvent = new ManualResetEvent(false);

        pingDoneEvent = new ManualResetEvent(false);

        receiveCallback = new AsyncCallback( PingReceiveCallback );

        icmpHeader = null;

        icmpv6Header = null;

        icmpv6EchoRequestHeader = null;

    }

 

    /// <summary>

    /// Constructor that overrides several members of the ping packet such as TTL,

    /// payload length, ping ID, etc.

    /// </summary>

    /// <param name="af">Indicates whether we're doing IPv4 or IPv6 ping</param>

    /// <param name="ttlValue">Time-to-live value to set on ping packet</param>

    /// <param name="payloadSize">Number of bytes in ping payload</param>

    /// <param name="sendCount">Number of times to send a ping request</param>

    /// <param name="idValue">ID value to put into ping header</param>

    public RawSocketPing(

        AddressFamily af,

        int ttlValue,

        int payloadSize,

        int sendCount,

        ushort idValue

        ) : this()

    {

        pingFamily = af;

        pingTtl = ttlValue;

        pingPayloadLength = payloadSize;

        pingCount = sendCount;

        pingId = idValue;

    }

   

    /// <summary>

    /// This routine is called when the calling application is done with the ping class.

    /// This routine closes any open resource such as events and socket handles. This

    /// routine closes the socket handle and then blocks on the pingDoneEvent. This is

    /// necessary as if there is an outstanding async ReceiveFrom pending, it will complete

    /// with an error once the socket handle is closed. It is cleaner to wait for any

    /// async operations to complete before disposing of resources it may depend on.

    /// </summary>

    public void Close()

    {

        try

        {

            // Close the socket handle which will cause any async operations on it to complete with an error.

            Console.WriteLine("In closing method...");

            if ( pingSocket != null )

            {

                pingSocket.Close();

                pingSocket = null;

            }

           

            // Wait for the async handler to signal no more outstanding operations

            if ( pingDoneEvent.WaitOne( 10000, false ) == false )

            {

                Console.WriteLine("Wait timed out: Outstanding operations: {0}", pingOutstandingReceives);

            }

 

            // Close the opened events

            if ( pingReceiveEvent != null )

                pingReceiveEvent.Close();

            if ( pingDoneEvent != null )

                pingDoneEvent.Close();

        }

        catch ( Exception err )

        {

            Console.WriteLine("Error occurred during cleanup: {0}", err.Message);

            throw;

        }

    }

   

    /// <summary>

    /// Since ICMP raw sockets don't care about the port (as the ICMP protocol has no port

    /// field), we require the caller to just update the IPAddress of the destination

    /// although internally we keep it as an IPEndPoint since the SendTo method requires

    /// that (and the port is simply set to zero).

    /// </summary>

    public IPAddress PingAddress

    {

        get

        {

            return destEndPoint.Address;

        }

        set

        {

            destEndPoint = new IPEndPoint( value, 0 );

        }

    }

   

    /// <summary>

    /// This routine initializes the raw socket, sets the TTL, allocates the receive

    /// buffer, and sets up the endpoint used to receive any ICMP echo responses.

    /// </summary>

    public void InitializeSocket()

    {

        IPEndPoint      localEndPoint;

 

        if ( destEndPoint.AddressFamily == AddressFamily.InterNetwork )

        {

            // Create the raw socket

            Console.WriteLine("Creating raw socket using Socket()...");

            pingSocket = new Socket(destEndPoint.AddressFamily, SocketType.Raw, ProtocolType.Icmp);

            localEndPoint = new IPEndPoint( IPAddress.Any, 0 );

 

            // Socket must be bound locally before socket options can be applied

            Console.WriteLine("Binding the socket using Bind()...");

            pingSocket.Bind( localEndPoint );

 

            Console.WriteLine("Using SetSocketOption()...");

            pingSocket.SetSocketOption(

                SocketOptionLevel.IP,

                SocketOptionName.IpTimeToLive,

                pingTtl

                );

 

            // Allocate the buffer used to receive the response

            Console.WriteLine("Allocating buffer for received response...");

            receiveBuffer = new byte[ Ipv4Header.Ipv4HeaderLength + IcmpHeader.IcmpHeaderLength + pingPayloadLength ];

            responseEndPoint = new IPEndPoint( IPAddress.Any, 0 );

            castResponseEndPoint = (EndPoint) responseEndPoint;

        }

        else if ( destEndPoint.AddressFamily == AddressFamily.InterNetworkV6 )

        {

            // Create the raw socket

            Console.WriteLine("Creating raw socket using Socket()...");

            pingSocket = new Socket(

                destEndPoint.AddressFamily,

                SocketType.Raw,

                (ProtocolType) 58       // ICMPv6 protocol value

                );

 

            localEndPoint = new IPEndPoint( IPAddress.IPv6Any, 0 );

            Console.WriteLine("IPEndPoint() for local is OK...");

         

            // Socket must be bound locally before socket options can be applied

            Console.WriteLine("Binding to the interface using Bind()...");

            pingSocket.Bind( localEndPoint );

 

            Console.WriteLine("Using the SetSocketOPtion()...");

            pingSocket.SetSocketOption(

                SocketOptionLevel.IPv6,

                SocketOptionName.IpTimeToLive,

                pingTtl

                );

 

            // Allocate the buffer used to receive the response

            Console.WriteLine("Allocating buffer for the received response...");

            receiveBuffer = new byte [ Ipv6Header.Ipv6HeaderLength +

                Icmpv6Header.Icmpv6HeaderLength + Icmpv6EchoRequest.Icmpv6EchoRequestLength +

                pingPayloadLength ];

 

            responseEndPoint = new IPEndPoint( IPAddress.IPv6Any, 0 );

            Console.WriteLine("IPEndPoint() for remote is OK...");

            castResponseEndPoint = (EndPoint) responseEndPoint;

        }

    }

 

    /// <summary>

    /// This routine builds the appropriate ICMP echo packet depending on the

    /// protocol family requested.

    /// </summary>

    public void BuildPingPacket()

    {

        // Initialize the socket if it hasn't already been done

        Console.WriteLine("Building the ping packet...");

        Console.WriteLine("Initializing the socket if not done yet...");

 

        if ( pingSocket == null )

        {

            InitializeSocket();

        }

 

        // Clear any existing headers in the list

        Console.WriteLine("Clearing any existing headers in the list using Clear()...");

        protocolHeaderList.Clear();

 

        if ( destEndPoint.AddressFamily == AddressFamily.InterNetwork )

        {

            // Create the ICMP header and initialize the members

            Console.WriteLine("Creating the ICMP header and initialize the members...");

            icmpHeader = new IcmpHeader();

 

            icmpHeader.Id       = pingId;

            icmpHeader.Sequence = pingSequence;

            icmpHeader.Type     = IcmpHeader.EchoRequestType;

            icmpHeader.Code     = IcmpHeader.EchoRequestCode;

 

            // Build the data payload of the ICMP echo request

            Console.WriteLine("Building the data payload of the ICMP echo request...");

            pingPayload = new byte [ pingPayloadLength ];

 

            for(int i=0; i < pingPayload.Length ;i++)

            {

                pingPayload[i] = (byte) 'e';

            }

 

            // Add ICMP header to the list of headers

            Console.WriteLine("Adding ICMP header to the list of headers using Add()...");

            protocolHeaderList.Add( icmpHeader );

        }

        else if ( destEndPoint.AddressFamily == AddressFamily.InterNetworkV6)

        {

            Ipv6Header      ipv6Header;     // Required for pseudo header checksum

            IPEndPoint      localInterface;

            byte [ ]         localAddressBytes = new byte [ 28 ];

 

            Console.WriteLine("This part is for IPv6...");

            // An IPv6 header is required since the IPv6 protocol specifies that the

            //    pseudo header checksum needs to be calculated on ICMPv6 packets which

            //    requires the source and destination address that will appear in the

            //    IPv6 packet.

            ipv6Header = new Ipv6Header();

 

            // We definitely know the destination IPv6 address but the stack will

            //    choose the "appropriate" local v6 interface depending on the

            //    routing table which may be different than the address we bound

            //    the socket to. Because of this we will call the Winsock ioctl

            //    SIO_ROUTING_INTERFACE_QUERY which will return the local interface

            //    for a given destination address by querying the routing table.

            Console.WriteLine("Implementing the IOControl()...");

            pingSocket.IOControl(

                WinsockIoctl.SIO_ROUTING_INTERFACE_QUERY,

                SockaddrConvert.GetSockaddrBytes( destEndPoint ),

                localAddressBytes

                );

 

            localInterface = SockaddrConvert.GetEndPoint( localAddressBytes );

 

            // Fill out the fields of the IPv6 header used in the pseudo-header checksum calculation

            Console.WriteLine("Filling out the IPv6 header fields...");

            ipv6Header.SourceAddress      = localInterface.Address;

            ipv6Header.DestinationAddress = destEndPoint.Address;

            ipv6Header.NextHeader         = 58;     // IPPROTO_ICMP6

 

            // Initialize the ICMPv6 header

            Console.WriteLine("Initializing the ICMPv6 header...");

            icmpv6Header      = new Icmpv6Header( ipv6Header );

            icmpv6Header.Type = Icmpv6Header.Icmpv6EchoRequestType;

            icmpv6Header.Code = Icmpv6Header.Icmpv6EchoRequestCode;

           

            // Initialize the payload

            Console.WriteLine("Initializing the payload...");

            pingPayload = new byte [ pingPayloadLength ];

 

            for(int i=0; i < pingPayload.Length ;i++)

            {

                pingPayload[i] = (byte) 'e';

            }

           

            // Create the ICMPv6 echo request header

            Console.WriteLine("Creating the ICMPv6 echo request header...");

            icmpv6EchoRequestHeader = new Icmpv6EchoRequest();

            icmpv6EchoRequestHeader.Id =  pingId;

 

            // Add the headers to the protocol header list

            Console.WriteLine("Adding the headers to the protocol header list...");

            protocolHeaderList.Add( icmpv6Header );

            protocolHeaderList.Add( icmpv6EchoRequestHeader );

        }

    }

 

 

 

 

    /// <summary>

    /// This is the asynchronous callback that is fired when an async ReceiveFrom.

    /// An asynchronous ReceiveFrom is posted by calling BeginReceiveFrom. When this

    /// function is invoked, it calculates the elapsed time between when the ping

    /// packet was sent and when it was completed.

    /// </summary>

    /// <param name="ar">Asynchronous context for operation that completed</param>

    static void PingReceiveCallback( IAsyncResult ar )

    {

        RawSocketPing   rawSock = (RawSocketPing) ar.AsyncState;

        TimeSpan        elapsedTime;

        int             bytesReceived = 0;

        ushort          receivedId = 0;

 

        try

        {

            // Keep a count of how many async operations are outstanding -- one just completed

            //    so decrement the count.

            Interlocked.Decrement( ref rawSock.pingOutstandingReceives );

 

            // If we're done because ping is exiting and the socket has been closed,

            //    set the done event

            if ( rawSock.pingSocket == null )

            {

                if ( rawSock.pingOutstandingReceives == 0 )

                    rawSock.pingDoneEvent.Set();

                return;

            }

 

            // Complete the receive op by calling EndReceiveFrom. This will return the number

            //    of bytes received as well as the source address of who sent this packet.

            bytesReceived = rawSock.pingSocket.EndReceiveFrom( ar, ref rawSock.castResponseEndPoint );

 

            // Calculate the elapsed time from when the ping request was sent and a response was

            //    received.

            elapsedTime = DateTime.Now - rawSock.pingSentTime;

 

            rawSock.responseEndPoint = (IPEndPoint) rawSock.castResponseEndPoint;

           

            // Here we unwrap the data received back into the respective protocol headers such

            //    that we can find the ICMP ID in the ICMP or ICMPv6 packet to verify that

            //    the echo response we received was really a response to our request.

            if ( rawSock.pingSocket.AddressFamily == AddressFamily.InterNetwork )

            {

                Ipv4Header  v4Header;

                IcmpHeader  icmpv4Header;

                byte [ ]     pktIcmp;

                int         offset = 0;

 

                // Remember, raw IPv4 sockets will return the IPv4 header along with all

                //    subsequent protocol headers

                v4Header = Ipv4Header.Create( rawSock.receiveBuffer, ref offset );

                pktIcmp = new byte [ bytesReceived - offset ];

                Array.Copy( rawSock.receiveBuffer, offset, pktIcmp, 0, pktIcmp.Length );

                icmpv4Header = IcmpHeader.Create( pktIcmp, ref offset );

 

                /*Console.WriteLine("Icmp.Id = {0}; Icmp.Sequence = {1}",

                    icmpv4Header.Id,

                    icmpv4Header.Sequence

                    );*/

 

                receivedId = icmpv4Header.Id;

            }

            else if ( rawSock.pingSocket.AddressFamily == AddressFamily.InterNetworkV6 )

            {

                Icmpv6Header        icmp6Header;

                Icmpv6EchoRequest   echoHeader;

                byte [ ]             pktEchoRequest;

                int                 offset = 0;

 

                // For IPv6 raw sockets, the IPv6 header is never returned along with the

                //    data received -- the received data always starts with the header

                //    following the IPv6 header.

                icmp6Header = Icmpv6Header.Create( rawSock.receiveBuffer, ref offset );

                pktEchoRequest = new byte [ bytesReceived - offset ];

                Array.Copy( rawSock.receiveBuffer, offset, pktEchoRequest, 0, pktEchoRequest.Length );

                echoHeader = Icmpv6EchoRequest.Create( pktEchoRequest, ref offset );

 

                /*Console.WriteLine("Icmpv6.Id = {0}; Icmp.Sequence = {1}",

                    echoHeader.Id,

                    echoHeader.Sequence

                    );*/

 

                receivedId = echoHeader.Id;

            }

 

            if ( receivedId == rawSock.pingId )

            {

                string  elapsedString;

 

                // Print out the usual statistics for ping

                if ( elapsedTime.Milliseconds < 1 )

                    elapsedString = "<1";

                else

                    elapsedString = "=" + elapsedTime.Milliseconds.ToString();

 

                Console.WriteLine("Reply from {0}: byte={1} time{2}ms TTL={3} ",

                    rawSock.responseEndPoint.Address.ToString(),

                    bytesReceived,

                    elapsedString,

                    rawSock.pingTtl

                    );

            }

 

            // Post another async receive if the count indicates for us to do so.

            if ( rawSock.pingCount > 0 )

            {

                rawSock.pingSocket.BeginReceiveFrom(

                    rawSock.receiveBuffer,

                    0,

                    rawSock.receiveBuffer.Length,

                    SocketFlags.None,

                    ref rawSock.castResponseEndPoint,

                    rawSock.receiveCallback,

                    rawSock

                    );

               

                // Keep track of outstanding async operations

                Interlocked.Increment( ref rawSock.pingOutstandingReceives );

            }

            else

            {

                // If we're done then set the done event

                if ( rawSock.pingOutstandingReceives == 0 )

                    rawSock.pingDoneEvent.Set();

            }

            // If this is indeed the response to our echo request then signal the main thread

            //    that we received the response so it can send additional echo requests if

            //    necessary. This is done after another async ReceiveFrom is already posted.

            if ( receivedId == rawSock.pingId )

            {

                rawSock.pingReceiveEvent.Set();

            }

        }

        catch ( SocketException err )

        {

            Console.WriteLine("Socket error occurred in async callback: {0}", err.Message);

        }

    }

 

    /// <summary>

    /// This function performs the actual ping. It sends the ping packets created to

    /// the destination and posts the async receive operation to receive the response.

    /// Once a ping is sent, it waits for the receive handler to indicate a response

    /// was received. If not it times out and indicates this to the user.

    /// </summary>

    public void DoPing()

    {

        // If the packet hasn't already been built, then build it.

        Console.WriteLine("In DoPing() method, do the pinging...");

        Console.WriteLine();

        if ( protocolHeaderList.Count == 0 )

        {

            BuildPingPacket();

        }

       

        Console.WriteLine("Pinging {0} with {1} bytes of data ", destEndPoint.Address.ToString(), pingPayloadLength);

 

        try

        {

            // Post an async receive first to ensure we receive a response to the echo request

            //    in the event of a low latency network.

            pingSocket.BeginReceiveFrom(

                receiveBuffer,

                0,

                receiveBuffer.Length,

                SocketFlags.None,

                ref castResponseEndPoint,

                receiveCallback,

                this

                );

            // Keep track of how many outstanding async operations there are

            Interlocked.Increment( ref pingOutstandingReceives );

 

            // Send an echo request and wait for the response

            while ( pingCount > 0 )

            {

                Interlocked.Decrement(ref pingCount);

 

                // Increment the sequence count in the ICMP header

                if ( destEndPoint.AddressFamily == AddressFamily.InterNetwork )

                {

                    icmpHeader.Sequence = (ushort) (icmpHeader.Sequence + (ushort) 1);

 

                    // Build the byte array representing the ping packet. This needs to be done

                    //    before ever send because we change the sequence number (which will affect

                    //    the calculated checksum).

                    pingPacket = icmpHeader.BuildPacket( protocolHeaderList, pingPayload );

                }

                else if ( destEndPoint.AddressFamily == AddressFamily.InterNetworkV6 )

                {

                    icmpv6EchoRequestHeader.Sequence = (ushort) ( icmpv6EchoRequestHeader.Sequence + (ushort) 1 );

 

                    // Build the byte array representing the ping packet. This needs to be done

                    //    before ever send because we change the sequence number (which will affect

                    //    the calculated checksum).

                    pingPacket = icmpv6Header.BuildPacket( protocolHeaderList, pingPayload );

                }

 

                // Mark the time we sent the packet

                pingSentTime = DateTime.Now;

 

                // Send the echo request

                pingSocket.SendTo( pingPacket, destEndPoint );

 

                // Wait for the async handler to indicate a response was received

                if ( pingReceiveEvent.WaitOne( pingReceiveTimeout, false ) == false )

                {

                    // timeout occurred

                    Console.WriteLine("Request timed out.");

                }

                else

                {

                    // Reset the event

                    pingReceiveEvent.Reset();

                }

               

                // Sleep for a short time before sending the next request

                Thread.Sleep( 1000 );

            }

        }

        catch ( SocketException err )

        {

            Console.WriteLine("Socket error occurred: {0}", err.Message );

            throw;

        }

    }

 


< C# Raw UDP Socket Program Example | Main | C# Raw Socket Ping Program Example (Cont...) >