A reliable, pipelined byte-stream transport protocol built from scratch on top of UDP — in plain C11, featuring a sliding window, bitmask-based SACK, and fast retransmission.
Protocol-7 (P7) is a hand-rolled transport protocol implemented directly over POSIX SOCK_DGRAM sockets. It recreates core TCP reliability mechanics from the ground up: byte-offset stream tracking, a 16-bit word-aligned checksum, pipelined sliding window transmission, selective acknowledgments (SACK), fast retransmission, and graceful connection teardown.
- Features
- Wire Format
- How It Works
- Getting Started
- CLI Reference
- Chaos Testing & Benchmarks
- Verification
- Project Layout
- Roadmap
- Known Limitations
- License
- Sliding Window Pipelining: Fixed-size 32-slot ring buffer allowing multiple unacknowledged segments in flight.
- I/O Multiplexing via
poll(): Non-blocking event loop handling segment bursts, incoming ACKs, and timeout scanning concurrently. - Byte-Offset Sequencing: Continuous byte-stream positioning (like TCP), independent of segment chunk sizes.
- Selective ACK (SACK): Custom 32-bit ACK mask header field allowing the receiver to report out-of-order buffered blocks.
- Fast Retransmit (RFC 5681): Triggers immediate retransmission upon receiving 3 duplicate ACKs without waiting for coarse RTO timers.
- Selective Repeat: Retransmits only unacknowledged (
!is_acked) expired slots rather than resetting the entire window (Go-Back-N). - 16-bit Word-Aligned Checksum: One's-complement arithmetic with end-around carry, correctly positioned at even byte boundaries to guarantee
0x0000self-cancellation. - Piggybacked FIN: Look-ahead double buffering allows setting the
FINflag on the final payload chunk, saving a round-trip. - TIME_WAIT State: Receiver maintains an active wait period after final ACK to catch and resolve lost FIN retransmissions.
- Chaos Proxy: Python UDP relay simulating packet loss, byte corruption, and duplicate delivery.
- Automated Benchmarks: Test suite measuring transfer throughput, integrity, and loss recovery across modes.
- Zero Memory Leaks: Clean runs under
valgrind --leak-check=fulland AddressSanitizer (-fsanitize=address,undefined).
Every segment starts with a fixed 16-byte header sent in network byte order (Big-Endian), followed by up to P7_MSS bytes of payload:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Selective ACK Mask (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum (16) | Flags (8) | Reserved (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
: Payload :
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Size | Description |
|---|---|---|
| Sequence Number | 32 bits | Byte offset of the segment's first payload byte in the stream. |
| Acknowledgment Number | 32 bits | Cumulative ACK: the next contiguous byte offset the receiver expects. |
| Selective ACK Mask | 32 bits | Bitmask tracking out-of-order received slots relative to ack_num. |
| Checksum | 16 bits | One's-complement sum over header + payload (aligned to byte 8). |
| Flags | 8 bits | Control bits: SYN (0x01), ACK (0x02), FIN (0x04), RST (0x08). |
| Reserved | 8 bits | Zeroed padding for memory structure alignment. |
The Maximum Segment Size (MSS) is derived from standard Ethernet MTU:
P7_MSS = 1500 (MTU) − 20 (IPv4) − 8 (UDP) − 16 (P7 Header) = 1456 bytes
- Window Filling: Computes in-flight bytes as
new_seq - base. Whilein_flight < (P7_WINDOW_SIZE * P7_MSS), reads file chunks and immediately transmits them viasendto(). - Event Loop (
poll): Listens with a 50ms timeout.- On incoming ACK (
activity > 0): Advancesbasecumulatively. Parses the 32-bitack_maskto mark out-of-order buffered slots as acknowledged (is_acked = 1). - Fast Retransmit: Tracks consecutive duplicate ACKs. Upon reaching 3 dup-ACKs, immediately retransmits the missing slot without waiting for a timeout.
- On Timeout (
activity == 0): Scans active in-flight slots and retransmits only unacknowledged segments whosesend_time_msexceeded the RTO threshold.
- On incoming ACK (
- Validates the one's-complement checksum (corrupted packets are dropped silently).
- Segments falling within the receive window are stored in their corresponding slot (
is_received = 1). - Sequential Disk Flush: Continuously drains consecutive received slots to disk starting from
expected_seq. Halts upon encountering the first gap. - Generates a cumulative
ack_num(pointing to the gap) combined with anack_maskrepresenting out-of-order segments buffered ahead of the gap.
- The sender sets the
FINflag on the final data chunk using double-buffering lookahead. - Upon receiving
FINand flushing all data, the receiver transmits the final ACK and enters aTIME_WAITloop. It stays active for several seconds, re-answering any duplicate FIN segments in case its final ACK was dropped.
- Linux (x86_64 / arm64)
- GCC or Clang (supporting C11),
make - Python 3 (for chaos proxy and benchmark runner)
make # Optimized build -> ./protocol7
make sanitize # ASan + UBSan build -> ./protocol7-sanitizedTerminal 1 — Receiver:
./protocol7 -r -p 6767 -f received.binTerminal 2 — Sender:
./protocol7 -s -H 127.0.0.1 -p 6767 -f input.binBoth sender and receiver run from the unified binary:
| Flag | Long Flag | Parameter | Description |
|---|---|---|---|
-s |
--send |
— | Run in sender mode. |
-r |
--recv |
— | Run in receiver mode. |
-H |
--host |
HOST |
Destination host address (default: 127.0.0.1). |
-p |
--port |
PORT |
UDP port. |
-f |
--file |
FILE |
Source file (send mode) or destination path (recv mode). |
-x |
--hex |
— | Print hex dump of transmitted packets (debug). |
-h |
--help |
— | Show CLI help. |
The Python proxy (tests/proxy.py) sits between sender and receiver to inject simulated network impairments:
python3 tests/proxy.py --aport 5656 --bport 6767 --mode drop| Mode | Impairment Simulation |
|---|---|
normal |
Transparent bidirectional proxying. |
drop |
Randomly drops of datagrams. |
corrupt |
Inverts random payload bits (triggers checksum drops). |
double |
Re-delivers datagram duplicates (tests receiver de-duplication). |
=== RESULT TABLE ===
Mode | Size (MB) | Impairment | Time (s) | Speed (MB/s) | Integrity
-------------------------------------------------------------------------------------
NORMAL | 10 | None | 0.151 | 66.19 | PASS
DROP | 10 | Drop: 5% | 5.403 | 1.85 | PASS
DROP | 10 | Drop: 15% | 121.971 | 0.08 | PASS
CORRUPT | 10 | Corrupt: 5% | 16.683 | 0.60 | PASS
DOUBLE | 10 | Double: 10% | 0.197 | 50.74 | PASS
- Data Integrity: Byte-for-byte fidelity confirmed via
diffandsha256sumacross all impairment modes. - Memory Cleanliness: 0 memory leaks, 0 uninitialized byte access reported under
valgrind --leak-check=fulland ASan.
protocol-7/
├── include/ # C Header files
│ ├── header.h # 16-byte wire-format definition & serialization
│ ├── checksum.h # Word-aligned one's-complement checksum
│ ├── args.h # Command-line parsing
│ ├── socket_utils.h # POSIX UDP socket wrappers
│ ├── sender.h # Sliding window, poll reactor, retransmission
│ ├── receiver.h # Receive buffer queue, sequential file sink
│ └── debug.h # Colorized timestamp logging & hex dumps
├── src/ # Implementation sources
├── tests/
│ ├── proxy.py # Python chaos proxy (drop/corrupt/double)
│ └── benchmark.py # Automated test runner
├── Makefile # Targets: default, sanitize, valgrind
└── LICENSE
- 3-Way Handshake (
SYN/SYN-ACK/ACK) with randomized Initial Sequence Numbers (ISN). - Congestion Control (Slow Start, AIMD / Reno-like dynamics).
- Jacobson/Karels dynamic RTT estimation and adaptive RTO calculation.
- Silly Window Syndrome avoidance (Nagle's and Clark's algorithms).
- Connection demultiplexing (concurrent multi-client sessions over a single socket).
- Fixed Window Size: Window size is statically set to 32 slots; lacks dynamic window scaling (
wscale). - No Handshake: Transfers begin immediately with implicit stream initialization.
- Single Session: The receiver accepts datagrams from one client per process lifetime.
- IPv4 Only: No IPv6 socket support.
MIT — see LICENSE.