MasterAler
MasterAler

Reputation: 1653

Check local port is free from Inno Setup without netstat usage

My question was asked here before, but I'm trying to implement a neater solution for my project. So, as title states, I'm creating a complex installer of the server application that has to check local IP address and choose a open port, so that the application could be properly configured. Inno Setup 5.6.1 is used.

Getting local IP addresses was not a problem, this solution helped me a lot. Then it came to the port's checking and here I've found the following three options:

As was mentioned above, getting IP address can be implemented via straightforward (ok, not really, it's a Pascal Script) WinAPI calls. So, I tried to do the same trick with ports, trying to call GetTcpTable():

[Code]

const
  ERROR_INSUFFICIENT_BUFFER = 122;


function GetTcpTable(pTcpTable: Array of Byte; var pdwSize: Cardinal;
  bOrder: WordBool): DWORD;
external '[email protected] stdcall';

{ ------------------------ }

function CheckPortIsOpen(port: Integer): Boolean;
var
  TableSize  : Cardinal;
  Buffer : Array of Byte; { Alas, no pointers here }
  RecordCount : Integer;
  i, j : Integer;
  portNumber : Cardinal;
  IpAddr : String;
begin
  Result := True;
  TableSize := 0;

  if GetTcpTable(Buffer, TableSize, False) = ERROR_INSUFFICIENT_BUFFER then
    begin
      SetLength(Buffer, TableSize);
      if GetTcpTable(Buffer, TableSize, True) = 0 then
        begin
          { some magic calculation from GetIpAddrTable calling example }
          RecordCount := (Buffer[1] * 256) + Buffer[0];
          For i := 0 to RecordCount -1 do
            begin
              portNumber := Buffer[i*20 + 8]; { Should work! }

              { Debugging code here }
              if (i < 5) then begin
                IpAddr := '';
                For J := 0 to 3 do
                 begin
                   if J > 0 then
                    IpAddr := IpAddr + '_';
                    IpAddr := IpAddr + IntToStr(Buffer[I*20+ 4 + J]);
                 end;
                SuppressibleMsgBox(IpAddr, mbError, MB_OK, MB_OK);
              end;
              { ------ }

              if port = portNumber then
                  Result := False;
            end;
        end;
    end;
end;

This GetTcpTable also returns information about addresses and ports (table of TCP connections to be exact), so trying to get any connection address is good for debugging purposes. More about this attempt:

So, everything is great fun... it just is not working =((

And yes, I've tried to print all the bytes one-by-one, to search for the desired data manually and understand the correct offset. To my disappointment, nothing looking like IPs and ports was found, the numbers were quite mysterious.

I know that sometimes the simplest solution is best, not the smartest, but if anyone could give me a key cooking this WinAPI function in a proper way, I would be deeply grateful.

Upvotes: 2

Views: 473

Answers (1)

Sertac Akyuz
Sertac Akyuz

Reputation: 54822

Your magic calculations are off.

portNumber := Buffer[i*20 + 8]; { Should work! }

Since Buffer is a byte array the above extracts one byte only. But the local port number is a DWORD in the TCP table. Though the documentation you linked states:

The local port number in network byte order for the TCP connection on the local computer.

The maximum size of an IP port number is 16 bits, so only the lower 16 bits should be used. The upper 16 bits may contain uninitialized data.

So we need two bytes. And we need to switch them, note "network byte order" above.

You are also forgetting to account for the 4 byte record count at the beginning of the table. So the local port number should become:

          portNumber := Buffer[i*20 + 12] * 256 +
                        Buffer[i*20 + 13];

Upvotes: 2

Related Questions