Getting active TCP/UDP connections on a box






4.81/5 (32 votes)
Jun 10, 2003
7 min read

333307

11563
This article shows an implementation of the main TCP/UDP functions of the IP Helper API that is used to get info about active connections including the process attached to a connection
Introduction
The main purpose of this library is to watch the UDP/TCP connections that are active ( like netstat does ) on your PC; it is mainly a wrapper to the IpHelperApi.dll, and it implements 4 generic functions that get statistics about TCP and UDP connections (GetUdpStats()/
GetTcpStats()
) and also get the tables for UDP and TCP connections (GetUdpConnexions()
/ GetTcpConnexions()
).
It also implements 2 undocumented functions of the IpHelperApi.dll that are similar to GetUdpConnexions
/GetTcpConnexions
except that they get the processID
attached to the connection; in the Win 32 API they are named : AllocateAndGetTcpExTableFromStack
and AllocateAndGetUdpExTableFromStack
.
Wrapping the IpHlpAPI.dll
The library is named IPHelper
and it is just a wrapper to IpHelperAPI.dll using P/Invoke mechanism of the .NET framework In the IPHlpAPI32.cs file 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 extern static int GetUdpStatistics (ref MIB_UDPSTATS pStats );
The SetLastError=true
flag allow us to get info about any errors raised in the P/Invoke mechanism, usually the error code is returned by the API function but this code is not very self-explanatory so I included a function to get the message description using the FormatMessage
function of the kernell32.dll :
public static string GetAPIErrorMessageDescription(int ApiErrNumber )
{
System.Text.StringBuilder sError = new System.Text.StringBuilder(512);
int lErrorMessageLength;
lErrorMessageLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
(IntPtr)0, ApiErrNumber, 0, sError, sError.Capacity, (IntPtr)0) ;
if(lErrorMessageLength > 0) {
string strgError = sError.ToString();
strgError=strgError.Substring(0,strgError.Length-2);
return strgError+" ("+ApiErrNumber.ToString()+")";
}
return "none";
}
This function just gets a message description from the system and if no description is found it returns "none".
In the MSDN docs all the iphlpapi functions fill their own structure using passed in arguments; for simple structures without array, pointer or nested structure, it is very easy to get the structure correctly filled, but for complex structures it could become much harder.
Let's see how to do that :
Getting the results from the API call
Simple structure Wrapping
First let see how to get the result for a simple function : GetTcpStats()
, this function is used to get several info about the TCP connections like, the number of active connections.
In the original library in C++ it is declared like this
typedef struct _MIB_TCPSTATS { //The structure holding the results
DWORD dwRtoAlgorithm;
//.. Other DWORD fields
DWORD dwNumConns;
}MIB_TCPSTATS, *PMIB_TCPSTATS
DWORD GetTcpStatistics(PMIB_TCPSTATS pStats);
this structure is very simple, all the DWORD
field can be replaced by integer in c# :
[StructLayout(LayoutKind.Sequential)]
public struct MIB_TCPSTATS
{
public int dwRtoAlgorithm;
//.. Other int fields
public int dwNumConns ;
}
[DllImport("iphlpapi.dll",SetLastError=true)]
public extern static int GetTcpStatistics (ref MIB_TCPSTATS pStats );
Note that the ref
keyword is required because in the C++ call, the function wants a pointer to the structure as argument.
So in the IPHelper
class you have a public method that fills the MIB_TCPSTATS
struct :
public void GetTcpStats()
{
TcpStats = new MIB_TCPSTATS();
IPHlpAPI32Wrapper.GetTcpStatistics(ref TcpStats);
}
Pretty straightforward.
Note that the GetUdpStats()
function works exactly in the same way
More complex structure Wrapping
The GetTcpTable()
function gets an array of all the active TCP connections including the local endpoint, the remote endpoint and the connection's state; those results are stored in 2 nested structures
typedef struct _MIB_TCPTABLE { // The top level structure
//that hold an array of the second structure
DWORD dwNumEntries;
MIB_TCPROW table[ANY_SIZE]; //an array of undefined size
} MIB_TCPTABLE, *PMIB_TCPTABLE;
typedef struct _MIB_TCPROW { //the structure that represent
//a single row in the tcp table
DWORD dwState;
DWORD dwLocalAddr;
DWORD dwLocalPort;
DWORD dwRemoteAddr;
DWORD dwRemotePort;
} MIB_TCPROW, *PMIB_TCPROW
DWORD GetTcpTable(PMIB_TCPTABLE pTcpTable,PDWORD pdwSize,BOOL bOrder);
The GetTcpTable
function takes three params, a pointer to the structure that will hold the results, the size of the buffer pointed to by the pTcpTable
parameter (note that if the buffer is too small, on output the function sets this parameter equal to the required buffer size), and a bool
flag to tell if we want the results to be sorted or not
The main problem here is that we can't use direct marshalling for those 2 structures because there is a undefined size array, but there are several solutions to solve this issue, I choose to use a simple generic method, replace the first param by a byte array.
[DllImport("iphlpapi.dll",SetLastError=true)]
public static extern int GetTcpTable(byte[] pTcpTable,
out int pdwSize, bool bOrder);
Then we have to use this function in the IPHelper
class in the GetTcpConnexion
member method.
The first thing to do is to get the size of the buffer (pTcpTable
) that we need to get all the results, this is done by calling the function with a dumb buffer, the function will then return the size of the necessary buffer in the pdwSize
param; note that is possible because the function has been declared with the out
keyword before the pdwSize
param :
public void GetTcpConnections()
{
int pdwSize = 20000;
byte[] buffer = new byte[pdwSize]; // Start with 20.000 bytes
//left for information about tcp table
int res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
if (res != NO_ERROR)
{
buffer = new byte[pdwSize];
res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
if (res != 0)
return; // Error. You should handle it
}
//.....
}
Once we have the right buffer size we call the function once again, to get the buffer correctly filled, then we just have to parse the byte to get the right value
TcpConnexion = new IpHlpApidotnet.MIB_TCPTABLE();
int nOffset = 0;
// number of entry in the
TcpConnexion.dwNumEntries = Convert.ToInt32(buffer[nOffset]);
nOffset+=4;
TcpConnexion.table = new MIB_TCPROW[TcpConnexion.dwNumEntries];
for(int i=0; i<TcpConnexion.dwNumEntries;i++)
{
// state
int st = Convert.ToInt32(buffer[nOffset]);
// state in string
((MIB_TCPROW)(TcpConnexion.table[i])).StrgState=convert_state(st);
// state by ID
((MIB_TCPROW)(TcpConnexion.table[i])).iState = st;
nOffset+=4;
// local address
string LocalAdrr = buffer[nOffset].ToString()+"."+
buffer[nOffset+1].ToString()+"."+
buffer[nOffset+2].ToString()+"."+buffer[nOffset+3].ToString();
nOffset+=4;
//local port in decimal
int LocalPort = (((int)buffer[nOffset])<<8) +
(((int)buffer[nOffset+1])) +
(((int)buffer[nOffset+2])<<24) + (((int)buffer[nOffset+3])<<16);
nOffset+=4;
// store the remote endpoint
((MIB_TCPROW)(TcpConnexion.table[i])).Local =
new IPEndPoint(IPAddress.Parse(LocalAdrr),LocalPort);
// remote address
string RemoteAdrr = buffer[nOffset].ToString()+"."+
buffer[nOffset+1].ToString()+"."+
buffer[nOffset+2].ToString()+"."+buffer[nOffset+3].ToString();
nOffset+=4;
// if the remote address = 0 (0.0.0.0) the remote port is always 0
// else get the remote port in decimal
int RemotePort;
//
if(RemoteAdrr == "0.0.0.0")
{
RemotePort = 0;
}
else
{
RemotePort = (((int)buffer[nOffset])<<8) +
(((int)buffer[nOffset+1])) +
(((int)buffer[nOffset+2])<<24) + (((int)buffer[nOffset+3])<<16);
}
nOffset+=4;
((MIB_TCPROW)(TcpConnexion.table[i])).Remote =
new IPEndPoint(IPAddress.Parse(RemoteAdrr),RemotePort);
}
That's it we have a structure called TcpConnexion
that holds all the active connections.
Note that we had to convert local and remote port form a DWORD
(UInt32
) to an Int16
which is done with bitwise operator. The GetUdpConnexion()
works exactly in the same way.
IpHelperApi Undocumented functions
The GetTcpConnexion()
and GetUdpConnexion()
functions are useful, but they can't be used to control the active connections, that means that we can't close any connections or even get more info of which software is using this connection. To be able to get control over the connection means that we have to know which process is attached to it, but those two functions are only available on windows XP.
After several days of seeking a way to get the processId
attached to a connection I found that the ipHlpApi has some undocumented functions : AllocateAndGetTcpExTableFromStack
and AllocateAndGetUdpExTableFromStack
- those two functions are acting exactly the same way that GetTcpTable
and GetUdpTable
except that they get the processID
attached to the connection, don't ask why they are not documented on MSDN, I really don't know.
So here is how to use them, let's see how AllocateAndGetTcpExTableFromStack
(AllocateAndGetUdpExTableFromStack
works in the same way) is declared in C++
typedef struct _MIB_TCPTABLE_EX
{
DWORD dwNumEntries;
MIB_TCPROW_EX table[ANY_SIZE];
} MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;
typedef struct _MIB_TCPROW_EX{
DWORD dwState;
DWORD dwLocalAddr;
DWORD dwLocalPort;
DWORD dwRemoteAddr;
DWORD dwRemotePort;
DWORD dwProcessId;
} MIB_TCPROW_EX, *PMIB_TCPROW_EX;
AllocateAndGetTcpExTableFromStack(PMIB_TCPTABLE_EX*,
BOOL,HANDLE,DWORD,DWORD);
The first param is a pointer on the structure that holds the result, the 2nd param is a bool
flag to order the result, the 3rd is a pointer to the heap of the process and the two others are flags that I don't know the meaning of.
This function follows the same pattern of GetTcpTable
and the results are stored in a structure that have an undefined size array. I used a solution lightly different to implement this in C# than I used for GetTcpTable
: The safe C# version of pointer.
Don't be afraid it is not so scary :) for those who are not familiar with Pointer let's (briefly) what it is about.
A Pointer is a data structure that store a memory address, for example if you have an integer that holds the number "100", a pointer on this integer will store the address of the value "100" and not the value itself, hope its clear.
So in C#, in a safe context, we use the IntPtr
structure :
[DllImport("iphlpapi.dll",SetLastError=true)]
public extern static int AllocateAndGetTcpExTableFromStack(
ref IntPtr pTable, bool bOrder, IntPtr heap ,int zero,int flags );
[DllImport("kernel32" ,SetLastError= true)] // this function is
// used to get the pointer on the process
// heap required by AllocateAndGetTcpExTableFromStack
public static extern IntPtr GetProcessHeap();
To use it I will basically follow the same path that I used for GetTcpTable
: call the function first time to get the number of connections (row) and so to get the correct buffer size, and then call the function a second time to get the correct results.
public void GetExTcpConnections()
{
// the size of the MIB_EXTCPROW struct = 6*DWORD
int rowsize = 24;
int BufferSize = 100000;
// allocate a dumb memory space in order to retrieve nb of connexion
IntPtr lpTable = Marshal.AllocHGlobal(BufferSize);
//getting infos
int res = IPHlpAPI32Wrapper.AllocateAndGetTcpExTableFromStack(
ref lpTable, true, IPHlpAPI32Wrapper.GetProcessHeap(),0,2);
if(res!=NO_ERROR)
{
Debug.WriteLine(
"Erreur : "+IPHlpAPI32Wrapper.GetAPIErrorMessageDescription(res)
+" "+res);
return; // Error. You should handle it
}
int CurrentIndex = 0;
//get the number of entries in the table
int NumEntries= (int)Marshal.ReadIntPtr(lpTable);
lpTable = IntPtr.Zero;
// free allocated space in memory
Marshal.FreeHGlobal(lpTable);
//...........
}
So we have to pass an IntPtr
to the function, the 1st thing to do is to define an IntPtr
that points to a memory space large enough to store all the result, so, as I said, we have to allocate an arbitrary memory space to call the function a 1st time, then we can get the number of connections with : int NumEntries = (int)Marshal.ReadIntPtr(lpTable);
here we are using a function from the Marshal
class to read the value pointed by a pointer; finally don't forget to free to memory previously allocated to avoid memory leaks.
Now let's get all the data :
///////////////////
// calculate the real buffer size nb of entrie *
// size of the struct for each entrie(24) + the dwNumEntries
BufferSize = (NumEntries*rowsize)+4;
// make the struct to hold the resullts
TcpExConnexions = new IpHlpApidotnet.MIB_EXTCPTABLE();
// Allocate memory
lpTable = Marshal.AllocHGlobal(BufferSize);
res = IPHlpAPI32Wrapper.AllocateAndGetTcpExTableFromStack(
ref lpTable, true,IPHlpAPI32Wrapper.GetProcessHeap() ,0,2);
if(res!=NO_ERROR)
{
Debug.WriteLine("Erreur : "+
IPHlpAPI32Wrapper.GetAPIErrorMessageDescription(res)+
" "+res);
return; // Error. You should handle it
}
// New pointer of iterating throught the data
IntPtr current = lpTable;
CurrentIndex = 0;
// get the (again) the number of entries
NumEntries = (int)Marshal.ReadIntPtr(current);
TcpExConnexions.dwNumEntries = NumEntries;
// Make the array of entries
TcpExConnexions.table = new MIB_EXTCPROW[NumEntries];
// iterate the pointer of 4 (the size of the DWORD dwNumEntries)
CurrentIndex+=4;
current = (IntPtr)((int)current+CurrentIndex);
// for each entries
for(int i=0; i< NumEntries;i++)
{
// The state of the connexion (in string)
TcpExConnexions.table[i].StrgState =
this.convert_state((int)Marshal.ReadIntPtr(current));
// The state of the connexion (in ID)
TcpExConnexions.table[i].iState = (int)Marshal.ReadIntPtr(current);
// iterate the pointer of 4
current = (IntPtr)((int)current+4);
// get the local address of the connexion
UInt32 localAddr = (UInt32)Marshal.ReadIntPtr(current);
// iterate the pointer of 4
current = (IntPtr)((int)current+4);
// get the local port of the connexion
UInt32 localPort = (UInt32)Marshal.ReadIntPtr(current);
// iterate the pointer of 4
current = (IntPtr)((int)current+4);
// Store the local endpoint in the struct and
// convert the port in decimal (ie convert_Port())
TcpExConnexions.table[i].Local = new IPEndPoint(localAddr,
(int)convert_Port(localPort));
// get the remote address of the connexion
UInt32 RemoteAddr = (UInt32)Marshal.ReadIntPtr(current);
// iterate the pointer of 4
current = (IntPtr)((int)current+4);
UInt32 RemotePort=0;
// if the remote address = 0 (0.0.0.0) the remote port is always 0
// else get the remote port
if(RemoteAddr!=0)
{
RemotePort = (UInt32)Marshal.ReadIntPtr(current);
RemotePort=convert_Port(RemotePort);
}
current = (IntPtr)((int)current+4);
// store the remote endpoint in the struct and
// convert the port in decimal (ie convert_Port())
TcpExConnexions.table[i].Remote = new IPEndPoint(
RemoteAddr,(int)RemotePort);
// store the process ID
TcpExConnexions.table[i].dwProcessId =
(int)Marshal.ReadIntPtr(current);
// Store and get the process name in the struct
TcpExConnexions.table[i].ProcessName =
this.get_process_name(TcpExConnexions.table[i].dwProcessId);
current = (IntPtr)((int)current+4);
}
// free the buffer
Marshal.FreeHGlobal(lpTable);
// re init the pointer
current = IntPtr.Zero;
So we call again the function with the right buffer size, and then we will "navigate" in the memory with the beginning of the allocated memory as the starting address.
The 1st 4 bytes are the number of entries, then we enter in a loop for each row in the connection table that begins at the starting address + 4 , in the 1st loop we will have the same mechanism : get each value ordered like in the MIB_TCPROW_EX
, and for each value iterate the pointer by 4, do this as many time as the number of rows.
That's it, we have all the connection AND the process ID attached to it, I added a few helper functions that, for example, get the process name by giving the process ID, but they are pretty simple and self-explanatory.
Remember those 2 functions are only available under WinXP.
How to use the library
I wrote a little app to test the lib, it just shows the results of all the functions in the library, in a listview.
Here are the methods of the lib :
GetTcpStats()
fill aMIB_TCPSTATS
structureGetUdpStats()
fill aMIB_UDPSTATS
structureGetTcpConnexions()
fill aMIB_TCPTABLE
structureGetUdpConnexions()
fill aMIB_UDPTABLE
structureGetExTcpConnexions()
fill aMIB_EXTCPTABLE
structureGetExUdpConnexions()
fill aMIB_EXUDPTABLE
structure
That's it, hope it will be useful.