[Question] Handling concurrent callers connections
Hello everyone!
I'm facing an issue where multiple clients, each with unique stream IDs, connect concurrently.
I need to accept or reject these connections based on their stream ID, but determining what to do takes some time. I want to avoid blocking the thread managing srt_listen_callback, because (if I understand correctly) blocking it would prevent any other client from connecting.
I noticed that the second argument in srt_listen_callback is the SRTSOCKET for the newly connected client. My plan was to link this socket with the stream ID provided in the handshake packet extension, also available in the callback. Later, when the decision whether or not to accept the client was had been made, I wanted to accept or reject the client. It seems impossible to do so because:
- I believe there is no way to reject client other than returning
-1from the acceptHook installed withsrt_listen_callback. - judging on the
srt_accept()implementation, callingsrt_accept()function always accepts the first socket from them_QueuedSocketsand there is no way to specify which socket should be accepted.
Is there anything that prevents us from accepting/rejecting the client sockets in a arbitrary order? (i.e. wouldn't it be possible to have srt_accept(SRTSOCKET ns) and srt_reject(SRTSOCKET ns) function that could be called asynchronously to the thread managing the listening socket)?
Or perhaps I am missing something and there is some other way to handle my scenario with multiple concurrent clients?
Best wishes!
Direct usage of the listener callback is only possible if this decision can be made quickly and without any extensive operations because anything that takes time holds up the receiver thread in the multiplexer that handles the connection - and on the listener side this very multiplexer handles also all sockets accepted off this listener, so you merely hold up reading from every single socket this way.
The only sensible idea is to use one intermediate connection, might also be over the same port (this isn't actually necessary, but might be used as an additional verification step on the listener side).
- Make the connection that is about to "ask for permission"; the query should be specifically defined as such int he StreamID string and with whatever other data are required for verification. This connection should be always accepted, but the only data interchanged here should be those required for further verification. The result should be a token sent back to the caller side. The verification then take as much time as it needs and in the end it should send back the token - whether allowing or not allowing for connection.
- The caller side should receive the token and then use it in the StreamID. All you have to do in the receiving application here is to verify the token whether it matches the previous request and whether the source IP address and port are the ones from which the original request was made.
If you want to improve security here, you might also send the public key in the caller's request so that the token returns encrypted.
If you would like to use this kind of workflow, please also use the standard key definitions from docs/features/access-control.md (the s and t keys specifically).
(Just additional info: I have informed myself specifically if there could be some malicious program that traces the UDP packets from your caller, could stop them, take over the contents with the token and spoof the IP address and port to that of your caller so that it makes the connection - so, technically this spoofing could be possible, but normally every ISP would not let that dog out :) ).
Thanks for the comprehensive explanation! So, if I get it right, it means that a client must as well comply to this "two-connections" authorization protocol you described but the clients are not required to support it - is that true?
In my case I wanted users to connect to my server using e.g. OBS (which, AFAIK, doesn't follow that "two-connections" authorization protocol). I wanted users to obtain a stream key and a passphrase via a side channel (such as an exchange of HTTP requests with my service). User would then send the stream key as a part of the stream ID, and the communication would be encrypted with the use of that pre-shared passphrase. It means that on the server side I would need to resolve the passphrase for given stream key in the srt_listen_callback so that to either reject the stream or set the socket's passphrase. Can I do any better than try to resolve the passphrase as fast as possible, so that not to block the receiver thread?
The only thing that comes to my mind in such a situation is to have the password assignments cached for some time, long enough for repeated connections, but short enough in case when you would like to revoke the access or change the passphrase. Then, in case of cold cache the connection will be always rejected, but the rejection reason would be the one suggesting that it can't process the request at the moment (SRT_REJX_OVERLOAD is the only one that comes to my mind, see Rejection codes). But once this is done, it should warm up the cache and keep it warm for some short time, in which you'd expect that it connect again - and this way the next connection should be processed already according to the rules (if it retries too fast, of course, you return the same rejection again). So, the application this way would have to connect twice, and wait a few seconds after the first failure before retrying. Not so elegant a solution, but it should work. And at least it doesn't require specific protocol handling in both sides.
I see, thanks for sharing that idea, it seems like it has a chance to work with most of the client implementations.
One final question I have - why does the decision about whether to accept or reject the connection cannot be postponed? Is it the matter of the internal libsrt implementation, or more like some protocol specific requirement (something like that the client needs to be immediately informed about acceptance or rejection of the connection)?
I am asking as I believe it's quite a common case that the decision cannot be made instantly and the workarounds seem to assume that the client behave in some more or less non-standard way (i.e. that it does the "two-connections" authorization or that it retries on SRT_REJX_OVERLOAD rejection code).