Search Forum
(58019 Postings)
Search Site/Articles

Archived Articles
712 Articles

C# Books
C# Consultants
What Is C#?
Download Compiler
Code Archive
Archived Articles
Advertise
Contribute
C# Jobs
Beginners Tutorial
C# Contractors
C# Consulting
Links
C# Manual
Contact Us
Legal

GoDiagram for .NET from Northwoods Software www.nwoods.com


              
Printable Version

C# Ping
By Gerhard Schmeusser

using System;
using System.Net;
using System.Diagnostics;
using System.Net.Sockets;
using System.Collections;

namespace cmpDWPing
{
 /// 
 //Implements an Ethernet Ping component
 //Ping is part of the ICMP (Internet Control Message Protocol) which serves
 //Hosts and Routers for the purpose of error messaging and exchanging status and commands.
 //It is part of the IP protocol and therefore cannot be considered as reliable.
 //The following ICMP messages are used here to implement a ping functionality:
 //Echo Request
 //Echo Answer
 
 
 //The interface is pretty straightforward:
 //the ping method performs the network ping and returns success or failure
 //in case of failure ask the ErrorMessage property for the reason
 //
 // There are some properties which you can use for setting and getting information. See the interface below
 //
 //
 //
 //
 //You can use this component as a COM object if
 //you create a COM callable wrapper (CCW) by the following command line programs:
 
    //the typelib is created by the project setting "register for COM interop":
    //regasm bin/debug/cmpDWPing.dll
    //if you also want to manually create a typelib use the following line
    //regasm /tlb:cmpDWPing.tlb bin/debug/cmpDWPing.dll
    
    //use the following lines to register the component in the global assembly cache
    //gacutil /u cmpDWPing
    //gacutil /i bin/debug/cmpDWPing.dll
    //
    //Beforehand justification: Because of the C-style design of the ICMP protocol data structures
    //which are meant to be cast this way and that and therefore not very
    //suitable to a strict language like C#, we have to
    //adopt some pretty awkward code here.
 /// 
 
 interface IDWPing
 {
     short  ping(string strHostName);  // 1 = success, 0 = failure
     int    Timeout {set;}             //default = 500msec
     int    Repeats {set;}             //default = 0
     int    AvgTime {get;}             //measured average response time
     int    AvgTTL  {get;}             //measured average number of routing nodes the ping traveled
     string ErrorMessage {get;}        //a verbose error message in case of failure 
 }
 
    struct ICMPHeader
    {
        public byte    type;
        public byte    code;
        public ushort  chksum;
        public ushort  id;
        public ushort  seq;
        public ulong   timestamp;
     
        public byte[] toByteArray()
        {
            //If you know a better way to serialize this into a byte array, let me know
            byte[] arResult = new byte[22];
            arResult[0] = this.type;
            arResult[1] = this.code;
            arResult[2] = (byte)chksum;
            arResult[3] = (byte)(chksum >> 8);
            arResult[4] = (byte)(chksum >> 16);
            arResult[5] = (byte)(chksum >> 24);
            arResult[6] = (byte)id;
            arResult[7] = (byte)(id >> 8);
            arResult[8] = (byte)(id >> 16);
            arResult[9] = (byte)(id >> 24);
            arResult[10] = (byte)seq;
            arResult[11] = (byte)(seq >> 8);
            arResult[12] = (byte)(seq >> 16);
            arResult[13] = (byte)(seq >> 24);
            arResult[14] = (byte)timestamp;
            arResult[15] = (byte)(timestamp >> 8);
            arResult[16] = (byte)(timestamp >> 16);
            arResult[17] = (byte)(timestamp >> 24);
            arResult[18] = (byte)(timestamp >> 32);
            arResult[19] = (byte)(timestamp >> 40);
            arResult[20] = (byte)(timestamp >> 48);
            arResult[21] = (byte)(timestamp >> 56);
       
            return arResult;
        }
    }

 public class CDWPing : IDWPing
 {
        private int      m_Timeout;   //in msec
        private int[]    m_arTime;    //response times statistic
        private bool[]   m_arResults; //results of pings
        private byte[]   m_arTTL;     //routing stations statistic
        private int      m_idxPing;
        private string   m_strErrorMessage;

     //----------------------------------------------------
 
  
  
  public int Timeout         { set{ m_Timeout = MinMax.max(value, 1); }}
  public int Repeats         { set
                               { int n = MinMax.max(value, 1);
                                 m_arTime    = new int[n];
                                 m_arTTL     = new byte[n];
                                 m_arResults = new bool[n];
                               }
                             }
  public int AvgTime         { get{ return this.calcAvgTime(); }}
  public int AvgTTL          { get{ return this.calcAvgTTL();  }}
  public string ErrorMessage { get{ return m_strErrorMessage; }}

        public CDWPing()
        {
            m_arTime    = new int[1];  //size of m_arTime is number of tries
            m_arTTL     = new Byte[1];
            m_arResults = new bool[1];
            m_strErrorMessage = "Don't know what happened";
            m_Timeout = 500;  //msec
        }


        public short ping(string strHostName)
        {
            m_strErrorMessage = "No error occured";

            this.clearStats();            
            
            short result = 0;
            
            //convert strHostName to an IPEndPoint
            try
            {
                IPEndPoint lep;
                
                //check if strHostname is already a dotted address
                //and create an IPEndpoint from it
                //note: port 7 is for echo but is irrelevant because of the socket type we are going to use
                const int echoPort = 7;
                
                if(true == this.isIPAddress(strHostName))
                {
                    IPAddress ipAddr = IPAddress.Parse(strHostName);
                    lep = new IPEndPoint(ipAddr, echoPort);
                }
                else
                {
                    IPHostEntry lipa = Dns.Resolve(strHostName);
                    lep = new IPEndPoint(lipa.AddressList[0], echoPort);
                }

                //number of tries
                for(m_idxPing = 0; m_idxPing < m_arTime.Length; m_idxPing++)
                {
                    if(true == tryPing(lep))
                    {
                        m_arResults[m_idxPing] = true;
                        result = 1;   //one successful ping = overall success
                    }
                    else
                    {
                        m_arResults[m_idxPing] = false;
                    }
                }
            }
            catch(SocketException ex)
            {
                result = 0;
                m_strErrorMessage = ex.Message;
            }
            catch(Exception ex )
            {
                result = 0;
                m_strErrorMessage = ex.Message;
            }

            return result;
        }
    
  private bool tryPing(IPEndPoint lep)
  {
      //do the ping
      bool bResult = false;
      
            //create an ICMP socket
            Socket sock  = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

            //set up ICMPHeader structure
            ICMPHeader header = new ICMPHeader();
            header.type         = 8; //ICMP_ECHO type
            header.code         = 0;
            header.id           = Convert.ToUInt16(Process.GetCurrentProcess().Id);  //lossy conversion
            header.seq          = 0;
            header.chksum       = 0;
            DateTime startTime  = DateTime.Now;
            header.timestamp    = Convert.ToUInt64(startTime.Ticks);
                
                
                
            //fill header into byte array
            byte[] arHeader = header.toByteArray();
                
            //create the array which is to be sent
            byte[] arBytes = new byte[arHeader.Length + 32];
                
            byte fill = Convert.ToByte('E');  //arbitrary fill data to be sent
                
            for(int i = 0; i < arBytes.Length; i++)
            {
                arBytes[i] = fill;
            }
                
            //copy header to array which is to be sent
            arHeader.CopyTo(arBytes, 0);
                
            header.chksum = this.generateIPChecksum(arBytes);
            //now this is goofy because after we inserted the checksum into
            //the header we have to recreate the byte array
            arHeader = header.toByteArray();
            arHeader.CopyTo(arBytes, 0);
                         
            sock.SendTo(arBytes, lep);
                
            // check socket receive asnychronously and heed timeout
            if(true == this.isSocketReadible(sock))
            {
                        
                // Creates an IpEndPoint to capture the identity of the sending host.
                IPEndPoint sender1 = new IPEndPoint(IPAddress.Any, 0);
                EndPoint tempRemoteEP = (EndPoint)sender1;

                // Creates a byte buffer to receive the message.
                byte[] receiveBuffer = new byte[1024];

                // Receives datagram from a remote host.  This call blocks!
                int nReceived = sock.ReceiveFrom(receiveBuffer, ref tempRemoteEP);

                bResult = this.verifyReceivedMessage(receiveBuffer, nReceived, arHeader.Length);

                m_arTTL[m_idxPing] = receiveBuffer[8];
            }
            else
            {
                m_strErrorMessage = "Echoing socket is not readible in the given timeout";
            }
      
      //number of routing stations is part of the IP Header
      
            //calculate elapsed time         
            DateTime now         = DateTime.Now;
            TimeSpan elapsedTime = new TimeSpan(now.Ticks - startTime.Ticks);
            m_arTime[m_idxPing]  = elapsedTime.Milliseconds;

      
      return bResult;
  }

        private bool isIPAddress(string strAddress)
        {
            //return true if the address is an IP Address e.g. 192.168.8.111
            //false points to a hostname like "INV111"
            bool bResult = true;
      
            foreach (char ch in strAddress)
            {
                if((false == Char.IsDigit(ch)) && (ch != '.'))
                {
                    bResult = false;
                    break;
                }
            }
      
            return bResult;
        }
  
        private ushort generateIPChecksum(byte[] arBytes)
        {
            //generate an IP checksum based on a given data buffer
            ulong chksum = 0;
            
            int nSize = arBytes.Length;
            int i     = 0;
            
            while(nSize > 1)
            {
                chksum += (ulong)((((ushort)arBytes[i+1]) << 8) + (ushort)arBytes[i]); 
                nSize--;
                nSize--;
                i++;
                i++;
            }
            
            if(nSize > 0)
            {
                chksum += arBytes[i];
            }
            
            chksum =  (chksum >> 16) + (chksum & 0xffff);
            chksum += (chksum >> 16);

            ushort result = (ushort)(~chksum);
            return result;
        }

        private bool isSocketReadible(Socket s)
        {
            //poll socket for data until timeout
            bool bResult = false;
            int n = 0;
            
            while(n < m_Timeout)
            {
                if(true == s.Poll(1000, SelectMode.SelectRead))  //100 usec = 1 msec
                {
                    bResult = true;
                    break;
                }
                
                n++;
            }
            
            return bResult;
        }

        private bool verifyReceivedMessage(byte[] arBytes, int nReceived, int minLengthSent)
        {
            //the first 4 bytes are the length of the IP Header in DWORDS
            int nLengthIPHeader = arBytes[0] & 0x0f;
            nLengthIPHeader *= 4; //in bytes
                
            //enough data received
            if(nLengthIPHeader + minLengthSent > nReceived)
            {
                m_strErrorMessage = "Not enough data echoed";
                return false;
            }
                
            //check if it is an ICMP_ECHOREPLY packet
            if(arBytes[nLengthIPHeader] != 0)
            {
                m_strErrorMessage = "Wrong type of telegram echoed";
                return false;
            }
            
            int nId = arBytes[nLengthIPHeader + 6] + arBytes[nLengthIPHeader + 7] * 256;
                
            //check echoed process id is ours
            if(nId != Convert.ToUInt16(Process.GetCurrentProcess().Id))  //lossy conversion
            {
                m_strErrorMessage = "Received echoed data was not sent by this process";
                return false;
            }
                        
            return true;
        }

  private int calcAvgTime()
  {
      int result = 0;
      
      foreach (int i in m_arTime)
      {
          result += i;
      }
      
      result /= m_arTime.Length;
      return result;
  }


        private int calcAvgTTL()
        {
            int result = 0;
      
            foreach (int i in m_arTTL)
            {
                //only count successful pings
                if(m_arResults[i] == true)
                {
                    result += i;
                }
            }
      
            result /= m_arTTL.Length;
            return result;
        }

        private void clearStats()
        {
            //clear statistical data
            for(int i = 0; i < m_arTime.Length; i++)
            {
                m_arTime[i] = 0;
                m_arTTL[i]  = 0;
            }
        }
 }
}