On the below piece of code i have some related questions
var
UseProxy : Boolean = True;
....
var
IdUDPClient : TIdUDPClient;
sText : string;
begin
IdUDPClient := TIdUDPClient.Create(nil);
try
IdUDPClient.Host := '10.10.10.10';
IdUDPClient.Port := 5555;
if UseProxy then begin
IdUDPClient.TransparentProxy.Enabled := True;
IdUDPClient.TransparentProxy.Host := '20.20.20.20';
IdUDPClient.TransparentProxy.Port := 8080;
IdUDPClient.OpenProxy;
end;
try
IdUDPClient.Connect;
except
Writeln('Connect Error.');
end;
if IdUDPClient.Connected then
Writeln('Connected')
else begin
Writeln('Not Connected');
Exit;
end;
try
IdUDPClient.Send('Foo');
try
sText := IdUDPClient.ReceiveString(1000);
Writeln('Received: ', sText);
except
Writeln('Receive Error.');
end;
except
Writeln('Send Error.');
end;
if UseProxy then
IdUDPClient.CloseProxy;
finally
IdUDPClient.Free;
end;
Readln;
end.
Why the try...except block is not catching errors on the UDP
client even if the Host is not reachable nor the port is closed e.g. IdUDPClient.Connected always True?
When using the proxy i'm not sure if my implementation is correct above
because if UseProxy is True the IdUDPClient try to directly send
the request to 10.10.10.10not thru the proxy server. How i can fix this? what i'm doing wrong?
My test scenario for the proxy was as follow:
My PC IP 30.30.30.30
UDP Server 10.10.10.10
Proxy Sever (Socks 5) 20.20.20.20
30.30.30.0/24 cant reach 10.10.10.10 but 20.20.20.20 does.
if my PC can access the UDP server directly and i put fake proxy server (unused IP with random port) to the client. the client can reach the UDP server which is not supposed to be reached because the proxy is down. How i cant prevent such scenario ?
UDP is connection-less, so Connected cannot tell you whether the remote peer is reachable or not.
"Connecting" a UDP socket simply assigns a local association with the peer IP/Port. That way, outbound packets are sent only to that peer, and only packets received from that peer are accepted. Nothing more. There is no actual connection, like in TCP.
And unlike TCP, sending a UDP packet just dumps the packet onto the network, there is no acknowledgement that the packet ever reaches the peer. Acks have to be implemented at the application layer.
As for exceptions, they are only raised when an actual error occurs. In UDP, the only way an unreachable Host would cause socket errors is if the network sends back an ICMP packet to indicate the Host is not reachable. A "connected" UDP socket will receive such packets internally and start reporting failures on subsequent reads/sends with the same peer. So, in your example, the call to Send() will never raise an error about not being about to reach the Host, because it simply does not know. ReceiveString() has a better chance of reporting such errors, however the way it is currently implemented, it would likely just ignore them because it checks if the socket is readable (has a pending UDP packet) before actually reading it. A ICMP packet may not make the socket enter a readable state.
Since you are specifying a timeout on ReceiveString(), you will just have to assume the peer is gone if the timeout elapses without receiving an actual string.
Your code is bypassing the proxy because you are not setting up the TransparentProxy correctly.
When you access the TransparentProxy property, if no proxy component is currently assigned then the property getter creates an internal TIdSocksInfo object. TIdSocksInfo has a Version property that defaults to svNoSocks. Since you are trying to connect through a SOCKS v5 proxy, you need to set the Version to svSocks5 instead:
if UseProxy then begin
(IdUDPClient.TransparentProxy as TIdSocksInfo).Version := svSocks5; // <--
IdUDPClient.TransparentProxy.Host := '20.20.20.20';
IdUDPClient.TransparentProxy.Port := 8080;
end;
The TransparentProxy.Enabled property setter is not used with TIdSocksInfo at all, it is a no-op. However, the Enabled property getter returns True/False depending on the value of the TIdSocksInfo.Version property.
And you don't need to call OpenProxy() and CloseProxy() manually, TIdUDPClient.Connect() and TIdUDPClient.Disconnect() will handle that for you when the TransparentProxy is enabled.
Now, with all of that said, try this:
var
UseProxy : Boolean = True;
...
var
IdUDPClient : TIdUDPClient;
IdSocksInfo : TIdSocksInfo;
sText : string;
begin
IdUDPClient := TIdUDPClient.Create(nil);
try
IdUDPClient.Host := '10.10.10.10';
IdUDPClient.Port := 5555;
if UseProxy then begin
IdSocksInfo := TIdSocksInfo.Create(IdUDPClient);
IdSocksInfo.Version := svSocks5;
IdSocksInfo.Host := '20.20.20.20';
IdSocksInfo.Port := 8080;
IdUDPClient.TransparentProxy := IdSocksInfo;
end;
try
IdUDPClient.Connect;
except
on E: Exception do begin
Writeln('Connect Error: ', E.Message);
Exit;
end;
end;
try
Writeln('Connected.');
try
IdUDPClient.Send('Foo');
except
on E: Exception do begin
Writeln('Send Error: ', E.Message);
Exit;
end;
end;
try
sText := IdUDPClient.ReceiveString(1000);
if sText <> '' then
Writeln('Received: ', sText)
else
Writeln('Nothing Received after 1 second.');
except
on E: Exception do begin
Writeln('Receive Error: ', E.Message);
end;
end;
finally
IdUDPClient.Disconnect;
end;
finally
IdUDPClient.Free;
end;
end.
Related
The Environment
I've created a web server in Delphi using Indy component TidHTTPServer. I'm using Delphi XE2 which came with Indy version 10.5.8. The
server is running as a desktop app with a form that displays a log of the connections and their requests. It is running on Windows 7
Professional. Requests are for SQL data from a Firebird database. The response is JSON. All traffic is HTTP.
The Challenge
When I was testing it with a small number of users everything worked great. Now that I have rolled it out to about 400 users there are
communication problems. The server stops responding to requests and the only way I can get it to respond again is to reboot the machine it is running on and then restart it. The need to reboot occurs more frequently during
high volume times.
The Symptoms
Using Windows netstat I have noticed that whenever a TCP connection of type CLOSE_WAIT occurs the server stops responding to requests and I have to reboot again
The Test Procedure
I have been able to simulate this hanging even when there is no traffic on the server. I created a web page that sends multiple requests with
a delay between each request.
The web page let's me specify the number of requests to make, how long to wait between each request, and how long to wait before timing out. Even at one millisecond between requests the server seems to respond without issue.
The Test Results
If I set the time out period of each request to a very small number, like 1 msec, I can make my Delphi HTTP Server hang. At a 1 msec timeout requests to my server fail every time, as I would expect. The time out is so short my server can't possibly respond quickly enough.
What I don't understand is that after I force this timeout at the client side, even a relatively small number of requests (fewer than 50), my Delphi web server no longer responds to any requests. When I run netstat on the server machine there are a number of CLOSE_WAIT socket connections. Even after an hour and after closing my server the CLOSE_WAIT socket connections persist.
The Questions
What is going on? Why does my Delphi Indy idHTTPServer stop responding when there are (even just one) CLOSE_WAIT socket connection? The CLOSE_WAITs don't go away and the server does not start responding again. I have to reboot.
What am I not doing?
Here is the results of netstat command showing CLOSE_WAITs:
C:\Windows\system32>netstat -abn | findstr 62000
TCP 0.0.0.0:62000 0.0.0.0:0 LISTENING
TCP 10.1.1.13:62000 9.49.1.3:57036 TIME_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57162 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57215 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57244 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57263 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57279 ESTABLISHED
TCP 10.1.1.13:62000 104.236.216.73:59051 ESTABLISHED
Here is the essence of my web server:
unit MyWebServer;
interface
Uses
...
Type
TfrmWebServer = class(TForm)
...
IdHTTPServer: TIdHTTPServer;
...
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure IdHTTPServerDisconnect(AContext: TIdContext);
procedure btnStartClick(Sender: TObject);
...
dbFirebird : TIBDatabase;
txFireird : TIBTransaction;
...
private
function CreateSomeResponseStringData: string;
end;
implementation
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and proit to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
{start the web server}
IdHTTPServer.Active := TRUE;
...
dbFirebird.Transactrion := txFirebird;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
qryFirebird : TIBSql;
function CreateSomeResponseStringData: string;
begin
qryFirebird := NIL;
qryFirebird := TIBSql.Create(IdHTTPServer);
qryFirebird.Database := dbFirebird;
dbFirebird.Connected := FALSE;
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
AResponseInfo.ContentText := CreateSomeResponseStringData;
{Clean up: What do I do here to make sure that the connection that was served is:
- properly closed so that I don't run out of resourses?
- anything that needs to be cleaned up is freed so no memory leaks
- TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
except;
AResponseInfo.ContentText := CreateAnErrorResponse;
end;
qryFirebird.Free;
end;
procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
begin
{Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of
TIME_WAIT tcp/ip connections accumulate. even after the app is closed}
AContext.Connection.Disconnect;
end;
end.
There are at least two major issues with this code that could cause the crashing:
The database and transaction objects are global to all threads created by IdHTTPServer. When you disconnect the database it would disconnect for all threads.
If there is a run time error assigning content text this line AResponseInfo.ContentText := CreateAnErrorResponse; is not in an exception block.
Here is how I would fix this:
...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and port to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Default.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
{start the web server}
IdHTTPServer.Active := TRUE;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
{make these local to each thread}
qryFirebird : TIBSql;
dbFirebird : TIBDatabase;
txFirebird : TIBTransaction;
function CreateSomeResponseStringData: string;
begin
dbFirebird := TIBDatbase.Create(IdHTTPServer);
txFirebird := TIBTransaction.Create(IdHTTPServer);
qryFirebird := TIBSql.Create(IdHTTPServer);
dbFirebird.Transaction := txFirebird;
qryFirebird.Database := dbFirebird;
...Add params that do the log in to database
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
try
...
AResponseInfo.ContentText := CreateSomeResponseStringData;
...
except;
try
AResponseInfo.ContentText := CreateAnErrorResponse;
except
{give up}
end;
end;
finaly
qryFirebird.Free;
dbFirebird.Free;
txFirebird.Free;
end;
end;
end.
I use Delphi 10.1 Update 2 and Indy 10.6.2.5341.
We experience access violations in SSL_accept. This happens if a TIdTCPServer is setup using SSL and there is an open connection that has NOT yet negotiated TLS if the TIdTCPServer is stopped.
This looks like a problem in Libssl32 or Indy. This can be simply reproduced with the following code and Putty using a RAW connection. Does anyone knows a solution (or workaround) to prevent these crashes?
procedure TSslCrash.HandlerOnExecute(AContext: TIdContext);
begin
//
end;
procedure TSslCrash.HandlerOnConnect(AContext: TIdContext);
begin
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := False;
end;
procedure TSslCrash.ButtonStartClick(Sender: TObject);
begin
LServer := TIdTCPServer.Create;
LIOHandler := TIdServerIOHandlerSSLOpenSSL.Create;
LIOHandler.SSLOptions.Mode := sslmServer;
LIOHandler.SSLOptions.Method := sslvTLSv1_2;
LIOHandler.SSLOptions.VerifyMode := [];
LIOHandler.SSLOptions.VerifyDepth := 0;
LIOHandler.SSLOptions.CertFile := 'localhost.crt';
LIOHandler.SSLOptions.RootCertFile := 'localhost.crt';
LIOHandler.SSLOptions.KeyFile := 'localhost.key';
LServer.Bindings.Add.Port := 10000;
LServer.IOHandler := LIOHandler;
LServer.OnExecute := HandlerOnExecute;
LServer.OnConnect := HandlerOnConnect;
LServer.Active := True;
//Now open a RAW connection with Putty on port 10000 and keep it open
end;
procedure TSslCrash.ButtonStopClick(Sender: TObject);
begin
if Assigned(LServer) then begin
LServer.Active := False; //This causes an AV in TIdSSLSocket.Accept
FreeAndNil(LIOHandler);
FreeAndNil(LServer);
end;
end;
When Putty is connected in Raw mode, there is no SSL/TLS handshake performed, so SSL_accept() is stuck waiting for a handshake request that never arrives.
When TIdTCPServer is being deactivated, it disconnects active socket connections, failing any blocking socket operations in progress in other threads. In the case of SSL_accept(), that should unblock it so it can exit with an error code that TIdSSLSocket.Accept() can then detect and wrap into a raised exception (EIdOSSLUnderlyingCryptoError, EIdOSSLAcceptError, EIdSocketError, etc depending on the nature of the error code) in the context of the client thread that is waiting for the handshake to complete.
However, when TIdTCPServer is disconnecting a socket connection during deactivation, TIdTCPConnection.Disconnect() is called, which calls TIdIOHandler.Close(), which TIdSSLIOhandlerSocketOpenSSL has overridden to free its internal TIdSSLSocket object - the same object that is calling SSL_accept(). So it is quite likely that the underlying OpenSSL SSL object is being freed in TIdSSLSocket.Destroy() (which calls SSL_shutdown() and SSL_free()) in the context of the deactivating thread while still actively being used in TIdSSLObject.Accept() (which calls SSL_accept()) in the context of the client thread, thus causing an Access Violation.
There is not much that can be done about this without altering Indy's source code. For instance, maybe change TIdCustomTCPServer.DoTerminateContext() to call AContext.Binding.CloseSocket() instead of AContext.Connection.Disconnect(False) so the IOHandler itself is not closed, just the underlying socket (similar to what TIdCustomTCPServer.StopListening() does when terminating its listening accept() threads).
I have opened a ticket in Indy's issue tracker for you:
#218: Access Violation in SSL_accept() when deactivating TIdTCPServer
I have a TIdPop3Server in one application that has a IdServerIOHandlerSSLOpenSSL1 attached to it and retrieves emails and sends them to a TIdPop3 client in another application (having TIdSSLIOHandlerSocketOpenSSL attached to it). Everything's fine when the connections are made insecure using port 110. But when I try to use SSL connection through port 995 I get error Connection Closed Gracefully after connect attemp from the client fails. This is my Pop3SeverOnConnect event :
procedure TMainForm.Pop3ServerConnect(AContext: TIdContext);
begin
if (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase) then
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough :=
(AContext.Binding.Port <> 995);
showmessage('SSL connection made!');
end;
And this is the client-side :
procedure TMainForm.btnCheckMailBoxClick(Sender: TObject);
begin
IdSSLIOHandlerSocketOpenSSL1.PassThrough := False;
POP3Client.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
with POP3Client do begin
AuthType := patUserPass;
Host := myHost;
UserName := myUserName;
Password := myPass;
Port := myPort;
end;
try
POP3Client.Connect;
Except on e : Exception do
showmessage('error=' + e.Message);
end;
// code for retrieving message data
end;
And I always get an exception from Pop3Client.Connect like I've already mentioned above (The message SSL connection made! in the server application never shows up). If I use however another mail client like for example Mozilla Thunderbird I achieve a successful SSL connection for port 995. So the problem should be somewhere in the client's procedure but who knows - that's why I'm asking you guys for help.
In your client code, you need to set the TIdPOP3.UseTLS property instead of the TIdSSLIOHandlerSocketOpenSSL.PassThrough property directly, eg:
procedure TMainForm.btnCheckMailBoxClick(Sender: TObject);
begin
with POP3Client do
begin
IOHandler := IdSSLIOHandlerSocketOpenSSL1;
AuthType := patUserPass;
UseTLS := utUseImplicitTLS; // <-- here
Host := myHost;
UserName := myUserName;
Password := myPass;
Port := myPort;
end;
try
POP3Client.Connect;
try
// code for retrieving message data
finally
POP3Client.Disconnect;
end;
except
on e : Exception do
ShowMessage('error=' + e.Message);
end;
end;
In your server code, you need to get rid of the ShowMessage(). TIdPOP3Server is multi-threaded, the OnConnect event is fired in the context of a worker thread, and ShowMessage() is not thread-safe. If you must display a popup message, use Windows.MessageBox() instead.
I am testing IndyFTP to upload a file to a server. The file is uploaded but has 0 bytes because there is a EIdAccessTimeout exception - 'Accept timed out". How can I prevent the exception? Is my code incorrect? The code is shown below:
procedure TForm1.FTPUpload1Click(Sender: TObject);
{ Indy FTP Upload. }
var
iHost: string;
iUsername: string;
iPassword: string;
iFolder: string;
iSourceFile: string;
iDestFile: string;
iAppend: boolean;
iStartPos: Int64;
begin
iHost := FTPHost1.Text;
iUsername := Username1.Text;
iPassword := Password1.Text;
iFolder := ServerFolder1.Text;
if FileExists(SourceFile1.Text) then
iSourceFile := SourceFile1.Text
else
Exit;
if FileExists(SourceFile1.Text) then
iDestFile := ExtractFileName(SourceFile1.Text)
else
Exit;
iAppend := False;
iStartPos := -1;
IdFTP1.Host := iHost;
IdFTP1.Username := iUsername;
IdFTP1.Password := iPassword;
IdFTP1.Connect;
IdFTP1.TransferType := ftBinary;
IdFTP1.Put(iSourceFile);
IdFTP1.Disconnect;
end;
There are some unused vars listed because I am just learning and have not used some of the parameters yet.
Most likely, your FTP client is set to ACTIVE mode, so this error means that after a successful login to the FTP server, the "reverse" connection couldn't be established (the file transfer).
In active mode FTP the client connects from a random unprivileged port
(N > 1023) to the FTP server's command port, port 21. Then, the client
starts listening to port N+1 and sends the FTP command PORT N+1 to the
FTP server. The server will then connect back to the client's
specified data port from its local data port, which is port 20.
Active FTP vs. Passive FTP, a Definitive Explanation
You can set to passive mode this way:
IdFTP1.Passive := True;
EDIT
In addition, use try-except-finally blocks, so you can do some error handling. Something like:
try
IdFTP1.Connect;
try
IdFTP1.Put(...);
finally
IdFTP1.Disconnect;
end;
except
// couldn't connect
end;
I have a TIdUDPClient created like this:
myUDPClient := TIdUDPClient.Create(nil);
myUDPClient.ReceiveTimeout := 500;
myUDPClient.Binding.Port := 300;
myUDPClient.Active := True;
The binding IP is not specified because I have 3 ethernet adapters with dynamic addressing (192.168.x.x, 10.10.x.x and 172.16.x.x), so the binding IP is generated by Indy and it is 0.0.0.0.
When I receive a packet I can determine the sender IP, but I cannot determine the local IP on which I received the packet (not 0.0.0.0, but one of the 3 IPs assigned to my computer).
Do you know any method to accomplish this?
Do not assign to Binding.Port directly, assign to myUDPClient.BoundPort instead and let TIdUDPClient assign to Binding.Port internally.
Since you are binding to 0.0.0.0 locally, there is no way to determine the destination IP when using TIdUDPClient to read the packet, since it uses the socket API recvfrom() function, hich does not report that info. The socket API getsockname() function will report 0.0.0.0 because that is what the socket is really bound to.
On Windows XP+, you can use GStack.ReceiveMsg() instead to receive packets. It has a TIdPacketInfo output parameter, which contains has a DestIP field (you have to use Binding.SetSockOpt() to enable ReceiveMsg() to collect that info), eg:
...
myUDPClient.Active := True;
myUDPClient.Binding.SetSockOpt(IPPROTO_IP, IP_PKTINFO, 1);
var
Buffer: TIdBytes;
BufLen: LongWord;
PktInfo: TIdPacketInfo;
begin
SetLength(Buffer, ...);
BufLen := GStack.ReceiveMsg(myUDPClient.Binding.Handle, Buffer, PktInfo);
// use Buffer up to BufLen bytes
// PktInfo.DestIP will be the IP that received the packet
end;
Alternatively, you can switch to TIdUDPServer instead, and create a separate entry in its Bindings collection for each local IP you want to receive packets on:
myUDPServer := TIdUDPServer.Create(nil);
myUDPServer.DefaultPort := 300;
myUDPServer.OnUDPRead := UDPRead;
LocalIPs := TStringList.Create;
try
GStack.AddLocalAddressesToList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.IP := LocalIPs[I];
finally
LocalIPs.Free;
end;
{
Or this, if you are using an up-to-date Indy 10 snapshot:
LocalIPs := TIdStackLocalAddressList.Create;
try
GStack.GetLocalAddressList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.SetBinding(LocalIPs[I].IPAddress, myUDPServer.DefaultPort, LocalIPs[I].IPVersion);
finally
LocalIPs.Free;
end;
}
myUDPServer.Active := True;
procedure TMyClass.DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
// ABinding.IP is the local IP address that received this packet...
end;