Search code examples
lazarusfreepascalindy10

TIdIPWatch returns blank IP (as does TIdStack)


I'm looking at simple solutions in Lazarus/FPC to get the local IP Address and certainly INDY seems simplest. This is on a parallels MacOS VM running Mojave running on a 2015 MBP. I'm using Lazarus 2.0.0RC3 and the code is in a DYLIB. I need the IP address of the local machine to get it's MAC Address.

I'm using TIdWatch, which should simply return the local IP address (from its CurrentIP property). I like both solutions, IdStack detailed further below, because the code is very simple, but of course, no matter how simple is not of any benefit if the code does not work:

uses
  IdBaseComponent,
  IdComponent,
  IdIPWatch,
  ...

function getLocalIP: string;
var
   IPW: TIdIPWatch;
begin
  IpW := TIdIPWatch.Create(nil);
  try
    if IpW.LocalIP <> '' then
      Result := IpW.LocalIP;
    ShowMessage('IP: ' + Result);
  finally
    IpW.Free;
  end;
end;

The actual IP address is 192.168.1.25, but the call to IdPWatch.LocalIP returns a blank string.

I tried setting Active to True (IpW.Active := True;), but that just kills the application (I am unsure why, it simply terminates abruptly).

Likewise, I have tried using IdStack and get the same issue, ie. it Returns a blank string:

uses
  IdStack,
  ...

function GetLocalIP : String;
begin
  TIdStack.IncUsage;
  try
    Result := GStack.LocalAddress;
    ShowMessage('IP: ' + Result);
  finally
    TIdStack.DecUsage;
  end;
end;

Again, this code was from the internet, but I have looked up documentation for both IdStack and TIdIPWatch, and according to my readings, should return the local IP address as stated on the Lazarus website.

EDIT:

As per Remy's comment, I have amended the function to use the GStack.GetLocalAddressList function rather than the deprecated functions I was previously using. Note that the result is the same and that IPList (the TIdStackLocalAddressList variable) has no elements:

function getLocalIP: string;
var
  IPList: TIdStackLocalAddressList;
  IPStrings: TStringList;
  i: integer;

begin
  try
    try
      Result:='';
      IPList:=TIdStackLocalAddressList.Create;
      IPStrings := TStringList.create;

      TIdStack.IncUsage;
      GStack.GetLocalAddressList(IPList);
      if IPList.count > 0 then
      begin
        WriteLog('DEBUG', 'No of Addresses: ' + inttostr(IPList.count));
        for i := 0 to IPList.Count - 1 do
        begin
          if IPList[i].IPVersion = Id_IPv4 then
          begin
            IPStrings.Add(IPList[i].IPAddress+':'+ TIdStackLocalAddressIPv4(IPList[i]).SubNetMask);
          end;
        end;
        // show IP Addresses in the log file
        if IPStrings.Count > 0 then
        begin
          for i := 0 to IPStrings.Count -1 do
            WriteLog('DEBUG', 'IP Address #' + inttostr(i) + ': ' + IPStrings[i]);
          Result := IPStrings[0];
          WriteLog('DEBUG', 'IP: ' + Result);
        end
        else
          WriteLog('DEBUG', 'IPStrings has no entries');
      end
      else
        WriteLog('DEBUG', 'TIdStackLocalAddressList has no entries');
    except
      On E:Exception do
      begin
        Result := '';
        WriteLog('ERROR', 'IP Error: ' + E.message);
      end;
    end;
  finally
    TIdStack.DecUsage;
  end;

end;

So my questions:

  1. Can I use TIdIPWatch or TIdStack in Lazarus/FPC to find the local IP address of the VM?

  2. Can anyone see any glaring errors in the code snippets I have posted preventing the return of the local IP address?

I am happy to post more code, and answer any questions if you feel I have left anything out.

EDIT2:

So both INDY and other methods set to use ifconfig to get the IP address. The solutions seem to be looking for 'net Add:'

In MacOS Mojave 10.14, IfConfig does not return 'net Addr:'. It simply returns 'net', at least in my Parallels VM. You may wish to adapt the code accordingly. Note the output from a VM and a physical system appear to be different:

Typical output from my Mojave Parallels VM system (the <> stuff up the block quote):

kevin$ ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
    options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
    inet 127.0.0.1 netmask 0xff000000 
    inet6 ::1 prefixlen 128 
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
    nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
UHC29: flags=0<> mtu 0
EHC253: flags=0<> mtu 0
XHC221: flags=0<> mtu 0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=2b<RXCSUM,TXCSUM,VLAN_HWTAGGING,TSO4>
    ether 00:1c:42:72:74:a3 
    inet6 fe80::3a:650d:9b24:c9c5%en0 prefixlen 64 secured scopeid 0x7 
    inet 192.168.1.25 netmask 0xffffff00 broadcast 192.168.1.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (1000baseT <full-duplex>)

Typical output from hard systems running Mojave (the actual IP address is at en0 (5), net address):

kevin$ ifconfig

awdl0 (8):
  flags         UP BROADCAST RUNNING PROMISC SIMPLEX MULTICAST
  mtu           1484

bridge0 (10):
  flags         UP BROADCAST NOTRAILERS RUNNING SIMPLEX MULTICAST
  mtu           1500

en0 (5):
  inet address  192.168.1.167
  netmask       255.255.255.0
  broadcast     192.168.1.255
  flags         UP BROADCAST NOTRAILERS RUNNING PROMISC SIMPLEX MULTICAST
  mtu           1500

en1 (7):
  flags         UP BROADCAST NOTRAILERS RUNNING PROMISC SIMPLEX MULTICAST
  mtu           1500

en2 (9):
  flags         UP BROADCAST NOTRAILERS RUNNING PROMISC SIMPLEX MULTICAST
  mtu           1500

lo0 (1):
  inet address  127.0.0.1
  netmask       255.0.0.0
  flags         UP LOOPBACK RUNNING MULTICAST
  mtu           16384

p2p0 (6):
  flags         UP BROADCAST RUNNING SIMPLEX MULTICAST
  mtu           2304

utun0 (11):
  flags         UP POINTOPOINT RUNNING MULTICAST
  mtu           2000

utun1 (12):
  flags         UP POINTOPOINT RUNNING MULTICAST
  mtu           1380

vnic0 (13):
  inet address  10.211.55.2
  netmask       255.255.255.0
  broadcast     10.211.55.255
  flags         UP BROADCAST RUNNING SIMPLEX MULTICAST
  mtu           1500

vnic1 (14):
  inet address  10.37.129.2
  netmask       255.255.255.0
  broadcast     10.37.129.255
  flags         UP BROADCAST RUNNING SIMPLEX MULTICAST
  mtu           1500

What's the bottom line, well it's confusing. On a VM you need to search for 'inet ' and on a hard system you need to search for 'inet address '. Now I am the first to admit 'not a doctor', this is not my area of expertise. So I am unsure whether INDY 10 is searching for 'inet:' and/or 'inet address:' with the colon at the end. If it is that would explain why it isn't working. I will further investigate.


Solution

  • GStack.GetLocalAddressList() is implemented for OSX, so I couldn't say why it is not returning any IP addresses. You will just have to step into Indy's source code with the debugger and find out where the failure is actually occurring. Internally, it should be calling the OSX getifaddrs() funding to get the IP addresses from the OS. Maybe that function is not working in your VM.

    On a side note, your code is not very exception-safe. Things like IncUsage/DecUsage, Create/Free, etc should be wrapped in individual try/finally blocks, not the way you are doing it (ie, an exception before IncUsage causes DecUsage to be called, unbalancing the refcount) . And you are leaking memory.

    Your code should look more like this instead:

    function getLocalIP: string;
    var
      IPList: TIdStackLocalAddressList;
      IPStrings: TStringList;
      i: integer;
    begin
      Result := '';
      try
        IPList := TIdStackLocalAddressList.Create;
        try
          TIdStack.IncUsage;
          try
            GStack.GetLocalAddressList(IPList);
          finally
            TIdStack.DecUsage;
          end;
    
          if IPList.Count > 0 then
          begin
            WriteLog('DEBUG', 'No of Addresses: ' + IntToStr(IPList.Count));
    
            IPStrings := TStringList.Create;
            try
              for i := 0 to IPList.Count - 1 do
              begin
                if IPList[i].IPVersion = Id_IPv4 then
                begin
                  IPStrings.Add(IPList[i].IPAddress + ':' + TIdStackLocalAddressIPv4(IPList[i]).SubNetMask);
                end;
              end;
    
              // show IP Addresses in the log file
              if IPStrings.Count > 0 then
              begin
                for i := 0 to IPStrings.Count -1 do
                  WriteLog('DEBUG', 'IP Address #' + IntToStr(i) + ': ' + IPStrings[i]);
                Result := IPStrings[0];
                WriteLog('DEBUG', 'IP: ' + Result);
              end else
                WriteLog('DEBUG', 'IPStrings has no entries');
            finally
              IPStrings.Free;
            end;
          end else
            WriteLog('DEBUG', 'TIdStackLocalAddressList has no entries');
        finally
          IPList.Free;
        end;
      except
        On E: Exception do
        begin
          Result := '';
          WriteLog('ERROR', 'IP Error: ' + E.message);
        end;
      end;
    end;