{"id":52023740,"url":"https://github.com/czhao-dev/userspace-tcp-stack","last_synced_at":"2026-08-01T05:01:32.099Z","repository":{"id":370944931,"uuid":"1282559283","full_name":"czhao-dev/userspace-tcp-stack","owner":"czhao-dev","description":"A user-space TCP/IP stack over a Linux TUN device — IP, ICMP, UDP, and a full eleven-state TCP state machine with sliding-window flow control, retransmission, and out-of-order reassembly","archived":false,"fork":false,"pushed_at":"2026-07-12T05:07:03.000Z","size":112,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-12T07:04:04.930Z","etag":null,"topics":["cmake","cpp","icmp","ip","network-stack","networking","tcp","tun","udp","user-space"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/czhao-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-27T23:46:58.000Z","updated_at":"2026-07-12T05:07:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/czhao-dev/userspace-tcp-stack","commit_stats":null,"previous_names":["czhao-dev/userspace-tcp-stack"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/czhao-dev/userspace-tcp-stack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/czhao-dev%2Fuserspace-tcp-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/czhao-dev%2Fuserspace-tcp-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/czhao-dev%2Fuserspace-tcp-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/czhao-dev%2Fuserspace-tcp-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/czhao-dev","download_url":"https://codeload.github.com/czhao-dev/userspace-tcp-stack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/czhao-dev%2Fuserspace-tcp-stack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36143736,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-01T02:00:05.789Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cmake","cpp","icmp","ip","network-stack","networking","tcp","tun","udp","user-space"],"created_at":"2026-08-01T05:01:31.384Z","updated_at":"2026-08-01T05:01:32.087Z","avatar_url":"https://github.com/czhao-dev.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MiniTCP — A User-Space TCP/IP Stack\n\n[![Rust](https://img.shields.io/badge/Rust-2021-000000?logo=rust\u0026logoColor=white)](https://www.rust-lang.org/)\n[![Cargo](https://img.shields.io/badge/build-Cargo-064F8C?logo=rust\u0026logoColor=white)](https://doc.rust-lang.org/cargo/)\n[![Docker](https://img.shields.io/badge/dev%20env-Docker-2496ED?logo=docker\u0026logoColor=white)](https://www.docker.com/)\n[![Platform](https://img.shields.io/badge/platform-Linux%20%2F%20TUN-FCC624?logo=linux\u0026logoColor=black)](https://www.kernel.org/)\n[![Tests](https://img.shields.io/badge/tests-16%2F16%20passing-brightgreen)](#test-results)\n[![Benchmark](https://img.shields.io/badge/GCP%20TUN%20echo-22.38%20MiB%2Fs-4c1)](#benchmark-gcp-tun-echo)\n[![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)\n\n\u003e A TCP/IP stack written in Rust that processes raw IP packets over a Linux TUN\n\u003e interface. It implements IPv4, ICMP, UDP, and an eleven-state TCP machine\n\u003e with retransmission, flow control, and out-of-order reassembly—and is\n\u003e exercised by unmodified `ping`, `curl`, and `netcat` clients.\n\n## Portfolio snapshot\n\n| Area | Evidence |\n|---|---|\n| **Protocol work** | IPv4 parsing/checksums, ICMP echo, UDP, and TCP from raw packet bytes upward |\n| **TCP reliability** | Handshake, all eleven RFC states, receive-window flow control, retransmission/backoff, reassembly, and teardown |\n| **Application boundary** | Socket-like TCP/UDP API with observable receive-timeout and buffer options |\n| **Validation** | 11 automated tests, lossy in-process link simulation, and real Linux interoperability checks |\n| **Measured performance** | 22.38 MiB/s median application-payload throughput on a GCP `e2-standard-2` TUN echo benchmark |\n\n---\n\n## Why this project\n\nMiniTCP moves the boundary normally hidden inside the kernel into a compact,\ninspectable Rust codebase. Linux provides a TUN interface that exchanges raw IP\npackets; MiniTCP owns everything above it: header parsing and checksums, packet\ndispatch, connection lifecycle, retransmission, buffering, and the\napplication-facing socket API.\n\nThat makes each familiar network operation concrete. A `curl` connection\nbecomes a SYN on the TUN device, then explicit state transitions and generated\nsegments. The implementation remains small enough to follow end to end while\nstill interoperating with the host's real networking tools and a second\nMiniTCP instance across kernel IP forwarding.\n\n---\n\n## Repo Structure\n\n```\nminitcp/\n├── README.md\n├── Cargo.toml\n├── docker/\n│   └── Dockerfile           ← Linux dev/runtime environment (TUN needs real Linux)\n├── docker-compose.yml\n├── src/\n│   ├── lib.rs               ← crate root: module declarations, re-exports\n│   ├── tun.rs               ← open/configure the TUN device (libc ioctl, the one real `unsafe`)\n│   ├── ip.rs                ← header parse/construct, checksum\n│   ├── icmp.rs              ← echo request/reply\n│   ├── udp.rs               ← UdpTable: bind registry + per-binding receive queues\n│   ├── tcp.rs               ← the core: state machine + reliability (TcpTable)\n│   ├── stack.rs             ← Stack: owns all protocol state, the socket API\n│   └── bin/\n│       ├── minitcp.rs       ← bare protocol demo: ICMP echo + UDP auto-echo\n│       ├── chat_server.rs   ← demo app: chat + HTTP, built on the socket API\n│       ├── chat_client.rs\n│       └── bench_echo_server.rs ← quiet TCP echo peer for repeatable measurements\n├── tests/\n│   ├── retransmission.rs    ← simulate packet loss, verify retry + backoff\n│   └── sockopt.rs           ← setsockopt/getsockopt, SO_RCVTIMEO actually timing out\n├── bench/                    ← GCP runner, raw results, and matplotlib plot\n├── scripts/\n│   ├── setup_tun.sh         ← create tun0/tun1 with point-to-point addressing\n│   └── teardown_tun.sh\n└── docs/\n    ├── protocol_notes.md    ← header layouts, byte offsets, checksum algorithm\n    └── state_machine.md     ← annotated FSM, RFC sections, scope cuts\n```\n\nUnit tests for `checksum16`, the TCP state machine, and the UDP socket layer\nlive as `#[cfg(test)] mod tests` blocks inside `ip.rs`/`tcp.rs`/`udp.rs`\nrespectively — `cargo test` discovers these automatically alongside the two\nintegration tests above, with no separate test-registration step (unlike\nCTest's `add_test`).\n\n---\n\n## What MiniTCP Implements\n\n**IP layer** — `src/ip.rs`\nParses and constructs IPv4 headers: version, header length, total length,\nTTL, protocol field, header checksum, source and destination addresses.\nValidates the header checksum on receipt and recomputes it on send.\n\n**ICMP** — `src/icmp.rs`\nResponds to ICMP Echo Request (type 8) with Echo Reply (type 0), so the\nreal `ping` command run against MiniTCP's virtual IP address gets a correct\nreply.\n\n**UDP** — `src/udp.rs`\nParses and constructs UDP headers (source port, destination port, length,\nchecksum, including the 12-byte pseudo-header). A datagram is delivered to\nan application socket if one is bound to its destination `(address, port)`;\notherwise it's echoed back to the sender, exactly as before the socket-level\nUDP API existed.\n\n**TCP** — `src/tcp.rs` (the centerpiece)\n\n- The three-way handshake: `SYN` → `SYN-ACK` → `ACK`\n- The full eleven-state connection state machine (RFC 793 / RFC 9293),\n  including the simultaneous-close edge case — see\n  [docs/state_machine.md](docs/state_machine.md)\n- Sliding-window flow control using the receive window field\n- Per-segment retransmission with exponential backoff (RFC 6298-style,\n  single timer per connection)\n- In-order delivery: out-of-order segments are buffered and spliced in once\n  gaps are filled\n- Graceful connection teardown in both directions: local-initiated close\n  and remote-initiated close are genuinely different code paths, both\n  tested against real `nc`\n- `TIME_WAIT` correctly re-ACKs a retransmitted FIN (the scenario\n  `TIME_WAIT` exists to handle) rather than silently dropping it\n\nSlow-start congestion control was scoped out — see\n[docs/state_machine.md](docs/state_machine.md#why-no-real-congestion-control)\nfor why.\n\n**Socket-like API** — `src/stack.rs`\nA `Stack` type that owns every piece of protocol state (the TCP connection\ntable, the UDP bind table, the TUN fd) and an application-facing API so\napplication code looks like BSD sockets and never touches `TcpConnection`/\n`TcpState`/`UdpBinding` directly — just an opaque, `Copy`-able `SocketId`:\n\n- TCP: `Stack::socket`, `listen`, `accept`, `connect`, `send`, `recv`,\n  `close`. Used by both demo apps below.\n- UDP: `Stack::udp_socket`, `bind`, `sendto`, `recvfrom`, using idiomatic\n  `std::net::SocketAddrV4` values.\n- Socket options: a typed `SockOpt` enum for receive timeouts, address reuse,\n  and receive/send buffer caps. Each option changes behavior, rather than\n  merely being stored as metadata.\n\n**Demo apps** — `src/bin/chat_server.rs`, `src/bin/chat_client.rs`\nA minimal chat server/client built only on the socket API. The server also\nrecognizes HTTP requests and answers with a fixed `200 OK`, so the same\nlistener serves both `nc` chat sessions and `curl`.\n\n---\n\n## TCP State Machine\n\nThis is the core TCP lifecycle implemented by MiniTCP. See\n[docs/state_machine.md](docs/state_machine.md) for an annotated version with\nthe RFC section and implementing function for every transition.\n\n```\n                         CLOSE                    CLOSE\n                  ┌────────────────┐       ┌───────────────────┐\n                  ▼                │       ▼                   │\n            ┌──────────┐           │ ┌──────────┐              │\n            │  CLOSED  │           │ │  CLOSED  │              │\n            └────┬─────┘           │ └────┬─────┘              │\n                 │ LISTEN          │      │ active OPEN        │\n                 ▼                 │      │ (send SYN)         │\n            ┌──────────┐           │      ▼                    │\n            │  LISTEN  │           │ ┌──────────┐              │\n            └────┬─────┘           │ │ SYN_SENT │              │\n                 │ recv SYN        │ └────┬─────┘              │\n                 │ (send SYN,ACK)  │      │ recv SYN,ACK       │\n                 ▼                 │      │ (send ACK)         │\n            ┌──────────┐           │      ▼                    │\n            │ SYN_RCVD │           │ ┌─────────────┐           │\n            └────┬─────┘           │ │ ESTABLISHED │◀──────────┘\n                 │ recv ACK        │ └──────┬──────┘\n                 └─────────────────┴────────┤\n                                   data transfer\n                                             │ close() (send FIN)\n                                             ▼\n                                      ┌─────────────┐\n                                      │ FIN_WAIT_1  │\n                                      └──────┬──────┘\n                              recv ACK       │       recv FIN+ACK\n                          ┌──────────────────┴──────────────────┐\n                          ▼                                     ▼\n                  ┌─────────────┐                       ┌─────────────┐\n                  │ FIN_WAIT_2  │                       │   CLOSING   │\n                  └──────┬──────┘                       └───────┬─────┘\n                         │ recv FIN (send ACK)                  │ recv ACK\n                         ▼                                      │\n                  ┌─────────────┐                               │\n                  │  TIME_WAIT  │◀──────────────────────────────┘\n                  └──────┬──────┘\n                         │ 2×MSL timeout\n                         ▼\n                  ┌──────────┐\n                  │  CLOSED  │\n                  └──────────┘\n```\n\n---\n\n## Architecture\n\n```\n                         Application\n          chat client/server or a manual test harness\n                              │\n                              ▼\n                        Socket-like API\n       TCP: connect/listen/accept/send/recv\n       UDP: bind/sendto/recvfrom + typed socket options\n                              │\n                              ▼\n                             TCP\n       state machine, sliding window, reassembly, retransmission\n                              │\n                              ▼\n                          UDP / ICMP\n       UDP binding/auto-echo                         ICMP echo reply\n                              │\n                              ▼\n                              IP\n       IPv4 header parse/build, checksum, route decision\n                              │\n                              ▼\n                         TUN device\n       `/dev/net/tun` exchanges raw IP packets with the kernel\n                              │\n                              ▼\n                 Linux routing and real network tools\n                 `ping`, `curl`, and `netcat`\n```\n\nThe TUN device is the key piece of infrastructure that makes this project\nboth tractable and demonstrable. The kernel handles Ethernet framing and\nphysical transmission; MiniTCP only ever sees and produces IP packets,\nwhich keeps the scope focused on the protocols that matter, while still\nproducing a stack that real, unmodified tools can talk to.\n\nDevelopment happens on macOS via Docker (a Linux container, since TUN/TAP\nneeds a real Linux kernel); the same `Dockerfile`/`docker-compose.yml` work\nunmodified on a native Linux host.\n\n---\n\n## Benchmark: GCP TUN echo\n\n![GCP MiniTCP TUN echo throughput and elapsed-time plot](docs/assets/gcp-tun-echo-throughput.png)\n\nMiniTCP reached a **22.38 MiB/s median** application-payload throughput on a\nGoogle Compute Engine `e2-standard-2` VM (two vCPUs), with five measured 4 MiB\necho transfers after one warm-up. Samples ranged from **21.83 to 22.60 MiB/s**;\nthe fourth trial is the slowest, but the plot shows a tight overall band and no\nrunaway variance.\n\nThis measures a kernel TCP client sending data through `tun0` to the quiet\n`bench_echo_server`, which echoes it through MiniTCP. It is intentionally a\nsingle-client, same-VM end-to-end TUN measurement: it validates the stack's\ndata path without claiming Internet throughput or multi-connection\nscalability. The figure is retained as portfolio documentation; raw benchmark\noutput is machine-specific and intentionally ignored by Git.\n\nTo reproduce on a Linux VM with `/dev/net/tun` and passwordless `sudo`:\n\n```bash\n./bench/run_gcp_vm.sh\n```\n\nThe runner installs its dependencies, builds the release binary, captures JSON\nand host metadata, renders the matplotlib plot, and removes `tun0` on exit.\n\n---\n\n## Example Session — Real Trace, Not a Mockup\n\nCaptured live: `chat_server --trace` running on `tun0` (self address\n`10.0.0.2`), a real `nc 10.0.0.2 8080` from a second shell in the same\ncontainer.\n\n```\nchat_server: listening on port 8080 (self=10.0.0.2, tun=tun0)\n[IP  ] recv  10.0.0.1 -\u003e 10.0.0.2   proto=TCP  len=60\n[TCP ] recv  SYN         seq=1842910416 ack=0 win=64240 len=20  state=LISTEN\n[TCP ] state LISTEN -\u003e SYN_RCVD\n[TCP ] send  SYN,ACK     seq=531850564 ack=1842910417 win=65535 len=0\n[IP  ] recv  10.0.0.1 -\u003e 10.0.0.2   proto=TCP  len=40\n[TCP ] recv  ACK         seq=1842910417 ack=531850565 win=64240 len=0  state=SYN_RCVD\n[TCP ] state SYN_RCVD -\u003e ESTABLISHED\nchat_server: client connected\n\n--- connection established, application can now send/recv ---\n\n[IP  ] recv  10.0.0.1 -\u003e 10.0.0.2   proto=TCP  len=46\n[TCP ] recv  ACK,PSH     seq=1842910417 ack=531850565 win=64240 len=6  state=ESTABLISHED\n[TCP ] send  ACK         seq=531850565 ack=1842910423 win=65530 len=0\nhello\n[TCP ] send  ACK,PSH     seq=531850565 ack=1842910423 win=65535 len=6\n\n[IP  ] recv  10.0.0.1 -\u003e 10.0.0.2   proto=TCP  len=40\n[TCP ] recv  ACK,FIN     seq=1842910423 ack=531850571 win=64234 len=0  state=ESTABLISHED\n[TCP ] send  ACK         seq=531850571 ack=1842910424 win=65535 len=0\n[TCP ] state ESTABLISHED -\u003e CLOSE_WAIT\nchat_server: client disconnected\n[TCP ] send  ACK,FIN     seq=531850571 ack=1842910424 win=65535 len=0\n[TCP ] state CLOSE_WAIT -\u003e LAST_ACK\n[IP  ] recv  10.0.0.1 -\u003e 10.0.0.2   proto=TCP  len=40\n[TCP ] recv  ACK         seq=1842910424 ack=531850572 win=64234 len=0  state=LAST_ACK\n[TCP ] state LAST_ACK -\u003e CLOSED\n```\n\n`curl` against the same listener, proving the stack is indistinguishable\nfrom a real one to a completely unmodified HTTP client:\n\n```\n$ curl -s -i http://10.0.0.2:8080/\nHTTP/1.1 200 OK\nContent-Length: 6\nConnection: close\n\nhello\n```\n\n---\n\n## Test Results\n\nTwelve unit tests (in-crate `#[cfg(test)]` modules) plus four integration\ntests (in `tests/`, three files, run as separate crates against the public\nAPI), all run via `cargo test`:\n\n```\nrunning 12 tests\ntest ip::tests::known_vector ... ok\ntest ip::tests::odd_length ... ok\ntest ip::tests::build_then_verify_round_trip ... ok\ntest udp::tests::echo_fallback_when_unbound ... ok\ntest udp::tests::sendto_recvfrom_round_trip ... ok\ntest udp::tests::recv_queue_cap_drops_oversized_datagram ... ok\ntest udp::tests::so_reuseaddr_listen_guard ... ok\ntest tcp::tests::full_lifecycle_handshake_ooo_and_close ... ok\ntest tcp::tests::rst_in_established_aborts_connection ... ok\ntest tcp::tests::rst_in_close_wait_aborts_connection ... ok\ntest tcp::tests::ooo_buffer_respects_recv_cap ... ok\ntest tcp::tests::close_defers_fin_until_send_pending_drains ... ok\n\ntest result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n\n     Running tests/retransmission.rs\ntest retransmission_under_packet_loss ... ok\n\n     Running tests/sockopt.rs\ntest getsockopt_round_trip ... ok\ntest so_rcvtimeo_actually_times_out ... ok\n\n     Running tests/zero_window_persist.rs\ntest zero_window_triggers_persist_probe ... ok\n```\n\n- **`ip::tests`** — the RFC 1071 worked example, a build/verify\n  round-trip, and an odd-length edge case.\n- **`tcp::tests::full_lifecycle_handshake_ooo_and_close`** — drives\n  `TcpTable::handle_segment()` directly with hand-crafted segments (no TUN\n  device): three data segments delivered **out of order** (`C, A, B`) are\n  reassembled correctly before being handed to the application, then the\n  connection is torn down via remote-FIN → local `close()`.\n- **`tcp::tests::rst_in_established_aborts_connection` /\n  `rst_in_close_wait_aborts_connection`** — a bare `RST` delivered\n  post-handshake (from `ESTABLISHED` and from `CLOSE_WAIT`) aborts the\n  connection to `CLOSED`, per RFC 9293 §3.10.7 — not just during the\n  handshake, which is all `SYN_SENT`/`SYN_RCVD` originally checked.\n- **`tcp::tests::ooo_buffer_respects_recv_cap`** — an out-of-order segment\n  that would push buffered bytes past a small `recv_buffer_cap` is dropped\n  rather than buffered, mirroring `udp::tests::recv_queue_cap_drops_oversized_datagram`.\n- **`tcp::tests::close_defers_fin_until_send_pending_drains`** — calling\n  `close()` while data is still queued behind a small advertised window\n  doesn't abandon it: the FIN is deferred until `send_pending` actually\n  drains.\n- **`tests/zero_window_persist.rs`** — a fake peer advertises a zero\n  receive window from the handshake onward; once data is queued behind it,\n  a 1-byte persist probe (RFC 9293 §3.8.6.1) goes out on the wire after\n  `INITIAL_RTO` elapses, so a lost window-reopening ACK can't stall the\n  connection forever.\n- **`tests/retransmission.rs`** — two in-process TCP endpoints connected\n  over a lossy simulated link (a real packet-loss harness, not a mock), run\n  at 0%, 10%, and 30% simulated loss in a single test:\n\n  ```\n  retransmission_test: drop_rate=  0%  completed OK  (simulated drops: 0, data: \"hello from minitcp client\")\n  retransmission_test: drop_rate= 10%  completed OK  (simulated drops: 1, data: \"hello from minitcp client\")\n  retransmission_test: drop_rate= 30%  completed OK  (simulated drops: 2, data: \"hello from minitcp client\")\n  ```\n\n  Every trial completes the full handshake → data transfer → simultaneous\n  teardown sequence correctly regardless of loss rate — just with more\n  retransmissions logged at higher loss.\n\n- **`udp::tests`** — drives `UdpTable`/`TcpTable` directly (no TUN device):\n  a `sendto`/`recvfrom`-style round trip through a bound `UdpBinding`, a\n  datagram dropped when it would exceed the `SO_RCVBUF` cap, the auto-echo\n  fallback still firing when nothing is bound to a port, and the\n  `SO_REUSEADDR` guard in `TcpTable::listen` — blocked by a connection\n  sitting in `TIME_WAIT`, then allowed through once `reuse_addr` is set.\n- **`tests/sockopt.rs`** — exercises `Stack::setsockopt`/`get_*` end to end\n  against a real (in-process) blocking call, using `Stack::init_with_fd()`\n  with a `socketpair` standing in for the TUN device: `SockOpt::RcvTimeo`\n  actually causes an idle `Stack::recvfrom()` to return `minitcp::TIMEOUT`\n  instead of blocking forever, and `SockOpt::ReuseAddr`/`RcvBuf`/`SndBuf`\n  round-trip correctly through the `get_*` accessors.\n\n**Manually verified against real, unmodified tools** (see\n[Build \u0026 Run](#build--run) for the exact commands):\n\n| Scenario | Tool | Result |\n|---|---|---|\n| ICMP echo | `ping -c 4 10.0.0.2` | 4/4 replies, correct trace |\n| UDP echo | `nc -u 10.0.0.2 9000` | line echoed back |\n| TCP handshake + chat, remote-initiated close | `nc 10.0.0.2 8080` | `SYN→SYN,ACK→ACK→ESTABLISHED`, data echoed, clean `FIN→CLOSE_WAIT→LAST_ACK→CLOSED` |\n| TCP + HTTP, local-initiated close | `curl http://10.0.0.2:8080/` | correct `200 OK`, clean `FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT→CLOSED` |\n| MiniTCP talking to MiniTCP | `chat_client` ↔ `chat_server` on separate TUN devices, bridged by real kernel IP forwarding | full two-party conversation over our own TCP implementation on both ends |\n| UDP socket API | a small program using `Stack::udp_socket`/`bind`/`recvfrom`/`sendto`, exercised with `nc -u 10.0.0.2 \u003cport\u003e` | bound port: trace shows `deliver -\u003e bound app socket`, the app echoes the datagram; a *different*, unbound port still auto-echoes as before |\n| `SO_RCVTIMEO` | the same program, left idle with a 1s receive timeout set | `Stack::recvfrom` returns `minitcp::TIMEOUT` once a second instead of blocking forever |\n\n---\n\n## Build \u0026 Run\n\nMiniTCP needs a real Linux TUN device, so on macOS (or any non-Linux host)\ndevelopment happens inside a Docker container. On native Linux, skip the\nDocker steps and just run the `cargo` + script commands directly.\n\n```bash\n# Build the dev image and start the container (repo is bind-mounted)\ndocker compose up -d\ndocker compose exec minitcp bash      # \"Shell A\", inside the container from here on\n\n# Build\ncargo build --release\n\n# Run the unit + integration tests (no TUN device needed — segments are\n# crafted in-memory, and the socketpair-based tests stand in for a TUN fd)\ncargo test\n\n# Create tun0 (point-to-point: 10.0.0.1 is the kernel side, 10.0.0.2 is\n# MiniTCP's own address — see the note below on why this matters)\nsudo ./scripts/setup_tun.sh\n\n# Run the chat/HTTP demo server\nsudo ./target/release/chat_server --trace --port 8080\n```\n\nIn a **second** `docker exec -it minitcp-dev bash` (a real second terminal\ninto the *same* container — see note below), exercise it with real tools:\n\n```bash\nping -c 4 10.0.0.2                      # ICMP path\nnc -u 10.0.0.2 9000                     # UDP echo\nnc 10.0.0.2 8080                        # TCP chat — type lines, see them echoed\ncurl http://10.0.0.2:8080/              # TCP + the toy HTTP response\n```\n\nTo watch MiniTCP talk to a *second instance of itself* across two TUN\ndevices bridged by the real kernel routing table:\n\n```bash\n# Still in Shell A / a third exec'd shell:\ncat /proc/sys/net/ipv4/ip_forward       # must be 1; if not: sudo sysctl -w net.ipv4.ip_forward=1\nsudo ip tuntap add dev tun1 mode tun\nsudo ip addr add 10.0.1.1 peer 10.0.1.2 dev tun1\nsudo ip link set tun1 up\n\n./target/release/chat_client --tun tun1 --addr 10.0.1.2 --server 10.0.0.2 --port 8080\n```\n\nTear down when done:\n\n```bash\nsudo ./scripts/teardown_tun.sh\n```\n\n### Why point-to-point addressing?\n\n`setup_tun.sh` assigns `10.0.0.1` to the kernel side of `tun0` and\n`10.0.0.2` as its **peer** address, then everything above (`ping`, `nc`,\n`curl`, `chat_client`) targets `10.0.0.2` — never `10.0.0.1`. A plain\nsame-subnet address (`10.0.0.1/24`) doesn't work for testing *inside a\nsingle container*: both the test client and MiniTCP would share one network\nnamespace, so the kernel would recognize `10.0.0.1` as already locally\nowned and answer pings itself via loopback delivery, without the packet\never reaching MiniTCP's `tun::tun_read()`. Point-to-point addressing forces\ntraffic to the peer address to actually route out through `tun0`, into\nMiniTCP's own code.\n\n---\n\n## Implementation Notes\n\nMiniTCP handles TCP reliability with a deliberately simple retransmission\nmodel: each connection tracks unacknowledged segments and uses a single\nper-connection retransmission timer with exponential backoff. This keeps the\nmechanism compact while still exercising the core behavior required for data\ndelivery over a lossy link.\n\nOut-of-order data is buffered until the missing sequence range arrives, then\nspliced back into the receive stream before being exposed through\n`Stack::recv`. `tcp::tests::full_lifecycle_handshake_ooo_and_close` covers\nthis by delivering three data segments in the order `C, A, B` and verifying\nthat the application receives the original byte stream.\n\nConnection teardown is implemented as separate local-close and remote-close\npaths. `TIME_WAIT` also re-ACKs retransmitted FINs, which is necessary when\nthe final ACK is lost and the peer retries its FIN.\n\nSelective acknowledgment (`SACK`) and full congestion control are natural\nfuture extensions. The current implementation focuses on correctness of the\ncore connection lifecycle, retransmission, in-order delivery, and\ninteroperability with real Linux networking tools.\n\nUDP's socket layer and the socket options don't change any existing call\nsite's behavior. `UdpTable::handle_datagram()` only stops auto-echoing a\nport once an application explicitly binds to it; nothing else observes the\ndifference. Socket options are genuinely wired up rather than just stored:\n`SockOpt::RcvTimeo` bounds the same `pump_once()`-driven blocking loops that\n`Stack::recv`/`accept` always used, `SockOpt::ReuseAddr` bypasses a real\n\"address in use\" check in `TcpTable::listen()` for a port held by a\nconnection in `TIME_WAIT`, and `SockOpt::RcvBuf`/`SndBuf` resize the buffer\ncaps that default to fixed constants on `TcpConnection`/`UdpBinding`.\nBecause a fresh socket never touches these options, every existing demo app\nand test keeps its original behavior by default.\n\n**On the migration from the original C++ implementation**: there is no\nglobal mutable state anywhere in this crate (the C++ version's\n`g_connections`/`g_listeners`/`g_udp_binds` file-level globals). A single\n`Stack` struct (`src/stack.rs`) owns the TCP connection table, the UDP bind\ntable, and the TUN fd, threaded through as `\u0026mut self`. Every place the C++\nversion used a `shared_ptr\u003cTCPConnection\u003e`/`shared_ptr\u003cUDPSocket\u003e` to let\nmultiple owners (the connection table, an accept queue, the application's\n`Socket`) alias the same object, this version instead uses a plain `Copy`\nkey (`ConnectionKey`, or a `(u32, u16)` bind key) into a `HashMap` that is\nthe *sole* owner of the data — no `Rc\u003cRefCell\u003c_\u003e\u003e` needed. Header\n(de)serialization uses explicit big-endian byte-slice parsing\n(`u16::from_be_bytes`/`to_be_bytes`) rather than packed structs and pointer\ncasts, so the only `unsafe` in the whole crate is the TUN device's\n`ioctl(TUNSETIFF)` call in `src/tun.rs` and the `poll(2)` call in\n`src/stack.rs`.\n\n---\n\n## Further Reading\n\n- [docs/protocol_notes.md](docs/protocol_notes.md) — header layouts, byte\n  offsets, the checksum algorithm with a worked example, and known\n  limitations\n- [docs/state_machine.md](docs/state_machine.md) — every transition mapped\n  to its RFC section and implementing code, plus the simultaneous-open/close\n  discussion\n- [RFC 9293 — Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) —\n  the current standard; supersedes the original RFC 793\n- [RFC 791 — Internet Protocol](https://www.rfc-editor.org/rfc/rfc791)\n- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) —\n  the standard reference for raw sockets and TUN/TAP setup\n- *TCP/IP Illustrated, Volume 1* — W. Richard Stevens — the canonical text;\n  Chapters 17–24 cover the TCP state machine and reliability mechanisms in\n  the depth this project implements\n- [level-ip](https://github.com/saminiir/level-ip) — a similar open-source\n  user-space TCP/IP stack; useful as a reference implementation to compare\n  design decisions against, not to copy from directly\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fczhao-dev%2Fuserspace-tcp-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fczhao-dev%2Fuserspace-tcp-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fczhao-dev%2Fuserspace-tcp-stack/lists"}