Mark- 31 Posted Monday at 01:43 PM 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
Angus Robertson 634 Posted Monday at 02:30 PM 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
Mark- 31 Posted Monday at 02:40 PM 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
Mark- 31 Posted Monday at 04:52 PM Well, the wall has been hit. Not sure what is going on. Using what I "think" should work ends with: 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
Angus Robertson 634 Posted Monday at 05:34 PM 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
Mark- 31 Posted Monday at 05:42 PM 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
Angus Robertson 634 Posted Monday at 06:00 PM Sorry, no time to debug this at the moment. Angus 1 Share this post Link to post
Mark- 31 Posted Monday at 06:29 PM 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
Kas Ob. 133 Posted 14 hours ago 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 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. 1 Share this post Link to post
Kas Ob. 133 Posted 14 hours ago 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. 1 Share this post Link to post
Mark- 31 Posted 12 hours ago 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
Angus Robertson 634 Posted 11 hours ago 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 2 Share this post Link to post
Kas Ob. 133 Posted 9 hours ago 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
Angus Robertson 634 Posted 9 hours ago 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 1 Share this post Link to post
Kas Ob. 133 Posted 8 hours ago 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 ! Share this post Link to post
Angus Robertson 634 Posted 6 hours ago 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
Mark- 31 Posted 6 hours ago 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