milestone 17: real deadlines, validator holes, upstream editor, trusted proxies, contract samples, badvers
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 4m55s
CI / frontend (push) Successful in 39s
CI / cross (push) Successful in 7m57s
CI / docker (push) Failing after 1h10m42s

This commit is contained in:
2026-08-07 17:55:59 +02:00
parent 9b12dbaaa0
commit c50c6d285a
57 changed files with 2926 additions and 126 deletions
+122 -1
View File
@@ -133,6 +133,9 @@ pub const Handler = struct {
dropped_malformed: std.atomic.Value(u64) = .init(0),
formerr: std.atomic.Value(u64) = .init(0),
notimp: std.atomic.Value(u64) = .init(0),
/// Queries asking for an EDNS version this server does not implement,
/// answered with BADVERS (RFC 6891 §6.1.3).
badvers: std.atomic.Value(u64) = .init(0),
servfail: std.atomic.Value(u64) = .init(0),
truncated: std.atomic.Value(u64) = .init(0),
refused: std.atomic.Value(u64) = .init(0),
@@ -239,6 +242,19 @@ pub const Handler = struct {
else
null;
// RFC 6891 §6.1.3: a query naming an EDNS version this server does not
// implement is answered with BADVERS, and the reply's OPT reports the
// highest version the server does implement. The check precedes the
// opcode check because the version governs the whole EDNS exchange,
// whatever the query asks for.
if (opt) |o| {
if (o.version != 0) {
bump(&self.stats.badvers);
const echo = if (hdr.qdcount == 1) packet.firstQuestion(p) else null;
return .{ .reply = buildBadvers(hdr, echo, o, response_buf) };
}
}
if (hdr.flags.opcode != .query) {
return synthesize(hdr, null, opt, .not_imp, &self.stats.notimp, response_buf);
}
@@ -887,6 +903,22 @@ fn build(
return b.finish();
}
/// Encodes a BADVERS reply. `build` cannot: RCODE 16 does not fit the header's
/// four bits, so the code splits across the header and the OPT record and the
/// reply carries an OPT even though it echoes none of the query's options.
fn buildBadvers(
hdr: header.Header,
q: ?question.Question,
request_opt: edns.OptRecord,
buf: []u8,
) []u8 {
const split = edns.splitRcode(edns.badvers);
var b = packet.ResponseBuilder.init(buf, hdr, q) catch unreachable;
b.setRcode(split.header);
b.addOptWithRcode(request_opt, request_opt.do_bit, split.extended) catch unreachable;
return b.finish();
}
fn bump(counter: *std.atomic.Value(u64)) void {
_ = counter.fetchAdd(1, .monotonic);
}
@@ -972,8 +1004,22 @@ const response_bytes =
const opt_len = 11;
const query_with_opt_len = query_bytes.len + opt_len;
/// `query_bytes` plus an OPT record advertising `payload_size`.
/// The EDNS version byte, counted from the start of the OPT record: past the
/// root owner name, TYPE, CLASS and the extended-RCODE byte.
const opt_version_offset = 6;
/// `query_bytes` plus an OPT record advertising `payload_size`, EDNS version 0.
fn queryWithOpt(buf: *[query_with_opt_len]u8, payload_size: u16, do_bit: bool) []const u8 {
return queryWithOptVersion(buf, payload_size, do_bit, 0);
}
/// `queryWithOpt` for a chosen EDNS version.
fn queryWithOptVersion(
buf: *[query_with_opt_len]u8,
payload_size: u16,
do_bit: bool,
version: u8,
) []const u8 {
@memcpy(buf[0..query_bytes.len], query_bytes);
std.mem.writeInt(u16, buf[10..12], 1, .big); // arcount
@@ -981,6 +1027,7 @@ fn queryWithOpt(buf: *[query_with_opt_len]u8, payload_size: u16, do_bit: bool) [
@memset(opt, 0);
opt[2] = @intFromEnum(types.Type.opt);
std.mem.writeInt(u16, opt[3..5], payload_size, .big);
opt[opt_version_offset] = version;
if (do_bit) opt[7] = 0x80; // the DO bit is bit 15 of the TTL word
return buf;
}
@@ -1414,6 +1461,80 @@ test "the truncated reply echoes the OPT record" {
try testing.expectEqual(@as(u64, 1), h.stats.truncated.load(.monotonic));
}
test "an EDNS version this server does not implement gets BADVERS" {
var t: TestIo = .init();
defer t.deinit();
var fake: FakeUpstream = .{ .reply = response_bytes };
var h = bare(fake.client());
var query_buf: [query_with_opt_len]u8 = undefined;
const query = queryWithOptVersion(&query_buf, 1232, true, 1);
var buf: [udp_limit_min]u8 = undefined;
const reply = try expectReply(udp(&h, t.io(), query, &buf));
const p = try packet.parse(reply);
const opt = try edns.parseOpt(reply, packet.findOptRecord(p).?);
// RCODE 16 lives in neither half alone: the header carries 0 and the OPT
// record carries 1.
try testing.expectEqual(types.Rcode.no_error, p.header.flags.rcode);
try testing.expectEqual(@as(u12, 16), edns.extendedRcode(p.header.flags.rcode, opt));
try testing.expectEqual(@as(u8, 0), opt.version);
try testing.expectEqual(true, opt.do_bit);
try testing.expectEqual(@as(u16, 1232), opt.udp_payload_size);
try testing.expectEqual(@as(u16, 0x1234), p.header.id);
try testing.expectEqual(true, p.header.flags.qr);
try testing.expectEqual(@as(u16, 1), p.header.qdcount);
try testing.expectEqual(@as(u16, 0), p.header.ancount);
try testing.expectEqual(@as(u64, 1), h.stats.badvers.load(.monotonic));
// The query never reached the upstream.
try testing.expectEqual(@as(u64, 0), h.stats.queries.load(.monotonic));
}
test "an EDNS version 0 query is not answered with BADVERS" {
var t: TestIo = .init();
defer t.deinit();
var fake: FakeUpstream = .{ .reply = response_bytes };
var h = bare(fake.client());
var query_buf: [query_with_opt_len]u8 = undefined;
const query = queryWithOptVersion(&query_buf, 1232, false, 0);
var buf: [udp_limit_min]u8 = undefined;
const reply = try expectReply(udp(&h, t.io(), query, &buf));
// The query is forwarded and the upstream's own answer comes back, so the
// BADVERS arm never ran.
const p = try packet.parse(reply);
try testing.expectEqual(types.Rcode.no_error, p.header.flags.rcode);
try testing.expectEqual(@as(u16, 1), p.header.ancount);
try testing.expectEqual(@as(u64, 0), h.stats.badvers.load(.monotonic));
try testing.expectEqual(@as(u64, 1), h.stats.queries.load(.monotonic));
}
test "a malformed OPT record is FORMERR before the version check reads it" {
var t: TestIo = .init();
defer t.deinit();
var fake: FakeUpstream = .{ .reply = response_bytes };
var h = bare(fake.client());
// The option list is broken and the EDNS version is 1. `parseOpt` fails
// first, so nothing trusts the version byte of an OPT that will not parse.
var query_buf: [query_with_bad_option.len]u8 = (query_with_bad_option ++ "").*;
query_buf[query_bytes.len + opt_version_offset] = 1;
var buf: [udp_limit_min]u8 = undefined;
const reply = try expectReply(udp(&h, t.io(), &query_buf, &buf));
const p = try packet.parse(reply);
try testing.expectEqual(types.Rcode.form_err, p.header.flags.rcode);
try testing.expectEqual(@as(u64, 0), h.stats.badvers.load(.monotonic));
try testing.expectEqual(@as(u64, 1), h.stats.formerr.load(.monotonic));
}
test "an OPT record with a non-root owner name gets FORMERR" {
var t: TestIo = .init();
defer t.deinit();
+6 -3
View File
@@ -2,8 +2,11 @@
//! the timestamp, so this file holds no clock, no `std.Io` operation and no
//! socket. `check` neither allocates nor fails.
//!
//! Not thread-safe. Phase 7 decides the locking when it wires the limiter into
//! the query path.
//! Not thread-safe. The handler owns the locking: every call goes through
//! `Handler.limiter_mutex` (handler.zig:124), taken uncancelably on the query
//! path — `handle` has no error union to carry `error.Canceled` out of — and
//! cancelably in the `runMaintenance` sweep that ages the table out
//! (app.zig, `maintenanceOnce`), which does.
//!
//! The window is fixed, not sliding (PLAN §10 reserves the token bucket for the
//! API limiter). A fixed window admits at most twice the limit across a window
@@ -116,7 +119,7 @@ pub const RateLimiter = struct {
/// Drops every entry whose window ended more than one full window before
/// `now`, that is `now - start_ns > 2 * window_ns`. Returns how many it
/// dropped. Phase 7 schedules it.
/// dropped. `app.runMaintenance` schedules it.
pub fn sweep(self: *RateLimiter, now: std.Io.Timestamp) u32 {
const stale_after = 2 * self.window_ns;
var stale_count: u32 = 0;
+6 -3
View File
@@ -77,9 +77,12 @@ const test_cfg: health.Config = .{
.max_backoff_ms = 60_000,
};
/// Nothing in this test is slow, so this budget only exists to stop a wedged
/// Nothing in this test is slow, so these budgets only exist to stop a wedged
/// attempt from hanging the run.
const attempt_timeout: std.Io.Clock.Duration = .{ .raw = .fromSeconds(10), .clock = .awake };
const pool_timeouts: pool.Timeouts = .{
.attempt = .{ .raw = .fromSeconds(10), .clock = .awake },
.total = .{ .raw = .fromSeconds(30), .clock = .awake },
};
fn queryWithId(buf: *[query_bytes.len]u8, id: u16) []const u8 {
buf.* = query_bytes.*;
@@ -247,7 +250,7 @@ test "the whole resolver answers over udp and tcp and fails over to a healthy up
testEntry("https://bad.example/dns-query", bad.client(), 10),
testEntry("tls://good.example", good.client(), 20),
};
var upstreams: pool.Pool = .init(&entries, test_cfg, attempt_timeout, 1);
var upstreams: pool.Pool = .init(&entries, test_cfg, pool_timeouts, 1);
var h = bareHandler(upstreams.client());
+2 -1
View File
@@ -58,7 +58,8 @@ pub fn wait(io: std.Io) std.Io.Cancelable!void {
}
/// The programmatic equivalent of the signal: what a test uses to shut the app
/// down, and what a Phase 8 restart endpoint would call.
/// down. No restart endpoint calls it — none exists, and none is planned; the
/// web API echoes `restart_required` and leaves the restart to the operator.
pub fn trigger(io: std.Io) void {
event.set(io);
}