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.
[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:
public void GetTcpConnections()
{
int AF_INET = 2;
int res = IPHlpAPI32Wrapper.GetExtendedTcpTable(buffer, out buffSize,
true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
if (res != Utils.NO_ERROR)
{
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();
int st = Convert.ToInt32(buffer[nOffset]);
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
:
public void GetUdpConnections()
{
int AF_INET = 2;
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.
public class TCPUDPConnections : IEnumerable<TCPUDPConnection>
{
private List<TCPUDPConnection> _list;
System.Timers.Timer _timer = null;
private int _DeadConnsMultiplier = 10;
private int _TimerCounter = -1;
public TCPUDPConnections()
{
_LocalHostName = Utils.GetLocalHostName();
_list = new List<TCPUDPConnection>();
_timer = new System.Timers.Timer();
_timer.Interval = 1000;
_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.
void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.Refresh();
}
The Refresh
method is quite simple:
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.
public void CheckForClosedConnections()
{
int interval = (int)_timer.Interval * this._DeadConnsMultiplier;
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 TCPUDPConnection
s are IndexOf
and Add
:
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)
{
continue;
}
if (i < 0)
{
Pos = index;
return null;
}
}
Pos = -1;
return null;
}
The methods GetTcpConnections
and GetUdpConnection
s return a sorted list of TCPUDPConnection
s. 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:
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:
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:
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:
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
- Axel Charpentier, "Getting active TCP/UDP connections in the box".