When the packets turn nasty
Everything so far assumed a peer that means well. The internet is not that peer. This last chapter is the one the whole series was walking toward: what happens when the packets are malformed on purpose, flooded on purpose, forged on purpose — and why, in Alquist, the honest answer is mostly "a counter goes up."
Resilience is not a feature you add at the end. It is the shape of every decision you already made, seen from the attacker's side. We did not bolt on a firewall. We built a stack whose worst failure mode is "refuse and keep counting" — because every table has a fixed size, every field is validated before it is trusted, and the hardware lives behind a wall the protocol brain can never climb.
The threat, in four shapes
Hostile traffic comes in a small number of recognisable shapes, and this chapter walks each against the defences built in Chapters 2 through 7. None of these defences is new here — that is the point. They were the design all along.
1. The malformed packet
The oldest attack: send bytes that don't obey the format and hope the parser trips. Alquist's answer is a principle we called validation is refusal — every layer checks every length and every field before using it, and a failure drops the packet and increments a categorised counter (Chapter 10) rather than proceeding on a bad assumption:
if (frame_length < NET_ETH_HEADER_SIZE + NET_IPV4_HEADER_MIN_SIZE) {
++g_net_diag_stats.ipv4_drop_hdr; return 0; /* too short: refuse, count, done */
}
/* ... version, header-length sanity, total-length <= frame, checksum ... each a refusal */
There is no code path that reads past a validated length, and no field is trusted before it is bounded. A truncated header, a lying length field, a bad checksum, a fragment we refuse to reassemble — each is a two-line refusal, not an adventure. The blast radius of a malformed anything is one dropped packet and one incremented counter.
2. The flood
The second attack doesn't send bad packets — it sends too many good ones, hoping to
exhaust memory or starve the CPU. Alquist has no dynamic allocation on the network path to
exhaust: every table is a fixed-size array. Four connections, four
listeners, an accept queue of two, bounded rings. A SYN flood cannot grow the connection
table because the connection table cannot grow — once its four slots are full, new SYNs
are refused with a RST and tallied in tcp_rst_tx:
conn = net_tcp_alloc_conn();
if (conn == 0) { /* table full -- */
net_tcp_send_rst(rx_info, &tcp_rx, local_mac, local_ip); /* refuse, loudly */
return; /* no memory consumed, no slot leaked */
}
Starving the single cooperative core is defended the same way: every pass does
bounded work then yields. The frame pump moves at most a fixed number of frames per
pass (NET_PUMP_RX_MAX); the EL0 brain processes at most a fixed batch
(NET_EL0_BATCH_MAX) before returning control. A million packets a second cannot
make any single pass run long; they can only make the counters climb faster. The flood
becomes throughput you drop, not a wedge that stops the box.
3. The spoof
The third attack forges the source: pretend to be someone trusted, or inject data into a connection you can't see. TCP's classic defence is an unpredictable initial sequence number, and Alquist generates one by mixing the timer, both addresses, both ports, and a rolling nonce (Chapter 6):
isn = net_tcp_mix32(nonce ^ counter_lo ^ src_ip);
isn = net_tcp_mix32(isn ^ counter_hi ^ dst_ip);
isn = net_tcp_mix32(isn ^ ports);
g_net_tcp_isn_nonce = nonce ^ net_tcp_mix32(counter_lo + ports + 0xA5A5A5A5u);
A blind attacker who cannot see our traffic cannot guess the sequence number, so cannot inject a valid segment into an established connection. And an out-of-window or out-of-order segment is never trusted — it is dropped and re-ACKed (Chapter 7), never blindly accepted. For the attack that gets the crypto involved, the answer is stronger still: once SSH's key exchange completes (Chapter 9), every byte is authenticated, and a forged packet fails the Poly1305 tag and is discarded. Spoofing buys the attacker a dropped packet.
4. The reflection
The fourth attack abuses you as a weapon: spoof a victim's address, send you a small
request, and let your large reply flood the victim. The defence is a posture we kept from the
very first chapter: silence is the default, and we open exactly one door at a time.
We answer ARP only for our own address, ICMP echo only for well-formed pings, UDP only on the
one port we chose — everything else is a counted drop (udp_drop_port), never
an error message. We never generate unsolicited replies, never emit ICMP errors an attacker
could amplify, and never reply larger than the request. There is almost nothing here to
reflect, because the stack's reflex, faced with anything it did not explicitly agree to, is to
say nothing.
The wall behind all of it
Under every defence above sits the structural one from Chapter 2: the protocol brain runs at EL0, unprivileged, and can only ever touch a copy of a frame that the EL1 pump handed it across a bounded, pointer-validated channel. So even the worst case — a bug in the parser that a malformed packet actually triggers — is contained. A hostile packet might, at absolute worst, crash the network service or a single SSH session. It cannot reach the NIC, cannot forge a DMA descriptor, cannot corrupt the kernel, cannot touch the disk. The wall turns "remote code execution" into "the network task restarts." That is the difference between an incident and a catastrophe, and it is architectural, not a patch.
Watching it happen
Because of Chapter 10, none of this is faith. Point a tool at the box — a SYN flood, a
malformed-header storm, a port scan — and the specific counter lights up:
tcp_rst_tx for the refused flood, ipv4_drop_hdr for the malformed
storm, udp_drop_port for the scan — while the box keeps answering legitimate
traffic and never stops responding to the shell. The attack doesn't produce an outage; it
produces a histogram. (Building out that adversarial test bench — the frameworks, the
rules of engagement, the kill switch — is its own effort, running in parallel.)
An honest accounting of the limits
Resilience is a claim, and a claim deserves honesty. Alquist does not yet do per-source rate limiting, connection-attempt throttling, or RTT-adaptive timers; its connection table is small enough that a flood of legitimate-looking handshakes will deny service to new clients even as it harms nothing. These are real gaps. But they are gaps of the right kind: the failure mode is always "refuse new work," never "exhaust memory," "corrupt state," or "escalate privilege." A stack whose worst day is "it stopped accepting new connections for a while" is in a different universe from one whose worst day is a kernel compromise. We built the second universe on purpose, and we can add the throttles later — on a foundation that was safe first.
And that is the whole climb: from ARP asking "who has this address?" to a stack that meets a hostile internet with fixed tables, validated fields, an unclimbable wall, and a ledger of every refusal. Not because we added armour at the end — because we never built a soft place for an attacker to land. Small, honest, and counting. That's the machine.