Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / MFC
Article

Getting active TCP/UDP connections on a box

Rate me:
Please Sign up or sign in to vote.
4.81/5 (37 votes)
17 Jun 20037 min read 322.5K   11.5K   99   58
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

Sample Image - netstat.jpg

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.

C#
[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 :

C#
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

C++
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# :

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 :

C#
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

C++
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.

C#
[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 :

C#
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

C#
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++

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 :

C#
[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.

C#
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 :

C#
///////////////////
// 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 a MIB_TCPSTATS structure
  • GetUdpStats() fill a MIB_UDPSTATS structure
  • GetTcpConnexions() fill a MIB_TCPTABLE structure
  • GetUdpConnexions() fill a MIB_UDPTABLE structure
  • GetExTcpConnexions() fill a MIB_EXTCPTABLE structure
  • GetExUdpConnexions() fill a MIB_EXUDPTABLE structure

That's it, hope it will be useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
I am a software engineer in Paris, I have worked on several projects mostly database driven web site, and industrial application

I am also MCAD.NET
(70-316) "Developing Windows application with the Visual C# .NET"
(70-315) Developing and Implementing Web Applications with Microsoft® Visual C#™ .NET and Microsoft® Visual Studio® .NET
(70-320) Developing XML Web Services and Server Components with Microsoft Visual C# .NET and the Microsoft .NET Framework

Comments and Discussions

 
GeneralError in last two lines of GetExTcpConnection() Pin
Gregory YuYe Lin6-May-05 11:46
Gregory YuYe Lin6-May-05 11:46 
Generalsankara Pin
Anonymous1-May-05 22:51
Anonymous1-May-05 22:51 
GeneralRe: sankara Pin
tsalomie1-Jun-05 1:37
tsalomie1-Jun-05 1:37 
GeneralRe: sankara Pin
thenetstriker22-Dec-06 2:06
thenetstriker22-Dec-06 2:06 
GeneralRe: sankara Pin
tsalomie22-Dec-06 6:52
tsalomie22-Dec-06 6:52 
GeneralForbidden Pin
Member 135965118-Apr-05 5:29
Member 135965118-Apr-05 5:29 
QuestionWhat about speed ? Pin
Smart K819-Dec-04 0:08
professionalSmart K819-Dec-04 0:08 
GeneralPossible memory leaks Pin
Mr Den4-Nov-04 17:08
Mr Den4-Nov-04 17:08 
GeneralRe: Possible memory leaks Pin
Mr Den4-Nov-04 17:11
Mr Den4-Nov-04 17:11 
GeneralRe: Possible memory leaks Pin
Mr Den4-Nov-04 21:01
Mr Den4-Nov-04 21:01 
GeneralRe: Possible memory leaks Pin
Mr Den4-Nov-04 21:02
Mr Den4-Nov-04 21:02 
GeneralRe: Possible memory leaks Pin
thenetstriker12-Oct-06 6:27
thenetstriker12-Oct-06 6:27 
GeneralRe: Possible memory leaks Pin
Mr Den13-Oct-06 2:09
Mr Den13-Oct-06 2:09 
GeneralQuestion out of the box Pin
khamis31-Aug-04 22:48
khamis31-Aug-04 22:48 
GeneralRe: Question out of the box Pin
Axel Charpentier4-Sep-04 0:50
Axel Charpentier4-Sep-04 0:50 
GeneralClosing TCP port Pin
ahmad jula22-Jul-04 19:31
sussahmad jula22-Jul-04 19:31 
GeneralRe: Closing TCP port Pin
Vitoto19-May-05 5:30
Vitoto19-May-05 5:30 
QuestionDoes IP HELP API can work on NT platform? Pin
hantaogsm8-Jun-04 17:08
hantaogsm8-Jun-04 17:08 
AnswerRe: Does IP HELP API can work on NT platform? Pin
Axel Charpentier12-Jun-04 3:47
Axel Charpentier12-Jun-04 3:47 
GeneralRe: Does IP HELP API can work on NT platform? Pin
hantaogsm12-Jun-04 4:14
hantaogsm12-Jun-04 4:14 
Questionhow to make it work on W2k Pin
ccache16-Apr-04 18:10
ccache16-Apr-04 18:10 
QuestionProcess id under NT4 ? Pin
K--8-Mar-04 23:37
sussK--8-Mar-04 23:37 
AnswerRe: Process id under win2k ? Pin
Johan Danforth12-May-04 21:20
Johan Danforth12-May-04 21:20 
GeneralThank you! Pin
5.5 cents25-Nov-03 21:12
suss5.5 cents25-Nov-03 21:12 
GeneralHelp needed Pin
mkdsoft28-Sep-03 12:37
mkdsoft28-Sep-03 12:37 

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.