good prior reading: https://tailscale.com/blog/how-nat-traversal-works/
DERP Regions documentation:
DERP Protocol documentation:
Our plan was to take the open source protocols from Tailscale, but implement them in Rust over QUIC rather than WireGuard. Specifically, taking Tailscale’s philosophy of having all of the hole punching happen in a “MagicSocket” and using the DERP (”Designated Encrypted Relay for Packets”) protocol for packet relays, and the Disco (”discovery”) protocol for hole punching.
However, we additionally need the ability to dial using PublicKey, which makes for a tricky dance with the quinn::Endpoint
and MagicSock
, since there is no concept of dialing via a public key in QUIC.
The API is centered around the “MagicSocket” or MagicSock
. Iroh creates a MagicSock
and uses that socket as an AsycnUdpSocket
in a quinn::Endpoint
. All the connection/hole punching work happens inside of the MagicSock
. As far as QUIC is concerned, all of the packets going in and out of the quinn::Endpoint
are being sent over UDP.
In reality, the Magicsock
binds an IPv4 and IPv6 UDP socket. It also spins up a DERP Client for each DERP server we are configured to connect with over HTTP/HTTPS. You need to be connected to at least one Derper (DERP + STUN server) in order to do any hole punching.
To dial a peer, you must first add the peer to MagicSock
explicitly via its public key (and any known addresses) and get a “mapped address” in return, which is an IPv6 Unique Local Address with a fixed Global ID and Subnet ID to easily identify it as such. This mapped address is what you pass to the quinn::Endpoint in order to dial that per. This ensures we are able to dial via a public key.
Each time you send packets to a particular peer, the MagicSock
will request the best_addr
from its internal peer address book. This means the address and socket on which we send the latest packet may be different from the one we sent previously.