On August 23, I got a nice feature merged into Transmission, a very popular cross-platform BitTorrent client. It’s not something visible in the UI, but it works under the hood to let peers connect to each other that otherwise couldn’t. More connectable peers means a healthier swarm, more sources to pull pieces from, which in turn tends to mean better download/upload performance, especially in restrictive network setups.
(For anyone not familiar with BitTorrent: it’s a peer-to-peer protocol for sharing files. Instead of downloading from one central server, you download pieces of a file from a bunch of other people who already have it, while uploading pieces back to them at the same time. Transmission is one of the more popular clients for actually using the protocol.)
I have always been interested in networking and peer-to-peer technology, going really back to old times, maybe even the early 2000s, right when that whole wave was taking off with things like BitTorrent.
On Linux and Mac, Transmission has been my go-to client. And I was really surprised to find out that it didn’t support the BEP 55 standard. BEP 55 is important because it allows connections where incoming connections aren’t allowed, for many different reasons.
For example, in my case, my ISP has CGNAT (Carrier-Grade NAT). For IPv4, they basically limit incoming connections because they’re sharing the same public IP address over multiple subscribers. And that’s something that’s becoming much more popular in many places of the world, depending on different constraints. My ISP runs CGNAT with dual stack, alongside IPv6. While I can open incoming connections on IPv6, it’s really common that on other ISPs with residential connections, it’s hard for people to do so, and UDP hole punching over IPv6 usually works well. Transmission does support IPv6, and I also made sure it worked correctly.
This directly affects things like peer-to-peer networking in general. And one way in which not only peer-to-peer clients but other tools that require direct connections, even things like WhatsApp or Teams, deal with this in some instances is by doing UDP hole punching.
UDP hole punching on the BitTorrent protocol actually started in libtorrent, Arvid Norberg’s BitTorrent library, back in 2015. Mihael Peklar wrote the formal BEP 55 spec in 2018 and credited GGist for reverse-engineering the protocol from libtorrent’s source. So the underlying idea has been around for more than a decade now, and it’s not some obscure corner of the protocol either: µTorrent, BitComet, and every libtorrent-based client (qBittorrent, Deluge, and others) already support it. Transmission just hadn’t gotten to it yet, so I made it a project, on a few weekends, to get it working on my Mac.
How Hole Punching Actually Works
So here’s roughly the mechanism, in plain terms. Say peer A can’t reach peer B directly, because B is behind a restrictive NAT. If A and B are both already connected to some peer C in the same swarm, A can ask C for help: A sends C a “rendezvous” message with B’s address. C, since it’s connected to both of them, forwards a “connect” message to A and to B at the same time, each one containing the other’s address. The moment both A and B get that message, they each fire off a uTP connection attempt to each other, at basically the same instant. That simultaneous attempt is the actual “punch”: both sides’ NATs see an outbound packet first, which opens a temporary hole for the other side’s inbound packet to slip through, right as it arrives.
C, the peer doing the introducing, doesn’t need to do anything clever after that, it’s just a relay for one pair of messages. All the real work is the timing of the simultaneous connection attempt on both ends.
My Experience Implementing It
So I first started diving into the BEP 55 spec, also using a bunch of different LLMs and coding harnesses (the agents that actually run the model against your code) along the way, from Codex and Claude Code to opencode, mixing in models from a bunch of different providers.
The cool part was being able to cross-check the BEP 55 spec against open source client implementations to see how they actually handled it. One concrete example: the spec calls for a 12/24-byte message with a trailing zero error code for non-error messages. Libtorrent emits that form and anacrolix’s client expects it, so it was good to have the implementations line up with the spec.
I have to mention, I don’t think I would have been able to do this work in a few free-time weekends without LLMs. Understanding the spec takes work, and Transmission is written in C++20 (they’re already running C++23 in CI to stay forward-compatible), so not writing C++ day to day was a hurdle too.
This also wasn’t just a change buried in libtransmission. It’s now visible across GTK, Qt, the Web UI, and the RPC API too. All of them show a peer flag/tooltip when a peer supports holepunch. I spent time looking at the existing conventions before adding anything: the flag ended up as lowercase h, since uppercase H was already taken for DHT-discovered peers, following the same D/d and U/u pattern already used elsewhere in the codebase. I also added unit tests for the encode/decode logic, including malformed payloads and error codes, not just the happy path. And on the design side, Transmission is deliberately conservative about who’s allowed to relay a rendezvous: only a peer that actually introduced you to the target via PEX can do it, following libtorrent’s model instead of letting any connected holepunch-capable peer relay for anyone.
Results
Before opening the PR, I ran multiple overnight sessions, real-world tests, across several public torrents, on two different network setups: a CGNAT / double-NAT IPv4 network with native IPv6 (where IPv4 inbound is blocked but IPv6 isn’t), and a regular dual-stack home network with open IPv4 inbound. Got dozens of successful holepunch connections, acting as initiator, responder, and relay, against real-world peers running Deluge, libtorrent, and qBittorrent. I even ran some of those sessions with tcpdump going, to double-check the logs against the actual packets on the wire, not just trust what Transmission was printing.
One real log line from that run, IPv4 CGNAT talking to a Deluge/libtorrent peer:
BEP 55: direct connect to <peer>:6881 failed, sent rendezvous via introducer <introducer>:41318
[Deluge/2.2.0 libtorrent/1.2.20.0]: got ut_holepunch connect from <introducer>:41318 -> <peer>:27743
BEP 55: initiating uTP holepunch connect to <peer>:27743
BEP 55: holepunch connection to <peer>:27743 succeeded
That’s the whole flow from the section above, happening for real: direct connect fails, Transmission falls back to a rendezvous through a peer it’s already connected to, gets a connect message back, and the uTP holepunch attempt actually succeeds.
I also want to say thanks to tearfur, who did a lot more than review: on top of the feedback, tearfur put together a solid chunk of refactor work that I applied to the branch. And thanks to ckerr for reviewing it and finally merging it in.
The PR itself, #8884, has the full write-up if you want to dig into the design decisions, or just want the receipts.
It’s not shipped in a release yet, but it’s merged into upstream’s main branch, so the next release should have it.
Kind of a nice full-circle thing, going from being a kid fascinated by peer-to-peer networking to actually shipping a piece of the protocol itself.