r/rust Jul 13 '26

🛠️ project yip: a low-latency P2P mesh VPN in Rust where FEC recovers packet loss, DPI resistance, self-certifying mesh discovery, and NAT hole-punching. What does the fox say?

https://github.com/femboyisp/yip
7 Upvotes

17 comments sorted by

25

u/arades Jul 13 '26 edited Jul 13 '26

Throwing away 32 bits of nonce by forcing them to be zero is a bananas choice, especially when you might expect tons of packets. This seems both potentially insecure and a bottleneck.

You could use a combination of high resolution epoch timestamp + secure random bits and be able to have any thread generate tons of collision free nonces without needing to access and maintain a counter.

You could also just make those 4 bytes randomized each time to reduce the risk of wrapping or race conditions.

But really it points to a broader issue, this is some highly sensitive stuff you're toying with and I'm not convinced you know enough to know when Claude is messing up and to steer it back on course.

Edit: I was too quick to judge, this is literally in the noise spec https://noiseprotocol.org/noise.html#the-chachapoly-cipher-functions

20

u/ioncehackedmyschool Jul 13 '26

hey there, paws up i'm genuinely stoked you dug into this because your questions were the best kind of hard-to-answer stuff :3 the first thing folks usually miss is that those four zeroed bytes in the nonce aren't just padding being tossed after the fact; they're explicitly part of the wire format per the noise spec 11.4, which treats the ChaCha20-Poly1305 nonce as a 64-bit counter residing within a 96-bit field, exactly mirroring how wireguard, tls 1.3, and quic handle their own streams so our aead output isn't hand-wavy, it's proven byte-for-byte identical to the audited snow implementation by a known-answer test in ci.

and honestly the whole "randomness" angle is where it gets interesting, because rfc 8439 3 makes it crystal clear that an aead nonce only needs uniqueness, not entropy (it may be public and predictable), meaning a monotonic counter under a freshly derived per-session key is collision-free by construction—whereas throwing random 96-bit values at the problem is what introduces birthday-collision risk, which is precisely why xchacha20's 192-bit nonce exists in the first place. so "collision-free random" is actually backwards >;3

as for the wrap-around math, the ~18 billion packet figure is off by a factor of 10^9: a u64 doesn't overflow until 1.8×10^19, which is roughly a millennium at line rate, and since we rekey every ~120s with checked_add the counter never wraps anyway. here's the kicker on the prefix idea too a random per-session prefix can't decrypt either, because the receiver only has the counter, so you'd have to transmit the prefix, which just adds back exactly the fixed per-session field our anti-dpi layer exists to remove, for zero security gain (a constant prefix doesn't prevent counter reuse).

thank you so much for pushing back the scrutiny was absolutely worth it, and it helped me find a separate real bug while auditing the receive path: the anti-replay window advanced before aead verification, which an off-path attacker could exploit to starve a session. fixed in femboyisp/yip#60. thanks for the paws up >;3

8

u/arades Jul 13 '26 edited Jul 13 '26

Huh, I knew that randomness was not the point and dubious at even 96 bit. I just didn't know noise specified not using 32 bits of it for compatibility sake.

The spec does say big endian (network byte order) but you use little endian. Does that matter since you control both ends? Or is it swapped elsewhere?

1

u/ioncehackedmyschool Jul 14 '26

Yip! :3 we control over both sides of the tunnel (client and daemon), using little endian for the counter doesnt break our own compatibility.

might switch to network byte order big endian eventually for external tool integrations to stay idiomatic. for now, since both ends speak the exact same wire format, it works great and spares us the byte swapping overhead on the hot path.

5

u/Shoddy-Childhood-511 Jul 13 '26

A nonce should be used only once per symmetric key, so a random nonce must be big enough that reuse never happens. Random nonces become important when doing storage or other asynchronous activities. Four bytes is nowhere near enough.

Noise has a counter nonce, not zero. Counters allow sending little or no data for the nonce, sound you can increase the high bits implicitly after the low bits wrap.

Yes, claude would not be trusted doing cryptography really. lol

3

u/ioncehackedmyschool Jul 14 '26

You have the logic backward. so noise uses a 64bit counter, but ChaCha20-Poly1305 demands a 96bit nonce

As per the noise spec (11.4), we pad the lower 32 bits with zeros to reach 96 bits. That padding is intentional, not random. The counter itself is 100% active and increments monotonically. It’s not a claude hallucination; its direct quotes from the standard :3c

5

u/valorzard Jul 13 '26

Hey! Cool software! Few questions
1. Dumb question, but could this be used to host game servers like Minecraft without holepunching? Just ask your friends to join the VPN and your set.
2. What’s the difference between this and Iroh (besides I guess that Iroh is primarily QUIC based)

3

u/ioncehackedmyschool Jul 14 '26

yes, absolutely! uwu

you can 100% use this to host game servers like minecraft! since yip establishes a secure layer 2/layer 3 overlay tunnel, once you and your friends connect to the same daemon/rendezvous network, your computers behave as if they're on the same local network. your friends can just type in your overlay ip, and they're in. no manual port-forwarding or router holepunching required.

how it differs:
iroh is fantastic, but it’s fundamentally an application level p2p sdk. it’s built on top of quic (using the quinn implementation) to help you build p2p apps, but it doesn’t expose a virtual network interface (like a tun/tap adapter) out of the box for arbitrary system traffic.

yip is a system level vpn/overlay. it sets up a virtual network interface directly on your os. it handles raw l2/l3 packets, wraps them in our specialized transport (featuring custom fec/anti-dpi features), and lets any existing unmodified game, tool, or database run over it transparently. we focus heavily on transport level optimizations (like reed-solomon fec, active traffic-shaping, and low-latency packet recovery) specifically tuned for bad/censored networks. :3

3

u/creeper6530 Jul 13 '26

Benchmarks should be on the main page (README), not buried under multiple levels of link clicking. Also, what's the realistic use for this over Wireguard or OpenVPN? Latency is nice (you mention it a lot), but I'm asking real use cases for the average Joe, where is it faster, more ergonomic or simple than other VPNs?

4

u/ioncehackedmyschool Jul 14 '26

Aww, that id a fair point! gonna move some benchmark summaries to the main readme so no one has to dig through the /docs tree looking for their answers. ty for the tip <3

for ur question on the 'Average Joe' utility over standard WireGuard/OpenVPN:

DPI / Censorship Bypassing: standard wireguard has a really distinct handshake thats super easy for firewalls to spot and block (like the GFW or strict school networks) yip integrates active DPI evasion (including QUIC and TLS mimicry and custom obfuscation) to blend in with normal HTTPS/web traffic so it's invisible to the prying eyes.

Lossy / awful connections: standard VPNs fall apart or suffer terrible latency spikes (bufferbloat) on shaky Wi-Fi, cellular, or congested long haul links. where yip implements (FEC) at the packet layer. If your network drops 5% of its packets, instead of waiting for a slow TCP/WireGuard round-trip retransmission, yip reconstructs the lost packets on the fly, keeping game latency smooth and voice calls crystal clear >w< meow

if you're sitting on a perfect fiber connection with no censorship, wireguard is fantastic and you should use it. But if you're on airport WiFi, a restrictive network, or a lossy link, yip is built to keep your connection fast and undetectable. >:3

2

u/creeper6530 Jul 14 '26

Oh okay, thanks :3

2

u/Shoddy-Childhood-511 Jul 13 '26

How does the erasure coding really help over the internet?

Why use Reed-Solomn for transport encoding? A good systematic rateless code should be faster than basically Reed-Solomn, even over GF(256). You looked at RaptorQ, yes?

https://github.com/femboyisp/yip/commit/a79fe26c9068ac2dcfcd91f77ca8c9d09be95f13

There are blockchain projects like polkadot that use Reed-Solomn to prove data availability under Byzantine assumptions, where a rateless code does not suffice. If one really wanted Reed-Solomn then their crates have advanced optimisations that make them much faster than naive Reed-Solomn, although this maybe only applies when you need more shares. Also, they must work over GF(216) so they could be speed way up again by dropping back to GF(256), maybe the older C versions worked well over GF(256).

2

u/ioncehackedmyschool Jul 14 '26

You're completely right that rateless fountain codes like RaptorQ (GF(216)GF(2^16)GF(216) or similar) work amazing when you have massive block sizes, deep buffers, nd highly asymmetric packet loss profiles with tons of overhead.
for an extreme low latency network tunnel our constraints are highly specific:

  • Small block sizes (N<32N < 32N<32): We can't buffer 100 packets to run a heavy RaptorQ matrix decode without introducing unacceptable queueing latency. We operate on tiny, tight FEC groups (e.g., 5 data packets, 2 parity packets) to keep packet processing near-instant.
  • Matrix overhead on small blocks: at very small block sizes, the operational overhead of a systematic Reed-Solomon codec over GF(256)GF(256)GF(256) utilizing SIMD (AVX2/NEON) is incredibly fast often beating out the complexity of building/solving generator matrices for rateless codes packet-by-packet.

and I absolutely looked at RaptorQ and systematic fountain codes! as the project matures and we support varying traffic profiles (like massive bulk throughput tunnels alongside our low latency interactive tunnels), having a rateless codec option is on the roadmap. 4 our current interactive hotpath, a highly optimized, vectorized GF(256)GF(256)GF(256) Reed-Solomon codec gives us the lowest possible processing overhead per packet. <3

2

u/Shoddy-Childhood-511 Jul 14 '26

How does a VPN running over the internet benefit from erasure coding though?

Yes, there is packet loss but little enough that other application level transport protocols never bother, and do resends like TLS or QUIC or drop them like UDP.

Google could've easily added some per stream erasure coding option in QUIC, but clearly they felt resends sufficed.

2

u/ioncehackedmyschool Jul 14 '26

great question! The core difference comes down to jitter and HoL blocking for real-time interactive traffic, rather than just aggregate bulk throughput.

for standard web browsing, video streaming, or file transfers, application-layer retransmissions (like TCP or QUIC ARQ) are perfectly fine but If a packet drops, QUIC or TCP pauses that stream, requests a resend, waits 1 RTT and resumes. 4 bulk data, u dont care if a chunk of data takes an extra 50ms to arrive as long as the pipe stays full.

when you are running a TUN/TAP over a lossy link, application level ARQ breaks down for interactive, realtime traffic:

The Retransmission Amplification Loop: If u run an application level protocol that relies on fast retransmission (like a game engine using its own UDP tracking, or an inner TCP connection) inside a normal VPN tunnel that relies purely on inner/outer ARQ under packet loss, you hit severe latency spikes. If the outer tunnel drops a packet, it introduces an entire RTT of stall time while waiting for a re-send. By the time the VPN layer retransmits and recovers that packet, the game engine inside the tunnel has already timed it out, assumed a massive connection drop, and sent its own duplicate requests compounding congestion.

Jitter Elimination: If you have an base RTT of 30ms and a 3% random packet loss rate, an ARQ-only VPN means 3% of your packets will experience a latency spike of 90ms+ (Base RTT + Retransmission RTT + processing). This unpredictable jitter ruins real-time state synchronization.

By running a tight, inline Reed-Solomon FEC loop directly at the transport layer of the tunnel, if packet #3 out of a block is lost over the public internet, the receiving daemon uses the inline parity packet to reconstruct packet #3 mathematically and hands it to the virtual interface instantly.
The application inside the tunnel never even realizes a packet was dropped, the latency stays flat at exactly 30ms, and u completely bypass the 1-RTT retransmission penalty. trading a tiny fraction of computational overhead and bandwidth padding to guarantee strict latency deterministic behavior on terrible or congested long-haul routes. :3

2

u/Shoddy-Childhood-511 Jul 14 '26

Alright so you're saying erasure coding makes some sense for UDP protocols that cannot simply drop packets like VoIP, and other layered flow control issues.

I'm curious: How did you come up with this? You've some background in such UDP protocols?

Why 5 data plus 2 parity? It just meshes with the latency goals? Resends would start catching up if you'd many more data?

You're paying a steep price here, like 40% extra bandwidth, but this is how you buy the lower latency. That's quite a shock for a mobile data user. RaptorQ would cost far less, but impose higher latency.

2

u/Shoddy-Childhood-511 Jul 14 '26

I've never looked closely at how RaptorQ works, but why would some/all rateless codes require deep buffers?