/* * \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) * * ::= | " " * * ::=