Building a Geneve tunnel over QUIC

I spent some time putting together a small Rust client and server for a tunnel experiment, and the design shifted a bit as I went.

The original idea was straightforward: move Ethernet frames between two Linux machines using Geneve. But the usual Geneve-over-IP approach has a limitation that mattered for my setup: both ends need to be reachable. In real environments, that is often the first thing that breaks down.

A lot of the systems I work with sit behind NAT, inside containers, or in small lab setups where only one side can accept inbound traffic. I wanted something that still worked in those conditions.

That is what pushed me toward QUIC.

What I built

This was never meant to be a production-ready overlay or a full tunneling system. The goal was smaller than that: build something simple, understandable, and real enough to be useful.

The result was:

The client connects out to the server, and the server listens. Tunnel traffic moves over QUIC datagrams, while setup and control can go over QUIC streams. That split made sense almost immediately.

There is nothing wrong with the normal Geneve model when both endpoints are easy to reach. But in practice, that assumption often does not hold.

I did not want the client side to depend on inbound access. QUIC solved that part cleanly. It runs over UDP, keeps a real connection model, and is much more comfortable in NATed environments than a raw UDP tunnel. That made it a better fit for this experiment.

So instead of forcing Geneve to deal with reachability, I left Geneve in charge of encapsulation and used QUIC to carry it.

Approach

The basic path was simple enough.

The client reads Ethernet frames from a TAP device, wraps them in a Geneve header, and sends them over QUIC datagrams.

On the other side, the server receives those datagrams, removes the Geneve header, and pushes the Ethernet payload into its local forwarding path.

Traffic in the reverse direction follows the same idea: the server sends frames back through the correct client session.

Closing note

In the end, this was just a small Rust tunneling experiment, but it made one thing clear to me: if I want to carry Geneve traffic without requiring both ends to be publicly reachable, QUIC is a very solid transport for it.

It fits the kinds of environments I actually use, and it keeps the overall design pretty clean.