pandax381/microps: a TCP/IP stack small enough to read, with a log that teaches
An implementation of a small TCP/IP protocol stack for learning.
At a glance
- What is it?
- microps is an MIT licensed C implementation of a small TCP/IP stack written for learning, covering Ethernet, ARP, IP, ICMP, UDP and TCP with a socket-like API, and its sample log of protocol registration and an ARP and ICMP exchange is the clearest teaching material in the repository. The same author has ported the design to Go and Rust and to two hobby kernels, and a third party has ported it to Zig.
- Who is it for?
- Adopt microps if you want to understand how a network stack actually works, because the code is small, the logs are readable and the whole thing builds with make and a C compiler, so the fastest way to learn is to change a field and watch the log change. Do not adopt it as a networking library for an application, since it is explicitly a learning implementation with a null device driver, a loopback and a TAP interface, not a stack you would put in front of a service.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A stack sized for reading, not for shipping
microps is an implementation of a small TCP/IP protocol stack for learning, and the phrase does the work. This is not a networking library, it is a teaching codebase, and the design decisions follow from that.
The repository is one flat directory of C source and headers, which is the clearest sign of the intent. There is `net.c` and `net.h` for the core, `ether.c` for Ethernet, `arp.c`, `ip.c`, `icmp.c`, `udp.c`, `tcp.c` and `sock.c` for the protocols and the socket layer, and `util.c` for the odds and ends. The `driver/` directory holds the device drivers, `platform/` holds the operating system glue, `app/` holds the sample programs, and `test/` holds the test executable. There is a `Makefile`, a `compile_flags.txt` for editor tooling and a `CONTRIBUTORS` file.
Each protocol is one file. There is no framework, no abstraction hierarchy to learn before you can read a packet handler, and no external dependency at all. That is the appeal. The cost is equally clear, and the README is upfront about it by linking a family of sibling projects, a Go reimplementation for people who love the Go language, a Rust reimplementation, ports to xv6 and xv6-riscv, a port to MikanOS, and a third party Zig reimplementation by @arata-nvm.
The existence of that family tells you the most about the project. A learning stack that other people have ported to four other environments is a design that other people found worth porting, and the ports are the best argument that the layering is sound.
Two abstractions, and everything else follows
The feature list is a checklist of the design, and the top of it is the abstraction layer, split in two.
There is a physical device abstraction, with a structure for it called `net_device`, supporting multiple link protocols and physical devices. And there is a logical interface abstraction, with a structure called `net_iface`, supporting multiple address families and logical interfaces.
The distinction is the one every real stack makes and that most teaching implementations skip. A device is the thing you read bytes from, a network interface is the thing that has an address, and one device can carry several interfaces. Separating them is what allows a single physical link to host several networks, and it is the reason the route table in the sample log can hold a loopback route and two Ethernet routes at once while the devices are separate.
Underneath sit the devices. There is a null device, which is the classic trick for a stack that has to run with no hardware at all, a loopback device, and Ethernet. Ethernet is implemented two ways on Linux, through TUN or TAP and through PF_PACKET, which are the two ways a user space program can get raw frames on that platform, and the log confirms the TAP driver is what initialises the Ethernet device in the sample run.
Above that sit the protocols, Ethernet, ARP, IP, ICMP, UDP and TCP, and above them a socket-like API. So the stack is layered the way the textbooks describe, and the layers are visible as directories and files.
The log is the tutorial
The most useful thing in the repository is the sample log, because it shows the whole stack starting up and handling two packets, with file and line references for every step.
The startup sequence is where the design is legible. Protocols register first, with the ARP type `0x0806` registered at the network layer and the IP type `0x0800` registered after it, and then IP registers the upper layer protocols, ICMP, UDP and TCP, each with its type number. Then the timers are registered, an ARP timer with a one second interval and a TCP timer with a hundred millisecond interval, and the timers are the part that tells you this stack has a scheduler rather than a poll loop pretending to be one.
Devices register after that, the null device, then the loopback, then the TAP Ethernet device, each with its own type. The order is not accidental, since the network layer has to be ready before a device can be attached to it, and the log makes the dependency explicit without a line of prose.
Then the routes and the interfaces, a loopback route for `127.0.0.0` with a `255.0.0.0` mask, an interface with the loopback address, then the Ethernet route for `192.0.2.0` with the address `192.0.2.2`, and finally a default route with a next hop of `192.0.2.1`, which is the gateway on the other side of the TAP link.
After that the run starts, devices are opened, and a background thread is created. Then the first packet arrives, an ARP request, and the log follows it all the way down and back up: the poll helper reads it, the input handler queues it, the network thread pops it, the ARP handler decodes the fields one by one, the cache is updated, a reply is built, the Ethernet layer transmits it, and the next packet is an IP packet carrying an ICMP echo request, which is decoded, answered with an echo reply, and the whole exchange is repeated in the opposite direction. Every line carries the source file and line number, so a reader can open the file and see the function that produced the message.
At the end, the interrupt shows the shutdown path, terminating the background thread and closing each device in turn.
The Makefile, and how the platform layer differs
The build is a plain Makefile with hand written rules, and it shows the portability approach.
Four sample programs are built, `udpc.exe` and `udps.exe` for UDP client and server, and `tcpc.exe` and `tcps.exe` for TCP, plus a `test.exe`, and the default target builds all of them:
$ make
$ make cleanThe object list is the stack itself, `util.o`, `net.o`, `ether.o`, `arp.o`, `ip.o`, `icmp.o`, `udp.o`, `tcp.o` and `sock.o`, with the null and loopback drivers always included.
The platform differences are handled by conditional blocks on `uname`. On Linux, the build adds the TUN/TAP and PF_PACKET drivers, the Linux scheduler and interrupt glue from `platform/linux/`, the `pthread` and `-lrt` flags, and the Linux include path. On macOS the driver list stays as it is, and the source carries a Darwin branch in the Makefile for the platforms where the TUN device is available in a different form.
The compiler flags are modest and instructive: `-g` for symbols, `-Wall`, the `-Wno-unused-parameter` suppression that a callback heavy stack needs, and `-iquote .` so that headers are found as local includes rather than by path. The rules are the old style suffix rules, `.c.o`, and the executables are built with a `%.exe : %.o` pattern, which is a conservative choice for a codebase meant to be read as much as compiled.
The `compile_flags.txt` file is a small nicety for anyone using an editor with a compilation database, and it is the sort of detail that says the project expects people to read the code rather than only run it.
The written material, in Japanese, is part of the project
The repository links two pieces of teaching material, and both are in Japanese. There is a set of step by step development guides hosted on Google Drive, and there is a book, the Japanese title of which translates as an introduction to building a TCP/IP protocol stack from scratch, published by Mynavi.
That is a real consideration for an English speaking reader, because the guides are where the reasoning behind the code is likely to be, not just the code itself. The README is written in English and the log output is language neutral, so the code can be followed without the guides, but the guides are where the author's own explanation of the design would be.
The book being a published volume rather than a blog series also tells you something about the project's longevity and intent. This is a project with a curriculum attached to it, and the fact that the author has then ported the result to other languages and to hobby operating systems suggests the curriculum is being applied repeatedly rather than written once.
Where microps is the wrong tool
The first and most obvious wrong case is using it as a networking library. There is no performance work, no congestion control worth the name, no IPv6, and no security. It exists so that you can read it, and a production stack is a different project with a different cost.
The second wrong case is expecting it to run anywhere. The Ethernet device is Linux only in the two implementations provided, TUN or TAP and PF_PACKET, and on other platforms the build includes only the null and loopback drivers, which means the stack runs but never touches a network. The macOS branch of the Makefile does not add an Ethernet driver.
The third wrong case is a learner who wants to write an application rather than a protocol. The socket-like API is the interface for testing the stack, and the sample applications are a UDP client and server and a TCP client and server, which is exactly the right set for learning and exactly the wrong set for a real service.
The fourth is a course that needs IPv6 or DHCP or a modern transport. The protocol list is Ethernet, ARP, IP, ICMP, UDP and TCP, and that is the whole of it, so a curriculum built around this code has to supply those layers itself.
On the state of the project, the last push to the repository was on 2026-07-24, the repository is not archived, the licence is MIT, and there are no GitHub releases, so the code is the artefact. The Makefile, the file naming and the logging style are all consistent with a project that is meant to be read top to bottom, and the ports to Go, Rust, Zig, xv6 and MikanOS are the best evidence that the layering survives translation.
Editorial conclusion
Adopt microps if you want to understand how a network stack actually works, because the code is small, the logs are readable and the whole thing builds with make and a C compiler, so the fastest way to learn is to change a field and watch the log change. Do not adopt it as a networking library for an application, since it is explicitly a learning implementation with a null device driver, a loopback and a TAP interface, not a stack you would put in front of a service. Verify first that you can create a TUN or TAP device on your Linux machine, since the Ethernet driver depends on it, and that you have a C toolchain with the platform support files the Makefile expects, since the Linux and macOS builds take different code paths.
Frequently asked questions
What does microps implement?
A small TCP/IP stack in C for learning: Ethernet, ARP, IP, ICMP, UDP and TCP, over a null device, a loopback device and an Ethernet device that is implemented through TUN/TAP and PF_PACKET on Linux, with a socket-like API on top.
What are the two abstraction layers in microps?
A physical device abstraction with a `net_device` structure supporting multiple link protocols and devices, and a logical interface abstraction with a `net_iface` structure supporting multiple address families and interfaces. The sample log shows the consequence, with a loopback route and two Ethernet routes over three separate devices.
How do I build microps?
With a C compiler and make. The Makefile builds four sample programs, a UDP client and server and a TCP client and server, plus a test executable, and it has conditional blocks per platform: on Linux it adds the TUN/TAP and PF_PACKET drivers, the Linux scheduler and interrupt glue from `platform/linux/`, and the `pthread` and `-lrt` flags.
What other implementations of microps exist?
The author links a Go reimplementation, a Rust reimplementation, ports to xv6 and xv6-riscv, and a port to MikanOS, and the README credits a third party Zig reimplementation to @arata-nvm.
Is there written material for microps?
Yes, in Japanese. The README links step by step development guides hosted on Google Drive and a book, an introduction to building a TCP/IP protocol stack from scratch, published by Mynavi.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pandax381-microps)Was this useful?