Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Tracert Component for .NET Framework 2.0

4.87/5 (25 votes)
25 Jun 2006CPOL7 min read 3   5.8K  
This article implements a tracert component based on the Ping class available in .NET Framework 2.0.

Screen shot of the tool

Introduction

Tracert, a command line utility in Windows, displays the IP addresses of the nodes that a network packet encounters while it travels to a destination host from a source. There are some articles, here on CP, that show how to implement tracert functionality:

  1. CTraceRoute v1.0 by P. J. Naughter.
  2. Trace route using raw sockets by Babar Qaisrani

The above articles use C++ code, and I needed something in C# for .NET framework 2.0.  Luckily, the .NET framework 2.0 has a Ping component which is used by the code presented in this article to implement a tracert component.

Using the Tracert Component?

The simplest way to use the component in your project is to add the component to the VS.NET toolbox and drag and drop to the appropriate designer surface. If you add the tracert project as a reference to another project, then the Tracert component is automatically available in the VS.NET toolbox as shown in the screenshot below. Also shown is how the component appears when it is dropped on the design surface.

VS.NET Toolbox

Once you have added the component to the design surface,  you need to set the properties of the component. The most important property you need to set is the HostNameOrAddress property, which can either be set to a host name or an IP address.

C#
tracert.HostNameOrAddress = "www.codeproject.com"; //Use the host name
tracert.HostNameOrAddress = "208.90.12.1"; //Use the IPAddress

Next, you need to assign event handlers to the events exposed by the component. This is because the component does its work asynchronously. There are two events you can use: RouteNodeFound and Done. You can handle the RouteNodeFound event to find out intermediate nodes as they are found.

C#
tracert.RouteNodeFound += 
   new EventHandler<RouteNodeFoundEventArgs>(tracert_RouteNodeFound);

In the code snippet below, the IP address of the nodes are added to a list view as they are found.

C#
private void tracert_RouteNodeFound(object sender, RouteNodeFoundEventArgs e)
{
    ListViewItem item = routeList.Items.Add(e.Node.Address.ToString());
    .
    .
}

Finally, you need to handle the Done event to find whether the trace process has finished.

C#
tracert.Done += new System.EventHandler(this.tracert_Done);

private void tracert_Done(object sender, EventArgs e)
{
   //Access all the nodes using tracert.Nodes proerty
   .
   .
   //IP Address of the i<SUP>th</SUP> node
   IPAddress address = tracert.Nodes[i].Address;   
}

Finally, the actual tracing itself can be started by calling the Trace method.

C#
tracert.Trace();

This is the simplest and the most common way to use the component. You can further control the behavior of the component by setting some additional properties as described in the next section.

Tracert Component Reference

This component has public properties as described below.

Property NameTypeDescription
MaxHops intMaximum number of network hops to take to reach the destination. The tracert process aborts after the maximum number of hops even if the destination is not reached. The default value is 30.
Nodes Array of TracertNodeThis returns all the nodes in the path.
HostNameOrAddressstringThe name of the host like www.codeproject.com/, or the IP address in string form like 209.12.1.99.
TimeoutintThe is the time, in milliseconds, to wait for a particular node to respond before moving to the next node in the path. The default value is 5000.
IsDoneboolIf true, then it indicates that the destination has been reached, or the maximum number of hops have been taken.

The methods of this control are as follows:

Method NameReturn ValueDescription
TracevoidThis method starts the actual route tracing. You must set the HostNameOrAddress property before calling this method. This method works asynchronously.

The events are as follows:

Event NameDescription
DoneThis event is fired when the route tracing is finished. This can happen either when the destination host is reached, or when the maximum hops have been taken.
RouteNodeFoundThis event is fired when an intermediate node has been found on a path. The RouteNodeFoundEventArgs argument supplied to the event handler contains additional information about the node.

RouteNodeFoundEventArgs Reference

An argument of type RouteNodeFoundEventArgs is passed to the event handler for the RouteNodeFound event. The properties of this class are the following:

Property NameTypeDescription
NodeTracertNodeIndicates the node found in the iteration.

TracertNode reference

The TracertNode class is used to encapsulate a node in the path; it has the following properties.

Property NameTypeDescription
AddressIPAddressIndicates the IP Address of the node. The address is equal to IPAddress.Any (0.0.0.0 ) if an error or timeout occurs.
RoundTripTimeintThe interval in milliseconds from the moment the packet was sent to the node and an acknowledgement received by the sender.
StatusIPStatusThe status of the ping request sent to the node. This can be either IPStatus.Success or IPStatus.Timeout.

How Tracert Works?

When you send a Ping request to a destination, you have an option to specify the TTL (time-to-live) value. As a network packet travels from one intermediate host to another, its TTL value is reduced by one. When the TTL value becomes zero, the sender gets an error acknowledgement - TTL exceeded. The sender can find out the IP address of the host from which this acknowledgement was received. So, if you set the TTL value to 1 and send a packet to the destination, the packet will be returned by the first node in the path. If you then set the TTL value to 2 and send the packet again, then it will be returned by the second host in the path, and so on. You can continue this process of incrementing the TTL values till the destination has arrived or the maximum number of hops has been reached. The figure below shows a sample network path and the behavior of a network packet:

Sample Newtrok Path

The arrows in the diagram show the direction of the packet. The bold line shows the path traversed by the packet. The dotted line indicates that the network packet may have traversed the path had the TTL not expired. Now that we have a brief understanding of the way Tracert works, let's look at how it can be implemented in code.

Implementing Tracert in Code

We will be using the .NET framework's Ping class to implement the tracert functionality. Let's start directly with a code snippet.

C#
_ping = new Ping(); 
_ping.PingCompleted += new PingCompletedEventHandler(OnPingCompleted); 
_options = new PingOptions(1, true); 
_ping.SendAsync(_destination, _timeout, Tracert.Buffer, _options, null);

The Ping class can send the ping request either synchronously using the Send method, or asynchronously using the SendAsync method. When the SendAsync method is used, the event PingCompleted is fired to notify the completion of the request. The SendAsync method has many different overloads. In the overload used in the above code, the parameters are as follows:

ParameterDescription
_destinationThis is the IP address of the final destination.
_timeoutThe time, in milliseconds, to wait for the network request to complete.
Tracert.BufferThe byte buffer to send as request. This is taken from a static variable. In our sample, the buffer is a 32 byte array filled with the ASCII code of character A.
_options

An object of type PingOptions which indicates the TTL values and also a boolean value that indicates whether the buffer should be fragemented or not. For the initial call, we choose a TTL value of 1. We also choose not to fragment the buffer (in my tests, I have not seen this value impacting the results in any way).

null (the last parameter)

The last parameter is a user defined object that is provided in the PingCompleted event handler. We will not be using this, so we pass a null reference.

Once the request completes, either successfully or unsucessfully, the event handler for the PingCompleted event is called. In our case, this is the OnPingCompleted method.

C#
void OnPingCompleted(object sender, PingCompletedEventArgs e)
{
  ProcessNode(e.Reply.Address, e.Reply.Status);

  if (!this.IsDone)
  {
    lock (this)
    {
        //If the object is disposed the _ping 
        //member variable is set to null
        if (_ping == null)
        {
            ProcessNode(_destination, IPStatus.Unknown);
        }
        else
        {
            _options.Ttl += 1;
            ping.SendAsync(_destination, _timeout, 
                           Tracert.Buffer, _options, null);
        }
    }
  }
}

The OnPingCompleted function is called with two arguments. The first argument is the sender object which will be the original Ping object on which we invoked the SendAsync method. The argument e is of type PingCompletedEventArgs, and provides more information about the event. The important member of the PingCompletedEventArgs is the Reply member. This member provides the IPAddress of the nodes where the packet expired, and a status value. The status value is set to IPStatus.TtlExpired when the destination is not reached by the packet; otherwise, it will be set to IPStatus.Success. We process the intermediate node in the ProcessNode method.

C#
protected void ProcessNode(IPAddress address, 
                           IPStatus status)
{
    long roundTripTime = 0;

    if (status == IPStatus.TtlExpired || 
        status == IPStatus.Success)
    {
        Ping pingIntermediate = new Ping(); 
        
        try
        {
            //Compute roundtrip time to the address by pinging it
            PingReply reply = pingIntermediate.Send(
                                                address,
                                                _timeout);
                                         
            roundTripTime = reply.RoundtripTime;
            status = reply.Status;
        }
        catch (PingException e)
        {
            //Do nothing
            System.Diagnostics.Trace.WriteLine(e);
        }
        finally
        {
            pingIntermediate.Dispose();
        }
    }

    TracertNode node = new TracertNode(address, roundTripTime, 
                                           status);

    lock (_nodes)
    {
        _nodes.Add(node);
    }

    if (RouteNodeFound != null)
        RouteNodeFound(this, 
                new RouteNodeFoundEventArgs(node, this.IsDone));
    
    this.IsDone = address.Equals(_destination);
    
    lock (_nodes)
    {
        if (!this.IsDone && _nodes.Count >= _maxHops - 1)
            ProcessNode(_destination, IPStatus.Success);
    }
}

In the ProcessNode method, we ping the node again. This is because we want to obtain the round trip time to the destination. The PingReply class does return the round trip time, but it does only when the packet has successfully reached the destination. Once we obtain the round trip time, we fire the RouteNodeFound event. Next, we add the destination node to the list of nodes. Finally, we check whether we  have reached the final destination. If it is true, we set the IsDone property to true. This is, in brief, how the component works.

History

  • June 25, 2006: Initial submission.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)