From 132e027c69442bd424baf49deea71ee85a2da9d1 Mon Sep 17 00:00:00 2001 From: Alice Domage Date: Mon, 18 Sep 2023 18:55:54 +0200 Subject: [PATCH] os/include/net: add support for the DNS protocol Issue genodelabs/genode#5003 --- repos/os/include/net/dns.h | 534 ++++++++++++++++++++++++++++++++ repos/os/run/dns.run | 41 +++ repos/os/src/test/dns/main.cc | 256 +++++++++++++++ repos/os/src/test/dns/target.mk | 8 + 4 files changed, 839 insertions(+) create mode 100644 repos/os/include/net/dns.h create mode 100644 repos/os/run/dns.run create mode 100644 repos/os/src/test/dns/main.cc create mode 100644 repos/os/src/test/dns/target.mk diff --git a/repos/os/include/net/dns.h b/repos/os/include/net/dns.h new file mode 100644 index 0000000000..0017635dc9 --- /dev/null +++ b/repos/os/include/net/dns.h @@ -0,0 +1,534 @@ +/* + * \brief DNS Request class and related + * \author Alice Domage + * \date 2023-09-13 + */ + +/* + * Copyright (C) 2023 Genode Labs GmbH + * Copyright (C) 2023 gapfruit ag + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _NET__DNS_H_ +#define _NET__DNS_H_ + +/* Genode */ +#include +#include +#include +#include +#include + + +namespace Net { + + using namespace Genode; + + class Domain_name; + class Dns_packet; + + static inline size_t ascii_to(char const *, Domain_name&); + +} + + +/* + * Domain Name format following RFC 1035 + * + * Various objects and parameters in the DNS have size limits. They are + * listed below. Some could be easily changed, others are more + * fundamental. + * + * labels 63 octets or less + * + * names 255 octets or less + * + * QNAME a domain name represented as a sequence of labels, where + * each label consists of a length octet followed by that + * number of octets. The domain name terminates with the + * zero length octet for the null label of the root. Note + * that this field may be an odd number of octets; no + * padding is used.* + */ +class Net::Domain_name +{ + public: + + static constexpr const size_t ZERO_LENGTH_OCTET = 1; + static constexpr const size_t NAME_MAX_LEN = 255; + static constexpr const size_t LABEL_MAX_LEN = 63; + static constexpr const size_t MIN_ROOT_LABEL = 3; + static constexpr const size_t MAX_ROOT_LABEL = 6; + + private: + + String _name { }; + + static char const *_next_label(char const *label) + { + size_t length = static_cast(*label); + ++label; + return label + length; + } + + public: + + Domain_name() = default; + + Domain_name(char const *name) { ascii_to(name, *this); } + + bool operator==(Domain_name const &other) const { + return _name == other._name; } + + size_t length() const { return _name.length(); } + + void copy(void *dest) const { + memcpy(dest, _name.string(), _name.length()); } + + void label(size_t label_length, char const *label) + { + if (label_length + _name.length() > NAME_MAX_LEN) { + return; } + + if (label_length > LABEL_MAX_LEN) { + return; } + + /* + * copy label into a separate buffer, first to easily avoid + * label_length to be converted to ascii. Also all the labels, + * including the bytes further than label_length would be copied + * into the string. + */ + char buff[LABEL_MAX_LEN + 1] { }; + buff[0] = static_cast(label_length); + memcpy(buff + 1, label, label_length); + + _name = String { _name, String(buff) }; + } + + size_t label_count() const + { + char const *label = _name.string(); + size_t count { 0 }; + + if (*label == 0) { + return 0; } + + while(*label) { + ++count; + label = _next_label(label); } + + return count; + } + + void print(Output &output) const + { + char const *label = _name.string(); + + if (*label == 0) { + return; } + + size_t label_length = static_cast(*label); + for (size_t idx = 0; idx < label_length; ++idx) { + output.out_char(*(label + 1 + idx)); } + + label = _next_label(label); + while (*label) { + label_length = static_cast(*label); + output.out_char('.'); + for (size_t idx = 0; idx < label_length; ++idx) { + output.out_char(static_cast(*(label + 1 + idx))); } + label = _next_label(label); + } + } + +}; + + +/** + * Data layout of this class conforms to a DNS Request layout (RFC 1035) + * + * DNS request header: + * + * +---------------------+ + * | Header | the request header + * +---------------------+ + * | Question | the question for the name server + * +---------------------+ + * | Answer | RRs answering the question + * +---------------------+ + * | Authority | RRs pointing toward an authority + * +---------------------+ + * | Additional | RRs holding additional information + * +---------------------+* + */ +class Net::Dns_packet +{ + public: + + enum class Type: uint16_t { + A = 1, /* a host address */ + NS = 2, /* an authoritative name server*/ + MD = 3, /* a mail destination (Obsolete - use MX) */ + MF = 4, /* a mail forwarder (Obsolete - use MX) */ + CNA = 5, /* the canonical name for an alias */ + SOA = 6, /* marks the start of a zone of authority */ + MB = 7, /* a mailbox domain name (EXPERIMENTAL) */ + MG = 8, /* a mail group member (EXPERIMENTAL) */ + MR = 9, /* a mail rename domain name (EXPERIMENTAL) */ + NUL = 10, /* a null RR (EXPERIMENTAL) */ + WKS = 11, /* a well known service description */ + PTR = 12, /* a domain name pointer */ + HIN = 13, /* host information */ + MIN = 14, /* mailbox or mail list information */ + MX = 15, /* mail exchange */ + TXT = 16, /* text strings */ + AXFR = 252, /* A request for a transfer of an entire zone */ + MAILB = 253, /* A request for mailbox-related records (MB, MG or MR) */ + MAILA = 254, /* A request for mail agent RRs (Obsolete - see MX) */ + WILDCARD = 255, /* A request for all records */ + }; + + enum class Class: uint16_t { + IN = 1, /* the Internet */ + CS = 2, /* the CSNET class (Obsolete - used only for examples in some obsolete RFCs) */ + CH = 3, /* the CHAOS class */ + HS = 4, /* Hesiod [Dyer 87] */ + WILDCARD = 255, /* any class */ + }; + + struct Dns_entry + { + Domain_name name { }; + Type net_type { }; + Class net_class { }; + uint32_t ttl { }; + Ipv4_address addr { }; + }; + + private: + + /** + * DNS Request Header + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ID | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QDCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ANCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | NSCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ARCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+* + */ + struct Header_datagram { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + } __attribute__((packed)); + + /* + * Flags struct to access Header::flags + */ + struct Flags: Register<16> { + struct Rcode : Register<16>::Bitfield<0, 4> { }; + struct Recursion_available : Register<16>::Bitfield<7, 1> { }; + struct Recursion_desired : Register<16>::Bitfield<8, 1> { }; + struct Truncation : Register<16>::Bitfield<9, 1> { }; + struct Authoritative_answer : Register<16>::Bitfield<10, 1> { }; + struct Opcode : Register<16>::Bitfield<11, 4> { }; + struct Querry : Register<16>::Bitfield<15, 1> { }; + }; + + /* + * Querry layout for dns request (RFC 1035) + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / QNAME / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QTYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QCLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ + struct Question_datagram { + uint16_t qtype; + uint16_t qclass; + } __attribute__((packed)); + + /* + * Response layout for dns request (RFC 1035) + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / / + * / NAME / + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | CLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TTL | + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | RDLENGTH | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| + * / RDATA / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + */ + struct Response_datagram { + uint16_t rtype; + uint16_t rclass; + uint32_t rttl; + uint16_t rdlength; + uint8_t rdata[0]; + } __attribute__((packed)); + + Header_datagram _header; + uint16_t _data[0]; + + uint16_t *_next_question_entry(void *curr_field) + { + uint8_t *label = reinterpret_cast(curr_field); + while(*label) ++label; + ++label; + label += sizeof(Question_datagram); + return reinterpret_cast(label); + } + + void _check_size_guard(Size_guard size_guard, void *ptr) + { + if (reinterpret_cast(ptr) > reinterpret_cast(_data)) { + if (reinterpret_cast(ptr) - reinterpret_cast(_data) > size_guard.unconsumed()) { + throw Size_guard::Exceeded(); } } + } + + public: + + enum { UDP_PORT = 53, }; + + void id(uint16_t id) { _header.id = host_to_big_endian(id); } + + uint16_t id() const { return big_endian_to_host(_header.id); } + + bool truncated() const { + return Flags::Truncation::get(big_endian_to_host(_header.flags)); } + + bool response() const { + return Flags::Querry::get(big_endian_to_host(_header.flags)); } + + static inline size_t sizeof_question(Domain_name const &dn) { + return sizeof(Question_datagram) + dn.length(); } + + uint16_t qdcount() const { return big_endian_to_host(_header.qdcount); } + + uint16_t ancount() const { return big_endian_to_host(_header.ancount); } + + void recursion_desired(bool value) + { + uint16_t flags = big_endian_to_host(_header.flags); + Flags::Recursion_desired::set(flags, value); + _header.flags = host_to_big_endian(flags); + } + + void question(Size_guard &size_guard, + Domain_name const &dn, + Type qtype = Type::A, + Class qclass = Class::IN) + { + /* only populate questions, when the message is a querry */ + if (response()) return; + + void *qslot = _data; + + /* skip existing question entries */ + for (size_t count = 0; count < big_endian_to_host(_header.qdcount); ++count) { + qslot = _next_question_entry(qslot); } + + size_guard.consume_head(dn.length() + sizeof(Question_datagram)); + dn.copy(qslot); + auto *q = construct_at((uint8_t*)qslot + dn.length()); + q->qtype = host_to_big_endian(static_cast(qtype)); + q->qclass = host_to_big_endian(static_cast(qclass)); + + /* adjust header's question count */ + _header.qdcount = host_to_big_endian( + static_cast(big_endian_to_host(_header.qdcount) + 1)); + } + + template + void for_each_entry(Size_guard &size_guard, FN fn) + { + /* only read responses, when the message is a response */ + if (!response()) return; + + /* by-pass questions entries */ + void *rslot = _data; + + Domain_name name; + + /* skip question entries */ + for (size_t idx = 0; idx < big_endian_to_host(_header.qdcount); ++idx) { + _check_size_guard(size_guard, rslot); + rslot = _next_question_entry(rslot); } + + /* for each answer entries */ + for (size_t idx = 0; idx < big_endian_to_host(_header.ancount); ++idx) { + + _check_size_guard(size_guard, rslot); + + Dns_entry entry { }; + + /* read domain name, adjusting methods if compression bits are set */ + uint16_t compression_octets = + big_endian_to_host(*reinterpret_cast(rslot)); + + char *label = reinterpret_cast(rslot); + + /* if compression bits are set, adjust label pointer */ + if (compression_octets & 0xC000) { + size_t offset = compression_octets & 0x3FFF; + label = reinterpret_cast(&_header) + offset; + _check_size_guard(size_guard, label); + } + + /* read domain name labels */ + while(*label) { + size_t size = *label; + char *str = label + 1; + entry.name.label(size, str); + label = str + size; + _check_size_guard(size_guard, label); } + + /* if compression bits are set, adjust rslot pointer */ + if (compression_octets & 0xC000) { + rslot = reinterpret_cast(rslot) + 1; + } else { + /* if no compersion bits are set, rslot continue after the domain name */ + rslot = label + 1; + } + + Response_datagram *rd = reinterpret_cast(rslot); + + _check_size_guard(size_guard, rd->rdata); + + entry.net_class = static_cast(big_endian_to_host(rd->rclass)); + entry.net_type = static_cast(big_endian_to_host(rd->rtype)); + entry.ttl = big_endian_to_host(rd->rttl); + + /* currently only support IPV4 formated response data */ + if (big_endian_to_host(rd->rdlength) != IPV4_ADDR_LEN) { + log("Dns address data length is unsupported, ignoring entry for ", entry.name); + continue; } + + entry.addr = Ipv4_address { rd->rdata }; + + fn(entry); + rslot = rd->rdata + big_endian_to_host(rd->rdlength); + } + } + +} __attribute__((packed)); + + +/* + * Domain Name Grammar (RFC 1035) + * + * ::= | " " + * + * ::=