TCP protocol implementation (RFC 9293)
0

Configure Feed

Select the types of activity you want to include in your feed.

C 70.9%
OCaml 27.4%
Go 0.5%
CMake 0.2%
Rust 0.1%
C++ 0.1%
Dune 0.1%
Roff 0.1%
Shell 0.1%
Other 0.6%
227 1 0

Clone this repository

https://git.vm.fail/gazagnaire.org/ocaml-tcp https://git.vm.fail/did:plc:rbiwajkcaiws66wmff25tvek
ssh://git@git.recoil.org:2222/gazagnaire.org/ocaml-tcp ssh://git@git.recoil.org:2222/did:plc:rbiwajkcaiws66wmff25tvek

For self-hosted knots, clone URLs may differ based on your setup.


README.md

ocaml-tcp#

Warning. This is a work-in-progress experimental fork of utcp. Do not use it in production; use utcp instead.

An implementation of the Transmission Control Protocol (RFC 9293) in pure OCaml whose core does no I/O and takes time as a parameter, so the same state machine runs under Eio, in a unikernel, and in deterministic tests.

It ships as two opam/ocamlfind packages -- nox-tcp (the core, module Tcp) and nox-tcp-eio (the Eio adapter, module Tcp_eio). Those names are an artefact of the published library namespace; the project is ocaml-tcp.

Installation#

Install with opam:

$ opam install nox-tcp

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install nox-tcp

Architecture#

ocaml-tcp follows the ocaml-protocols four-layer model: the core does no I/O. It is a pure function from the incoming bytes and the current connection state to the outgoing bytes and the next state, with the reading and writing of sockets kept in a separate adapter. It is built bottom up, each layer a thin wrapper over the one below and usable on its own:

Layer Module Role
Codec -- wire combinators + primitives wire + Tcp.Sequence binary combinators and RFC 1982 sequence-number arithmetic
Message -- option codec Tcp.Options TCP options codec (RFC 2018, 7323)
Packet -- segment framing + checksum Tcp.Segment RFC 9293 header codec and the IP pseudo-header checksum
Protocol -- connection state machine Tcp.Connection the RFC 9293 FSM; no I/O, time as an explicit parameter
IO adapter (nox-tcp-eio) Tcp_eio drives the state machine over a link and presents the stack as an Eio.Net.t

Dependency is one-way, and keeping it that way is what makes the core portable: the core (nox-tcp) has no dependency on eio, lwt, or unix. Tcp.Connection never reads a clock, never touches a socket, and never schedules -- it consumes a decoded segment and returns the next state, the segments to send, and the events to surface (RFC 9293 s.3.3.2). The same Connection powers the production Eio adapter, the fuzz harness, and the deterministic in-memory test suite; only Tcp_eio knows what a socket is.

The adapter is just another network device. Tcp_eio.Net.eio presents a running stack as a first-class Eio.Net.t, so any existing Eio program -- an HTTP server, an iperf3 client or server -- runs over this TCP stack unmodified: hand it this Eio.Net.t in place of the OS network and Eio.Net.listen / connect / accept work as usual. (UDP and Unix sockets are unsupported and raise.) Built over a utun relay link (Tcp_eio.Net.over_flow) the stack is a real endpoint that stock iperf3 -c <ip> driving the host kernel can reach.

Implemented#

Tcp.Connection covers the RFC 9293 state machine with time as an explicit parameter:

  • the three-way handshake, in-order data transfer, and the FIN/RST teardown;
  • out-of-order reassembly with a queue and in-order splicing (RFC 9293 s.3.4);
  • selective acknowledgement: SACK-permitted negotiation and SACK blocks on duplicate acks (RFC 2018);
  • window scaling negotiation and application (RFC 7323);
  • SYN and SYN-ACK retransmission.

Congestion control and timers#

All of the following live in lib/connection.ml:

  • slow start and congestion avoidance -- an initial window of four segments, exponential then linear growth of cwnd (RFC 5681);
  • fast retransmit and NewReno fast recovery -- retransmit on the third duplicate ack, halve ssthresh, and inflate then deflate the window through partial and full acks until the recovery point is reached (RFC 5681, 6582);
  • timeout collapse -- a retransmission timeout drops cwnd to one segment and re-enters slow start (RFC 5681);
  • adaptive retransmission timeout -- a smoothed RTT estimator with variance, Karn's algorithm (no sample from a retransmitted segment), and exponential backoff on each timeout (RFC 6298).

The Eio adapter adds software GSO: a single call into the state machine (handle, send, or timer) returns a whole burst of segments -- up to a cwnd's worth -- which the adapter gathers into one vectored write, so the burst costs a single writev instead of one syscall per segment. See writev_burst in eio/net.ml.

It is tested with an in-memory client/server through every transition (including reassembly, SACK emission, cwnd growth, fast retransmit, RTT-driven RTO, and window-scale negotiation), fuzzed for crash-safety, and carries CVE-class regression tests (bad data offsets, the SACK-panic class, degenerate options). The lineage is robur-coop/utcp (translated from the Cambridge Netsem HOL4 executable specification) and the socket API of robur-coop/mnet, retargeted from Miou to Eio.

Example#

Encoding a single segment reaches straight into the codec layer -- no connection state required:

let src = Ipaddr.of_string_exn "10.0.0.1"
let dst = Ipaddr.of_string_exn "10.0.0.2"

let syn =
  {
    Tcp.Segment.src_port = 12345;
    dst_port = 80;
    seq = Tcp.Sequence.of_int 0x01020304;
    ack = Tcp.Sequence.zero;
    flags = { Tcp.Segment.no_flags with syn = true };
    window = 65535;
    urg_ptr = 0;
    options = [ Tcp.Options.Mss 1460 ];
  }

let wire = Tcp.Segment.to_string ~src ~dst ~payload:Rope.empty syn

Credits#

The wire codec is forked from mirage/mirage-tcpip (Anil Madhavapeddy, Mindy Preston, Balraj Singh, and the MirageOS team); the state machine from robur-coop/utcp and the socket API from robur-coop/mnet (Hannes Mehnert and the Robur team), all under the ISC license. The cstruct codec is replaced by the wire library, byte streaming by bytesrw, the IPv4 layer by nox-ip, and the checksum by nox-crc. The original copyright notices are preserved in LICENSE.md.