Implementing QUIC from Scratch with Rust: A Fresh Start

on 2024-12-15

Why Start This Project?


Self-Learning and Notes

I always want to write tech blog posts. Every time I stumble on a fun detail I want to share it, yet I never take action. There are too many good games and too little spare time—surely it cannot be because I am lazy, right? 🐶 This sudden plan of building QUIC from scratch in Rust feels like starting a brand-new AAA game, so it gives me the push to finally begin. I hope it becomes the first solid step for this blog.

When I first joined the industry many people told me to read code from projects I like, re-create it, and then improve it. They said that loop turns you into a better programmer. A thread on reddit asks the same question. The screenshot below shows John Carmack’s reply, and most answers repeat the same advice: explore, build, refactor, and enjoy the trip instead of worrying about silly mistakes. I also love the YouTube host Jon Gjengset; he streams himself writing Rust tools, does live Q&A, and shares his thoughts, and I learn a lot from him.

arch-01

After years of work I understand this view even more. Implementing something yourself surfaces many hidden details. The programmer I admire, agentzh, once shared a “hand-copy the code” learning method that does the same thing because paper knowledge is never enough. Knowing those details helps a lot when you handle on-call issues, urgent customer tickets, or daily feature work—you make better choices, narrow the fault faster, find the root cause, and ship sturdier features.

Think about Sherlock Holmes. He solves cases fast because he notices what others miss. If we keep training our eyes for small technical signals, maybe one day we can be the Sherlock Holmes of our team. 😂

My Connection with QUIC

I still remember 2018, right after graduation. Teammates in the next room tried to add QUIC to our custom NGINX build. I had never heard of QUIC or done much UDP programming. I only heard their late-night chats and thought it sounded amazing. Back then only a few QUIC libraries existed and they were unstable, so putting QUIC into NGINX was a serious challenge.

About two years ago I joined a new company, and my first task was to bring QUIC into our product. I read the RFC over and over and stepped through ngtcp2, nginx-quic, and msquic. Even though I had worked on UDP protocols such as the simpler KCP or a custom protocol we built on top of WebRTC, I still kept saying wow while studying QUIC—it really is a great transport protocol.

As time went by I noticed that I was forgetting the details I had spent so much energy on, and that upset me. So I now want to implement a QUIC stack and client myself and write down what I learn.

While coding the stack I will split it into core features and traits. I also have many related engineering stories, so each feature might lead to another blog post. For example, QUIC migration inside a cluster brings many problems to solve, and classic UDP topics such as hot upgrades for a busy server have their own traps. Writing this series not only sharpens my QUIC skills, it also helps me wrap up past field notes. It feels like making a whole plate of dumplings just so I can dip one spoon of vinegar.

This series will surely contain mistakes, but writing them down lets me fix them later as I learn more. Maybe a kind reader will point out issues too, and that feedback is part of my motivation.

Hoping to Deepen My Rust Understanding

There are already many articles praising Rust, so I will not repeat them. As a C programmer I liked Rust the moment I tried it. C and C++ projects have endless build setups and constant memory safety risks, so I used sanitizers to calm myself even though they miss bugs. Rust checks memory safety during compile time, which feels great, and writing Rust is far more pleasant than writing C because it is a modern language.

I cannot use Rust at work, so I code fun side projects with it. Building a QUIC stack is another chance to write Rust. I only started learning Rust this year, so I am still trying to write idiomatic code. I plan to refactor this project often and try advanced features such as async so that practice will deepen my understanding of the language.

My FC 25 Account Got Wrongfully Banned

Lastly—and most importantly—my FC 25 game account was wrongfully banned, so I suddenly have plenty of time for this project. Here is my message to EA: You are the worst gaming company on the planet. Your game is full of cheaters and I fought them every week, yet you lack the will to fix it. Instead you built the laziest detection system and banned honest players like me (the forums are full of similar cases). I will never touch your games again. And to TGA: giving FC 25 the 2024 Best Sports Game award is just laughable.

What Kind of QUIC Project Do I Want to Build?


First, this is not for production. It is purely for personal practice. QUIC handshakes are so fun that I refuse to lean on rustls or OpenSSL. I will call the crypto primitives directly through a crate like ring, so the project will stay far away from any production-ready bar.

While I build the stack I will also create a QUIC client that feels more like a lab tool. It should expose as many knobs as possible, not just the transport parameters. For example, the client can choose when to trigger key updates—after sending a set number of packets or after running for a set duration. Flexible controls like that make it easier to study the protocol. Back when I learned QUIC I had to hack open-source projects to trigger certain scenes, which was annoying.

Below are the feature groups I want to implement:

Basic Features

These are the bare minimum for a working QUIC client:

  1. QUIC Handshake
  2. QUIC Reliability
  3. QUIC Connection Termination
  4. QUIC Key Update
  5. QUIC Streams
  6. QUIC Flow Control

Quality Features

These functions often decide whether the stack behaves well in the real world:

  1. QUIC 0-RTT
  2. QUIC Congestion Control
  3. QUIC Migration
  4. QUIC MTU Discovery
  5. QUIC Unreliable Datagram

Experimental Features

This bucket covers fun QUIC drafts such as multipath plus tools like qlog. I also plan to plug the stack into tokio, since async runtimes are the norm. If time allows I even want to try HTTP/3, because most QUIC traffic serves HTTP/3 anyway. If all goes well, I hope the client can talk to this static blog, which already runs on OpenResty with HTTP/3 support—though to be fair, I only did a tiny bit of that setup. 😄

Preparations


With the plan out of the way, here is the prep work I want to do before writing actual code.

Documentation

The QUIC Working Group site is my go-to index for every QUIC RFC. It is super handy. Of course RFCs alone are not enough. I often hit sections I do not fully understand, and that is on me rather than the RFC, because RFCs define standards instead of teaching basics. As long as I spend some time learning the prerequisites I can pick up reading again.

Debugging Tools

I built NGINX and OpenSSL from source with debug info and verbose logs (by flipping their DEBUG macros). They will serve as the remote peer for my client. If my code acts weird, the server logs show which protocol step failed. I will not detail those build steps here because the docs and communities already cover them. Quick feedback like this is vital when you work on a side project.

Besides server logs, packet tools such as Wireshark also help me see what QUIC is doing. Someone might say QUIC traffic is encrypted so you cannot inspect it, but that is easy to fix. I plan to implement TLS myself, so I can write the symmetric keys through Wireshark’s sslkeylog hook and let Wireshark decrypt the capture. I usually work over ssh + tmux, so I rely on tshark, the command-line version of Wireshark—it is great, give it a try if you have not.

Rust Tooling

Rust comes with nice quality tools. When I filed my first Rust pull request to aya in May, the CI introduced me to many of them. Before that I only used cargo fmt. Now I know about clippy, miri, and friends, so I will wire them into this project’s CI/CD flow.

Testing

Strong tests are a must, but I will not add unit tests too early because I expect heavy refactors. Once the project has the basic features I will add automated tests to guard stability, and later on I might ask tools like Cursor to help cover more cases.

Finally, I created a GitHub repo for this project: feather-quic. I believe this trip will be fun—if you made it this far, maybe drop the project a Star to cheer me on. 😁