2

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:

  • Using external DLL from installer. Actually, previous solution consisted of a C++ DLL, that exported two exact convenience functions, they worked great, installer used them, but sometimes, rarely on some Windows versions this DLL didn't want to get loaded causing error. That's why all the more messing with Pascal Script here.
  • Launching netstat via cmd and getting the output. This is still an option, though I feel this solution is crutchy like hell and would like to avoid it. Details could be found in other SO answer.
  • Getting information from WinAPI call. Looks best if possible.

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 'GetTcpTable@IpHlpApi.dll 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:

  • RecordCount is calculated the same way as in the code I used as an example, because obtained struct there is very similar to the nasty struct I need.
  • That i*20 + 8 is written that way, because 20 = sizeof(single record struct) and 8 = 2 * sizeof(DWORD). Local TCP connection address is being "parsed" one-by-one-byte at an offset of 1 DWORD, as you can see.

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.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
MasterAler
  • 1,614
  • 3
  • 23
  • 35
  • Does the code even run? In pascal local variables are not initialized, the first call might not have tablesize 0 and hence the function might be failing with an entirely different error. – Sertac Akyuz Apr 23 '19 at 19:34
  • It does. `TableSize` is passed with `var`, so the first call actually sets it to the correct value, a common winapi technique to obtain the required buffer size it is. Same trick worked with other function call, in the answer that was mentioned in the question. And 'debugging message' worked, that's how I got to know that the data is messy. – MasterAler Apr 23 '19 at 20:08
  • Thanks, I'm familiar with winapi, that's an in/out parameter. If it coincidentally carries a large enough value, the function will not fail with insufficient buffer but instead will try to write to some invalid memory. You need to initialize it, although I understand that's not the only problem. – Sertac Akyuz Apr 23 '19 at 20:19
  • As @SertacAkyuz says, the first call should use an initialized variable. Your *common WinAPI technique* is in fact common, but the way you tell the API call that you want the buffer size is to pass NULL (in C, `0`) to the function in the first call. It's highly unlikely that an uninitialized local variable contains zero (NULL), so the API call will think you're passing a buffer instead, meaning you're getting something other than you think you are when it returns. **Always** initialize variables instead of making assumptions. – Ken White Apr 24 '19 at 00:25
  • Okay, sorry for inventing "meaningfull excuses" instead of proper variable initialization =)). All of you are totally right. Thanks! That will be the very first thing I'll fix. – MasterAler Apr 24 '19 at 09:45
  • @Master - Actually for this function it doesn't seem to matter since the documentation says that the function also checks for a null pointer. It just so happens that you're indeed passing a nil buffer in the first call, since a dynamic array in Pascal is a managed type. But that's just implementation detail, good habit is to initialize vars. Anyway, could you check my answer? – Sertac Akyuz Apr 24 '19 at 11:44
  • @SertacAkyuz, your answer is great, I'm trying it right now =) Gonna write an expanded comment as soon as I'm finished. – MasterAler Apr 24 '19 at 12:01

1 Answers1

2

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];
Community
  • 1
  • 1
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • OMG, that's it! Thanks a lot! Your explanations looked like a working solution from the first glance, it just took me some time to struggle with the strange pascalscript behavior -- it didn't do the proper calculation util I added explicit casts to integer, like `portNumber := integer(Buffer[i*20 + 12]) * 256 + integer(Buffer[i*20 + 13]);`. I wonder, why `RecordCount` had not required the same to be correct, despite I declared both variables as integers. But anyway, now these are definitely the correct port numbers, just like in `netstat -an`. Thank you once again =)) – MasterAler Apr 24 '19 at 12:17
  • You're welcome. Record count is not in network byte order (I guess) but in fact a DWORD. Correct it if you expect more than max(word) connections (which I don't know if it's possible). `Buffer[3] * 65536 + Buffer[2] * 4096 + Buffer[1] * 256 + Buffer[0]` . Good job on typecasting, I didn't know it was necessary... – Sertac Akyuz Apr 24 '19 at 12:59