Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Getting the active TCP/UDP connections using the GetExtendedTcpTable function

4.67/5 (11 votes)
12 Jun 2006CPOL3 min read 1   10.2K  
This article shows how to use some TCP/UDP functions of the IP Helper API to get the active connections and the processes attached to a connection.

Sample Image - iphlpapi2.gif

Introduction

The main purpose of this library is to be able to watch the active UDP/TCP connections on your PC. It's an extended version of the library published by Axel Charpentier, "Getting active TCP/UDP connections in the box".

Axel Charpentier used the undocumented (deprecated) functions AllocateAndGetTcpExTableFromStack and AllocateAndGetUdpExTableFromStack to get active TCP/UDP connections and get the processes associated with the connections. After going through MSDN, I found two well documented functions: GetExtendedTcpTable and GetExtendedUdpTable.

I've made a wrapper to the IpHelperApi.dll, and implemented these two functions that get the tables for the UDP and TCP connections and also gets the processID attached to the connection.

Wrapping the IpHlpAPI.dll

The library is named IPHelper, and it is just a wrapper to IpHelperAPI.dll in the IPHlpAPI32.cs file, using the P/Invoke mechanism of the .NET framework. There are all the declarations of the functions and structs from the IPHlpApi.dll; it uses standard attributes from the System.Runtime.InteropServices namespace.

C#
[DllImport("iphlpapi.dll", SetLastError=true)]

public static extern int GetExtendedTcpTable(byte[] pTcpTable, 
       out int dwOutBufLen, bool sort, int ipVersion, 
       TCP_TABLE_CLASS tblClass, int reserved);
[DllImport("iphlpapi.dll", SetLastError=true)]

public static extern int GetExtendedUdpTable(byte[] pUdpTable, 
       out int dwOutBufLen, bool sort, int ipVersion, 
       UDP_TABLE_CLASS tblClass, int reserved);

IpHelperApi functions GetExtendedTcpTable & GetExtendedUdpTable

To get info from GetExtendedTcpTable, I use a simple piece of code:

C#
public void GetTcpConnections()
{
  int AF_INET = 2; // IP_v4
  int res = IPHlpAPI32Wrapper.GetExtendedTcpTable(buffer, out buffSize, 
            true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
  if (res != Utils.NO_ERROR) //If there is no enouth memory to execute function
  {
    buffer = new byte;
    res = IPHlpAPI32Wrapper.GetExtendedTcpTable(buffer, out buffSize, 
          true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
    if (res != Utils.NO_ERROR)
    {
      return;
    }
  }
  int nOffset = 0;
  int NumEntries = Convert.ToInt32(buffer[nOffset]);
  nOffset += 4;
  for (int i = 0; i < NumEntries; i++)
  {
    TCPUDPConnection row = new TCPUDPConnection();
    // state
    int st = Convert.ToInt32(buffer[nOffset]);
    // state by ID
    row.iState = st;
    nOffset += 4;
    row.Protocol = Protocol.TCP;
    row.Local = Utils.BufferToIPEndPoint(buffer, ref nOffset, false);
    row.Remote = Utils.BufferToIPEndPoint(buffer, ref nOffset, true);
    row.PID = Utils.BufferToInt(buffer, ref nOffset);
    this.Add(row);
  }
}

and a similar code for GetExtendedUdpTable:

C#
public void GetUdpConnections()
{
  int AF_INET = 2; // IP_v4
  int res = IPHlpAPI32Wrapper.GetExtendedUdpTable(buffer, out buffSize, 
            true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
  if (res != Utils.NO_ERROR)
  {
    buffer = new byte;
    res = IPHlpAPI32Wrapper.GetExtendedUdpTable(buffer, 
          out buffSize, true, AF_INET, 
          UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
    if (res != Utils.NO_ERROR)
    {
      return;
    }
  }
  int nOffset = 0;
  int NumEntries = Convert.ToInt32(buffer[nOffset]);
  nOffset += 4;
  for (int i = 0; i < NumEntries; i++)
  {
    TCPUDPConnection row = new TCPUDPConnection();
    row.Protocol = Protocol.UDP;
    row.Local = Utils.BufferToIPEndPoint(buffer, ref nOffset, false);
    row.PID = Utils.BufferToInt(buffer, ref nOffset);
    this.Add(row);
  }
}

In the library, I used two additional classes: TCPUDPConnection and TCPUDPConnections : IEnumerable<TCPUDPConnection>. The first one is used to store a row of the TCP/UDP table, and the second one is used to store a list of TCP/UDP connections. Remember, the GetExtendedTcpTable and GetExtendedUdpTable functions are only available under WinXP SP2 or Win2003 SP1.

TCPUDPConnections

Unfortunately, I couldn't find any way to immediately get information about new processes and ports, so I used the System.Timers.Timer class to periodically (every second) refresh the list of TCP/UDP connections.

C#
/// <summary>
/// Store information concerning TCP/UDP connections
/// </summary>
public class TCPUDPConnections : IEnumerable<TCPUDPConnection> 
{
    private List<TCPUDPConnection> _list;
    System.Timers.Timer _timer = null;
    private int _DeadConnsMultiplier = 10; //Collect dead connections each 5 sec.
    private int _TimerCounter = -1;
    public TCPUDPConnections()
    {
        _LocalHostName = Utils.GetLocalHostName();
        _list = new List<TCPUDPConnection>();
        _timer = new System.Timers.Timer();
        _timer.Interval = 1000; // Refresh list every 1 sec.
        _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
        _timer.Start();
    }
...
}

The class constructor is very simple. First of all, I create an inner _list variable needed to store the Generic collection, TCPUDPConnection. In the constructor, I also initiate a timer which periodically calls the Elapsed event to refresh a list of TCP/UDP collections.

C#
void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    this.Refresh();
}

The Refresh method is quite simple:

C#
public void Refresh()
{
    lock (this)
    {
        this._LastRefreshDateTime = DateTime.Now;
        this.GetTcpConnections();
        this.GetUdpConnections();
        _TimerCounter++;
        if (_DeadConnsMultiplier == _TimerCounter)
        {
            this.CheckForClosedConnections();
            _TimerCounter = -1;
        }
    }
}

I store the current DateTime in a variable this._LastRefreshDateTime before running GetTcpConnections() and GetUdpConnections() to determinate dead connections in future. Then, GetTcpConnections() and GetUdpConnections() are called to refresh the list of active TCP/UDP connections. Also, in some _timer cycles, the method this.CheckForClosedConnections() is called.

C#
/// <summary>
/// Method detect and remove from list all dead connections.
/// </summary>
public void CheckForClosedConnections()
{
    int interval = (int)_timer.Interval * this._DeadConnsMultiplier;
    //Remove item from the end of the list
    for (int index = _list.Count - 1; index >= 0; index--)
    {
        TCPUDPConnection conn = this[index];
        TimeSpan diff = (this._LastRefreshDateTime - conn.WasActiveAt);
        int interval1 = Math.Abs((int)diff.TotalMilliseconds);
        if (interval1 > interval)
        {
            this.Remove(index);
        }
    }
}

The aim of this method is to remove all dead connections. The method scans all connections in the list, and compares the time of the connection creation and the time of the last total refresh. All dead connections are removed. Unfortunately, I don't have any idea on how to optimize this procedure for now.

The most interesting methods of TCPUDPConnections are IndexOf and Add:

C#
public TCPUDPConnection IndexOf(TCPUDPConnection item, out int Pos)
{
    int index = -1;
    foreach (TCPUDPConnection conn in _list)
    {
        index++;
        int i = _connComp.CompareConnections(item, conn);
        if (i == 0)
        {
            Pos = index;
            return conn;
        }
        if (i > 0) // If current an item more then conn,
                      // try to compare with next one until finding equal or less.
        {
            continue; //Skip
        }
        if (i < 0) // If there is an item in list with row less
                      // then current, insert current before this one.
        {
            Pos = index;
            return null;
        }
    }
    Pos = -1;
    return null;
}

The methods GetTcpConnections and GetUdpConnections return a sorted list of TCPUDPConnections. They are sorted by local address, local port, remote address, and then by remote port. So, to minimize the number of cycles to find existing items, or to detect if items didn't exist in the list, I use information about the sorting order. To do that, I used this method:

C#
public virtual int CompareConnections(TCPUDPConnection first, TCPUDPConnection second)
{
    int i;
    i = Utils.CompareIPEndPoints(first.Local, second.Local);
    if (i != 0)
        return i;
    if (first.Protocol == Protocol.TCP &&
        second.Protocol == Protocol.TCP)
    {
        i = Utils.CompareIPEndPoints(first.Remote, second.Remote);
        if (i != 0)
            return i;
    }
    i = first.PID - second.PID;
    if (i != 0)
        return i;
    if (first.Protocol == second.Protocol)
        return 0;
    if (first.Protocol == Protocol.TCP)
        return -1;
    else
        return 1;
}

Add is used with IndexOf to decide what to do: insert to specified position to satisfy sort order, add item at the end of the list, or change properties of the connection:

C#
/// <summary>
/// Add new <seealso cref="TCPUDPConnection"/> connection.
/// </summary>
/// <param name="item"></param>
public void Add(TCPUDPConnection item)
{
    int Pos = 0;
    TCPUDPConnection conn = IndexOf(item, out Pos);
    if (conn == null)
    {
        item.WasActiveAt = DateTime.Now;
        if (Pos > -1)
        {
            this.Insert(Pos, item);
        }
        else
        {
            _list.Add(item);
            ItemAddedEventHandler(item);
        }
    }
    else
    {
        _list[Pos].WasActiveAt = DateTime.Now;
        if (conn.iState != item.iState ||
        conn.PID != item.PID)
        {    
            conn.iSta    te = item.iState;
            conn.PID = item.PID;
            ItemChangedEventHandler(conn, Pos);
        }
    }
}

So, using GetExtendedTcpTable and GetExtendedUdpTable, we have documented a way to get active TCP/UDP connections and their corresponding ProcessID.

How to use the library

I wrote a little demo program to test the lib. It just shows the results of the functions GetExtendedTcpTable and GetExtendedUdpTable (upper listview) and AllocateAndGetTcpExTableFromStack and AllocateAndGetUdpExTableFromStack (lower listview).

Problems

There are two main problems: memory and CPU consumption. For the first one, I decided to insert a small piece of code:

C#
public Form1()
{
    try
    {
        Process loProcess = Process.GetCurrentProcess();
        loProcess.MaxWorkingSet = loProcess.MaxWorkingSet;
        loProcess.Dispose();
    }
    catch { }
    System.Timers.Timer ShrinkTimer = new System.Timers.Timer();
    ShrinkTimer.Interval = 60000;
    ShrinkTimer.Elapsed += new System.Timers.ElapsedEventHandler(ShrinkTimer_Elapsed);
    ShrinkTimer.Start();
}
 
void ShrinkTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    try
    {
        Process loProcess = Process.GetCurrentProcess();
        loProcess.MaxWorkingSet = loProcess.MaxWorkingSet;
        loProcess.Dispose();
    }
    catch { }
}

The method is not correct enough, but I didn't know another one. After hard code optimization, I decreased my max, CPU consumption from 22% to 2%. Probably, somebody knows how to minimize more?

For dessert

And for dessert, some useful functions to get TCP/UDP connections, unfortunately without the ProcessID:

C#
public TcpConnectionInformation[] GetTcpConnectionsNative()
{
  IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
  return properties.GetActiveTcpConnections();
}
public IPEndPoint[] GetUdpListeners()
{
  return IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
}
public IPEndPoint[] GetTcpListeners()
{
  return IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
}

That's all, hope it will be useful.

References

  1. Axel Charpentier, "Getting active TCP/UDP connections in the box".

License

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