top | item 42601530

(no title)

dennis-tra | 1 year ago

This is an excellent article!

The tribal knowledge seems to be that you shouldn't do TCP-based hole punching because it's harder than UDP. The author acknowledges this:

> You can do NAT traversal with TCP, but it adds another layer of complexity to an already quite complex problem, and may even require kernel customizations depending on how deep you want to go.

However, I only see marginally added complexity (given the already complex UDP flows). IMO this complexity doesn't justify discarding TCP hole punching altogether. In the article you could replace raw UDP packets to initiate a connection with TCP SYN packets plus support for "simultaneous open" [0].

This is especially true if networks block UDP traffic which is also acknowledged:

> For example, we’ve observed that the UC Berkeley guest Wi-Fi blocks all outbound UDP except for DNS traffic.

My point is that many articles gloss over TCP hole punching with the excuse of being harder than UDP while I would argue that it's almost equally feasible with marginal added complexity.

[0] https://ttcplinux.sourceforge.net/documents/one/tcpstate/tcp...

discuss

order

dfawcus|1 year ago

The existence of stateful firewalls, and the fact that most NAT filters are EDF rather than EIF means that simultaneous open (send) is necessary even for UDP.

Hence the added complexity of doing a simultaneous open via TCP is fairly minor. The main complication is communicating the public mapping, and coordinating the "simultaneous" punch/open. However that is generally needed for UDP anyway...

One possible added complexity with TCP is one has to perform real connect() calls, rather than fake up the TCP SYN packet. That is becase some firewalls pay attention to the sequence numbers.

LegionMammal978|1 year ago

Yeah, I've gotten somewhat annoyed by the name of 'NAT traversal' for these methods. It seems to make some people think that cutting out NAT will lead to a beautiful world of universal P2P connections. But really, these methods are needed for traversing between any two networks behind stateful firewalls, which will pose a barrier to P2P indefinitely.

Also, wouldn't it be easier for stateful firewalls to block simultaneous TCP open (intentionally or not)? With UDP, the sender's firewall must create a connection as soon as it sends off the first packet, even if that packet bounces off the other firewall: the timing doesn't have to be particularly tight. But with TCP, the firewall might plausibly wait until the handshake is complete before allowing incoming packets, and it might only allow the 3-way SYN/SYN-ACK/ACK instead of the simultaneous SYN/SYN/ACK/ACK.

Uptrenda|1 year ago

I think this is a really good point. As someone who has implemented TCP hole punching myself and now has a very good implementation for it I will say that obviously a major benefit of using TCP is you don't have to subsequently roll a poorman's TCP on-top of UDP once the hole is open. The other issue with TCP hole punching though is it looks very similar to a SYN flood compared to UDP packets. This may mean lower success rates for some networks. Though in practice I haven't seen much filtering so far.

TCP hole punching is very fun. The way I do it is to use multiple NTP readings to compute a "clock skew" -- how far off the system clock is from NTP. Then the initiator sets a future meeting time that is relative to NTP. It honestly gets quite accurate. It even works for TCP hole punching between sockets on the same interface which is crazy if you think about it.

The reason I wanted to support this strange, local-based punching mode is if it works that efficiently to be able to succeed in host-based punching then likely it will be fast enough to work on the LAN and Internet, too. My code is Python and my very first attempt at this was eye opening to say the least. Due to how timing-sensitive TCP hole punching is I was having failures from using Python with old-school self-managed sockets. I was using threading and a poormans event loop (based on my C socket experience)... which is ah... just not the way to do it in Python.

The only way I could get that code to work was to ensure the Python process had a high priority so other processes on the system didn't deprioritize it and introduce lag between the punching attempts. That is how time-critical the code is (with an inefficient implementation.) My current implementation now uses a process pool that each has its own event loop to manage punching. I create a list of tasks that are distributed over time. Each task simply opens a connection that is reused from the same socket. I determined this code was the best approach (in Python anyway) after testing it on every major OS.

You are right about TCP and UDP hole punching difficulty being similar. The main difficulty to both is the NAT prediction step. I haven't written code yet for symmetric NAT bypass but I am starting to see how I'd integrate it (or possibly write a new plugin for it.)

CMCDragonkai|1 year ago

Do you have a GitHub repo for this? I'd be interested in reading about it's implementation since we implemented QUIC based hole punching.

Uptrenda|1 year ago

I did just think of another drawback for TCP vs UDP punching that I think puts a major point in UDP's favour. It may have been touched on others already. But TCP would require the router to record connection state. This is bad because the table for routers is very small and some of these punching techniques are quite aggressive. Like the algorithm that tries to bypass symmetric NATs. If you're opening hundreds of TCP connections its possible you might even DoS the router. For UDP its plausible optimizations for state management would make it less likely that your punching would render the whole router inoperable. This is only speculation though.

duskwuff|1 year ago

> If you're opening hundreds of TCP connections its possible you might even DoS the router.

This was sometimes an issue for underpowered home/SOHO routers in the mid-2000s, but most modern routers have enough memory to support decently sized connection-tracking tables.

In any case, both TCP and UDP require connection tracking; there's no inherent advantage to UDP.