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
+41
View File
@@ -275,6 +275,25 @@ pub fn extendedRcode(header_rcode: types.Rcode, opt: ?OptRecord) u12 {
return (@as(u12, o.extended_rcode) << 4) | low;
}
/// The write-side pair of `extendedRcode`: splits a 12-bit RCODE into the four
/// bits the header carries and the eight the OPT record carries. A reply that
/// needs a code above 15 (BADVERS, RFC 6891 §6.1.3) has no other way to say it.
pub fn splitRcode(rcode: u12) SplitRcode {
return .{
.header = @enumFromInt(@as(u4, @truncate(rcode))),
.extended = @truncate(rcode >> 4),
};
}
pub const SplitRcode = struct {
header: types.Rcode,
extended: u8,
};
/// RCODE 16, BADVERS (RFC 6891 §6.1.3): the query asked for an EDNS version
/// this server does not implement.
pub const badvers: u12 = 16;
const testing = std.testing;
/// An OPT record with a 4096-byte payload size, DO set, and no options.
@@ -700,3 +719,25 @@ test "extendedRcode composes the twelve bits" {
high.extended_rcode = 0xff;
try testing.expectEqual(@as(u12, 0xfff), extendedRcode(@enumFromInt(15), high));
}
test "splitRcode splits BADVERS the way RFC 6891 does" {
const split = splitRcode(badvers);
try testing.expectEqual(types.Rcode.no_error, split.header);
try testing.expectEqual(@as(u8, 1), split.extended);
}
test "splitRcode round-trips every twelve-bit rcode through extendedRcode" {
var rcode: u12 = 0;
while (true) : (rcode += 1) {
const split = splitRcode(rcode);
const opt: OptRecord = .{
.udp_payload_size = 4096,
.extended_rcode = split.extended,
.version = 0,
.do_bit = false,
.options = .{ .offset = 0, .len = 0 },
};
try testing.expectEqual(rcode, extendedRcode(split.header, opt));
if (rcode == std.math.maxInt(u12)) break;
}
}
+50 -5
View File
@@ -261,10 +261,11 @@ pub fn decrementTtls(bytes: []u8, elapsed_seconds: u32) ParseError!?u32 {
/// and which answers to add is the caller's policy.
///
/// Sections must be filled in wire order, so every `addAnswer` call has to
/// precede `addOptEcho` — the OPT record belongs to the additional section, and
/// an answer written after it would land in the wrong section. `addOptEcho`
/// also runs at most once, because RFC 6891 §6.1.1 allows one OPT record per
/// message. Both rules are programmer errors, so both are assertions.
/// precede the OPT record — it belongs to the additional section, and an answer
/// written after it would land in the wrong section. Only one of `addOptEcho`
/// and `addOptWithRcode` runs, and only once, because RFC 6891 §6.1.1 allows
/// one OPT record per message. Both rules are programmer errors, so both are
/// assertions.
pub const ResponseBuilder = struct {
writer: Writer,
header: header.Header,
@@ -348,10 +349,23 @@ pub const ResponseBuilder = struct {
/// comes back unchanged and the DO bit passes through. No options are
/// echoed — nxdns implements none of them.
pub fn addOptEcho(self: *ResponseBuilder, request_opt: edns.OptRecord, do_bit: bool) Error!void {
return self.addOptWithRcode(request_opt, do_bit, 0);
}
/// `addOptEcho` with the upper eight bits of a 12-bit RCODE, which only the
/// OPT record can carry (RFC 6891 §6.1.3). Pair it with the header half from
/// `edns.splitRcode`. The reply's EDNS version stays 0 either way: it states
/// the highest version this server implements, not the version asked for.
pub fn addOptWithRcode(
self: *ResponseBuilder,
request_opt: edns.OptRecord,
do_bit: bool,
extended_rcode: u8,
) Error!void {
std.debug.assert(!self.opt_added);
const opt: edns.OptRecord = .{
.udp_payload_size = request_opt.udp_payload_size,
.extended_rcode = 0,
.extended_rcode = extended_rcode,
.version = 0,
.do_bit = do_bit,
.options = .{ .offset = 0, .len = 0 },
@@ -791,6 +805,37 @@ test "ResponseBuilder passes the DO bit through" {
}
}
test "addOptWithRcode carries the upper eight bits of a twelve-bit rcode" {
const request = try parse(query_bytes);
const request_opt = try edns.parseOpt(query_bytes, findOptRecord(request).?);
const split = edns.splitRcode(edns.badvers);
var buf: [128]u8 = undefined;
var b = try ResponseBuilder.init(&buf, request.header, firstQuestion(request).?);
b.setRcode(split.header);
try b.addOptWithRcode(request_opt, false, split.extended);
const bytes = b.finish();
const p = try parse(bytes);
const opt = try edns.parseOpt(bytes, findOptRecord(p).?);
try testing.expectEqual(@as(u8, 0), opt.version);
try testing.expectEqual(@as(u12, edns.badvers), edns.extendedRcode(p.header.flags.rcode, opt));
}
test "addOptEcho leaves the extended rcode clear" {
const request = try parse(query_bytes);
const request_opt = try edns.parseOpt(query_bytes, findOptRecord(request).?);
var buf: [128]u8 = undefined;
var b = try ResponseBuilder.init(&buf, request.header, firstQuestion(request).?);
try b.addOptEcho(request_opt, false);
const bytes = b.finish();
const p = try parse(bytes);
const opt = try edns.parseOpt(bytes, findOptRecord(p).?);
try testing.expectEqual(@as(u8, 0), opt.extended_rcode);
}
test "ResponseBuilder sets an rcode and an empty answer section" {
const request = try parse(query_bytes);