chmichael 12 Posted April 24 Hello, How can i check if a TCP port is open over Internet with timeout using ICS ? Thank you Share this post Link to post
Angus Robertson 574 Posted April 24 The simplest way is with the TIcsIpStrmLog component, look at the doSocketRemoteClick procedure in the OverbyteIcsSnippets sample, although you can ignore SSL. Failing to open a normal TCP connection will timeout after about 30 to 40 seconds, you can not easily make this any shorter, and you can not re-use the socket until the connection attempt fails, even if you abort it earlier. If you need to check a lot of ports, either use multiple components running in parallel (no threads needed for hundreds) or use the ping feature of TIcsIpStrmLog to see if at least the IP address exists before checking the port. Angus Share this post Link to post
Remy Lebeau 1402 Posted April 24 (edited) 17 minutes ago, Angus Robertson said: The simplest way is with the TIcsIpStrmLog component Why TIcsIpStrmLog and not TSslWSocket directly? 17 minutes ago, Angus Robertson said: Failing to open a normal TCP connection will timeout after about 30 to 40 seconds, you can not easily make this any shorter Are you saying that ICS does not support connect timeouts on TCP sockets at all? Or just that the TIcsIpStrmLog component specifically does not support this? In general, there are ways to implement a connect timeout on a TCP socket - namely: by using a non-blocking socket with select() or (e)poll() for the timeout. by aborting the socket from another thread. I would be surprised if ICS does not use the first one. Edited April 24 by Remy Lebeau 1 Share this post Link to post
Angus Robertson 574 Posted April 24 Quote Why TIcsIpStrmLog and not TSslWSocket directly? The magic word was simplest, less code, fewer errors. Quote by using a non-blocking socket with select() or (e)poll() for the timeout. Windows supports none blocking DNS lookups and sockets by using a thread, what is the API to stop those threads early? Angus Share this post Link to post
Remy Lebeau 1402 Posted April 24 (edited) 2 hours ago, Angus Robertson said: Windows supports none blocking DNS lookups and sockets by using a thread, what is the API to stop those threads early. DNS lookups and connect attempts are two different things. I don't think you can do non-blocking DNS lookups using the standard hostbyname()/getaddrinfo() APIs that most people's socket code uses. You would have to use platform-specific APIs (ie, like DnsQueryEx() on Windows), or send manual DNS queries over non-blocking sockets. A connect() timeout can be implemented by just using a non-blocking socket and close() the socket after the timeout elapses. If it uses a thread internally, that is just an implementation detail of the OS, but that shouldn't block the app from being able to move on. Edited April 24 by Remy Lebeau 1 Share this post Link to post
chmichael 12 Posted April 24 (edited) 16 hours ago, Remy Lebeau said: A connect() timeout can be implemented by just using a non-blocking socket and close() the socket after the timeout elapses. If it uses a thread internally, that is just an implementation detail of the OS, but that shouldn't block the app from being able to move on. Well I'm using the following code to do what you're saying but for some reason it doesn't always work! Eg. even if the server port is closed it will return "True". Edited April 25 by chmichael Share this post Link to post
chmichael 12 Posted April 24 5 hours ago, Angus Robertson said: The simplest way is with the TIcsIpStrmLog component, look at the doSocketRemoteClick procedure in the OverbyteIcsSnippets sample, although you can ignore SSL. Failing to open a normal TCP connection will timeout after about 30 to 40 seconds, you can not easily make this any shorter, and you can not re-use the socket until the connection attempt fails, even if you abort it earlier. If you need to check a lot of ports, either use multiple components running in parallel (no threads needed for hundreds) or use the ping feature of TIcsIpStrmLog to see if at least the IP address exists before checking the port. Angus Hello Angus, How can i get the result ? (Is open or not) Do i have to use application.processmessages ? I'm going to use this routine in a thread. Thank you function IsInternetPortOpenICS(const AIPAddress: AnsiString = ''; const APort: Word = 0; const ATimeOut: NativeUInt = INFINITE): Boolean; var IpLogClient: TIcsIpStrmLog; begin try IpLogClient := TIcsIpStrmLog.Create(nil); IpLogClient.MaxSockets := 1; IpLogClient.LogProtocol := logprotTcpClient ; IpLogClient.RemoteHost := AIPAddress; IpLogClient.RemoteIpPort := APort.ToString; IpLogClient.CheckPing := False; IpLogClient.StartLogging; Result := // Check if connected ??? IpLogClient.StopLogging; finally if Assigned(IpLogClient) and IpLogClient.AnyStateOK then IpLogClient.StopLogging; FreeAndNil(IpLogClient); end; end; Share this post Link to post
Angus Robertson 574 Posted April 25 Most ICS functions are async, not blocking, you missed IpLogClient.onLogProgEvent := onCliLogProgEvent; from snippets, and in the event you check IpLogClient.States[0] for logstateOK or something else if failed. As is often discussed here, it is rarely necessary or useful to use ICS components in a thread, but if so you can not use application.processmessages since that is the main thread, you have to create the component within the thread Execute method, set the MultiThreaded property to true, and call the component ProcessMessages method instead. Angus 1 Share this post Link to post
chmichael 12 Posted April 25 (edited) 2 hours ago, Angus Robertson said: Most ICS functions are async, not blocking, you missed IpLogClient.onLogProgEvent := onCliLogProgEvent; from snippets, and in the event you check IpLogClient.States[0] for logstateOK or something else if failed. As is often discussed here, it is rarely necessary or useful to use ICS components in a thread, but if so you can not use application.processmessages since that is the main thread, you have to create the component within the thread Execute method, set the MultiThreaded property to true, and call the component ProcessMessages method instead. Angus Ok, i fixed the routine to work both on main and on a thread. It works correctly locally but remotely it always connect OK even if the "server/port" is closed. Any ideas ? btw, doSocketRemoteClick has the same behavior. function IsInternetPortOpenICS(const AIPAddress: AnsiString = ''; const APort: Word = 0; const ATimeOut: NativeUInt = INFINITE): Boolean; var IpLogClient: TIcsIpStrmLog; FThreadID, FTimeout: NativeUInt; begin Result := False; try FThreadID := GetCurrentThreadId; IpLogClient := TIcsIpStrmLog.Create(nil); IpLogClient.LogProtocol := logprotTcpClient; IpLogClient.RemoteHost := AIPAddress; IpLogClient.RemoteIpPort := APort.ToString; //IpLogClient.onLogProgEvent := Form9.onCliLogProgEvent; //IpLogClient.CheckPing := True; //IpLogClient.PingWaitSecs := 1; if FThreadID <> MainThreadID then IpLogClient.MultiThreaded := True; IpLogClient.StartLogging; FTimeout := 0; while FTimeout < ATimeout do begin if FThreadID = MainThreadID then Application.ProcessMessages else IpLogClient.ProcessMessages; Result := IpLogClient.States[0] = logstateOK; if Application.Terminated or Result then Break; Inc(FTimeout, 10); Sleep(10); end; IpLogClient.StopLogging; finally if Assigned(IpLogClient) and IpLogClient.AnyStateOK then IpLogClient.StopLogging; FreeAndNil(IpLogClient); end; end; Edited April 25 by chmichael Share this post Link to post
Angus Robertson 574 Posted April 25 The sample OverbyteIcsIpStmLogTst is the proper test bed for the TIcsIpStrmLog component. For a successful remote connection, the events triggered are: 11:55:09 C[0] State: Starting 11:55:09 C[0] TCP/Client Opening Connection to [2a00:1940:0:c::128]:80 11:55:09 C[0] TCP/Client Connected OK 11:55:09 C[0] State: OK (manually stopped) 11:55:24 C[0] State: Stopping 11:55:24 C[0] TCP/Client Disconnected 11:55:24 C[0] State: None But for an unsuccessful connection it normally keeps trying to reconnect to the remote IP, so there is no immediate state change: 11:50:56 C[0] State: Starting 11:50:56 C[0] TCP/Client Opening Connection to [2a00:1940:0:c::128]:5000 11:51:17 C[0] TCP/Client Failed Connection - Connection timed out (#10060) - Retrying in 10 secs 11:51:27 C[0] TCP/Client Opening Connection to [2a00:1940:0:c::128]:5000 This is one of the 'features' of the component, if the client connection fails or drops it keeps trying to restore the connection, although that is exactly what you don't want! With RetryAttempts set to -1, the events called are: 12:06:45 C[0] State: Starting 12:06:45 C[0] TCP/Client Opening Connection to [2a00:1940:0:c::128]:6666 12:07:06 C[0] State: Stopping 12:07:06 C[0] State: None 12:07:06 C[0] TCP/Client Failed Connection - Connection timed out (#10060) 12:07:06 C[0] TCP/Client Disconnected So you need to check Stopping and/or None for failure, there is no specific State: Failed event, unless you watch the literals for timeout out. Angus 1 Share this post Link to post
Clément 148 Posted April 25 I while back I was researching "TCP ping" for my application. I ended up encapsulating the calls in a class. I'm using vanilla sockets for this. unit dhs.tcpPing; interface uses WinSock, Winsock2; type TNotifyPingRequest = procedure( aSender : TObject; const apcHost : String; aPort : Integer; aConnected : Boolean; aResponseTime : Double ) of object; TdhsTCPping = class private fWSAStartup : Integer; fWSAData : TWSAData; fHints : TAddrInfoW; fAddrInfo : TAddrinfoW; fCurrentAI : PAddrInfoW; private fOnNotifyPingRequest: TNotifyPingRequest; function EstablishConnection( address : TAddrInfoW; ping_timeout : integer; var errorcode : integer; blocking : Boolean ):TSocket; function CheckAddrInfo( const pcHost : String; port : integer; ipv : integer ) : Boolean; function DoWinsock( const pcHost : String; port : Integer; times_to_ping : integer; ping_interval : double; ping_timeout : integer; blocking : boolean ) : Integer; function DoWinSock_Single( const apcHost : String; aPort : Integer; times_to_ping : integer; ping_interval : double; ping_timeout : integer; blocking : boolean ) : Integer; procedure DoNotifyPingRequest( const apcHost : String; aPort : Integer; aConnected : Boolean; aResponseTime : Double ); public constructor Create; destructor Destroy; override; function ping( const pcHost : String; port : Integer; times_to_ping : integer; ping_interval : double; ping_timeout : integer ) : Integer; property OnPingRequest : TNotifyPingRequest read fOnNotifyPingRequest write fOnNotifyPingRequest ; end; function ioctlsocket(s: TSocket; cmd: u_long; var argp: u_long): Integer; stdcall; implementation uses Winapi.Windows, System.SysUtils; constructor TdhsTCPping.Create; begin fWSAStartup := WSAStartup(MakeWord(1,1), fWSAData);; end; destructor TdhsTCPping.Destroy; begin if fWSAStartup=0 then WSACleanup; inherited; end; const ws2_32 = 'ws2_32.dll'; function ioctlsocket; external ws2_32 name 'ioctlsocket'; function TdhsTCPping.CheckAddrInfo( const pcHost : String; port : integer; ipv : integer ) : Boolean; var lPort : Array[0..5] of Char; lHostName : Array[0..255] of Char; lpAddrInfo : PaddrinfoW; r : integer; begin ZeroMemory(@fHints, sizeof(TAddrInfoW)); fHints.ai_family:= PF_UNSPEC; fHints.ai_socktype := SOCK_STREAM; StrCopy(@lHostName, PCHar( pcHost ) ); StrCopy(@lPort, PChar(Format('%d',[Port]))); lpAddrInfo := @fAddrInfo; r := getaddrinfoW(@lHostName[0],@lPort[0], fHints, lpAddrInfo ); fCurrentAI := lpAddrInfo; result := false; while Assigned(fCurrentAI) do begin if ((fCurrentAI.ai_family = AF_UNSPEC) and (ipv = 0)) or ((fCurrentAI.ai_family = AF_INET) and (ipv <> 6)) or ((fCurrentAI.ai_family = AF_INET6) and (ipv <> 4)) then begin Result := true; break; end; fCurrentAI := fCurrentAI^.ai_next; end; end; procedure TdhsTCPping.DoNotifyPingRequest(const apcHost: String; aPort: Integer; aConnected : Boolean; aResponseTime : Double ); begin if Assigned( fOnNotifyPingRequest ) then fOnNotifyPingRequest( self, apcHost, aPort, aConnected, aResponseTime ); end; function TdhsTCPping.DoWinsock( const pcHost: String; port: Integer; times_to_ping : integer; ping_interval : double; ping_timeout : integer; blocking : boolean ): Integer; var lAI : TAddrInfoW; lErrorCode , lLoopCount : Integer; cpu_frequency : int64; response_timer1 , response_timer2 : int64; response_time : Double; sd : TSocket; begin lLoopCount := 0; if CheckAddrInfo( pcHost, port, 4 ) then begin while (lLoopCount<times_to_ping) or ( times_to_ping = -1) do begin // QueryPerformanceCounter isn't thread safe unless we do this SetThreadAffinityMask(GetCurrentThread(), 1); // start the timer right before we do the connection QueryPerformanceFrequency(cpu_frequency); QueryPerformanceCounter(response_timer1); sd := EstablishConnection( fCurrentAI^, ping_timeout, lErrorCode, blocking ); // grab the timeout as early as possible QueryPerformanceCounter(response_timer2); response_time := (response_timer2 - response_timer1) * 1000.0 / cpu_frequency; inc( lLoopCount ); DoNotifyPingRequest( pcHost, port, sd<>INVALID_SOCKET, response_time ); if sd<>INVALID_SOCKET then begin shutdown( sd, SD_BOTH ); closesocket(sd); end; end; end; end; function TdhsTCPping.DoWinSock_Single( const apcHost : String; aPort : Integer; times_to_ping : integer; ping_interval : double; ping_timeout : integer; blocking : boolean ) : integer ; begin result := DoWinsock( aPCHost, aPort, times_to_ping, ping_interval, ping_timeout, blocking ); end; function TdhsTCPping.EstablishConnection( address : TaddrinfoW; ping_timeout : Integer; var errorcode: integer; blocking: Boolean): TSocket; (*-------------------------------------------------------------------------- Set the socket I/O mode: Blocking -> iMode = 0 !Blocking -> iMode <>0 https://learn.microsoft.com/pt-br/windows/win32/api/winsock/nf-winsock-ioctlsocket //------------------------- // Set the socket I/O mode: In this case FIONBIO // enables or disables the blocking mode for the // socket based on the numerical value of iMode. // If iMode = 0, blocking is enabled; // If iMode != 0, non-blocking mode is enabled. -------------------------------------------------------------------------- *) var timer1 : Int64 ; timer2 : Int64 ; cpu_freq : Int64 ; time_so_far : double; sd : TSocket; iMode : u_long; conResult : integer; sendstatus : integer; done : Boolean; begin // Create a stream socket sd := socket(address.ai_family, address.ai_socktype, address.ai_protocol); iMode := 1; if blocking then iMode := 0; ioctlsocket(sd, DWord( FIONBIO ), iMode); QueryPerformanceCounter( Timer1); QueryPerformanceFrequency( cpu_freq ); conResult := -999; conResult := connect(sd, address.ai_addr^, NativeUint(address.ai_addrlen) ); if (conResult = SOCKET_ERROR) and (iMode = 0) then begin errorcode := WSAGetLastError(); closesocket(sd); exit( INVALID_SOCKET ); end; sendstatus := 1000; done := false; while (not done) do begin sendstatus := send(sd, '', 0, 0); if (sendstatus = 0) then done := true; QueryPerformanceCounter(timer2); time_so_far := ((timer2 - timer1) * 1000.0) / (cpu_freq *1.0) ; if (time_so_far >= ping_timeout) then done := true else begin // Todo: Get rid of this sleep! if (time_so_far < 200) then Sleep(0) else Sleep(1); end; end; closesocket(sd); errorcode := WSAGetLastError(); if sendstatus = 0 then exit( sd ) else exit( INVALID_SOCKET ); end; function TdhsTCPping.ping(const pcHost: String; port, times_to_ping: integer; ping_interval: double; ping_timeout: integer): Integer; begin result := DoWinSock_Single(pcHost,port, times_to_ping, ping_interval,ping_timeout, false); end; end. And the example procedure TForm143.event_PingRequest(aSender: TObject; const apcHost: String; aPort: Integer; aConnected: Boolean; aResponseTime: Double); const _Conn : Array[ Boolean ] of String = ('','Connected'); begin Memo1.lines.add( Format('%s:%d %3.4f (%s)', [apcHost, aPort, aResponseTime, _Conn[aConnected]]) ); end; procedure TForm143.FormCreate(Sender: TObject); begin with TdhsTCPping.create do begin OnPingRequest := event_PingRequest; ping('192.168.0.5',80,5,1000,1000); free; end; end; In a TMemo the output looks like: 192.168.0.5:80 17.2611 (Connected) 192.168.0.5:80 0.5438 (Connected) 192.168.0.5:80 0.5148 (Connected) 192.168.0.5:80 0.3728 (Connected) 192.168.0.5:80 0.4739 (Connected) HTH 1 Share this post Link to post
Kas Ob. 121 Posted April 26 Hi @Clément , Well, i couldn't let it go, as that loop is stuck in my head and it need fixing. Firstly and as a rule of thumb in this era always disable Nagle algorithm TCP_NODELAY, even if Windows says it will be disabled by default https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt Secondly : That "While not done do " loop is confusing, what is the point of it ? it is acting as blocking or non blocking ? I mean what will happen if Send retuned an error? then you are spiking the CPU for 200ms with that Sleep(0) over erroneous/broken socket, also it is acting as blocking, then the logic sense is to replace it with more reliable approach. And here the suggestions : 1) Don't Send a zero length TCP (or UDP) packet !, yes it works for now, but trust me there is many firewalls with different flavors and smells, and they will block that as DoS, and it is rightfully they do. 2) Is your 0 length for saving traffic ? if so, then its make less sense, the the packet header for IPv4 and the TCP is over 44 bytes and most likely will be 56 bytes, so send something, if you are checking your own server then send the current time (current tick) and let the server returned it, thus you will have the round trip measured in ms, in my projects i do have similar ping but i do define the protocol as (2+1)*4 then (3+1)*4 bytes, client send its time and the last received server time, the server will add its current time and last received client time and return it, hence both will have live delay, specially for the server it can map the clients latencies. (+1 is for the ping and pong header and its option) 3) Why creating the socket each and every time, create once and reuse it, i always use the same one for the real data traffic, if you are pinging a HTTP server then perform a small request and let the server response with minimal response. 4) Before sending it always better to check for readiness for write, aka writability, so in your code above you can perform select() then and only then perform the send. Share this post Link to post
Clément 148 Posted April 27 (edited) Hi @Kas Ob. Your insight is always welcome. I'm implementing a "Ping" using TCP protocol. (Don't say it out loud !) I'm spending some time with wireshark, trying to get this sequence of packets: -> [SYN] <- [SYN,ACK] -> [ACK] -> [FIN,ACK] <- [FIN,ACK] -> [ACK] I noticed sending a 0 length packet seem to be a step in the right direction. I really don't know what to send.. Probably a glorious "tcp ping packet sent by dhsPinger" message in the final release The "not done" loop is handling both blocking and non-blocking calls. In this case, blocking makes no sense since the connection might take longer than a specified timeout. But the code is so simple and it makes no difference at this time... The "sleep", well, it's the poor man's way of not hogging the CPU in a loop The class need a lot more polishing, I'm focused on getting the right packet sequence. Edited April 27 by Clément Share this post Link to post
Kas Ob. 121 Posted April 27 I do understand, and it will work. Just handle an error after that send to exit the loop, and i want to add this fact: The TCP peer that does close the socket will trigger TIME_WAIT on the remote side, TIME_WAIT is absolutely harmless and in fact it is a good thing, just don't panic if like in this case on server side you see them accumulate. Share this post Link to post
Kas Ob. 121 Posted April 27 Also, i would love to see if there a change in timing in your result with TCP_NODELAY. Share this post Link to post
Clément 148 Posted April 27 (edited) 14 hours ago, Kas Ob. said: Also, i would love to see if there a change in timing in your result with TCP_NODELAY. Using TCP_NODELAY: 192.168.0.5:80 8.1591 (Connected) 192.168.0.5:80 0.3725 (Connected) 192.168.0.5:80 0.3950 (Connected) 192.168.0.5:80 0.5158 (Connected) I made some changes.. but I can send that last ACK. It's driving me nuts. Wire shark is reporting this: But it should show me: I must be messing the connection shutdown / close Edited April 27 by Clément Share this post Link to post
Clément 148 Posted April 27 This is the shutdown procedure. I'm trying everything, but I'm not able to send that last ACK 🤯 function TdhsTCPping.DoShutdownConnection(aSocket: TSocket): Boolean; const DEFAULT_BUFLEN = 1024; var conResult : integer; lErrorCode : Integer; lBufferRead : Array of byte; lBytesRead : Integer; Done : Boolean; begin Result := false; conResult := shutdown(aSocket, SD_SEND ); // Shutdown connection since no more data will be sent if (conResult = SOCKET_ERROR) then begin lErrorCode := WSAGetLastError(); raise EdhsTCPPing_SocketError.CreateFmt('shutdown SD_SEND failed with error: %d/0x%X', [lErrorCode,lErrorCode]); end; // Receive until the peer closes the connection. { SetLength(lBufferRead, DEFAULT_BUFLEN ); done := false; while (not done) do begin conResult := recv(aSocket,lBufferRead[0],DEFAULT_BUFLEN,0); case conResult of SOCKET_ERROR : begin lErrorCode := WSAGetLastError; if lErrorCode <> WSAEWOULDBLOCK then begin Done := true; DoNotifyPingError('Done!'); end else begin // DoNotifyPingError('Waiting response...'); Sleep(50); // Done := true; end; end; 0 : begin done := true; // Connection closed Result := true; end; else // bytes received!! // Ignore them for now, and keep readinf until the socket closes end; end;} conResult := closesocket(aSocket); if (conResult = SOCKET_ERROR) then begin lErrorCode := WSAGetLastError(); raise EdhsTCPPing_SocketClose.CreateFmt('CloseSocket failed with error: %d/0x%X', [lErrorCode,lErrorCode]); end else Result := true; end; Some say I must read into a buffer while receiving "WSAEWOULDBLOCK", but this is loop never ends as I keep receiving this error all the time. If I set the buffer length to 64k eventually I get a WSAECONNRESET but Wiresharks reports 7 to 10 TCP Retransmission before the [RST,ACK] Is there any option I must when configuring the socket to close that connection gracefully? Share this post Link to post
Kas Ob. 121 Posted April 28 9 hours ago, Clément said: I must be messing the connection shutdown / close No, you are not ! I cannot compile your code, but have to point that SACK (Selective Acknowledgements) is enabled and this option might solve your problem Disable delayed ACK like Remy suggestion https://stackoverflow.com/questions/55034112/c-disable-delayed-ack-on-windows But away from that, i highly recommend to use Select before any read or receive or even close, select is your best friend and guidance for these ACK, select will trigger sending pending ACKs. Now to the real cause : you have to know this very important fact about shutdown( , SD_SEND ): It will send FIN and wait for ACK but will with high probability prevent sending any further traffic even ACK !!!!, this behavior with ACK has being changed over many Windows versions and it is different from OS to OS. Also your Wireshark capture is is only for the client, this is half useful, you will get the better picture by capture the traffic in both side, so the ACK was an acknowledgment for what exactly ? notice the failed one and retransmitted is the the ACK with SEQ=1 so the failed to sent is the FIN not the ACK, the packet before the last one. Share this post Link to post
Kas Ob. 121 Posted April 28 @Clément Try this please: unit uTCPPortCheckBlocking; interface uses Windows, Winsock2; function WSAInitialize(MajorVersion, MinorVerion: Integer): Boolean; function WSADeInitialize: Boolean; function CheckTCPPortBlocking(const IP: string; Port: Integer; out TimeMS: Integer): Boolean; implementation function CheckTCPPortBlocking(const IP: string; Port: Integer; out TimeMS: Integer): Boolean; var s: TSocket; Addr: TSockAddrIn; SAddr: TSockAddr absolute Addr; QPC1, QPC2, QPF: Int64; begin Result := False; s := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if s = INVALID_SOCKET then Exit; Addr.sin_family := AF_INET; Addr.sin_addr.S_addr := inet_addr(PAnsiChar(AnsiString(IP))); Addr.sin_port := htons(Port); QueryPerformanceFrequency(QPF); QueryPerformanceCounter(QPC1); Result := connect(s, SAddr, SizeOf(SAddr)) <> SOCKET_ERROR; QueryPerformanceCounter(QPC2); TimeMS := (QPC2 - QPC1) div (QPF div 1000); if Result 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. This is blocking mode, and you can reduce the timeout before connect to 1 second, And i really angry now as i can't disable SACK for connect !, i not only sure it was disabled for connect but i had to enforce enabling it with my 5 years old project when i needed to deep analyze FileZilla SSH fast behavior and replicate it with my code, back then i found SACK played huge role in achieving stable 8MB/s of file downloading over 100mbps connection between Ukraine and France with RTC and SecureBlackBox. But now with my current Windows (10.0.19045.4291) it is enabled and can't be disabled as i can see, back then my Windows 10 was version 17something. Anyway: the code above is simple stupid but will do the will perform fast without the need to send or perform recv it will detect if remote port is listening and i think this is what you are looking for. This is my result with google IP var PortScanRes: Boolean; Time: Integer; begin PortScanRes := CheckTCPPortBlocking('142.251.36.46', 80, Time); Memo1.Lines.Add(BoolToStr(PortScanRes, True) + ' ' + IntToStr(Time)); Quote True 42 True 41 True 42 True 42 True 41 True 42 True 41 True 42 Share this post Link to post
Clément 148 Posted April 28 7 hours ago, Kas Ob. said: No, you are not ! I cannot compile your code, but have to point that SACK (Selective Acknowledgements) is enabled and this option might solve your problem Disable delayed ACK like Remy suggestion https://stackoverflow.com/questions/55034112/c-disable-delayed-ack-on-windows But away from that, i highly recommend to use Select before any read or receive or even close, select is your best friend and guidance for these ACK, select will trigger sending pending ACKs. Now to the real cause : you have to know this very important fact about shutdown( , SD_SEND ): It will send FIN and wait for ACK but will with high probability prevent sending any further traffic even ACK !!!!, this behavior with ACK has being changed over many Windows versions and it is different from OS to OS. Also your Wireshark capture is is only for the client, this is half useful, you will get the better picture by capture the traffic in both side, so the ACK was an acknowledgment for what exactly ? notice the failed one and retransmitted is the the ACK with SEQ=1 so the failed to sent is the FIN not the ACK, the packet before the last one. Still no Joy. For some reason SACK_PERM is still there. I just can't understand whats wrong. It is pinging all right, but that last ACK is missing and TCP Retransmission goes on for a few ms... In case you need to have some sunday fun day no ACK day, I attached a sample project. tcpPing_test.zip Share this post Link to post
Remy Lebeau 1402 Posted April 29 12 hours ago, Kas Ob. said: @Clément Try this please: You are leaking the socket handle if connect() fails. You have to close the handle if socket() succeeds, regardless of whether connect() succeeds. 12 hours ago, Kas Ob. said: This is blocking mode, and you can reduce the timeout before connect to 1 second, You can't reduce (or even set) the timeout on a blocking connect(). You have to use a non-blocking connect() and then use select() or equivalent to implement the timeout. 12 hours ago, Kas Ob. said: And i really angry now as i can't disable SACK for connect ! AFAIK, you can't disable SACK on a per-socket basis. You have to use the Registry or command-line netsh utility to disable it globally: https://superuser.com/questions/1808254/how-to-disable-tcp-sack-in-windows-xp https://www.linkedin.com/advice/3/how-can-tcp-selective-acknowledgment-sack-improve-heatc 1 Share this post Link to post
Kas Ob. 121 Posted April 29 6 hours ago, Remy Lebeau said: You are leaking the socket handle if connect() fails. You have to close the handle if socket() succeeds, regardless of whether connect() succeeds. Thank you for pointing this. 6 hours ago, Remy Lebeau said: You can't reduce (or even set) the timeout on a blocking connect(). You have to use a non-blocking connect() and then use select() or equivalent to implement the timeout. Yup, you are right there as i can't remember trying this on blocking connect and it seems Windows still doesn't support TCP_USER_TIMEOUT even it was RFCed since 2009 https://datatracker.ietf.org/doc/html/rfc5482 Thank you again. 6 hours ago, Remy Lebeau said: AFAIK, you can't disable SACK on a per-socket basis. You have to use the Registry or command-line netsh utility to disable it globally: SACK can be disabled per socket, that i am sure of, and you can test it, but from what i can see PERM_SACK is always globally available either disabled or enabled, PERM_SACK does allow the peer to use and utilize SACK. Share this post Link to post
Kas Ob. 121 Posted April 29 @Clément Try this non blocking refined code with leakage : unit uTCPPortCheckNonBlocking; 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 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); 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. the result and that is clean WireShark view, don't you think ? also the timing is very close the ICMP ping, adjust CHECKPOINT_TIMEOUT_MS, in the above code it is 1 sec. Now to the real question because i still don't understand you are you trying to achieve, see, i will put my logic circulating in my brain, and please either correct me ( show me what i am missing ) or try be convinced with different approach. Trying to utilize TCP as ping somehow, is the point is to keep the connection alive then this will not help at all you need to 1) Use the same TCP connection you are trying to keep alive, meaning adding specific protocol Ping/Pong between you Client/Server (explained earlier), or you don't need to keep a connection alive you need to check server availability on specific port, then fine, you need to 2) A simple Connect and like (1) or, connect and then disconnect like the samples above, but in this case you don't need to send anything or wait to receive, so sending zero length data is like calling for undefined behavior or unknown consequences, like as i mentioned firewalls interactions or in this case if PERM_SACK is always there and signaling the peer (server) is it OK to selectively send ACK, then server after connect and ACK the connection it might hold on ACK after that zero length data packet, because it is within its right, keep it as much as 300ms may be, while answering your packet with another zero packet hence your client received an answer but didn't receive ACK for the send, this will cause confusion. 3) you don't want to just detect listening port ( aka open port and port scan style), you want to check the server logic part is answering requests then simple connect and disconnect is not suitable and you need to 4) Connect and send something not zero length to avoid (2) then we returned to (1) or (2) again because of (3) This loop that is bugging me. Anyway the code in this post is working fine and it is non blocking and will detect running server on specific TCP port, if you want to send and receive then the change is minimal: Repeat the select loop twice, the first to check readiness for write, the same loop above, then perform send, then another loop but for read instead of write, you need FDR just like FDW without FDW, you can use the same variable, after that you perform recv then close, do not call shutdown, and to be perfect then another loop after recv to check for read same as the first loop, and that it is. Hope that helps. Share this post Link to post
Clément 148 Posted May 1 Ok... This is the final version. tcpPing_test.zip I had to make some changes. Your sample helped a lot @Kas Ob. ! I had to remove TCP_NODELAY. Even in your sample, using this fellow messed up the last ACK leading to the retransmission sequence. I had to change some calls and used "TAddrInfo" instead of "TAddrInfoW". In the final example, I manage to make it work with both, but kept the TaddrInfo call. I remove the call to "bind". I'm no longer using "send" but "select". The class might need some polishing, but I believe it will be an example of using vanilla sockets... (good or bad) 😉 At least I got the sequence right, and I believe the application is not leaking any handles HTH, Clément Share this post Link to post