Support TCP for protocol messages - #3636
Conversation
5e1a658 to
0ae51e2
Compare
7ad1d1f to
d939e5b
Compare
|
So the next stage of implementation has been achieved: client-side support in the Connect dialog.
It has been tested by using Examples for a directory-enabled server running on port 22120:
Note that
|
|
The next step is to try implementing the connected-mode TCP described here |
| bool bUseTranslation = true; | ||
| bool bCustomPortNumberGiven = false; | ||
| bool bEnableIPv6 = false; | ||
| bool bEnableTcp = false; |
There was a problem hiding this comment.
Since we'll have a long time for the 4.0 release, I'd enable it by default soon (of course once we've tested that the basics work)
There was a problem hiding this comment.
No, I disagree. It's a server-only option, and most servers operators will not need to enable TCP support. Only those running large directories or large servers will need to, and they also need to understand and configure their firewall requirements.
TCP support in the client will indeed be enabled by default, but will only take effect when talking to a directory or server that has enabled it.
If a server operator enables TCP without having configured their firewall correctly, client users could have problems as the server would advertise TCP support to the client, but the client could be unable to connect.
There was a problem hiding this comment.
Can we not give an error message or fallback procedure in case the TCP connection timed out?
There was a problem hiding this comment.
Yes, I'm sure we can. I haven't yet tested that scenario.
But it doesn't negate my view that server-side TCP support needs to be an explicit option.
There was a problem hiding this comment.
There was a problem hiding this comment.
Since when do we have a long time?
There's absolutely no benefit to anyone involved in the project - developers or users - in having long release cycles.
A version cut takes very little time - apart from the translation process. Even then, getting used to more, small changes is likely to speed things up generally.
|
Well I've finished implementing everything I intended to, for directory, server and client, so it's ready for reviewing and trying out, as and when time permits (post 3.12.0). I have a private directory and server built and running with TCP support, at In order to demonstrate the use of TCP in a new client's connect dialog, it will be necessary to use custom firewall filters on the client end to temporarily drop incoming UDP Jamulus protocol messages containing a server list or connected clients list. There is full forward and backward compatibility between clients and servers built with TCP support and older versions. |
|
Keeping as draft, because it will need quite a few debug messages removed before merging. |
This allows both --serverbindip4 and --serverbindip6 to be honoured correctly for TCP as well as UDP.
|
Note 📡 STAND BY FOR AN LLM-AUTHORED MESSAGE. @softins — two things on the pasted review, one of which saves you work. Its first merge-blocker is already done. The part it raised in one clause and then dropped is the one worth sizing: an IP match can't separate two clients at the same address. We operate a fleet of Jamulus servers and a directory-sampling census, so we can put numbers on it rather than argue from intuition. What the data saysAuthoritative client-IP observations at our own servers, 15.9-day window, operator addresses excluded — 592 distinct clients across 626 distinct addresses:
Joining that against per-minute presence data, so this is genuine simultaneity rather than "sometime that fortnight":
A separate correlation engine we run reaches the same conclusion independently: its concurrent multi-client-per-address detector fired on 7 distinct addresses in the same window, and one address appears in both datasets. Caveat on the sample: these are players who touched our servers, not a random draw from all Jamulus users. The direction that I think actually decides itThe check isn't only too weak. It is also too strict. Our correlation engine's single largest source of failed attributions is carrier NAT re-binding: the mapping expires during a pause and the client reappears on a different egress address, not merely a different port. In the same window, 75 of 592 clients (12.7%) were observed on more than one address. So a legitimate client whose egress address changes between establishing the UDP session and opening the TCP connection hits The suggestionHave the server issue an unguessable handle and have the client echo it on TCP, instead of sending the channel ID. It removes both failures at once, because it stops requiring the address to be stable in either direction. Two things make this cheap specifically now:
Keep the address comparison as defence in depth if you like, but as a hint rather than the authenticator — that also lets you log a mismatch instead of dropping a client who did nothing wrong. If you'd rather not change the wire format at this stage, the minimum floor is to refuse to replace a live association rather than overwrite it: Evidence I can produce if it would helpHappy to run any of these — they're queries against data we already hold, not new instrumentation:
Also glad to exercise the association path under forced loss with a netns fault-injection harness we built for this PR, if you want the failure modes demonstrated rather than argued. Happy to be told the risk is acceptable for a first version — that's a reasonable call, and documenting the limitation as the review suggests would be a fine outcome. I'd just rather you make it with the numbers in hand, since the too-strict direction affects working users and not just hostile ones. |
|
Regarding the above, it's reasonable to take the numeric statistics with a grain of salt, because IP:identity matching is only about 80% sound. I see a risk of making a wall of text, but I decided to post that comment in response to your comment because I draw from my own statistics (some even reliable) and from a pretty full repo discussion history. |
|
But it's a valid suggestion though, to use an un-guessable token to relate the TCP connection to the existing UDP session. I'll see what I can do. |
Set token to a new random value for each new channel. This will be used to authenticate TCP channel associations.
|
MY LLM WROTE: One more on the TCP path, in the mirror image of a question @ann0see already asked here. On 2 July, reviewing
Q_ASSERT ( InetAddr == Channel.GetAddress() );
ConnLessProtocol.CreateCLClientIDMes ( InetAddr, iClientID, PROTO_TCP_LONG );using Nothing filters the source.
The third point is the one I would not want to ship. The first two are noise by comparison. On how reachable this is, being precise: no address spoofing is involved, because the sender needs its real address to receive the connection, so this is reachable off-path rather than only on-path. A client's UDP port is bound in 22124–22224 ( Suggested fix, 9 insertions and 2 deletions: case PROTMESSID_CLM_CLIENT_ID:
// Only the server we are connected to may ask us to open a session
// connection. Connectionless messages are not source filtered, and
// Q_ASSERT is compiled out of release builds, so check this at runtime.
if ( !( InetAddr == Channel.GetAddress() ) )
{
qWarning() << "Ignoring unexpected CLM_TCP_SUPPORTED for client ID from" << InetAddr.toString();
break;
}
// if client ID already received, make TCP connection to server
bTcpSupported = true;
if ( iClientID != INVALID_INDEX )
{
// *** Make TCP connection
qDebug() << Q_FUNC_INFO << "need to make TCP connection for client ID" << iClientID;
ConnLessProtocol.CreateCLClientIDMes ( Channel.GetAddress(), iClientID, PROTO_TCP_LONG );
}
break;Three notes on that shape:
Verified that the patched file compiles with Separately, the frame-length bounds check in |
|
Cool, I have plenty to be looking at now, and some suggested changes already in progress. Probably best to wait until my WIP is stable and review again then. |
This is instead of using the IP address, which could be shared between multiple clients, or even be different between UDP and TCP (although unlikely)
This test is made independently for IPv4 and IPv6
|
I have updated the branch with a number of changes based on the review feedback. The main changes are:
Other responses I have to the review comments are:
|
|
Great! Thanks. I still think that we should rename CLM_TCP_SUPPORTED to something more descriptive. CLM_TCP_SUGGESTED ? |
I don't think so. We're not suggesting they use it, we are saying it is available to be used if they need to. Maybe |
|
But SUPPORTED is not the semantics - If it was only used to tell clients that the server supports TCP, it would be enough to have the capability bit set in the newly introduced protocol message. |
|
MY LLM WROTE: @softins Re-reviewed at The channel token is generated by a non-cryptographic PRNG, and the server hands out its raw output on request.
That would be a minor nit if the token were defence in depth, but Measured, Qt 5.15.13: 700 values from With a predicted token, Fix is one word: vecChannels[iNewChanID].SetChannelToken ( QRandomGenerator::system()->generate() );Once per channel init, so the cost doesn't matter here. I agree with dropping the IP check — with separate v4/v6 listeners a client's UDP and TCP addresses can legitimately differ — which is precisely why the token has to carry the whole load. Two small notes:
@ann0see on the name — no opinion on which word wins, but one fact that may bear on it: the capability advertisement already exists separately as |
- Use better randomn number generator - Only include TCP in server features if a listener is running (which it always should be)
The semantics are that it is an offer, not a suggestion or a requirement. The client decides whether it needs to take up the offer. So I think the closest we would get might be either |
Just on this, we have two booleans, |
Those two booleans are only set on startup to confirm that the TCP listeners were successfully started. It should be vanishingly rare that they wouldn't, but it does allow the server not to offer TCP support if it doesn't have a listener on the corresponding protocol. Each boolean is global to the server, not specific to any client. I agree with AI saying it's better that |
Is probably better then. |
Ah, my misunderstanding.
I'm not certain, even, whether TCP is any more a "feature" than IPv4/IPv6. In the same way that you might It's easier to probe actively to determine whether a UDP IPv4 / IPv6 port exists by sending a CLM feature req; similarly, the reliably way to find out if a TCP connection is available is to probe with a similar pair of CLM feature req messages. If the feature response comes back, you know what's there. (Obviously this is from a Server's perspective and I'm not suggesting any random IP could send such a message and expect to get a response.) |
Short description of changes
Support fallback to TCP for protocol messages, in order to overcome potential loss of large messages due to UDP fragmentation.
Currently an incomplete draft, for comment as development continues.CHANGELOG: Client/Server: Support TCP fallback for protocol messages.
Context: Fixes an issue?
Discussed in issue #3242.
Does this change need documentation? What needs to be documented and how?
It will need documentation once design and development are complete. Particularly need to explain the firewall requirements for a server or directory.
Status of this Pull Request
Incomplete, still under development. Main server side complete and working. Client side development in progress.Complete and ready for review and testing.Still marked draft asit needs some of the debug messages to be commented out before merging.What is missing until this pull request can be merged?
A lot of testing of both server and client. Intended for Jamulus 4.0.0.
Checklist