Jump to content

FearDC

Members
  • Content Count

    28
  • Joined

  • Last visited

Everything posted by FearDC

  1. Hi. I'm currently working on a heavily-loaded TCP server in Delphi 12 using latest version of Indy 10, which will run on both Windows64 and Linux64. There are over 3.000 concurrent clients connected to the server, every each of them is a persistent TCP connection using custom protocol - request/response based. Each connection is handshaked with as unique user with unique nickname/ID. These requests are properly validated by several helpers like protocol parser, ban list, protocol feature support, flood detection, etc. So they involve shared memory access, hard-drive access, within and outside Indy classes. The requests can be multiple, they can be one at a time, they can be ten or more at a time, from each client. Main priority for the server is to follow the request/response order, otherwise protocol exchange will get out of sync between each connection and server. Connected users are also notified about connection/disconnection of other users. So the actual logical advises I'm seeking is the correct/best way to perform this protocol synchronization, aswell as serving the requests in correct order. I will supply a basic class implementation of my application and write a few comments inside about which does what. Server class: type TOnUserConnect = procedure(AConn: TIdContext) of object; TOnUserDisconnect = procedure(AConn: TIdContext) of object; TOnUserDataIn = procedure(AConn: TIdContext; const AData: String) of object; TOnUserDataOut = procedure(AConn: TIdContext; const AData: String) of object; type // server listener TMyServer = class(TIdCustomTCPServer) protected procedure InitComponent; override; private FOnUserConnect: TOnUserConnect; FOnUserDisconnect: TOnUserDisconnect; FOnUserDataIn: TOnUserDataIn; procedure OnSocketConnect(AConn: TIdContext); // note: thread safety procedure OnSocketDisconnect(AConn: TIdContext); // note: thread safety procedure OnSocketExecute(AConn: TIdContext); // note: thread safety procedure DoParseProtocol(AConn: TIdContext; const AData: String); public LWaitFor: String; // command separator FOnUserDataOut: TOnUserDataOut; property OnUserConnect: TOnUserConnect read FOnUserConnect write FOnUserConnect; property OnUserDisconnect: TOnUserDisconnect read FOnUserDisconnect write FOnUserDisconnect; property OnUserDataIn: TOnUserDataIn read FOnUserDataIn write FOnUserDataIn; property OnUserDataOut: TOnUserDataOut read FOnUserDataOut write FOnUserDataOut; end; procedure TMyServer.InitComponent; begin inherited InitComponent; Self.ContextClass := TMyClient; Self.OnConnect := Self.OnSocketConnect; Self.OnDisconnect := Self.OnSocketDisconnect; Self.OnExecute := Self.OnSocketExecute; // Self.OnUserDataIn is bound below only to show short version, // otherwise it is specified in TMyServer parent class which is // actual protocol parser, user validator, and main worker Self.OnUserDataIn := DoParseProtocol; end; procedure TMyServer.OnSocketConnect(AConn: TIdContext); begin TThread.Queue(nil, procedure begin if Assigned(Self.FOnUserConnect) then Self.FOnUserConnect(AConn); end ); end; procedure TMyServer.OnSocketDisconnect(AConn: TIdContext); begin TThread.Queue(nil, procedure begin if Assigned(Self.FOnUserDisconnect) then Self.FOnUserDisconnect(AConn); end ); end; procedure TMyServer.OnSocketExecute(AConn: TIdContext); var AData: String; begin AConn.Connection.IOHandler.ReadTimeout := 5000; AData := AConn.Connection.IOHandler.WaitFor(LWaitFor); TThread.Queue(nil, procedure begin if Assigned(Self.FOnUserDataIn) then Self.FOnUserDataIn(AConn, AData); end ); end; procedure TMyServer.DoParseProtocol(AConn: TIdContext; const AData: String); var AUser: TMyClient; begin AUser := AConn as TMyClient; if AData.Equals('Validate') then begin AUser.FNick := '<parsed data>'; // below code will send response data to user directly - the connection will be written to, // but note that it's done inside Thread.Queue() - is this correct, or do i need to // step outside and perform the actual write? maybe some kind of internal buffer queue // that will write on OnServerExecute() to each connection? AUser.SendData('Hello'); end else if AData.Equals('<bad command>') then begin // below code will disconnect the user, also inside Thread.Queue() - is this safe from here? AUser.Connection.Disconnect; end; // this is the actual protocol parser, from here user connection will be // verified, protocol parsed, responses written, other helper classes // will check for bans, shared memory access will be performed, file // contents will be read from hard drive and sent back to users, // thread-unsafe objects will be used, users will get disconnected, // basically all the main load will be performed here end; { ... } Client class: type // user connection TMyClient = class(TIdServerContext) protected // private FNick: String; FFeatures: TStrings; procedure SendData(const AData: String); public constructor Create(AConn: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override; destructor Destroy; override; end; constructor TMyClient.Create(AConn: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); begin inherited Create(AConn, AYarn, AList); FNick := ''; FFeatures := TStrings.Create; end; destructor TMyClient.Destroy; begin FFeatures.Free; inherited Destroy; end; procedure TMyClient.SendData(const AData: String); begin if Self.Connection.Connected then begin Self.Connection.IOHandler.Write(AData); if Assigned((Self.Server as TMyServer).FOnUserDataOut) then (Self.Server as TMyServer).FOnUserDataOut(Self, AData); end; end; // in this class connection will be written to, disconnected, etc { ... } I know about Indys internal write buffering, it will be filled with an amount and written when maximum size is reached - this is exactly what I need, so basically I don't need some kind of internal buffer implementation for outbound data. Thread syncing is required to step outside Indys threads in order to access shared data - what ever that could be. I do that, but I'm not sure if the actual write or disconnect against clients is the right place to perform. Well, I hope you get the logic of my server - if not, please let me know, I will describe further. Any advise is appreciated. Regards.
  2. Thank you for help @Remy Lebeau. I will figure out some sort of priority outbound buffer with instant flushing. I might ask more questions, but later in that case. Regards.
  3. Thank you for your reply @Remy Lebeau. I'm having hard time to understand what an "unsolicited" notification/response is - kind of "unexpected"? I will provide you with short example of client to server handshake and further communication. Client to server handshake: Next part is a free communication where client talks first: Another moment is where server talks first notifying everyone that someone enters or leaves: That's it basically. When a client connects, the server creates a separate thread where it handles reads and writes from/to client. It first welcomes the client, exchanges the handshake information and waits for requests from client. Possibly sends public notifications to client. I don't really understand why the above would not work. Or do you mean that client will not be written to while server is reading the client - client thread is frozen and waiting for data from client? In that case there is timeout - is that the delay you are talking about? Sorry, english is not my primary language, so it's not always I understand everything even if I try to translate. Also the logical part is the hardest part for me, even in real life, while writing the code is the easiest. 😛 Regards.
  4. FearDC

    TLS v1.3

    Thank you for your reply @DelphiUdIT. I have been reading on Wiki, but I use D12. Currently Indy does not have 290 package it seems. There is a PR#517, but it mentions 280 too many times (btw I left a review comment for that PR author). I was thinking rather some solution like pull/299#issuecomment-675003145 - atleast for now, until 290 is fully supported. I could be a test case for sure, to help finding issues, if there are any. Anyway @DelphiUdIT, could you please tell more about problems that Indy upgrade caused to your D11? Regards.
  5. FearDC

    [Need help] Linux recv() timeout

    I need to perform simple TCP socket peek during Indy server event. To do this I use recv(), but it stays blocking forever if I don't receive required data, not allowing any further server <> client communication. procedure OnServerBeforeConnect(AContext: TIdContext); var mPeek: Array [0..1] of AnsiChar; mTime, mRes: Integer; begin { // returns error -1 mTime := 5; mRes := SetSockOpt(AContext.Binding.Handle, SOL_SOCKET, SO_RCVTIMEO, mTime, SizeOf(mTime)); } { // nothing happens AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_RCVTIMEO, 5); } // hangs forever if (Recv(AContext.Binding.Handle, mPeek, 2, MSG_PEEK) = 2) and (mPeek[0] = #22) and (mPeek[1] = #03) then TIdSSLIOHandlerSocketOpenSSL(AContext.Connection.IOHandler).PassThrough := False; end; On Windows this works just fine, setting SO_RCVTIMEO helps to exit recv() even if needed data is not received. Please help. Regards.
  6. FearDC

    TLS v1.3

    I'm sorry but I don't understand how to do this properly. I did clone https://github.com/IndySockets/Indy/pull/299 into local directory, added it to library path. I have Delphi 12 and up-to-date IdCTypes.pas and IdGlobal.pas. So what is next? 😛
  7. FearDC

    [Very unsure] Indy vs sgcIndy

    Hi to everyone. Today I've been looking for any official news about TLS 1.3 in Indy (included in latest Delphi CE or later version) and came across a new project that I never seen before, it's called sgcIndy. At first all sounds very nice, OpenSSL 1.1.1 and 3.0.0 support is available - exactly what I need. But then it shows up that sgcIndy don't have source code available for free users - I'm kind of person that actually wants to see what's inside before actually using the third-party stuff. Then I looked again on the website that provides sgcIndy and the menu says "Other products > Indy > Download Indy" - so I get even more unsure, it's a different project, but it's called Indy. Very confusing. As I understand, sgcIndy is not Indy, right? Then what is it @esegece? I mean the original Indy is a free project, but if there is another project that builds on it, shouldn't there be at least a changelog or something that explains exactly what has been changed in the original code? It says that sgcIndy always builds on latest Indy, but do we speak about latest release, latest Git commits, unpulled requests? If sgcIndy had a different name with a simple list of new features, then I would understand. No mean to be rude, anything that is new is welcome on the market, and I really respect that, and I understand that you spend your time rewriting a lot of the code and making sure it works with latest Delphi and OpenSSL. But Indy is free, your project is not, while your project is called Indy, while it's not. I got really confused here. @Remy Lebeau what do you say about all this? I really would like to hear. Maybe I'm just too paranoid. And a question to the rest of community: Has anyone actually tested the free version of sgcIndy, what is the difference, does anything work sketchy at runtime? I don't know, maybe someone paid for full version and actually took a look at code and compared it with Indy? Again, no mean to be rude to anyone. Just asking what's new, what's old, why and why not. Regards.
  8. FearDC

    [Need help] Linux recv() timeout

    Oh, cool. Does IdStackBSDBase have recv() too?
  9. Environment: Indy 10.6.2.0, Delphi 12, Ubuntu 20.04 I'm going completely mad about this, because there is no socket I/O what so ever after accept: Same code works fine on Windows: program LinuxMapper; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, IdMappedPortTCP, IdContext, IdGlobal; type TMyClass = class protected // private mServ: TIdMappedPortTCP; procedure OnServerConnect(AContext: TIdContext); procedure OnServerExecute(AContext: TIdContext); public // end; procedure TMyClass.OnServerConnect(AContext: TIdContext); begin TThread.Queue(nil, procedure begin WriteLn('OnConnect from ' + AContext.Binding.PeerIP + ' to ' + AContext.Binding.Port.ToString); end ); end; procedure TMyClass.OnServerExecute(AContext: TIdContext); begin TThread.Queue(nil, procedure begin WriteLn('OnExecute from ' + AContext.Binding.PeerIP + ' to ' + AContext.Binding.Port.ToString); end ); end; var mSelf: TMyClass; begin try mSelf := TMyClass.Create; mSelf.mServ := TIdMappedPortTCP.Create; mSelf.mServ.OnConnect := mSelf.OnServerConnect; mSelf.mServ.OnExecute := mSelf.OnServerExecute; mSelf.mServ.DefaultPort := 1234; with mSelf.mServ.Bindings.Add do begin IPVersion := Id_IPv4; IP := '1.2.3.4'; Port := 1234; end; mSelf.mServ.MappedHost := '4.3.2.1'; mSelf.mServ.MappedPort := 4321; mSelf.mServ.Active := True; while True do Sleep(100); // .. except on E: Exception do WriteLn(E.ClassName, ': ', E.Message); end; end. Regards.
  10. FearDC

    TIdMappedPortTCP not executing on Linux

    Thank you for hint @Remy Lebeau.
  11. FearDC

    [Need help] Linux recv() timeout

    Finally got it working. type TTimeVal = record tv_sec, tv_usec: LongInt; end; var mTime: TTimeVal; mRes: Integer; begin mTime.tv_sec := 5; mTime.tv_usec := 0; // returns 0 - no error mRes := Posix.SysSocket.SetSockOpt(AContext.Binding.Handle, SOL_SOCKET, SO_RCVTIMEO, &mTime, SizeOf(TTimeVal)); Thank you for hints @Virgo.
  12. FearDC

    [Need help] Linux recv() timeout

    SetSockOpt from Posix.SysSocket/SysSocketAPI.inc The second one is Indys built-in. I know that Posix wants a time struct, but how to do that in Delphi? Regards.
  13. FearDC

    TIdMappedPortTCP not executing on Linux

    Sorry, I was tired yesterday. Everything works as expected! I was simply connecting to wrong mapped address.
  14. Hi. I have a simple question when it comes to ICS SSL/TLS server socket. Is it possible to fallback to non-encrypted communication between a SSL/TLS-encrypted server and a non-encrypted client when such client connects to a such server? The situation is as simple as there is SSL/TLS-encrypted server, it receives a connection from a client who does not support SSL/TLS-encryption. Could this client be able to continue the communication without SSL/TLS-encryption on same port? This does sound wrong, because SSL/TLS-encrypted communication is encrypted from the very beginning. But, there are magic bytes that could be detected before actually starting the actual SSL/TLS handshake > https://github.com/verlihub/tls-proxy/blob/master/proxy.go#L186 < This application managed to do so, so why not trying the same approach using ICS SSL/TLS server socket? I have already tried IcsHosts with BindSslPort and BindNonPort, but it does not work the way I'm expecting. If this magic byte detection could be implemented into ICS, then this suite would be the first one to allow encrypted and non-encpypted communication on same port. This would become the number one solution to non-HTTP protocols that do not support redirects as easy as HTTP protocol does. Regards.
  15. Thank you @Remy Lebeau, you are very helpful as usual! procedure OnServerBeforeConnect(AContext: TIdContext); var mTime: Integer; mPeek: Array [0..1] of AnsiChar; begin AContext.Binding.GetSockOpt(Id_SOL_SOCKET, Id_SO_RCVTIMEO, mTime); AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_RCVTIMEO, 5); if (Recv(AContext.Binding.Handle, mPeek, 2, MSG_PEEK) = 2) and (mPeek[0] = #22) and (mPeek[1] = #03) then // client hello TIdSSLIOHandlerSocketOpenSSL(AContext.Connection.IOHandler).PassThrough := False; AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_RCVTIMEO, mTime); end;
  16. @Remy Lebeau Since ICS does not support Linux yet, I would like to use Indy instead. My application requires TLS 1.3, but I will figure this out later, since I heard that TLS 1.3 is possible with Indy using some modifications. Now to my project.. There is a TCP server listening on incoming connections, it checks whether client supports TLS or not using magic bytes detection as described earlier (client hello > client sends 22+03 bytes on first read), then accepting TLS or passing through, and mapping protocol data to destination server. I have tried using TIdMappedPortTCP, but without success, guess because InputBuffer has already been received by the point where OnExecute is executed. So it seems that I need to clone and modify TIdMappedPortTCP - which I did too. But in order to start proper "PassThrough := False" and "StartSSL" after "AContext.Connection.IOHandler.CheckForDataOnSource(0)" and "AContext.Connection.IOHandler.InputBuffer.PeekByte(0) == 22 and AContext.Connection.IOHandler.InputBuffer.PeekByte(1) = 03", it seems that I need to modify TIdCustomTCPServer aswell, in order to "undo" already received buffer. I'm almost there anyway, but I need your help, since you are expert in Indy. Could you please help me? If something of what I said is unclear, please let me know. I will try to describe even better. Regards.
  17. FearDC

    ICS for Linux?

    So you mean that it is not possible to just install kqueue library + headers on my Linux distro? It has rather be a part of Linux kernel, like on BSD or MacOS? If yes, then what if we conditionally replace kqueue with epoll, like you already started implementing in ICS10, will it work? With other words - is kqueue the last thing that can't get past compilation in ICS, or are there more similar cases? Regards.
  18. FearDC

    ICS for Linux?

    @Fr0sT.Brutal Did you find a workaround for this? I'm on Ubuntu, tried installing libkqueue-dev, event.h is present. ICS 9.0, Delphi 12, OverbyteICSWSocket.
  19. Finally got some time to get back to my project and completed the goal. Thanks for the hints @Remy Lebeau. Here are the results if anyone needs the hack without modifying TSSLWSocketServer class: procedure TMainForm.SSLServerClientCreate(Sender: TObject; Client: TWSocketClient); var mConn: TClient; begin mConn := Client as TClient; mConn.OnDataAvailable := SSLServerDataAvailable; mConn.mTest := False; end; procedure TMainForm.SSLServerClientConnect(Sender: TObject; Client: TWSocketClient; Error: Word); var mConn: TClient; begin mConn := Client as TClient; mConn.SSLEnable := False; end; procedure TMainForm.SSLServerDataAvailable(Sender: TObject; ErrCode: Word); var mConn: TClient; mPeek: TBytes; mData: String; begin mConn := Sender as TClient; if not mConn.mTest then begin // first read SetLength(mPeek, 2); mConn.PeekData(mPeek, 2); mConn.mTest := True; if (mPeek[0] = 22) and (mPeek[1] = 3) then begin // client hello SetLength(mPeek, 0); mConn.SSLEnable := True; mConn.SSLContext := SSLContext; mConn.AcceptSSLHandshake; Exit; end; SetLength(mPeek, 0); end; mData := mConn.ReceiveStr; end; The only thing I'm worried about is memory leak due to not reading connected socket after data available call. At some point I had experience of that issue in another project. Regards.
  20. FearDC

    ICS V9.0 new components and samples

    Great news, thank you! Right now however - https://wiki.overbyte.eu/arch/icsv90-D110.zip - results in 404.
  21. FearDC

    ICS V8.66 announced

    It works! Several of SOCKS5 that I tried were faulty. Thank you!
  22. FearDC

    ICS V8.66 announced

    @Angus Robertson I've been testing the SOCKS5 support on TSslHttpCli this morning, which did not work. OverbyteIcsHttpProt.pas has this code: { V8.62 if proxy URL is passed, parse it as proxy properties } { http://[user[:password]@]host:port } if (Length(FProxyURL) > 6) and (Pos (':', Copy(FProxyURL, 6, 9999)) > 5) then begin ParseURL(FProxyURL, Proto, User, Pass, Host, Port, Path); { pending, check https for SSL proxy, maybe socks for socks proxy } if Proto = 'http' then begin FProxy := Host; FProxyPort := Port; FProxyUsername := User; FProxyPassword := Pass; if (FProxyUsername <> '') and (FProxyPassword <> '') then FProxyAuth := httpAuthBasic; end; end; This was the first attempt, but only "http" is supported. Pending means not implemented? The second attempt was setting TSslHttpCli.SocksServer, TSslHttpCli.SocksPort, TSslHttpCli.SocksLevel. Which ended up in HTTP client connecting to SOCKS IP address and port 443 because the TSslHttpCli.URL is a "https://" URL. All this is really messed up. TSslHttpCli.CtrlSocket is being configured correctly but TSslHttpCli bypasses those settings. Am I doing something wrong? Is there a way to trick the HTTP client? Tried searching Google and this forum for solution. I'm on ICS 8.70.
  23. FearDC

    [Very unsure] Indy vs sgcIndy

    Great, thank you RL.
  24. FearDC

    [Very unsure] Indy vs sgcIndy

    Thank you everyone. Now sgcIndy website displays correct names, so people can distinguish two different projects and stop thinking that Indy has become a commercial project, because it's not. Also I wonder if any of Indy or sgcIndy have fixed the SOCKS4 response issue. SendV4Response returns LResponse[0] := 4 - which is wrong according to > https://en.wikipedia.org/wiki/SOCKS#SOCKS4 - it should be null/zero instead. Aswell as Sendv4Response(AContext, 90) is faulty in HandleConnectV4. @Remy Lebeau I was going to commit this fix a couple of years ago, but did not remember to do so.
  25. Nevermind about ICSHosts right now, it's not what I currently want, I known that it works only with different ports. If I will get interested in ICSHosts, I will create another thread about it. Now back to SSLEnable option. I managed to set it to False at runtime on DataAvailable callback, and actually got both SSL and non-SSL connection working on same port. Now only need to find a way to detect those bytes and set SSLEnable eccordingly. To be continued...
×