2011. február 5., szombat

Getting the IP address and mask for ALL TCP/IP interfaces


Problem/Question/Abstract:

We have seen a lot of methods obtaining the IP address of a machine. This is the "correct" method listing all addresses, network masks, broadcast addresses and status for all interfaces, including the loopback 127.0.0.1 - Requires WinSock 2

Answer:

This is a complete Delphi unit. By adding it to a project you can call :

EnumInterfaces(var s string): Boolean;

that returns a CRLF separated string of all IP addresses, netmasks, broadcast addresses and interface status.


unit USock;

interface

uses Windows, Winsock;

{

  This function enumerates all TCP/IP interfaces and
  returns a CRLF separated string containing:

  IP, NetMask, BroadCast-Address, Up/Down status,
  Broadcast support, Loopback

  If you feed this string to a wide TMEMO (to its memo.lines.text
  property) you will see cleary the results.

  To use this you need Win98/ME/2K, 95 OSR 2 or NT service
  pack #3 because WinSock 2 is used (WS2_32.DLL)

}

function EnumInterfaces(var sInt: string): Boolean;

{ Imported function WSAIOCtl from Winsock 2.0 - Winsock 2 is }
{ available only in Win98/ME/2K and 95 OSR2, NT srv pack #3 }

function WSAIoctl(s: TSocket; cmd: DWORD; lpInBuffer: PCHAR; dwInBufferLen:
  DWORD;
  lpOutBuffer: PCHAR; dwOutBufferLen: DWORD;
  lpdwOutBytesReturned: LPDWORD;
  lpOverLapped: POINTER;
  lpOverLappedRoutine: POINTER): Integer; stdcall; external 'WS2_32.DLL';

{ Constants taken from C header files }

const
  SIO_GET_INTERFACE_LIST = $4004747F;
  IFF_UP = $00000001;
  IFF_BROADCAST = $00000002;
  IFF_LOOPBACK = $00000004;
  IFF_POINTTOPOINT = $00000008;
  IFF_MULTICAST = $00000010;

type
  sockaddr_gen = packed record
    AddressIn: sockaddr_in;
    filler: packed array[0..7] of char;
  end;

type
  INTERFACE_INFO = packed record
    iiFlags: u_long; // Interface flags
    iiAddress: sockaddr_gen; // Interface address
    iiBroadcastAddress: sockaddr_gen; // Broadcast address
    iiNetmask: sockaddr_gen; // Network mask
  end;

implementation

{-------------------------------------------------------------------

1. Open WINSOCK
2. Create a socket
3. Call WSAIOCtl to obtain network interfaces
4. For every interface, get IP, MASK, BROADCAST, status
5. Fill a CRLF separated string with this info
6. Finito

--------------------------------------------------------------------}

function EnumInterfaces(var sInt: string): Boolean;
var
  s: TSocket;
  wsaD: WSADATA;
  NumInterfaces: Integer;
  BytesReturned, SetFlags: u_long;
  pAddrInet: SOCKADDR_IN;
  pAddrString: PCHAR;
  PtrA: pointer;
  Buffer: array[0..20] of INTERFACE_INFO;
  i: Integer;
begin
  result := true; // Initialize
  sInt := '';

  WSAStartup($0101, wsaD); // Start WinSock
  // You should normally check
  // for errors here :)

  s := Socket(AF_INET, SOCK_STREAM, 0); // Open a socket
  if (s = INVALID_SOCKET) then
    exit;

  try // Call WSAIoCtl
    PtrA := @bytesReturned;
    if (WSAIoCtl(s, SIO_GET_INTERFACE_LIST, nil, 0, @Buffer, 1024, PtrA, nil,
      nil)
      <> SOCKET_ERROR) then
    begin // If ok, find out how
      // many interfaces exist

      NumInterfaces := BytesReturned div SizeOf(INTERFACE_INFO);

      for i := 0 to NumInterfaces - 1 do // For every interface
      begin
        pAddrInet := Buffer[i].iiAddress.addressIn; // IP ADDRESS
        pAddrString := inet_ntoa(pAddrInet.sin_addr);
        sInt := sInt + ' IP=' + pAddrString + ',';
        pAddrInet := Buffer[i].iiNetMask.addressIn; // SUBNET MASK
        pAddrString := inet_ntoa(pAddrInet.sin_addr);
        sInt := sInt + ' Mask=' + pAddrString + ',';
        pAddrInet := Buffer[i].iiBroadCastAddress.addressIn; // Broadcast addr
        pAddrString := inet_ntoa(pAddrInet.sin_addr);
        sInt := sInt + ' Broadcast=' + pAddrString + ',';

        SetFlags := Buffer[i].iiFlags;
        if (SetFlags and IFF_UP) = IFF_UP then
          sInt := sInt + ' Interface UP,' // Interface up/down
        else
          sInt := sInt + ' Interface DOWN,';

        if (SetFlags and IFF_BROADCAST) = IFF_BROADCAST then // Broadcasts
          sInt := sInt + ' Broadcasts supported,' // supported or
        else // not supported
          sInt := sInt + ' Broadcasts NOT supported,';

        if (SetFlags and IFF_LOOPBACK) = IFF_LOOPBACK then // Loopback or
          sInt := sInt + ' Loopback interface'
        else
          sInt := sInt + ' Network interface'; // normal

        sInt := sInt + #13#10; // CRLF between
        // each interface
      end;
    end;
  except
  end;
  //
  // Close sockets
  //
  CloseSocket(s);
  WSACleanUp;
  result := false;
end;

end.

here's a step-by-step on how to use it:

1. Copy/Paste the code to a text file and name it USock.pas (it's important that you name it like that, otherwise it won't work)
2. Open (Run) Delphi
3. Add a Memo control to the page and a button
4. Click on File/Save All and save the project somewhere (make a folder for it)
5. Put USock.pas in that folder
6. In Delphi, click on Project menu item and choose Add to Project
7. Select the USock.pas file and click on Open
8. NOW, doubleclick on the button you added to your project and write this:
in VAR section, write:

s: string

After begin (the main part) write:

EnumInterfaces(s);
memo1.lines.text := s;

So, the final procedure will look like:

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string;
begin
  EnumInterfaces(s);
  memo1.lines.text := s;
end;

9. Now, go to the top of your unit to the USES section and add USock to other uses things there, it will look like:

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,
    USock;

Then, run the project and click on that button. You will see the info in your memo box...
Here's what i get (the second part of it):

IP=127.0.0.1, Mask=255.0.0.0, Broadcast=255.255.255.255, Interface UP, Broadcasts supported, Loopback interface

So, as you see, all that info is in "s" variable...

1 megjegyzés:

  1. Your declaration of sockaddr_gen is wrong. It's a C union (aka a Delphi record with variant...) and you're "filler" using the "char" type will obviosly break in Unicode Delphi (where char is 16 bit wide). If you want a 8 byte filler, use the Byte type explicitly, not char, Delphi is not C...

    VálaszTörlés