Click here to Skip to main content
Click here to Skip to main content

Getting active TCP/UDP connections on a box

, 17 Jun 2003
Rate this:
Please Sign up or sign in to vote.
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.

[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 Smile | :) 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 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

Share

About the Author

Axel Charpentier
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

 
Suggestion[My vote of 2] Improve your coding skills! PinmemberMember 83340737-Jan-14 22:57 
GeneralRe: [My vote of 2] Improve your coding skills! Pinmembertsmarios3-Jun-14 1:08 
QuestionCLOSE_WAIT (problem) [modified] Pinmembersurenkra11-Apr-13 19:58 
GeneralMy vote of 4 PinmemberBurak Tunçbilek7-Jan-13 10:12 
QuestionI retail your souce code PinmemberMember 89009016-Apr-12 11:42 
QuestionHow throttle each connection TCP/UDP? Pinmemberegcf21-Nov-10 11:45 
GeneralMy vote of 1 PinmemberJack019011-Apr-10 4:04 
GeneralRe: My vote of 1 PinmemberStonie19-May-10 15:39 
AnswerRe: My vote of 1 PinmembereRRaTuM11-Oct-10 5:31 
GeneralThe bridge to 64 bit computing PinmemberYeorwned19-Aug-09 12:59 
GeneralUpdate to 3,5 framework???cannot modify the result of an unboxing conversion Pinmembersimpa17-Mar-08 2:34 
GeneralRe: Update to 3,5 framework???cannot modify the result of an unboxing conversion PinmemberScott Wagner17-May-09 17:51 
Generaludp receive problem wher receiver is behid router Pinmemberhafidz13-Feb-08 22:39 
QuestionWhat about remote system? Pinmemberkvrnkiran4-Jul-07 21:29 
Generalgetting app path Pinmembercarl_sti30-Dec-06 22:43 
QuestionAnd what about IPGlobalProperties? PinmemberPetr Zima4-Oct-06 0:27 
AnswerRe: And what about IPGlobalProperties? PinmemberAngusP7-Mar-10 10:39 
GeneralRead another articles conc subject PinmemberWarlib14-Jun-06 6:43 
GeneralCombined 100% fix (AFAICT) for memory leaks PinmemberIcarusDevelopment24-Mar-06 2:36 
GeneralRe: Combined 100% fix (AFAICT) for memory leaks Pinmemberksi11-Mar-08 23:15 
Laugh | :laugh: Oh! sir.
you are greate man.
yesterday, I'm sad and gray, but today, I'm so happy.
it's lucky to find your word, ha ha ha,
thank you, sir, have a nice day.
GeneralPID PinmemberVitoto25-Jan-06 14:05 
GeneralTcpTable does not return all connections Pinmemberbigga16-Sep-05 5:17 
GeneralUDP PinmemberGeorgi Petrov9-Jun-05 12:23 
GeneralThe last parameter in AllocateAndGetTcpExTableFromStack PinmemberNarayan Subramanian7-Jun-05 20:07 
GeneralUpgrade you Sample : SetTcpEntry PinmemberVitoto7-May-05 19:12 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 18 Jun 2003
Article Copyright 2003 by Axel Charpentier
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid