ddnet / ddnet

DDraceNetwork, a free cooperative platformer game
https://ddnet.org
Other
587 stars 415 forks source link

TCP handshake before UDP connection #8210

Open def- opened 6 months ago

def- commented 6 months ago

Suggested by Davide, apparently FiveM uses this too.

First do a TCP handshake ~(on the same port number)~ with the server, then connect over UDP.

If you implement the TCP port "punching" and you allow only the IPs who complete the handshake to send packets over the UDP port And you can actually using this system to block small/medium DDoS bypass. For example on a provider like OVH But still, even if the provider doesn't support that, you can run your own UFW/iptables rules

I'm somehow skeptical of this, since it requires an extra open port to listen to per server, conflicts with the websocket servers (not so important), and I guess many hosters would not care to support this scheme. But I might just be bitter about this topic after > 10 years of DoS attacks against us, so it's probably worth trying. Anyone want to implement this?

EDIT (heinrich5991): Doesn't have to be on the same port number.

Rei-Tw commented 6 months ago

Using TCP is a discussion we've been having for more than a year with heinrich & Learath on DDNet discord. That could help at some point but still some providers such as OVH will still filter legitimate traffic (UDP), before it can even reach your server. But for some providers like Davide, maybe that can help to build a more efficient protection.

Edit: One thing that heinrich once suggested (I think on DDNet's discord) is also to have an unique server-side port per client once the TCP handshake has been done. (Maybe I'm wrong on that, @heinrich5991 will probably remember about that)

heinrich5991 commented 6 months ago

Thanks for creating an issue for this, I would've bet we already had one, but apparently we don't.

I think this should probably be a https:// URL we GET before connecting to the server. This could simply be added to the register protocol. However, this has a weird interaction with the serverbrowser connect string. The serverbrowser connect string currently contains all the information needed to connect to a server, so we'd need to come up with some syntax to include the TCP connection metadata/HTTP URL in there.

Jupeyy commented 6 months ago

As a first step i think it would also work if this meta data is searched in the current server browser info.

Even if this means you cannot connect to these servers without a server browser info loaded, this is the situation as is too. So it would at least be an improvement over the current behavior.

heinrich5991 commented 6 months ago

As a first step i think it would also work if this meta data is searched in the current server browser info.

If we don't plan for the later step, I doubt it'll get fixed at all. The first step is quite trivial.

Jupeyy commented 6 months ago

As a first step i think it would also work if this meta data is searched in the current server browser info.

If we don't plan for the later step, I doubt it'll get fixed at all. The first step is quite trivial.

nobody can type what you envision by hand anyway.

if i type ger10.ddnet.org i would never add some extra stuff, that most likely is not user friendly either.

Other than that i don't see why step 1 blocks anything in future. if you break the connect syntax that will be a breaking change anyway

heinrich5991 commented 6 months ago

nobody can type what you envision by hand anyway.

That's not what I'm concerned about.

Currently, connecting just depends on the connect string. This makes debugging very easy. I'd like to keep it that way.

If it starts depending on other external state such as the server list being loaded, a server entry being selected and the address not manually typed, the address not manually edited, etc., then I see us having debugging issues.

if you break the connect syntax that will be a breaking change anyway

I don't think we need to break existing connect syntax.

Jupeyy commented 6 months ago

If it starts depending on other external state such as the server list being loaded, a server entry being selected and the address not manually typed, the address not manually edited, etc., then I see us having debugging issues.

I get your point, but at the expensive of making the connect string less intuitive for a user. "Can you send me the server ip".

So the question for me is, do you want to trade simplicity on user side vs simplicity of code. We can support both

heinrich5991 commented 6 months ago

I think I have an idea: We could let the server register a HTTPS URL that returns the connect addresses to the servers.

The connect URL for ger2 could look like this: https://ger2.ddnet.org/8303.

This even allows distributing different IP addresses/port numbers per client. Might make whitelisting easier.

heinrich5991 commented 6 months ago

I get your point, but at the expensive of making the connect string less intuitive for a user. "Can you send me the server ip".

It's not entirely clear to me that having potentially different behavior per user is more intuitive for the user. One user might be able to connect, sending the IP address (but lacking the TCP handshake part), and the other one might not be able to connect.

Jupeyy commented 6 months ago

I think I have an idea: We could let the server register a HTTPS URL that returns the connect addresses to the servers.

The connect URL for ger2 could look like this: https://ger2.ddnet.org/8303.

This even allows distributing different IP addresses/port numbers per client. Might make whitelisting easier.

How would this method make sure that a IP from the response is assosiated with ddnet.org?

Jupeyy commented 6 months ago

I get your point, but at the expensive of making the connect string less intuitive for a user. "Can you send me the server ip".

It's not entirely clear to me that having potentially different behavior per user is more intuitive for the user. One user might be able to connect, sending the IP address (but lacking the TCP handshake part), and the other one might not be able to connect.

It's not entirely clear to me that if a user enters the ip that he has to expect different behavior between two servers, because he does not know what a connect syntax is.

Either you get my point or you don't. Apparently you are not, that's ok, but a pseudo argument doesn't help then (IMO the consequence would then be to disallow connecting by ip completely)

Jupeyy commented 6 months ago

I think I have an idea: We could let the server register a HTTPS URL that returns the connect addresses to the servers. The connect URL for ger2 could look like this: https://ger2.ddnet.org/8303. This even allows distributing different IP addresses/port numbers per client. Might make whitelisting easier.

How would this method make sure that a IP from the response is assosiated with ddnet.org?

I know this is a bit off-topic, but i am thinking about the way i want to implement encryption. Even tho i can completely ignore how this project handles it, it might be worth thinking about how the response exactly looks like, e.g. a JSON to contain additional information?

Other then that, what if the IP does not relate to the domain directly. Would that mean the client might connect to a "arbitrary" server?

heinrich5991 commented 6 months ago

It's not entirely clear to me that if a user enters the ip that he has to expect different behavior between two servers, because he does not know what a connect syntax is.

Can you elaborate? My plan would be that the connect syntax captures the entire connection establishment. If you type a different URL in a web browser, e.g. omitting the port part, you're going to connect differently. That behavior does not change between servers.

Do you mean that some servers can be connected to by bare IP address and others can't?

Jupeyy commented 6 months ago

Do you mean that some servers can be connected to by bare IP address and others can't?

Ah yeah that's basically what I mean in this case. I am sure users can get used to a connecting syntax that is URL-looking, but i find it weird if both works.

Other than that, I try to make my arguments clear, and I know I am biased in this case:

  1. connecting to a https server in the browser also doesn't have all information in the URL. the CA is hidden. To me the CA is similar to how I imagine our server list because
  2. Putting stuff in the URL is kinda insecure. For this issue it's probably not a big deal, but going further having encryption support would either require people to rely on a CA mechanism that the client would automatically use, or the client has to rely on the server list information.
  3. This isn't a big point, but as hinted, the URL method can be manipulated. I don't think that's a huge deal. As you said, in browsers changing the path also leads to a different address. But browser also never really ping two servers at the same time (here i mean from the URL syntax alone). I guess it depends on how we implement it, if we do smth like: twudp+06://ip:port/whitelist=https://google.com then that kinda violates CORS xdd. As said not my biggest point, just a keep in mind

For the https connecting method it could, in theory, allow MITM attacks too with the encryption argument, because the server list is my version of CA not a server that randomly responds IPs + maybe the public key hash. <- As said i am bit baised here as how i imagine the server list, ofc you can disagree with that, if you prefer to rely on existing CA logic or similar.

heinrich5991 commented 2 months ago

Duplicate #6808.