[HN Gopher] A most elegant TCP hole punching algorithm
___________________________________________________________________
A most elegant TCP hole punching algorithm
Author : Uptrenda
Score : 204 points
Date : 2026-03-15 03:29 UTC (19 hours ago)
(HTM) web link (robertsdotpm.github.io)
(TXT) w3m dump (robertsdotpm.github.io)
| jcalvinowens wrote:
| If you're asking "where is the listener", you don't need one:
| https://datatracker.ietf.org/doc/html/rfc9293#simul_connect
| cperciva wrote:
| RFCs may say that simultaneous connect must be allowed, but
| that doesn't mean that firewalls can't block it. Plenty of
| setups block incoming SYN,!ACK packets, and if both sides do
| that, the connection is never getting established.
| jcalvinowens wrote:
| In my experience most consumer routers are dumber than you're
| assuming they are, and will DNAT any inbound TCP packet that
| matches the 4-tuple after seeing the initial outbound SYN,
| including an inbound SYN. But yes, it doesn't work
| everywhere.
|
| I wrote little paper on this technique in school and did some
| practical tests, at the time I was actually unable to find an
| example of consumer grade router that it _didn 't_ work on!
| But my resources were rather limited, they certainly do
| exist.
| huhtenberg wrote:
| > Plenty of setups block incoming SYN,!ACK packets
|
| Even in the presence of a conntrack entry created by an
| earlier outbound SYN,!ACK ?
|
| Got a source?
| cperciva wrote:
| I've seen plenty of firewall rulesets over the past 25
| years which only consult state after doing some initial
| stateless inspection.
|
| I don't have a convenient source though.
| huhtenberg wrote:
| Sanity checks, sure, but SYN,!ACK packets cannot be
| rejected before the conntrack for obvious reasons.
|
| > _Plenty of setups block incoming SYN,!ACK packets_
|
| Nowhere close to being "plenty". It's doable, but this is
| extremely niche.
| cperciva wrote:
| _for obvious reasons_
|
| What are the obvious reasons? If you're protecting a
| client system, you don't want to allow in any bare SYNs.
| (And for that matter, if you're protecting a server, you
| probably want to discard ill-targeted bare SYNs without
| consulting conntrack anyway, just as a matter of avoiding
| extra CPU work.)
| gzread wrote:
| Does this mean by establishing a new connection with a
| SYN,ACK bypasses some firewalls? I expect at least one OS
| out there ignores the extraneous ACK flag and proceeds to
| establish a new connection.
| huhtenberg wrote:
| Why would it mean that?
|
| All inbound packets are matched against existing
| sessions. In this case none will turn up, so the packet
| will go through the "new session" flow and be subject to
| the same filtering as a bare SYN. Look up how connection
| tracking works, e.g. in the Linux kernel, it's rather
| simple and logical.
| jcalvinowens wrote:
| It's not uncommon with routable internal networks to only
| drop inbound SYN,!ACK to disallow inbound connections
| while permitting outbound ones, since it doesn't require
| connection tracking (which can be resource intensive).
|
| I can't really imagine why you would do it for NAT'd v4
| since you can't avoid the connection tracking overhead,
| but you certainly _could_ , and I don't doubt OP has run
| into it in the wild. I've seen much weirder firewall
| rules :)
| EnigmaCurry wrote:
| > Many home routers try to preserve the source port in external
| mappings. This is a property called "equal delta mapping" - it
| won't work on all routers but for our algorithm we're sacrificing
| coverage for simplicity.
|
| It is precisely this point that has flummoxed me when connecting
| my p2p wireguard config[1] with a friend that uses a pfsense
| router, no matter what we tried, pfsense always chooses a random
| source port.
|
| But in the simple case this blog outlines, if both ends use the
| same source port, this method punches through 2 firewalls
| effortlessly:
|
| [1] https://blog.rymcg.tech/blog/linux/wireguard_p2p/
| jonathanlydall wrote:
| Does your friend setting up port forwarding on their pfSense
| not help in your scenario?
| EnigmaCurry wrote:
| Yes, that solves it completely. But the exercise we were
| trying to do was to do it without that.
| hdgvhicv wrote:
| You're getting into birthday paradox territory, throw a few
| hundred packets in each direction and one will get through
|
| This hs a good diagram to understand the options
|
| https://rajsinghtech.github.io/claude-
| diagrams/diagrams/netw...
| patjensen wrote:
| This is easily solved in your source NAT configuration on
| pfSense. It's a single checkbox to not randomize ports on
| outbound flows. This will enable full cone NAT.
|
| You can scope it to just your IPsec service, or whatever it
| is your hosting, or you can enable full cone for the whole
| subnet.
|
| It is not DNAT, nor is it port forwarding. If you host a
| SIP proxy, SBC or peer to peer gaming, it will enable these
| use cases as well.
|
| https://docs.netgate.com/pfsense/en/latest/nat/outbound.htm
| l
| hdgvhicv wrote:
| In my experience, Cisco ASA does source port persistence by
| default (when it can't do it then it falls back to random),
| fortigates can do it (in various ways depending on version,
| although fallback method in the map-ports doesn't work),
| juniper SRXs can't, unless you guarentee a 1:1 map.
| athrowaway3z wrote:
| - you know each others IP's (or have a way to signal it)
|
| - can't decide on a port in the same message
|
| - don't suffer from NAT port randomization
|
| I'm not saying it will never happen, but the Venn diagram of this
| being the minimum complexity solution just doesn't seem very
| large?
| Arch485 wrote:
| I think many people know how to google "what is my IP" and send
| that to a friend, but don't necessarily know what a port is.
|
| NAT randomization, I don't know. Depends on your setup, I
| guess.
| sholladay wrote:
| This is a great algorithm!
|
| In this era where AI is eating away at how deterministic
| computers are, I really appreciate reading about an elegant
| solution to a real problem using deterministic logic.
| CamelCaseCondo wrote:
| We still live in an age of deterministic computers. It's the
| software that's become fuzzy. (And since we're on the subject:
| there's no AI)
| sholladay wrote:
| Yes, but a computer is just a paperweight without its
| software. Also, increasingly the hardware is being
| specifically designed and optimized for that non-
| deterministic software. The experience of using computers is
| changing and we're still in the early days of that shift.
|
| Of course there's still plenty of deterministic software you
| can run... for now.
| ufocia wrote:
| I can almost guarantee that all of AI runs on deterministic
| hardware and software. AI is just (near?) the top of the
| stack. There is no reason, and probably never will be to
| have a purely heuristic computer. Deterministic systems are
| way simpler and cheaper to handle very routine well defined
| tasks. Even AI authors code behind the scenes to process
| data files deterministically.
| wolttam wrote:
| > there's no AI
|
| This is a theistic statement at this point, no?
| CharlesW wrote:
| Many people conflate "AI" and "AGI". It's disappointing to
| find people who don't know the difference on HN, though.
| mycall wrote:
| data = code in the AI age. Fuzzy data = fuzzy code.
|
| Now combining AI with deterministic tool calling brings the
| best of both worlds.
| ray_v wrote:
| Are you saying that "Artificial Intelligence" is just not
| properly named for now.we.currently use it? Feels like at
| least the distinction of qualifying it with the term, "LLM"
| is popular.
| Veserv wrote:
| Needing to punch holes in NAT is one of the most idiotic own-
| goals in the entire field of networking.
|
| NAT is effectively your router doing DHCP with a 17-bit suffix
| (16-bit port + 1 bit for UDP vs TCP) to each of your applications
| and then not telling you the address it gave you or how long it
| is good for (which is what a regular DHCP lease does). This is in
| addition to it, most likely, already doing regular DHCP and
| allocating you a IP address that it does tell you about, but
| which is basically worthless since routing to just that prefix
| without the hidden suffix goes into a black hole.
|
| If you could just ask your router for a lease on a chunk of
| IP+NAT addresses that you could allocate to your applications and
| rotate them as they expire, you would not need this horrifying
| mess.
|
| The router would just need to maintain the last-leg routing table
| (what a concept, a router doing routing with routing tables) just
| like it already does DHCP.
|
| The applications would have short-term stable addresses that they
| could just tell their peers and just directly tell the
| router/firewall to block anybody except the desired peer short-
| term address.
| eptcyka wrote:
| Why not use plain IPv6 instead?
| cbdevidal wrote:
| V6 adoption has reached 46.82%[1]. So it is increasingly
| viable for this.
|
| [1] https://www.google.com/intl/en/ipv6/statistics.html
| TuxPowered wrote:
| Even with IPv6 you still might have stateful firewalls
| allowing only for outbound connection at both ends (e.g. a
| CPE a.k.a. "WiFi router") and to establish communication
| you'd need to punch a hole in those firewalls.
| brewmarche wrote:
| That's true we won't get rid of hole-punching with IPv6.
| But at least it will get rid of TURN.
| gzread wrote:
| The hole punching is so much simpler because you don't
| need to guess your own address and port - you just know
| it
| takipsizad wrote:
| it's been already done ISPs just don't properly implement it
| (NAT-PMP and it's relatives)
| lxgr wrote:
| > If you could just ask your router for a lease on a chunk of
| IP+NAT addresses
|
| The "just" is doing a lot of lifting there. I'm glad the
| various port mapping protocols didn't really take off and it
| looks like IPv6 is going to actually make it instead. Much less
| complexity in most parts of the stack and network.
| Veserv wrote:
| It is always a mystery how people just randomly misinterpret
| what I write. At literally no point did I mention port
| mapping.
|
| I am pointing out how the problem NAT "solves" is just
| dynamic address configuration. They have implemented a N+K
| bit address where the N-bit prefix is routed and allocated
| using IP and the low K-bits are routed and allocated like a
| custom fever dream.
|
| You can just do it all the same way instead of doing it
| differently and worse for the low bits.
|
| To be clear, the router should rewrite zero bits in the
| packet under the scheme I am describing just like how routers
| have no need to rewrite any bits when routing to a specific
| globally-routable IP address.
|
| You get a lease for a /N+K address. /N routes to your router
| which routes the last K bits just like normal as if it had a
| /N-M to a /N route. This is a generic description of
| homogenous hierarchical routing.
| GoblinSlayer wrote:
| NAT allocates ports. If you reserve a port, that's old good
| port forwarding.
| lxgr wrote:
| If I understand it correctly, you're suggesting formalizing
| a way to make parts of the (host-specific) port canonically
| part of the network-wide address, no?
|
| This still sounds like a very bad mixing of layers, even if
| done in a perfectly standardized and uniform way.
|
| > It is always a mystery how people just randomly
| misinterpret what I write.
|
| If this is intended literally and not as a general
| complaint: My main problem of understanding your suggestion
| is that I don't know what you mean by "IP+NAT address". NAT
| is a translation scheme, not an address.
|
| Maybe it would be clearer if you could provide an example?
| gzread wrote:
| The original SOCKS proxy specification was something like
| this. You'd LD_PRELOAD a library that would make the
| application think it was running directly on the proxy
| server, and it supported both connecting outbound and
| listening.
| Veserv wrote:
| I did provide a example:
|
| > You get a lease for a /N+K address. /N routes to your
| router which routes the last K bits just like normal as
| if it had a /N-M to a /N route.
|
| > This still sounds like a very bad mixing of layers,
| even if done in a perfectly standardized and uniform way.
|
| No, I am describing a generalization of IP to arbitrary
| concatenated routing prefixs.
|
| NAT has the same problems as if we lived in a alternate
| world where we decomposed IPv4 into 4 8-bit layers and
| then used a different protocol for each layer. That is
| obviously stupid because the subdivision of a /8 into
| /16s and a /16 into /24s is fractally similar. You can
| just use the same protocol 4 times. Or even better, use
| one protocol (i.e IP) that just handles arbitrary
| subdivision.
|
| In the IPv4 (no NAT) world your application has a 49-bit
| address. Your router is running a DHCPv4 server and
| allocates your computer a /32 and your computer is
| "running" a DHCPvPort server that allocates a 17-bit
| prefix to your applications.
|
| In the IPv4+NAT world your application has a 49-bit
| address. Your router is "running" a DHCPv4+Port server
| and allocates your applications a /49, but only tells
| them their /32 and then rewrites the packets because the
| applications do not know their address because the stupid
| router did not tell them.
|
| In good world your application has a 49-bit address. Your
| router is "running" a DHCPv4+Port server and allocates
| your applications a /49 and tells them their /32 prefix
| and 17-bit segment. No packet rewriting is necessary.
|
| Your router could also choose to allocate your computer a
| /32 subnet and leave DHCPvPort to your computer. Or it
| could give your computer a /31 if you have 8 interfaces.
| Or a /34 as a /32 subnet with 2-bit port prefix. Each
| node routes as much or as little routing prefix as it
| understands/cares about.
|
| This is a generalization of IP that can handle arbitrary-
| length, arbitrarily-concatenated routing in a completely
| uniform manner and all the pieces are basically already
| there, just over-specialized.
| enoint wrote:
| I didn't see it as mysterious. 25 years ago, the problem as
| stated went through lots of consensus to become IPv6. It
| took a few years for SLAAC to emerge. But we don't need it
| to be homogeneous; the router advertises different feature
| levels via ICMPv6.
| hrmtst93837 wrote:
| Assuming IPv6 kills NAT is optimistic, plenty of orgs still
| stack private addressing and firewalls on top.
| lxgr wrote:
| Firewalls aren't nearly as bad as NAT.
| hdgvhicv wrote:
| Basically the same thing. If you legitimately need to
| establish a connection then put a firewall rule in,
| whether that needs nat or pat is a function of your
| available addresses.
|
| If you are tying to work around your firewall because it
| isn't yours, that's not a legitimate use.
| lxgr wrote:
| Love it when random people tell me whether my use case is
| legitimate or not without apparently even knowing it
| exists!
|
| Take mobile data connections, for example: Most people
| don't want to pay for metered (by the byte) inbound
| traffic they didn't ask for that also drains their
| battery, but do want to be able to establish P2P
| connections for lower latency VoIP etc.
|
| This is a firewall that's definitionally "not theirs",
| but that still also serves their interests, yet usually
| doesn't offer any user-accessible management interface.
|
| So may I please traverse this firewall now, or is my use
| case still illegitimate?
| hdgvhicv wrote:
| If you are trying to break through a firewall you don't
| own then that's not legitimate.
|
| If you are buying firewall as a service then request a
| user interface or change your service provider.
| lxgr wrote:
| Are you even acknowledging my example? Where does it
| exist in your bimodal model of reality of "my firewall"
| and "somebody else's firewall"?
|
| What provider would you suggest somebody wanting to make
| VoIP calls on their smartphone switch to that allows port
| forwarding of the kind you describe? And which popular
| VoIP app would support statically forwarded ports like
| that?
| ufocia wrote:
| You're assuming that the firewall was configured
| correctly or that the firewall admin is cooperative.
| That's a big ask.
|
| On the other hand, there is plenty of badly written
| networked software. I bet most of the networked software
| developers have no idea how to correctly plumb their
| software. They just open whatever connection, e.g.
| sockets, their OS provides and just run with it without
| care of the underlying layers. The OSI model theory in
| fact encourages this ignorance.
| gzread wrote:
| P2P traffic is illegitimate according to you? Like Skype
| calls? You think Skype should not exist? (Well it doesn't
| exist any more, but whatever replaced it)
| littlestymaar wrote:
| Hole punching is doing exactly what you describe, just in a
| non-standardized way.
|
| We could have a standard for doing that directly at the NAT box
| level instead of relying on a third party STUN server, it
| simply didn't happen (and in fairness, the benefits would be
| quite minimal).
| jeroenhd wrote:
| If only router manufacturers could be trusted to implement UPnP
| safely, then none I'd this bullshit would be necessary.
|
| At least with IPv6 this crap becomes a little easier because
| you no longer have randomized source ports (which this article
| just ignores because some devices indeed maintain the same
| source port) and the IP address contains all the routing
| information you need. A simple simultaneous open is all you
| need.
| gzread wrote:
| If you use UDP transport you don't even need to try to make
| it simultaneous.
| lxgr wrote:
| Does TCP hole punching actually work with common CPEs and CG-
| NATs?
|
| I don't think I've ever seen it done successfully and have often
| wondered if it's for a lack of use cases or due to its bad
| success rate and complexity compared to UDP hole punching.
|
| That said, I really wish there was a standardized way to do it.
| Some sort of explicit (or at least implicit but unambiguous)
| indicator to all firewalls that a connection from a given
| host/port pair is desired for the next few seconds. Basically a
| lightweight, in-band port mapping protocol.
|
| It could have well been an official recommendation to facilitate
| TCP hole punching, but I guess it's too late now, as firewall
| behaviors have had decades to evolve into different directions.
| ignoramous wrote:
| > _really wish there was a standardized way to do it. Some sort
| of explicit (or at least implicit but unambiguous) indicator to
| all firewalls that a connection from a given host /port pair is
| desired for the next few seconds_
|
| _NAT Behavioural Requirements for Unicast UDP_ ,
| https://datatracker.ietf.org/doc/html/rfc4787
|
| _NAT Behavioural Requirements for TCP_ ,
| https://datatracker.ietf.org/doc/html/rfc5382
| lxgr wrote:
| > NAT Behavioural Requirements for TCP
|
| TIL, thank you! I've been looking for this for quite a while
| after hearing it indirectly referenced recently, but only
| found host-side specifications for TCP simultaneous open.
|
| Do you happen to know if common firewalls and NATs support
| it? If they do, I really wonder why TCP hole punching isn't
| more common.
| aboardRat4 wrote:
| The standard way to do it is called ipv6. Implementing it is
| probably easier than any of those RFCs
| patrakov wrote:
| No, it isn't. Many middleboxes (including OpenWrt by default)
| drop unsolicited inbound TCP connections even on IPv6, and
| therefore the same hole-punching algorithm is needed. The
| hole being punched is in the stateful firewall's connection
| tracker, not in the NAT. Basically, both parties need to
| convince their router that it is an outgoing connection
| initiated by them, not a prohibited-by-policy incoming
| connection.
| gzread wrote:
| All you should need is for both sides to connect to each
| other. Side A connecting to side B opens a hole in side A's
| firewall and is blocked by side B, then B connects to A,
| opening B's firewall and going through the already open
| hole in A's firewall.
|
| It might work better with UDP but I don't think those
| firewalls boxes tear down the mapping immediately on
| getting an RST - they wait until it times out.
| IgorPartola wrote:
| There are two separate problems with IPv4 and only one
| applies to IPv6. Allowing incoming connections through a
| restrictive firewall is applicable to both. Address
| mangling via NAT applies only to one. Note also that in the
| IPv4 world you might be behind more than one layer of NAT
| which will make everything infinitely worse.
|
| Honestly ISPs really missed an opportunity to essentially
| provide IPv6-only as a service and add an IPv4
| compatibility layer to that (IPv6 already has a mechanism
| built in for this but grandma's old laptop might not fully
| support it so you might need a local router provided by the
| ISP to give you native local IPv4 that allows you to access
| the internet) instead of CGNAT. But they chose to go with
| duct tape, spit, paper clips, and hope instead of investing
| in the correct solution. Shame on them and too bad for us.
| patrakov wrote:
| Exactly. And look, the linked Python script only solves
| one problem: making both firewalls believe that the party
| behind them is the one who initiated the connection.
| Address/port mangling is not addressed at all, both
| public addresses need to be provided externally.
|
| And it's simply not true that there is no NAT in the wild
| with IPv6: every OPNsense installation with two uplinks
| and the need for anything better than an "arbitrary and
| uncontrollable" choice of the correct uplink for each
| outbound connection needs network prefix translation, as
| the residential dual-homing story for IPv6 is vaporware
| otherwise. NPT is used not for address space
| conservation, but to defer the decision about the correct
| source address to the router that has the knowledge of
| the correct policy.
|
| And in this sense, IPv6 is worse than IPv4: there are too
| many people assuming no firewall and no NAT for IPv6, and
| designing their applications based on these almost-
| working (but de-facto now broken) premises. The correct
| premises are the same as for IPv4.
| ectospheno wrote:
| IPv6 nat is a thing that exists and is used. IPv6 purists
| like to imagine it doesn't exist which is cute.
| ufocia wrote:
| Meh. "It is assumed another process will coordinate the running
| of this tool." Coordination is the crux of the problem for fast
| convergence. Otherwise you're stuck with an infinity cubed,
| hypercubed, or worse problem.
| ata-sesli wrote:
| The timestamp bucket idea for generating shared port candidates
| is clever.
|
| Do you find this works reliably outside routers that preserve
| source ports? My understanding was that TCP punching tends to
| depend heavily on NAT behavior.
| enoint wrote:
| Looks like a typo in the degraded timestamp "bucket". That
| "window" value should be based on the min threshold.
| sylware wrote:
| Dudes: IPv6, please, come on, meh.
| melson wrote:
| I made a udp Windows wintun based p2p vpn tunnel
| https://github.com/mascarenhasmelson/Windows-P2P-UDP
| jder wrote:
| I don't think the bucket-choosing algorithm works? The two hosts
| can be just on opposite sides of a bucket edge. For example if
| one host sees t=61 and another sees t=62, they will get different
| buckets despite being less than 20 seconds apart. You've got to
| check adjacent buckets within your error tolerance, not expand
| the bucket windows in size based on it.
| abcd_f wrote:
| Claimed elegance is based on a very bold assumption that the NAT
| device preserves the source port of outbound connection.
|
| Hardly the case in even half of typical deployment cases.
| taftster wrote:
| I like your comment, but it seems the author acknowledged this
| as a caveat to the algorithm.
|
| _> Many home routers try to preserve the source port in
| external mappings. This is a property called "equal delta
| mapping" - it won't work on all routers but for our algorithm
| we're sacrificing coverage for simplicity._
|
| So to what percentage is this coverage sacrificed exactly? No
| idea. Not as useful if the percentage is high, as you are
| implying.
| killbot5000 wrote:
| It's the same assumption is required for any hole punching
| handshake (including STUN).
|
| > This is a property called "equal delta mapping"
|
| FWIW I've worked in computer networking for 20 years and have
| never heard it called this. This blog is the only source that
| comes up when I search for that exact term. I wonder where
| the author got it from.
| ray_v wrote:
| I wonder how many new technical terms are going to be
| created by LLMs - not to say that this post was N
| necessarily written by an LLM (but, who knows!)
| abcd_f wrote:
| > It's the same assumption is required for any hole
| punching handshake (including STUN).
|
| This is incorrect.
|
| Hole punching requires being able to predict external port.
| That's it. If the port remains the same, it certainly
| simplifies things, but ports going up (or down) by 1 (or 2,
| or 5) with each new mapping is quite common, trivial to
| detect and to punch through.
| K0IN wrote:
| i was also thinking about this, what keeps you from just using
| a (predefined) random port (or 10)?
| ray_v wrote:
| One thing about this get I just realized is that if the defect
| exists on some home networking equipment, then the defect can be
| leveraged in corporate network environments to disable this
| functionality. If the user of the algorithm (or, person using an
| app using it for userspace) isn't aware of the limitations, that
| would be super frustrating!
___________________________________________________________________
(page generated 2026-03-15 23:01 UTC)