commit 07b84f8aef941ee094ace5d9aab648c11befc9a7
parent 1246c7255551d45d77cd42bc190928a0b68708dc
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 21 Jun 2021 10:16:08 -0400
net::dns: flesh out message decoder
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
5 files changed, 168 insertions(+), 63 deletions(-)
diff --git a/net/dns/encoding.ha b/net/dns/encoding.ha
@@ -1,5 +1,130 @@
use endian;
use fmt;
+use strings;
+
+export type decoder = struct {
+ buf: []u8,
+ cur: []u8,
+ qdcount: u16,
+ ancount: u16,
+ nscount: u16,
+ arcount: u16,
+};
+
+// Decodes a DNS message, heap allocating the resources necessary to represent
+// it in Hare's type system. The caller must use [[message_free]] to free the
+// return value. To decode without use of the heap, see [[decoder_init]].
+export fn decode(buf: []u8) *message = {
+ abort(); // TODO
+ return alloc(message { ... });
+};
+
+// Initializes a DNS message decoder. All storaged used by the decoder is either
+// stack-allocated, provided by the caller, or borrowed from the input buffer.
+//
+// Call [[decode_header]] next.
+export fn decoder_init(buf: []u8) decoder = decoder {
+ buf = buf,
+ cur = buf,
+ ...
+};
+
+fn decode_u16(dec: *decoder) (u16 | format) = {
+ if (len(dec.cur) < 2) {
+ return format;
+ };
+ const val = endian::begetu16(dec.cur);
+ dec.cur = dec.cur[2..];
+ return val;
+};
+
+fn decode_u32(dec: *decoder) (u32 | format) = {
+ if (len(dec.cur) < 4) {
+ return format;
+ };
+ const val = endian::begetu32(dec.cur);
+ dec.cur = dec.cur[4..];
+ return val;
+};
+
+// Decodes a DNS message's header and advances the decoder to the
+// variable-length section of the message. Following this call, the user should
+// call [[decode_question]] for each question given by the header's qdcount,
+// then [[decode_rrecord]] for each resource record given by the ancount,
+// nscount, and arcount fields, respectively.
+export fn decode_header(dec: *decoder, head: *header) (void | format) = {
+ head.id = decode_u16(dec)?;
+ let rawop = decode_u16(dec)?;
+ op_decode(rawop, &head.op);
+ head.qdcount = decode_u16(dec)?;
+ head.ancount = decode_u16(dec)?;
+ head.nscount = decode_u16(dec)?;
+ head.arcount = decode_u16(dec)?;
+ dec.qdcount = head.qdcount;
+ dec.ancount = head.ancount;
+ dec.nscount = head.nscount;
+ dec.arcount = head.arcount;
+};
+
+// Partially decodes a [[question]] and advances the decoder. Returns a slice
+// representing the name field, which can be passed to [[decode_name]] to
+// interpret.
+export fn decode_question(dec: *decoder, q: *question) ([]u8 | format) = {
+ let name = extract_name(dec)?;
+ q.qtype = decode_u16(dec)?: qtype;
+ q.qclass = decode_u16(dec)?: qclass;
+ return name;
+};
+
+// Partially decodes a [[rrecord]] and advances the decoder. Returns a slice
+// representing the name field, which can be passed to [[decode_name]] to
+// interpret.
+export fn decode_rrecord(dec: *decoder, r: *rrecord) ([]u8 | format) = {
+ let name = extract_name(dec)!;
+ r.rtype = decode_u16(dec)!: rtype;
+ r.class = decode_u16(dec)!: class;
+ r.ttl = decode_u32(dec)!;
+ let rdz = decode_u16(dec)!;
+ r.rdata = dec.cur[..rdz];
+ dec.cur = dec.cur[rdz..];
+ return name;
+};
+
+fn extract_name(dec: *decoder) ([]u8 | format) = {
+ if (dec.cur[0] & 0b11000000 == 0b11000000) {
+ const name = dec.cur[..2];
+ dec.cur = dec.cur[2..];
+ return name;
+ };
+ for (let i = 0z; i < len(dec.cur); i += 1) {
+ let z = dec.cur[i];
+ if (z == 0) {
+ const name = dec.cur[..i + 1];
+ dec.cur = dec.cur[i + 1..];
+ return name;
+ };
+ i += z;
+ };
+ return format;
+};
+
+// Decodes a name from a question or resource record, returning the decoded name
+// and the remainder of the buffer. The caller should pass the returned buffer
+// into decode_name again to retrieve the next name. When the return value is an
+// empty string, all of the names have been decoded. It is a programming error
+// to call decode_name again after this, and the program will abort.
+export fn decode_name(dec: *decoder, buf: []u8) (([]u8, str) | format) = {
+ let z = buf[0];
+ if (z == 0) {
+ return ([]: []u8, "");
+ };
+ if (z & 0b11000000 == 0b11000000) {
+ let offs = endian::begetu16(buf) & ~0b1100000000000000u16;
+ return decode_name(dec, dec.buf[offs..]);
+ };
+ // TODO: Validate ASCII here
+ return (buf[z + 1..], strings::fromutf8(buf[1..z + 1]));
+};
// Encodes a DNS message, returning its size.
export fn encode(buf: []u8, msg: *message) size = {
@@ -33,64 +158,6 @@ export fn encode(buf: []u8, msg: *message) size = {
return z;
};
-// Decodes a DNS message's header only, leaving the other fields unmodified. See
-// [[decode]] to completely decode the message (requiring the use of the heap),
-// or [[decode_question]] and [[decode_rrecord]] for the iterative decoders
-// (which can be used statically).
-export fn decode_header(buf: []u8, h: *header) size = {
- let z = 0z;
- h.id = endian::begetu16(buf[z..]);
- z += 2;
- let rawop = endian::begetu16(buf[z..]);
- op_decode(rawop, &h.op);
- z += 2;
- h.qdcount = endian::begetu16(buf[z..]);
- z += 2;
- h.ancount = endian::begetu16(buf[z..]);
- z += 2;
- h.nscount = endian::begetu16(buf[z..]);
- z += 2;
- h.arcount = endian::begetu16(buf[z..]);
- z += 2;
- return z;
-};
-
-// Decodes a DNS message, heap allocating the resources necessary to represent
-// it in Hare's type system. The caller must use [[message_free]] to free the
-// return value.
-export fn decode(buf: []u8) *message = {
- abort(); // TODO
- return alloc(message { ... });
-};
-
-// Partially decodes a [[question]], returning a byte slice (borrowed from the
-// buf parameter) which represents the value of the qname field (leaving the
-// corresponding field in the question structure untouched). See [[decode_name]]
-// to interpret the return value.
-export fn decode_question(buf: []u8, q: *question) []u8 = {
- abort(); // TODO
- return [];
-};
-
-// Partially decodes a [[rrecord]], returning a byte slice (borrowed from the
-// buf parameter) which represents the value of the name field (leaving the
-// corresponding field in the question structure untouched). See [[decode_name]]
-// to interpret the return value.
-export fn decode_rrecord(buf: []u8, r: *rrecord) []u8 = {
- abort(); // TODO
- return [];
-};
-
-// Decodes a name from a question or resource record, returning the decoded name
-// and the remainder of the buffer. The caller should pass the returned buffer
-// into decode_name again to retrieve the next name. When the return value is an
-// empty string, all of the names have been decoded. It is a programming error
-// to call decode_name again after this, and the program will abort.
-export fn decode_name(buf: []u8) ([]u8, str) = {
- abort(); // TODO
- return (buf, "");
-};
-
fn question_encode(buf: []u8, q: *question) size = {
// TODO: Assert that the labels are all valid ASCII?
let z = 0z;
@@ -142,17 +209,16 @@ fn rrecord_encode(buf: []u8, r: *rrecord) size = {
return z;
};
-fn op_encode(op: *op) u16 = endian::htonu16(
+fn op_encode(op: *op) u16 =
(op.qr: u16 << 15u16) |
(op.opcode: u16 << 11u16) |
(if (op.aa) 0b0000010000000000u16 else 0u16) |
(if (op.tc) 0b0000001000000000u16 else 0u16) |
(if (op.rd) 0b0000000100000000u16 else 0u16) |
(if (op.ra) 0b0000000010000000u16 else 0u16) |
- op.rcode: u16);
+ op.rcode: u16;
fn op_decode(in: u16, out: *op) void = {
- let in = endian::ntohu16(in);
out.qr = ((in & 0b1000000000000000) >> 15): qr;
out.opcode = ((in & 0b01111000000000u16) >> 11): opcode;
out.aa = in & 0b0000010000000000u16 != 0;
diff --git a/net/dns/error.ha b/net/dns/error.ha
@@ -0,0 +1,38 @@
+use fmt;
+
+// The DNS message was poorly formatted.
+export type format = !void;
+
+// The name server was unable to process this query due to a problem with the
+// name server.
+export type server_failure = !void;
+
+// The domain name referenced in the query does not exist. Meaningful only for
+// responses from an authoritative name server.
+export type name_error = !void;
+
+// The name server does not support the requested kind of query.
+export type not_implemented = !void;
+
+// The name server refuses to perform the specified operation for policy
+// reasons.
+export type refused = !void;
+
+// Any other server-provided error condition not known to Hare.
+export type unknown_error = !u8;
+
+// All error types which might be returned from [[net::dns]] functions.
+export type error = (format | server_failure | name_error
+ | not_implemented | refused | unknown_error);
+
+export fn strerror(err: error) const str = {
+ static let buf: [64]u8 = [0...];
+ match (err) {
+ format => "The DNS message was poorly formatted",
+ server_failure => "The name server was unable to process this query due to a problem with the name server",
+ name_error => "The domain name referenced in the query does not exist",
+ not_implemented => "The name server does not support the requested kind of query",
+ refused => "The name server refuses to perform the specified operation for policy reasons",
+ ue: unknown_error => fmt::bsprintf(buf, "Unknown DNS error {}", ue: u8),
+ };
+};
diff --git a/net/dns/types.ha b/net/dns/types.ha
@@ -1,5 +1,3 @@
-use endian;
-
// Record type
export type rtype = enum u16 {
A = 1,
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -552,6 +552,7 @@ net() {
net_dns() {
printf '# net::dns\n'
gen_srcs net::dns \
+ error.ha \
encoding.ha \
types.ha
gen_ssa net::dns endian net net::udp net::ip fmt
diff --git a/stdlib.mk b/stdlib.mk
@@ -822,6 +822,7 @@ $(HARECACHE)/net/net.ssa: $(stdlib_net_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_
# net::dns
# net::dns
stdlib_net_dns_srcs= \
+ $(STDLIB)/net/dns/error.ha \
$(STDLIB)/net/dns/encoding.ha \
$(STDLIB)/net/dns/types.ha
@@ -1942,6 +1943,7 @@ $(TESTCACHE)/net/net.ssa: $(testlib_net_srcs) $(testlib_rt) $(testlib_io) $(test
# net::dns
# net::dns
testlib_net_dns_srcs= \
+ $(STDLIB)/net/dns/error.ha \
$(STDLIB)/net/dns/encoding.ha \
$(STDLIB)/net/dns/types.ha