milestone 16: behavioral fixes for silent failures, locks, counters and the query log
CI / test (push) Failing after 11s
CI / cross (push) Failing after 25s
CI / docker (push) Failing after 24s
CI / test-aarch64 (push) Failing after 2m22s
CI / frontend (push) Successful in 43s

This commit is contained in:
2026-08-07 01:54:40 +02:00
parent 5802148887
commit 25455e5ae2
31 changed files with 2054 additions and 297 deletions
+28
View File
@@ -103,8 +103,15 @@ pub const Class = struct {
///
/// `negative_ttl_max == 0` disables negative caching outright: the response is
/// not stored at all (milestone-6 ruling 6).
///
/// A TC=1 response is never cached, whatever its rcode. It is a partial message
/// whose tail the sender dropped, and RFC 2181 §9 forbids keeping one: served
/// from the cache the TC bit reaches a client that has no truncation to recover
/// from, and a client that retries over TCP is answered with the same truncated
/// bytes again.
pub fn classify(response: []const u8, negative_ttl_max: u32) ?Class {
const p = packet.parse(response) catch return null;
if (p.header.flags.tc) return null;
const rcode = p.header.flags.rcode;
if (rcode == .no_error and p.header.ancount > 0) {
@@ -643,6 +650,27 @@ test "classify refuses a zero ttl and unparsable bytes" {
try testing.expectEqual(@as(?Class, null), classify(response[0 .. response.len - 1], 3600));
}
/// The TC bit is bit 9 of the flags word, which is the second 16-bit word of
/// the header.
fn setTruncated(bytes: []u8) []u8 {
const flags = std.mem.readInt(u16, bytes[2..4], .big);
std.mem.writeInt(u16, bytes[2..4], flags | 0x0200, .big);
return bytes;
}
test "classify refuses a truncated response whatever its rcode" {
var buf: [512]u8 = undefined;
const answer = try buildAnswer(&buf, &.{300});
try testing.expectEqual(@as(u32, 300), classify(answer, 3600).?.ttl_seconds);
try testing.expectEqual(@as(?Class, null), classify(setTruncated(answer), 3600));
// The negative path reads the same bit: a truncated NXDOMAIN is a partial
// message too.
var nx_buf: [nxdomain_bytes.len]u8 = undefined;
@memcpy(&nx_buf, nxdomain_bytes);
try testing.expectEqual(@as(?Class, null), classify(setTruncated(&nx_buf), 3600));
}
test "classify caches NXDOMAIN with the SOA minimum" {
const class = classify(nxdomain_bytes, 3600).?;
try testing.expectEqual(@as(u32, 600), class.ttl_seconds);