milestone 17: real deadlines, validator holes, upstream editor, trusted proxies, contract samples, badvers
This commit is contained in:
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user