Search code examples
delphiwinsockipv6ipv4winsock2

TCP netstat ipv4/ipv6 in Delphi


I am trying to "reproduce" netstat for Delphi and came across some questions:

This is my code so far:

program NetstatExample;

{$APPTYPE CONSOLE}

uses
  Windows,
  Winsock2;

const
  TCP_TABLE_OWNER_PID_ALL = 5;
  ANY_SIZE                = 1;

type
  TCP_TABLE_CLASS = Integer;

  in6_addr = record
    case Integer of
      0: (Byte: array [0..15] of u_char);
      1: (Word: array[0..7] of u_short);
      2: (s6_bytes: array [0..15] of u_char);
      3: (s6_addr: array [0..15] of u_char);
      4: (s6_words: array[0..7] of u_short);
  end;
  TIn6Addr = in6_addr;
  PIn6Addr = ^in6_addr;

  PTMib_TCP6Row = ^TMib_TCP6Row;
  TMib_TCP6Row = packed record
    LocalAddr       : IN6_ADDR    ;
    dwLocalScopeId  : DWORD       ;
    dwLocalPort     : DWORD       ;
    RemoteAddr      : IN6_ADDR    ;
    dwRemoteScopeId : DWORD       ;
    dwRemotePort    : DWORD       ;
    dwState         : DWORD       ;
    dwProcessId     : DWORD       ;
  end;

  PTMIB_TCP6TABLE = ^TMIB_TCP6TABLE;
  TMIB_TCP6TABLE = record
    dwNumEntries : DWORD;
    Table: array[0..ANY_SIZE - 1] of TMib_TCP6Row;
  end;

var
  GetExtendedTcpTable   : function (pTcpTable: Pointer; dwSize: PDWORD; bOrder: BOOL; lAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWord; stdcall;

  iphHandle             : HMODULE;
  TableSize             : DWORD;
  TCPTable              : PTMIB_TCP6TABLE;
  I                     : Integer;

begin
  try
    iphHandle := LoadLibrary('iphlpapi.dll');
    if iphHandle = 0 then Exit;
    GetExtendedTcpTable := GetProcAddress(iphHandle, 'GetExtendedTcpTable');
    if @GetExtendedTcpTable = NIL then Exit;
    if GetExtendedTcpTable(nil, @TableSize, False, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) <> ERROR_INSUFFICIENT_BUFFER then Exit;
    GetMem(TCPTable, TableSize);
    try
      if GetExtendedTcpTable(TCPTable, @TableSize, False, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) <> NO_ERROR then Exit;
      for I := 0 to TCPTable^.dwNumEntries - 1 do
      begin
        // Detect AF_INET6 and/or AF_INET4 family
        // Display Remote Address in proper format for each family - XP compatible?!
      end;
    finally
      FreeMem(TCPTable, TableSize);
    end;

  finally
    readln;
  end;
end.

Here, I am using AF_INET6 so I can also get the ipv6 connections as well.

Questions are:

  1. How can I safely distinguish ipv4 from ipv6?
  2. How can I display the remote address in the proper format for both families and still make it XP compatible as well? (InetNtop did not yet exist in Windows XP)

Solution

  • Q1: Your GetExtendedTcpTable call returns addresses in the specified address family, ie. if you pass it AF_INET6 it returns IPv6 addresses only, if you pass it AF_INET it returns IPv4 addresses only. So there is no need to distinguish, you just cast the returned table to the right table type, as described in the Remarks section of the documentation (linked above).

    Q2: Here's my implementation (I hope it's correct):

    function AddrStr(Addr: Cardinal): string;
    var
      P: PAnsiChar;
    begin
      P := inet_ntoa(PInAddr(@Addr)^);
      SetString(Result, P, StrLen(P));
    end;
    
    function Addr6Str(const Addr: IN6_ADDR): string;
    var
      I: Integer;
    begin
      Result := '';
    
      for I := 0 to 7 do
      begin
        if Result <> '' then
          Result := Result + ':';
        Result := Result + LowerCase(IntToHex(ntohs(Addr.Word[I]), 1));
      end;
      Result := '[' + Result + ']';
    end;
    

    ...or have a look at Free Pascal's Sockets unit, NetAddrToStr and NetAddrToStr6.