Jump to content
Mark-

TWSocket,,,

Recommended Posts

Hello,

 

A computer with multiple networks interfaces. The "port" and "addr" are set. "LocalAddr" is not set. No SSL.

"Connect" command is called.

The question, how to determine the IP address of the network interface used.

 

Cheers,

 

Mark

Share this post


Link to post

Generally, you set the LocalAddr, LocalAddr6 and SocketFamily to specify which of multiple IP addresses is to be used. 

 

Currently, TWSocket does not have any properties to show which IP address Windows choose for an outbound connection only for incoming listen connections. 

 

Windows Vista added a getsockopt of SO_BSP_STATE that should return the local and remote addresses and ports for a connection, but it is not used by ICS, you try it with the TSocket handle.  

 

I'll put it on my long term wish list, but we seem to have managed for a long time without knowing the real local address. 

 

Angus

 

Share this post


Link to post
3 minutes ago, Angus Robertson said:

I'll put it on my long term wish list, but we seem to have managed for a long time without knowing the real local address.

Thanks Angus.

 

 

Share this post


Link to post

Well, the wall has been hit.

Not sure what is going on.

 

Using what I "think" should work ends with:

image.png.2f70c58f2f29997277c500181d7b57b9.png

 

Replacing

 

add_info:CSADDR_INFO;

with

add_info:CSADDR_INFO2

and no error.

 

type
 SOCKET_ADDRESS = record
  lpSockaddr:PSOCKADDR;
  iSockaddrLength:integer;
end;

type
 CSADDR_INFO = record
  LocalAddr:SOCKET_ADDRESS;
  RemoteAddr:SOCKET_ADDRESS;
  iSocketType:Integer;
  iProtocol:Integer;
 end;

type
 CSADDR_INFO2 = record
  space:array [0..127] of byte;
end;

procedure TForm3.FormDestroy(Sender: TObject);
begin
 WSocket1.Abort;
end;

function GetLastSocketErrorMessage: string;
var
 ErrorCode:integer;
 Buffer:array[0..255] of Char;
begin
 ErrorCode := WSAGetLastError;
 if ErrorCode <> 0 then
  begin
   FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
                 nil, ErrorCode, 0, Buffer, SizeOf(Buffer), nil );
   Result := Format('Socket Error %d: %s', [ErrorCode, StrPas(Buffer)]);
  end
 else
  Result := 'No socket error.';
end;

procedure TForm3.FormShow(Sender: TObject);
var
 optLen:integer;
 add_info:CSADDR_INFO;
begin
 WSocket1.Connect;

 OptLen:=SizeOf(add_info);
 FillChar(add_info,OptLen,#0);

 if WSocket_getsockopt(WSocket1.HSocket,
                       SOL_SOCKET,
                       SO_BSP_STATE,
                       @add_info,
                       OptLen) = SOCKET_ERROR then
  ShowMessage(GetLastSocketErrorMessage);

Any ideas?

 

Mark

 

Share this post


Link to post

You should wait until the socket is connected before trying to get socket connected information, use the OnSessionConnected event. 

 

Angus

 

Share this post


Link to post
7 minutes ago, Angus Robertson said:

You should wait until the socket is connected before trying to get socket connected information, use the OnSessionConnected event.

Thanks for the response.

 

Same result.

Share this post


Link to post
6 minutes ago, Angus Robertson said:

Sorry, no time to debug this at the moment.

No problem.

 

I found this on SO.

 

"... that illustrates that in fact SO_BSP_STATE requires a buffer more than sizeof(CSADDR_INFO), which is in direct contrast to the Microsoft published documentation: ...

 

Still looking for a working example in any language.

 

Share this post


Link to post
14 hours ago, Mark- said:

Still looking for a working example in any language.

Here a fully working example, modified from a code for different thing, yet it shows successful use of SO_BSP_STATE 

unit uReadSocketInfo;

interface

uses
  Windows, Winsock2;

function WSAInitialize(MajorVersion, MinorVerion: Integer): Boolean;

function WSADeInitialize: Boolean;

function CheckTCPPortNB(const IP: string; Port: Integer; out TimeMS: Integer): Boolean;

var
  CHECKPOINT_TIMEOUT_MS: integer = 1000;

implementation

procedure GetSocketInformation(s: TSocket);
var
  pSockAddresssI: PCSADDR_INFO;
  Res, WsaLError, OptLen: Integer;
begin

  OptLen := SizeOf(CSADDR_INFO) + 128;    // yes the original size is 24, but more is needed and will fail on 24
                                          // for IPv4 an extra of 32 bytes is enough instead of 128
  pSockAddresssI := GetMemory(OptLen);
  try

    Res := getsockopt(s, SOL_SOCKET, SO_BSP_STATE, PAnsiChar(pSockAddresssI), OptLen);
    if Res = SOCKET_ERROR then
      WsaLError := WSAGetLastError
    else
      Writeln('Socket information :');

  finally
    FreeMemory(pSockAddresssI);
  end;
end;

function CheckTCPPortNB(const IP: string; Port: Integer; out TimeMS: Integer): Boolean;
var
  s: TSocket;
  Addr: TSockAddrIn;
  SAddr: TSockAddr absolute Addr;
  QPF, QPC1: Int64;
  NonBlockMode: DWORD;
  Res: Integer;
  FDW, FDE: fd_set;
  TimeVal: TTimeVal;

  function GetElapsedTime: Integer;
  var
    QPC2: Int64;
  begin
    QueryPerformanceCounter(QPC2);
    Result := (QPC2 - QPC1) div (QPF div 1000);
  end;

begin
  Result := False;
  TimeMS := 0;

  s := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if s = INVALID_SOCKET then
    Exit;

  NonBlockMode := 1;
  ioctlsocket(s, Integer(FIONBIO), NonBlockMode);

  Addr.sin_family := AF_INET;
  Addr.sin_addr.S_addr := inet_addr(PAnsiChar(AnsiString(IP)));
  Addr.sin_port := htons(Port);

  QueryPerformanceFrequency(QPF);
  QueryPerformanceCounter(QPC1);

  Res := connect(s, SAddr, SizeOf(SAddr));
  if (Res = SOCKET_ERROR) and (WSAGetLastError = WSAEWOULDBLOCK) then
  begin
    TimeVal.tv_sec := 0;          // 1 sec = 1000000 usec
    TimeVal.tv_usec := 1000;      // 1 ms  = 1000    usec

    repeat
      FDW.fd_count := 1;
      FDW.fd_array[0] := s;
      FDE.fd_count := 1;
      FDE.fd_array[0] := s;
      TimeMS := GetElapsedTime;
      Res := select(1, nil, @FDW, @FDE, @TimeVal);
    until (Res > 0) or (TimeMS >= CHECKPOINT_TIMEOUT_MS);
  end;

  Result := (FDW.fd_count = 1) and (FDE.fd_count = 0);

  ///    we have connected socket with full valid information
  if Result then
  begin
    GetSocketInformation(s);
  end;
  ///


  TimeMS := GetElapsedTime;

  if s <> INVALID_SOCKET then
    closesocket(s);
end;

function WSAInitialize(MajorVersion, MinorVerion: Integer): Boolean;
var
  WSA: TWsaData;
begin
  Result := WSAStartup(MakeWord(MajorVersion, MinorVerion), WSA) = 0;
  if Result then
  begin
    Result := (Byte(WSA.wVersion shr 8) = MinorVerion) and (Byte(WSA.wVersion) = MajorVersion);
    if not Result then
    begin
      Result := False;
      WSADeInitialize;
    end;
  end;
end;

function WSADeInitialize: Boolean;
begin
  Result := WSACleanup = 0;
end;

initialization
  WSAInitialize(2, 2);


finalization
  //WSADeInitialize;

end.

a small project to use it 

program SocketInformation;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  uReadSocketInfo in 'uReadSocketInfo.pas';

var
  Time : Integer;
begin
  try
    CheckTCPPortNB('142.251.36.46', 80, Time);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Put a break point with debugger on the getsockopt and you will get 

image.thumb.png.b2d1557c291ee4b32e49ce48dae7db3e.png

 

32 bytes additional bytes (on top the 24 bytes, the structure size) is needed for IPv4 so i suspect more is needed for IPv6,

this is undocumented behavior.

  • Like 1

Share this post


Link to post

I forgot to mention important thing about that structure and its size.

 

You can't and must not put it on the stack !, it will overflow and destroy/corrupt the stack, so it must be on the heap and must be zeroed before usage as best practice, because there is two addresses (pointing to two structures) will be filled by that API and it will put them right after the initial structure and fix the addresses.

  • Like 1

Share this post


Link to post
2 hours ago, Kas Ob. said:

I forgot to mention important thing about that structure and its size.

 

You can't and must not put it on the stack !, it will overflow and destroy/corrupt the stack, so it must be on the heap and must be zeroed before usage as best practice, because there is two addresses (pointing to two structures) will be filled by that API and it will put them right after the initial structure and fix the addresses.

Thank you.

 

Not being on the stack is the only difference I see at the moment.

I will give it a go and post the result.

 

Share this post


Link to post

I've just got this working with new types

 

  Socket_Address = record          { V9.5 used to store an IPv4 or IPv6 address }
    Sockaddr: PSockAddrIn6;
    SockaddrLength: Integer;
  end;
  TSocketAddress = Socket_Address;

  CSADDR_INFO = record             { V9.5 used to connection IP information }
    LocalAddr: TSocketAddress;     { family, address and port  }
    RemoteAddr: TSocketAddress;
    iSocketType: Integer;          { SOCK_STREAM or SOCK_DGRAM }
    iProtocol: Integer;            { IPPROTO_TCP or IPPROTO_UDP }
    Buffer: array[0..64] of Byte;  { space for PSockAddrIn6 records }
 end;
 TCSAddrInfo = CSADDR_INFO;

 

Need to ensure it handles all ways of connecting, and update a sample to show the result, hopefully later today.

 

Angus

 

  • Like 2

Share this post


Link to post
2 hours ago, Angus Robertson said:

Buffer: array[0..64] of Byte;  { space for PSockAddrIn6 records }

I believe the needed buffer is 56 (2*28) for IPv6 so [0..55] bytes should be enough, if i am not missing something.

Share this post


Link to post

I'm sure you are correct, but a few spare bytes in a buffer might provide future proofing.  

 

I'm surprised Socket_Address has not been used for other APIs, Microsoft has so many of these similar but not quite the same structures.  Fortunately, ICS has a simple function to convert PSockAddrIn6 into a string.

 

Angus

  • Like 1

Share this post


Link to post
38 minutes ago, Angus Robertson said:

I'm sure you are correct, but a few spare bytes in a buffer might provide future proofing.  

 

I'm surprised Socket_Address has not been used for other APIs, Microsoft has so many of these similar but not quite the same structures.  Fortunately, ICS has a simple function to convert PSockAddrIn6 into a string.

 

Angus

Please, pretty please, make it [0..63] not [0..64], it is triggering my OCD !

Also having goosebumps from that off by one to an odd number !

aJQz5QX4_700w_0.jpg.ca9ff1c66039b504fff83794de40540d.jpg 

Share this post


Link to post

After doing a couple of tests, it seems the SO_BSP_STATE API returns the local address allocated to the socket, usually 0.0.0.0, rather than the address chosen by Windows. 

 

We do get the random local port, so that could be used with the IpHlpConnsTable function to get a list of all connections on the PC, and search for the remote IP and local port, to find the local IP, major overhead to get a few bytes.

 

Angus

 

Share this post


Link to post
6 minutes ago, Angus Robertson said:

After doing a couple of tests, it seems the SO_BSP_STATE API returns the local address allocated to the socket, usually 0.0.0.0, rather than the address chosen by Windows.

Thank you Angus.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×