Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / Windows Forms
Article

Getting the active TCP/UDP connections using the GetExtendedTcpTable function

Rate me:
Please Sign up or sign in to vote.
4.67/5 (11 votes)
12 Jun 2006CPOL3 min read 144.9K   10.2K   50   19
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)


Written By
Web Developer
Russian Federation Russian Federation
I am a CIO in Nizhny Novgorod, Russia.

Comments and Discussions

 
Questionwhat should be changed to be run on Vista 64 bit? Pin
keshtkar224-Jan-13 0:04
keshtkar224-Jan-13 0:04 
QuestionSlow ! Pin
Gisle Vanem15-Jan-12 16:40
Gisle Vanem15-Jan-12 16:40 
AnswerRe: Slow ! Pin
STRICQ24-Sep-12 5:35
STRICQ24-Sep-12 5:35 
General[My vote of 2] Doesn't even build. Pin
CSDev2-Feb-11 17:03
CSDev2-Feb-11 17:03 
GeneralRe: [My vote of 2] Doesn't even build. Pin
Yash pal27-Jul-15 6:38
Yash pal27-Jul-15 6:38 
GeneralWindows XP, source code compiler error "cannot modify the result of an unboxing conversion" Pin
icl777-Apr-10 9:55
icl777-Apr-10 9:55 
GeneralRe: Windows XP, source code compiler error "cannot modify the result of an unboxing conversion" Pin
Skim'a13-Jul-10 10:26
Skim'a13-Jul-10 10:26 
GeneralAVG gives virus alert for a Trojan inside the demo executable Pin
Member 413277510-Nov-08 1:38
Member 413277510-Nov-08 1:38 
GeneralRe: AVG gives virus alert for a Trojan inside the demo executable Pin
Tinkering Turtle8-May-09 11:32
Tinkering Turtle8-May-09 11:32 
GeneralYour problem of cpu Pin
BBOU@29-Aug-08 3:25
BBOU@29-Aug-08 3:25 
GeneralUsing an event approach instead polling approach Pin
lisario28-May-08 23:02
lisario28-May-08 23:02 
GeneralThanks! [modified] Pin
yinan26320-Feb-08 21:40
yinan26320-Feb-08 21:40 
GeneralWindows 2003 Pin
dross1145028-Aug-07 4:31
dross1145028-Aug-07 4:31 
QuestionHow about remote system? Pin
kvrnkiran4-Jul-07 20:29
kvrnkiran4-Jul-07 20:29 
AnswerRe: How about remote system? Pin
STRICQ24-Sep-12 5:38
STRICQ24-Sep-12 5:38 
GeneralCannot run in Windows Vista Pin
Ares200614-Feb-07 3:49
Ares200614-Feb-07 3:49 
GeneralRe: Cannot run in Windows Vista Pin
Warlib14-Feb-07 4:11
Warlib14-Feb-07 4:11 
QuestionRe: Cannot run in Windows Vista Pin
OneLineSky8-May-12 15:51
OneLineSky8-May-12 15:51 
GeneralConsumption of memory Pin
gasve30-Jan-07 10:51
gasve30-Jan-07 10:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.