arp \
dhcpv4 \
dhcpv6 \
+ dns \
eap/aka-sim \
ethernet \
freeradius \
--- /dev/null
+# -*- text -*-
+#
+#
+# $Id$
+
+#######################################################################
+#
+# = The DNS Virtual Server
+#
+# The `dns` virtual server is an example of using `dns` style functionality in FreeRADIUS.
+#
+# ## The Virtual Server
+#
+# This is the `dns` virtual server.
+#
+# It is (for now) only a toy. It only decodes nested attributes, which `unlang`
+# cannot (yet) handle well. It only handles a few types of RRs. You have to manually
+# do pretty much everything necessary to make DNS "work". There's no DB integration.
+#
+# It's not meant to be fast. Don't use it as a root server, or as a server for an ISP
+# with millions of users. But it should be able to do thousands to tens of thousands
+# of queries per second, without really trying hard.
+#
+# It's meant to be a _flexible_ DNS server. Want to give different answers to VoIP phones
+# and desktops? It can do that.
+#
+server dns {
+ #
+ # namespace:: The protocol / dictionary to use.
+ #
+ namespace = dns
+
+ listen {
+ type = query
+
+ transport = udp
+
+ #
+ # Dont use "port = 53" unless you want to break things
+ #
+ udp {
+ ipaddr = *
+ port = 5300
+ }
+ }
+
+
+recv query {
+ ok
+}
+
+send query.response {
+ ok
+}
+
+}
PROTOCOL SNMP 7 format=4
PROTOCOL ARP 8
PROTOCOL TFTP 9
+PROTOCOL TLS 10
+PROTOCOL DNS 11
PROTOCOL EAP-SIM 101
PROTOCOL EAP-AKA 102
+PROTOCOL EAP-FAST 103
PROTOCOL Control 255
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2021 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+#
+# The FreeRADIUS dictionary for ARP
+#
+# Version: $Id$
+#
+
+PROTOCOL DNS 11 format=2
+BEGIN-PROTOCOL DNS
+
+$INCLUDE dictionary.freeradius.internal
+$INCLUDE dictionary.rfc1034
+
+END-PROTOCOL DNS
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2021 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+FLAGS internal
+
+ATTRIBUTE Packet-Type 1000 uint32 clone=packet.opcode
+
+VALUE Packet-Type query.response 16
+VALUE Packet-Type iquery.response 17
+VALUE Packet-Type status.response 18
+VALUE Packet-Type notify.response 20
+VALUE Packet-Type update.response 21
+VALUE Packet-Type dns-stateful-operations.response 22
+
+VALUE Packet-Type Do-Not-Respond 256
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2021 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+#
+# For encoding / decoding, just have an associated array which
+# contains a series of uint16_t offsets into the packet.
+# these offsets contain valid targets for compressed pointers.
+#
+ATTRIBUTE packet 1 struct
+MEMBER id uint16
+MEMBER query bit[1]
+MEMBER opcode bit[4]
+MEMBER authoritative bit[1]
+MEMBER truncated-response bit[1]
+MEMBER recursion-desired bit[1]
+MEMBER recursion-available bit[1]
+MEMBER reserved bit[1]
+MEMBER authentic-data bit[1]
+MEMBER checking-disabled bit[1]
+MEMBER rcode bit[4]
+
+VALUE query query 0
+VALUE query response 1
+
+VALUE opcode query 0
+VALUE opcode iquery 1
+VALUE opcode status 2
+VALUE opcode notify 4
+VALUE opcode update 5
+VALUE opcode dns-stateful-operations 6
+
+VALUE rcode no-error 0
+VALUE rcode format-error 1
+VALUE rcode server-error 2
+VALUE rcode name-error 3
+VALUE rcode not-implemented 4
+VALUE rcode refused 5
+VALUE rcode yx-domain 6
+VALUE rcode yx-rr-set 7
+VALUE rcode nx-rr-set 8
+VALUE rcode not-auth 9
+VALUE rcode not-zone 10
+VALUE rcode dso-type-not-implemented 11
+VALUE rcode bad-signature 16
+VALUE rcode bad-key 17
+VALUE rcode bad-time 18
+VALUE rcode bad-mode 19
+VALUE rcode bad-name 20
+VALUE rcode bad-algorithm 21
+VALUE rcode bad-truncation 22
+VALUE rcode bad-cookie 23
+
+MEMBER qdcount uint16
+MEMBER ancount uint16
+MEMBER nscount uint16
+MEMBER arcount uint16
+
+ATTRIBUTE question 2 struct
+MEMBER qname string dns_label
+MEMBER qtype uint16 # from packet.type
+MEMBER qclass uint16
+
+VALUE qclass internet 1
+VALUE qclass chaos 3
+VALUE qclass hesiod 4
+VALUE qclass none 254
+VALUE qclass any 255
+
+ATTRIBUTE rr 3 struct
+MEMBER name string dns_label
+MEMBER type uint16 key
+MEMBER class uint16
+MEMBER ttl time_delta
+
+######################################################################
+#
+# Now we have resource records
+#
+######################################################################
+STRUCT a type 1 length=uint16
+MEMBER ip-address ipaddr
+
+STRUCT ns type 2 length=uint16
+MEMBER domain-name string dns_label
+
+STRUCT cname type 5 length=uint16
+MEMBER domain-name string dns_label
+
+STRUCT soa type 6 length=uint16
+MEMBER mname string dns_label
+MEMBER rname string dns_label
+MEMBER serial uint32
+MEMBER refresh time_delta
+MEMBER retry time_delta
+MEMBER expire time_delta
+MEMBER minimum time_delta
+
+STRUCT ptr type 12 length=uint16
+MEMBER domain-name string dns_label
+
+STRUCT mx type 15 length=uint16
+MEMBER preference uint32
+MEMBER domain-name string dns_label
+
+STRUCT txt type 16 length=uint16
+MEMBER data string
+
+STRUCT aaaa type 28 length=uint16
+MEMBER ipv6-address ipv6addr
+
+STRUCT dhcid type 49 length=uint16
+MEMBER identifier-type-code uint16
+MEMBER digest-type-code uint8
+MEMBER digest octets
+
+VALUE identifier-type-code chaddr 0
+VALUE identifier-type-code dhcpv4-client-identifier 1
+VALUE identifier-type-code dhcpv6-client-duid 2
+
+VALUE digest-type-code sha-256 1
+
+#
+# opt MUST have:
+#
+# 1 octet 0 - name is root
+# 2 octets class - requestors UDP payload size
+# 4 octets TTL - extended rcode and flags
+# 2 octets RDLEN
+# RRDATA of 16 bits code, 16 bits length of data, data
+STRUCT opt type 41
+MEMBER options tlv
+
+ATTRIBUTE llq .1 struct
+MEMBER version uint16
+MEMBER opcode uint16
+MEMBER error uint16
+MEMBER ID uint64
+MEMBER lease time_delta
+
+#ATTRIBUTE ul .2
+ATTRIBUTE nsid .3 octets
+
+ATTRIBUTE edns-client-subnet .8 struct
+MEMBER family uint16
+MEMBER source-prefix-length uint8
+MEMBER scope-prefix-length uint8
+# MUST be truncated to bytes determined by source-prefix-length
+MEMBER ip combo-ip
+
+# existence signals EXPIRE
+ATTRIBUTE expire .9 bool
+
+# https://datatracker.ietf.org/doc/html/rfc7873
+ATTRIBUTE cookie .10 struct
+
+# pseudo-random-function(client ip, server ip, client secret)
+MEMBER client octets[8]
+
+# pseudo-random-function(client ip, client cookie, server secret), at least 64 bits
+MEMBER server octets
+
+# Name server RR
+ATTRIBUTE ns 4 struct clone=rr
+
+# additional "glue" RR, or OPT RR for peer signalling
+ATTRIBUTE ar 4 struct clone=rr
}
ret = fr_dns_label_from_value_box(&need,
- cc->buffer_start, cc->buffer_end - cc->buffer_start, enc_p, true, box);
+ cc->buffer_start, cc->buffer_end - cc->buffer_start, enc_p, true, box, NULL);
talloc_free(box);
if (ret < 0) RETURN_OK_WITH_ERROR();
end = data + COMMAND_OUTPUT_MAX;
for (i = 0; i < total; i += slen) {
- slen = fr_dns_label_to_value_box(box, box, cc->buffer_start, total, cc->buffer_start + i, false);
+ slen = fr_dns_label_to_value_box(box, box, cc->buffer_start, total, cc->buffer_start + i, false, NULL);
if (slen <= 0) {
error:
talloc_free(box);
${Q}echo "#pragma once" > $$@
${Q}grep ^PROTOCOL $$< | ${NORMALIZE} | awk '{print "#define FR_PROTOCOL_"$$$$2" " $$$$3 " //!< AUTOGENERATED PROTOCOL NUMBER DEFINITION"}' >> $$@
${Q}grep ^ATTRIBUTE $$< | egrep -v '[ ]\.' | ${NORMALIZE} | awk '{print "#define FR_"$$$$2 " " $$$$3 " //!< AUTOGENERATED ATTRIBUTE DEFINITION"}' >> $$@
- ${Q}grep ^STRUCT $$< | ${NORMALIZE} | awk '{print "#define FR_"$$$$2 " " $$$$3 " //!< AUTOGENERATED ATTRIBUTE DEFINITION"}' >> $$@
+ ${Q}grep ^STRUCT $$< | ${NORMALIZE} | awk '{print "#define FR_STRUCT_"$$$$2 " " $$$$3 " //!< AUTOGENERATED ATTRIBUTE DEFINITION"}' >> $$@
${Q}grep ^VALUE $$< | ${NORMALIZE} | awk '{print "#define FR_"$$$$2"_VALUE_"$$$$3 " " $$$$4 " //!< AUTOGENERATED VALUE DEFINITION"}' >> $$@
endef
$(foreach x,$(DICT),$(eval $(call DICT_TO_HEADER,$(addsuffix .h,$(subst dictionary.,,$(patsubst share/dictionary/%,protocol/%,$(x)))),$(x))))
#include <freeradius-devel/util/talloc.h>
#include <freeradius-devel/util/value.h>
#include <freeradius-devel/util/dns.h>
+#include <freeradius-devel/util/proto.h>
+
+fr_dns_labels_t *fr_dns_labels_init(TALLOC_CTX *ctx, uint8_t const *packet, int max_labels)
+{
+ fr_dns_labels_t *lb;
+
+ lb = (fr_dns_labels_t *) talloc_zero_array(ctx, uint8_t, sizeof(fr_dns_labels_t) + sizeof(lb->blocks[0]) * max_labels);
+ if (!lb) return NULL;
+
+ talloc_set_name_const(lb, "fr_dns_labels_t");
+
+ lb->start = packet;
+ lb->max = max_labels;
+
+ /*
+ * Always skip the DNS packet header.
+ */
+ lb->blocks[0].start = 12;
+ lb->blocks[0].end = 12;
+ lb->num = 1;
+
+ return lb;
+}
+
+static int dns_label_add(fr_dns_labels_t *lb, uint8_t const *start, uint8_t const *end)
+{
+ size_t offset, size = end - start;
+ fr_dns_block_t *block;
+
+ /*
+ * If we don't care about tracking the blocks, then don't
+ * do anything.
+ */
+ if (!lb) return 0;
+
+ fr_assert(start >= lb->start);
+ fr_assert(end >= start);
+
+ offset = start - lb->start;
+ fr_assert(offset < 65536);
+ fr_assert(size < 65535);
+ fr_assert((offset + size) < 65535);
+
+ FR_PROTO_TRACE("adding label at offset %zu", offset);
+
+ if (offset > 65535) return -1;
+ if (size > 65535) return -1;
+ if ((offset + size) > 65535) return -1;
+
+ /*
+ * We add blocks append-only. No adding new blocks in
+ * the middle of a packet.
+ */
+ block = &lb->blocks[lb->num - 1];
+ fr_assert(block->start <= offset);
+ fr_assert(offset);
+
+ FR_PROTO_TRACE("Last block (%d) is %u..%u", lb->num - 1, block->start, block->end);
+
+ /*
+ * Fits within an existing block.
+ */
+ if (block->end == offset) {
+ block->end += size;
+ FR_PROTO_TRACE("Expanding last block (%d) to %u..%u", lb->num - 1, block->start, block->end);
+ return 0;
+ }
+
+ /*
+ * It's full, die.
+ */
+ if (lb->num == lb->max) return -1;
+
+ lb->num++;
+ block++;
+
+ block->start = offset;
+ block->end = offset + size;
+ FR_PROTO_TRACE("Appending block (%d) to %u..%u", lb->num - 1, block->start, block->end);
+
+ return 0;
+}
+
+static bool dns_pointer_valid(fr_dns_labels_t *lb, uint16_t offset)
+{
+ int i;
+
+ if (!lb) return true; /* we have no idea, so allow it */
+
+ for (i = 0; i < lb->num; i++) {
+ FR_PROTO_TRACE("Checking block %d %u..%u against %u",
+ i, lb->blocks[i].start, lb->blocks[i].end, offset);
+
+ if (offset < lb->blocks[i].start) return false;
+
+ if (offset < lb->blocks[i].end) return true;
+ }
+
+ return false;
+}
/** Compare two labels in a case-insensitive fashion.
*
return true;
}
-/** Compress "label" by looking at it recursively.
+/** Compress "label" by looking at the label recursively.
*
* For "ftp.example.com", it searches the input buffer for a matching
* "com". It only does string compares if it finds bytes "03 xx xx
* no existing 3-label name which matches a 3-label name in the
* buffer.
*
+ * Note that this function does NOT follow pointers in the input
+ * buffer!
+ *
*
* A different and more straightforward approach is to loop over all
* labels in the name from longest to shortest, and comparing them to
* the input buffer. In contrast, because our algorithm does not do
* pointer following, it only compares "com" to "org" once.
*
+ * @param[in] packet where the packet starts
* @param[in] start input buffer holding one or more labels
* @param[in] end end of the input buffer
* @param[out] new_search Where the parent call to dns_label_compress()
* - false, we didn't compress the input
* - true, we did compress the input.
*/
-static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t const **new_search,
+static bool dns_label_compress(uint8_t const *packet, uint8_t const *start, uint8_t const *end, uint8_t const **new_search,
uint8_t *label, uint8_t **label_end)
{
uint8_t *next;
uint8_t const *q, *ptr, *suffix, *search;
uint16_t offset;
+ bool compressed = false;
/*
* Don't compress "end of label" byte or pointers.
* point to it. This check is mainly for
* static analyzers.
*/
- if ((q - start) > (1 << 14)) return false;
+ if ((q - packet) > (1 << 14)) return false;
/*
* Only now do case-insensitive
* that we managed to compress this
* label.
*/
- offset = (q - start);
+ offset = (q - packet);
label[0] = (offset >> 8) | 0xc0;
label[1] = offset & 0xff;
*label_end = label + 2;
* ourselves recursively in order to compress it.
*/
if (*next < 63) {
- if (!dns_label_compress(start, end, &search, next, label_end)) return false;
+ if (!dns_label_compress(packet, start, end, &search, next, label_end)) return false;
/*
* Else it WAS compressed.
*/
+ compressed = true;
}
/*
* static analysis tools.
*/
if (*next < 0xc0) {
- return false;
+ return compressed;
}
/*
* Remember where our suffix points to.
*/
- suffix = start + ((next[0] & ~0xc0) << 8) + next[1];
+ suffix = packet + ((next[0] & ~0xc0) << 8) + next[1];
/*
* Our label now ends with a compressed pointer. Scan
* buffer. Check for a match.
*/
ptr = q + *q + 1;
- if (ptr > end) return false;
+ if (ptr > end) return compressed;
/*
* Label lengths aren't the same, skip it.
* Pointer is too far away. Don't point
* to it.
*/
- if ((q - start) > (1 << 14)) return false;
+ if ((q - packet) > (1 << 14)) return compressed;
/*
* Only now do case-insensitive
* that we managed to compress this
* label.
*/
- offset = (q - start);
+ offset = (q - packet);
label[0] = (offset >> 8) | 0xc0;
label[1] = offset & 0xff;
*label_end = label + 2;
/*
* Who knows what it is, we couldn't compress it.
*/
- return false;
+ return compressed;
}
* - 0 could not encode anything, an error has occurred.
* - <0 the number of bytes the dbuff should have had, instead of "remaining".
*/
-ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value)
+ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb)
{
ssize_t slen;
size_t need = 0;
- slen = fr_dns_label_from_value_box(&need, dbuff->p, fr_dbuff_remaining(dbuff), dbuff->p, compression, value);
+ slen = fr_dns_label_from_value_box(&need, dbuff->p, fr_dbuff_remaining(dbuff), dbuff->p, compression, value, lb);
if (slen < 0) return 0;
if (slen == 0) return -need;
* @param[out] where Where to write this label
* @param[in] compression Whether or not to do DNS label compression.
* @param[in] value to encode.
+ * @param[in] lb label tracking data structure
* @return
* - 0 no bytes were written, see need value to determine
* - >0 the number of bytes written to "where", NOT "buf + where + outlen"
* - <0 on error.
*/
ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression,
- fr_value_box_t const *value)
+ fr_value_box_t const *value, fr_dns_labels_t *lb)
{
uint8_t *label;
uint8_t const *end = buf + buf_len;
*(data++) = 0; /* end of label */
/*
- * Only one label, don't compress it. Or, the label is
- * already compressed.
+ * If we're compressing it, and we have data to compress,
+ * then do it.
*/
- if (!compression || (buf == where) || ((data - where) <= 2)) goto done;
+ if (compression && ((data - where) > 2)) {
+ if (lb) {
+ int i;
- /*
- * Compress it, AND tell us where the new end buffer is located.
- */
- (void) dns_label_compress(buf, where, NULL, where, &data);
+ /*
+ * Loop over the parts of the packet which have DNS labels.
+ *
+ * Note that the dns_label_compress() function does NOT follow pointers in the
+ * start/end block which it's searching! It just tries to compress the *input*,
+ * and assumes that the input is compressed last label to first label.
+ *
+ * In addition, dns_label_compress() tracks where in the block it started
+ * searching. So it only scans the block once, even if we pass a NULL search
+ * parameter to it.
+ *
+ * We could start compression from the *last* block. When we add
+ * "www.example.com" and then "ftp.example.com", we could point "ftp" to the
+ * "example.com" portion. which is already in the packet. However, doing that
+ * would require that dns_label_compress() follows pointers in the block it's
+ * searching. Which would greatly increase the complexity of the algorithm.
+ *
+ *
+ * We could still optimize this algorithm a bit, by tracking which parts of the
+ * buffer have DNS names of label length 1, 2, etc. Doing that would mean more
+ * complex data structures, but fewer passes over the packet.
+ */
+ for (i = 0; i < lb->num; i++) {
+ bool compressed;
+
+ FR_PROTO_TRACE("Trying to compress %s in block %d of %u..%u",
+ value->vb_strvalue, i,
+ lb->blocks[i].start, lb->blocks[i].end);
+
+ compressed = dns_label_compress(lb->start, lb->start + lb->blocks[i].start,
+ lb->start + lb->blocks[i].end,
+ NULL, where, &data);
+ if (compressed) {
+ FR_PROTO_TRACE("Compressed label in block %d", i);
+ if (*(where + *where + 1) >= 0xc0) {
+ FR_PROTO_TRACE("Next label is compressed, stopping");
+ }
+ }
+ }
+
+ dns_label_add(lb, where, data);
+
+ } else if (buf != where) {
+ if (dns_label_compress(buf, buf, where, NULL, where, &data)) {
+ FR_PROTO_TRACE("Compressed single label %s to %zu bytes",
+ value->vb_strvalue, data - where);
+ } else {
+ FR_PROTO_TRACE("Did not compress single label");
+ }
+ }
+ } else {
+ FR_PROTO_TRACE("Not compressing label");
+ }
-done:
fr_assert(data > where);
return data - where;
}
*
* Note that a bare 0x00 byte has length 1, to account for '.'
*
+ * @param[in] packet where the packet starts
* @param[in] buf buffer holding one or more DNS labels
* @param[in] buf_len total length of the buffer
* @param[in,out] next the DNS label to check, updated to point to the next label
+ * @param[in] lb label tracking data structure
* @return
* - <=0 on error, offset from buf where the invalid label is located.
* - > 0 decoded size of this particular DNS label
*/
-ssize_t fr_dns_label_uncompressed_length(uint8_t const *buf, size_t buf_len, uint8_t const **next)
+ssize_t fr_dns_label_uncompressed_length(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const **next, fr_dns_labels_t *lb)
{
uint8_t const *p, *q, *end, *label_end;
uint8_t const *current, *start;
size_t length;
bool at_first_label, already_set_next;
- if (!buf || (buf_len == 0) || !next) return 0;
+ if (!packet || !buf || (buf_len == 0) || !next) {
+ fr_strerror_printf("Invalid argument");
+ return 0;
+ }
start = *next;
/*
* Don't allow stupidities
*/
- if (!((start >= buf) && (start < (buf + buf_len)))) return 0;
+ if (!((start >= packet) && (start < (buf + buf_len)))) {
+ fr_strerror_printf("Label is not within the buffer");
+ return 0;
+ }
end = buf + buf_len;
p = current = start;
* packets which will cause the decoder
* to do bad things.
*/
- if (offset >= (p - buf)) {
+ if (offset >= (p - packet)) {
fr_strerror_printf("Pointer %04x at offset %04x is an invalid forward reference",
offset, (int) (p - buf));
return -(p - buf);
}
- q = buf + offset;
+ /*
+ * If we're tracking which labels are
+ * valid, then check the pointer, too.
+ */
+ if (!dns_pointer_valid(lb, offset)) {
+ fr_strerror_printf("Pointer %04x at offset %04x does not point to a DNS label",
+ offset, (int) (p - buf));
+ return -(p - buf);
+ }
+
+ q = packet + offset;
/*
* As an additional sanity check, the
*/
if (!already_set_next) *next = p; /* should be <='end' */
+ (void) dns_label_add(lb, start, *next);
+
return length;
}
/** Verify that a network buffer contains valid DNS labels.
*
+ * @param[in] packet where the packet starts
* @param[in] buf buffer holding one or more DNS labels
* @param[in] buf_len total length of the buffer
* @param[in] start where to start looking
+ * @param[in] lb label tracking data structure
* @return
* - <=0 on error, where in the buffer the invalid label is located.
* - > 0 total size of the encoded label(s). Will be <= buf_len
*/
-ssize_t fr_dns_labels_network_verify(uint8_t const *buf, size_t buf_len, uint8_t const *start)
+ssize_t fr_dns_labels_network_verify(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const *start, fr_dns_labels_t *lb)
{
ssize_t slen;
uint8_t const *label = start;
break;
}
- slen = fr_dns_label_uncompressed_length(buf, buf_len, &label);
+ slen = fr_dns_label_uncompressed_length(packet, buf, buf_len, &label, lb);
if (slen <= 0) return slen; /* already is offset from 'buf' and not 'label' */
}
* @param[in] len Length of the buffer to decode
* @param[in] label This particular label
* @param[in] tainted Whether the value came from a trusted source.
+ * @param[in] lb label tracking data structure
* @return
* - >= 0 The number of network bytes consumed.
* - <0 on error.
*/
ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst,
uint8_t const *src, size_t len, uint8_t const *label,
- bool tainted)
+ bool tainted, fr_dns_labels_t *lb)
{
ssize_t slen;
uint8_t const *after = label;
uint8_t const *current, *next;
+ uint8_t const *packet = src;
uint8_t *p;
char *q;
+ if (lb) packet = lb->start;
+
/*
* Get the uncompressed length of the label, and the
* label after this one.
*/
- slen = fr_dns_label_uncompressed_length(src, len, &after);
- if (slen <= 0) return slen;
+ slen = fr_dns_label_uncompressed_length(packet, src, len, &after, lb);
+ if (slen <= 0) {
+ FR_PROTO_TRACE("dns_label_to_value_box - Failed getting length");
+ return slen;
+ }
fr_value_box_init_null(dst);
* only returns 0 when the current byte is 0x00,
* which it can't be.
*/
- slen = dns_label_decode(src, ¤t, &next);
+ slen = dns_label_decode(packet, ¤t, &next);
/*
* As a sanity check, ensure we don't have a
* buffer overflow.
*/
if ((p + slen) > (uint8_t *) q) {
+ FR_PROTO_TRACE("dns_label_to_value_box - length %zd Failed at %d", slen, __LINE__);
+
fail:
fr_value_box_clear(dst);
return -1;
* buffer exactly.
*/
if (p != (uint8_t *) q) {
+ FR_PROTO_TRACE("dns_label_to_value_box - Failed at %d", __LINE__);
goto fail;
}
*/
return after - label;
}
-
-/** Get the *network* length of a DNS label in a buffer
- * i.e. the number of bytes in the encoded representation of the dns label.
- *
- * @param[in] buf buffer holding one or more DNS labels
- * @param[in] buf_len total length of the buffer
- * @return
- * - <=0 on error, offset from buf where the invalid label is located.
- * - > 0 network length of this particular DNS label
- */
-ssize_t fr_dns_label_network_length(uint8_t const *buf, size_t buf_len)
-{
- uint8_t const *p = buf;
- uint8_t const *end = buf + buf_len;
-
- while (p < end) {
- if (*p == 0x00) {
- p++;
- break;
- }
-
- if (*p < 64) {
- if ((p + *p + 1) > end) {
- return -(p - buf);
- }
-
- p += *p + 1;
- continue;
- }
-
- if (*p < 0xc0) {
- return -(p - buf);
- }
-
- if ((p + 1) > end) {
- return -(p - buf);
- }
-
- p += 2;
- break;
- }
-
- return p - buf;
-}
extern "C" {
#endif
-ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buflen, uint8_t *where, bool compression, fr_value_box_t const *value);
+typedef struct {
+ uint16_t start;
+ uint16_t end;
+} fr_dns_block_t;
-ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value);
+typedef struct {
+ uint8_t const *start; //!< start of packet
+ int num; //!< number of used labels
+ int max; //! maximum number of labels
+ fr_dns_block_t blocks[]; //!< array holding "max" labels
+} fr_dns_labels_t;
-ssize_t fr_dns_label_uncompressed_length(uint8_t const *buf, size_t buf_len, uint8_t const **p_label);
+ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buflen, uint8_t *where, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb);
-ssize_t fr_dns_label_network_length(uint8_t const *buf, size_t buf_len);
+ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb);
-ssize_t fr_dns_labels_network_verify(uint8_t const *buf, size_t buf_len, uint8_t const *start) CC_HINT(nonnull);
+ssize_t fr_dns_label_uncompressed_length(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const **p_label, fr_dns_labels_t *lb);
+
+ssize_t fr_dns_labels_network_verify(uint8_t const *packet, uint8_t const *buf, size_t buf_len, uint8_t const *start, fr_dns_labels_t *lb) CC_HINT(nonnull(1,2,4));
ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst,
uint8_t const *src, size_t len, uint8_t const *label,
- bool tainted);
+ bool tainted, fr_dns_labels_t *lb);
+
+fr_dns_labels_t *fr_dns_labels_init(TALLOC_CTX *ctx, uint8_t const *packet, int max_labels) CC_HINT(nonnull);
#ifdef __cplusplus
}
*/
ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len,
- void *decode_ctx,
+ bool nested, void *decode_ctx,
fr_decode_value_t decode_value, fr_decode_value_t decode_tlv)
{
unsigned int child_num;
fr_dict_attr_t const *child;
fr_pair_list_t head;
fr_dcursor_t child_cursor;
- fr_pair_t *vp, *key_vp;
+ fr_pair_t *vp, *key_vp, *struct_vp = NULL;
unsigned int offset = 0;
+ TALLOC_CTX *child_ctx;
- fr_pair_list_init(&head);
if (data_len < 1) return -1; /* at least one byte of data */
FR_PROTO_HEX_DUMP(data, data_len, "fr_struct_from_network");
/*
- * Record where we were in the list when this function was called
+ * Start a child list.
*/
- fr_dcursor_init(&child_cursor, &head);
+ if (!nested) {
+ fr_pair_list_init(&head);
+ fr_dcursor_init(&child_cursor, &head);
+ child_ctx = ctx;
+ } else {
+ fr_assert(parent->type == FR_TYPE_STRUCT);
+
+ struct_vp = fr_pair_afrom_da(ctx, parent);
+ if (!struct_vp) return -1;
+
+ fr_dcursor_init(&child_cursor, &struct_vp->vp_group);
+ child_ctx = struct_vp;
+ }
child_num = 1;
key_vp = NULL;
size_t struct_len;
struct_len = (p[0] << 8) | p[1];
- if ((struct_len + 2) > data_len) goto unknown;
+ if ((struct_len + 2) > data_len) {
+ FR_PROTO_TRACE("too much data?");
+ goto unknown;
+ }
data_len = struct_len + 2;
end = data + data_len;
uint64_t value;
num_bits = offset + child->flags.length;
- if ((end - p) < fr_bytes_from_bits(num_bits)) goto unknown;
+ if ((end - p) < fr_bytes_from_bits(num_bits)) {
+ FR_PROTO_TRACE("not enough data for bit decoder?");
+ goto unknown;
+ }
memset(array, 0, sizeof(array));
memcpy(&array[0], p, fr_bytes_from_bits(num_bits));
value >>= (8 - offset); /* move it to the lower bits */
value >>= (56 - child->flags.length);
- vp = fr_pair_afrom_da(ctx, child);
+ vp = fr_pair_afrom_da(child_ctx, child);
if (!vp) {
FR_PROTO_TRACE("fr_struct_from_network - failed allocating child VP");
goto unknown;
break;
default:
+ FR_PROTO_TRACE("Can't decode unknown type?");
goto unknown;
}
* Decode EVERYTHING as a TLV.
*/
while (p < end) {
- slen = decode_tlv(ctx, &child_cursor, fr_dict_by_da(child), child, p, end - p, decode_ctx);
- if (slen < 0) goto unknown;
+ slen = decode_tlv(child_ctx, &child_cursor, fr_dict_by_da(child), child, p, end - p, decode_ctx);
+ if (slen < 0) {
+ FR_PROTO_TRACE("failed decoding TLV?");
+ goto unknown;
+ }
p += slen;
}
if (decode_value) {
ssize_t slen;
- slen = decode_value(ctx, &child_cursor, fr_dict_by_da(child), child, p, child_length, decode_ctx);
- if (slen < 0) return slen - (p - data);
+ slen = decode_value(child_ctx, &child_cursor, fr_dict_by_da(child), child, p, child_length, decode_ctx);
+ if (slen < 0) {
+ FR_PROTO_TRACE("Failed decoding value");
+ return slen - (p - data);
+ }
p += slen; /* not always the same as child->flags.length */
child_num++; /* go to the next child */
break;
}
- vp = fr_pair_afrom_da(ctx, child);
+ vp = fr_pair_afrom_da(child_ctx, child);
if (!vp) {
FR_PROTO_TRACE("fr_struct_from_network - failed allocating child VP");
goto unknown;
unknown:
fr_pair_list_free(&head);
- vp = fr_raw_from_network(ctx, parent, data, data_len);
+ vp = fr_raw_from_network(child_ctx, parent, data, data_len);
if (!vp) return -1;
/*
/*
* Nothing more to decode, don't decode it.
*/
- if (p >= end) goto done;
+ if (p >= end) {
+ FR_PROTO_TRACE("Expected substruct, but there is none. We're done decoding this structure");
+ goto done;
+ }
enumv = fr_dict_enum_by_value(key_vp->da, &key_vp->data);
if (enumv) child = enumv->child_struct[0];
* have to look up, or keep track of, the
* number of children of the key field.
*/
- child = fr_dict_unknown_afrom_fields(ctx, key_vp->da,
+ child = fr_dict_unknown_afrom_fields(child_ctx, key_vp->da,
fr_dict_vendor_num_by_da(key_vp->da), 0);
- if (!child) goto unknown;
+ if (!child) {
+ FR_PROTO_TRACE("failed allocating unknown child?");
+ goto unknown;
+ }
- vp = fr_raw_from_network(ctx, child, p, end - p);
+ vp = fr_raw_from_network(child_ctx, child, p, end - p);
if (!vp) {
+ FR_PROTO_TRACE("Failed creating raw VP from malformed or unknown substruct");
fr_dict_unknown_free(&child);
return -(p - data);
}
} else {
fr_assert(child->type == FR_TYPE_STRUCT);
- slen = fr_struct_from_network(ctx, &child_cursor, child, p, end - p,
+ slen = fr_struct_from_network(child_ctx, &child_cursor, child, p, end - p, nested,
decode_ctx, decode_value, decode_tlv);
- if (slen <= 0) goto unknown_child;
+ if (slen <= 0) {
+ FR_PROTO_TRACE("substruct %s decoding failed", child->name);
+ goto unknown_child;
+ }
p += slen;
}
fr_dict_unknown_free(&child);
- fr_dcursor_head(&child_cursor);
- fr_dcursor_tail(cursor);
- fr_dcursor_merge(cursor, &child_cursor); /* Wind to the end of the new pairs */
-
/*
* Else return whatever we decoded. Note that if
- * the substruct ends in a TLV, we decode only
- * the fixed-length portion of the structure.
+ * the substruct ends in a TLV, we decode only as
+ * many TLVs as the various "length" fields say.
*/
- return p - data;
+ data_len = p - data;
}
done:
- fr_dcursor_head(&child_cursor);
- fr_dcursor_tail(cursor);
- fr_dcursor_merge(cursor, &child_cursor); /* Wind to the end of the new pairs */
+ if (!nested) {
+ fr_dcursor_head(&child_cursor);
+ fr_dcursor_tail(cursor);
+ fr_dcursor_merge(cursor, &child_cursor); /* Wind to the end of the new pairs */
+ } else {
+ fr_assert(struct_vp != NULL);
+ fr_dcursor_append(cursor, struct_vp);
+ }
+ FR_PROTO_TRACE("used %zd bytes", data_len);
return data_len;
}
ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len,
- void *decode_ctx,
+ bool nested, void *decode_ctx,
fr_decode_value_t decode_value, fr_decode_value_t decode_tlv) CC_HINT(nonnull(2,3,4));
typedef ssize_t (*fr_encode_dbuff_t)(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, unsigned int depth,
dv = fr_dict_enum_by_name(attr_packet_type, value, -1);
if (!dv || (dv->value->vb_uint32 >= FR_DHCPV6_CODE_MAX)) {
- cf_log_err(ci, "Unknown DHCPv4 packet type '%s'", value);
+ cf_log_err(ci, "Unknown DHCPv6 packet type '%s'", value);
return -1;
}
{ FR_CONF_POINTER("networks", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) networks_config },
{ FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_dhcpv6_udp_t, max_packet_size), .dflt = "8192" } ,
- { FR_CONF_OFFSET("max_attributes", FR_TYPE_UINT32, proto_dhcpv6_udp_t, max_attributes), .dflt = STRINGIFY(DHCPV4_MAX_ATTRIBUTES) } ,
+ { FR_CONF_OFFSET("max_attributes", FR_TYPE_UINT32, proto_dhcpv6_udp_t, max_attributes), .dflt = STRINGIFY(DHCPV6_MAX_ATTRIBUTES) } ,
CONF_PARSER_TERMINATOR
};
--- /dev/null
+# proto_dhcp
+## Metadata
+<dl>
+ <dt>category</dt><dd>protocols</dd>
+</dl>
+
+## Summary
+Implements DNS (Domain Name System).
--- /dev/null
+SUBMAKEFILES := proto_dns.mk proto_dns_udp.mk
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file proto_dns.c
+ * @brief DHCPV6 master protocol handler.
+ *
+ * @copyright 2020 Network RADIUS SARL (legal@networkradius.com)
+ */
+#define LOG_PREFIX "proto_dns - "
+
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/io/listen.h>
+#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/unlang/base.h>
+#include <freeradius-devel/util/debug.h>
+#include "proto_dns.h"
+
+extern fr_app_t proto_dns;
+static int type_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, CONF_PARSER const *rule);
+static int transport_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, CONF_PARSER const *rule);
+
+static const CONF_PARSER priority_config[] = {
+ { FR_CONF_OFFSET("query", FR_TYPE_VOID, proto_dns_t, priorities[FR_DNS_QUERY]),
+ .func = cf_table_parse_int, .uctx = &(cf_table_parse_ctx_t){ .table = channel_packet_priority, .len = &channel_packet_priority_len }, .dflt = "normal" },
+ CONF_PARSER_TERMINATOR
+};
+
+
+static CONF_PARSER const limit_config[] = {
+ { FR_CONF_OFFSET("idle_timeout", FR_TYPE_TIME_DELTA, proto_dns_t, io.idle_timeout), .dflt = "30.0" } ,
+
+ { FR_CONF_OFFSET("max_connections", FR_TYPE_UINT32, proto_dns_t, io.max_connections), .dflt = "1024" } ,
+
+ /*
+ * For performance tweaking. NOT for normal humans.
+ */
+ { FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_dns_t, max_packet_size) } ,
+ { FR_CONF_OFFSET("num_messages", FR_TYPE_UINT32, proto_dns_t, num_messages) } ,
+ { FR_CONF_POINTER("priority", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) priority_config },
+
+ CONF_PARSER_TERMINATOR
+};
+
+/** How to parse a DNS listen section
+ *
+ */
+static CONF_PARSER const proto_dns_config[] = {
+ { FR_CONF_OFFSET("type", FR_TYPE_VOID | FR_TYPE_MULTI | FR_TYPE_NOT_EMPTY, proto_dns_t,
+ allowed_types), .func = type_parse },
+ { FR_CONF_OFFSET("transport", FR_TYPE_VOID, proto_dns_t, io.submodule),
+ .func = transport_parse },
+
+ { FR_CONF_POINTER("limit", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) limit_config },
+
+ CONF_PARSER_TERMINATOR
+};
+
+static fr_dict_t const *dict_dns;
+
+extern fr_dict_autoload_t proto_dns_dict[];
+fr_dict_autoload_t proto_dns_dict[] = {
+ { .out = &dict_dns, .proto = "dns" },
+ { NULL }
+};
+
+static fr_dict_attr_t const *attr_packet_type;
+
+extern fr_dict_attr_autoload_t proto_dns_dict_attr[];
+fr_dict_attr_autoload_t proto_dns_dict_attr[] = {
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dns},
+ { NULL }
+};
+
+/** Wrapper around dl_instance which translates the packet-type into a submodule name
+ *
+ * @param[in] ctx to allocate data in (instance of proto_dns).
+ * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
+ * @param[in] parent Base structure address.
+ * @param[in] ci #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
+ CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+ proto_dns_t *inst = talloc_get_type_abort(parent, proto_dns_t);
+ fr_dict_enum_value_t *dv;
+ CONF_PAIR *cp;
+ char const *value;
+
+ cp = cf_item_to_pair(ci);
+ value = cf_pair_value(cp);
+
+ dv = fr_dict_enum_by_name(attr_packet_type, value, -1);
+ if (!dv || (dv->value->vb_uint32 >= FR_DNS_CODE_MAX)) {
+ cf_log_err(ci, "Unknown DNS packet type '%s'", value);
+ return -1;
+ }
+
+ inst->allowed[dv->value->vb_uint32] = true;
+ *((char const **) out) = value;
+
+ return 0;
+}
+
+/** Wrapper around dl_instance
+ *
+ * @param[in] ctx to allocate data in (instance of proto_dns).
+ * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
+ * @param[in] parent Base structure address.
+ * @param[in] ci #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int transport_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
+ CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+ char const *name = cf_pair_value(cf_item_to_pair(ci));
+ dl_module_inst_t *parent_inst;
+ proto_dns_t *inst;
+ CONF_SECTION *listen_cs = cf_item_to_section(cf_parent(ci));
+ CONF_SECTION *transport_cs;
+
+ transport_cs = cf_section_find(listen_cs, name, NULL);
+
+ /*
+ * Allocate an empty section if one doesn't exist
+ * this is so defaults get parsed.
+ */
+ if (!transport_cs) transport_cs = cf_section_alloc(listen_cs, listen_cs, name, NULL);
+
+ parent_inst = cf_data_value(cf_data_find(listen_cs, dl_module_inst_t, "proto_dns"));
+ fr_assert(parent_inst);
+
+ /*
+ * Set the allowed codes so that we can compile them as
+ * necessary.
+ */
+ inst = talloc_get_type_abort(parent_inst->data, proto_dns_t);
+ inst->io.transport = name;
+
+ return dl_module_instance(ctx, out, transport_cs, parent_inst, name, DL_MODULE_TYPE_SUBMODULE);
+}
+
+/** Decode the packet
+ *
+ */
+static int mod_decode(void const *instance, request_t *request, uint8_t *const data, size_t data_len)
+{
+ proto_dns_t const *inst = talloc_get_type_abort_const(instance, proto_dns_t);
+ fr_io_track_t const *track = talloc_get_type_abort_const(request->async->packet_ctx, fr_io_track_t);
+ fr_io_address_t const *address = track->address;
+ RADCLIENT const *client;
+ fr_dns_packet_t const *packet = (fr_dns_packet_t const *) data;
+ fr_dcursor_t cursor;
+ fr_dns_ctx_t packet_ctx;
+
+ /*
+ * Set the request dictionary so that we can do
+ * generic->protocol attribute conversions as
+ * the request runs through the server.
+ */
+ request->dict = dict_dns;
+
+ RHEXDUMP3(data, data_len, "proto_dns decode packet");
+
+ client = address->radclient;
+
+ /*
+ * @todo -
+ */
+ request->packet->code = packet->opcode;
+ request->packet->id = (data[0] << 8) | data[1];
+ request->reply->id = request->packet->id;
+
+ request->packet->data = talloc_memdup(request->packet, data, data_len);
+ request->packet->data_len = data_len;
+
+ packet_ctx.tmp_ctx = talloc(request, uint8_t);
+ packet_ctx.packet = request->packet->data;
+ packet_ctx.packet_len = data_len;
+
+ packet_ctx.lb = fr_dns_labels_init(packet_ctx.tmp_ctx, packet_ctx.packet, 256);
+ fr_assert(packet_ctx.lb != NULL);
+
+
+ /*
+ * Note that we don't set a limit on max_attributes here.
+ * That MUST be set and checked in the underlying
+ * transport, via a call to fr_dns_ok().
+ */
+ fr_dcursor_init(&cursor, &request->request_pairs);
+ if (fr_dns_decode(request->request_ctx, request->packet->data, request->packet->data_len, &cursor, &packet_ctx) < 0) {
+ talloc_free(packet_ctx.tmp_ctx);
+ RPEDEBUG("Failed decoding packet");
+ return -1;
+ }
+ talloc_free(packet_ctx.tmp_ctx);
+
+ /*
+ * Set the rest of the fields.
+ */
+ request->client = UNCONST(RADCLIENT *, client);
+
+ request->packet->socket = address->socket;
+ fr_socket_addr_swap(&request->reply->socket, &address->socket);
+
+ REQUEST_VERIFY(request);
+
+ if (!inst->io.app_io->decode) return 0;
+
+ /*
+ * Let the app_io do anything it needs to do.
+ */
+ return inst->io.app_io->decode(inst->io.app_io_instance, request, data, data_len);
+}
+
+static ssize_t mod_encode(void const *instance, request_t *request, uint8_t *buffer, size_t buffer_len)
+{
+ proto_dns_t const *inst = talloc_get_type_abort_const(instance, proto_dns_t);
+ fr_io_track_t *track = talloc_get_type_abort(request->async->packet_ctx, fr_io_track_t);
+ fr_io_address_t const *address = track->address;
+ fr_dns_packet_t *reply = (fr_dns_packet_t *) buffer;
+ fr_dns_packet_t *original = (fr_dns_packet_t *) request->packet->data;
+ ssize_t data_len;
+ RADCLIENT const *client;
+ fr_dns_ctx_t packet_ctx;
+
+ /*
+ * Process layer NAK, never respond, or "Do not respond".
+ */
+ if ((buffer_len == 1) ||
+ (request->reply->code == FR_DNS_DO_NOT_RESPOND) ||
+ (request->reply->code >= FR_DNS_CODE_MAX)) {
+// track->do_not_respond = true;
+ return 1;
+ }
+
+ client = address->radclient;
+ fr_assert(client);
+
+ if (buffer_len < DNS_HDR_LEN) {
+ REDEBUG("Output buffer is too small to hold a DNS packet.");
+ return -1;
+ }
+
+ /*
+ * If the app_io encodes the packet, then we don't need
+ * to do that.
+ */
+ if (inst->io.app_io->encode) {
+ data_len = inst->io.app_io->encode(inst->io.app_io_instance, request, buffer, buffer_len);
+ if (data_len > 0) return data_len;
+ }
+
+ packet_ctx.tmp_ctx = talloc(request, uint8_t);
+ packet_ctx.packet = buffer;
+ packet_ctx.packet_len = buffer_len;
+
+ packet_ctx.lb = fr_dns_labels_init(packet_ctx.tmp_ctx, buffer, 256);
+ fr_assert(packet_ctx.lb != NULL);
+
+ data_len = fr_dns_encode(&FR_DBUFF_TMP(buffer, buffer_len), &request->reply_pairs, &packet_ctx);
+ talloc_free(packet_ctx.tmp_ctx);
+ if (data_len < 0) {
+ RPEDEBUG("Failed encoding DHCPv6 reply");
+ return -1;
+ }
+
+ reply->id = original->id;
+
+ RHEXDUMP3(buffer, data_len, "proto_dns encode packet");
+
+ request->reply->data_len = data_len;
+ return data_len;
+}
+
+static int mod_priority_set(void const *instance, uint8_t const *buffer, size_t buflen)
+{
+ int opcode;
+ fr_dns_packet_t const *packet = (fr_dns_packet_t const *) buffer;
+ proto_dns_t const *inst = talloc_get_type_abort(instance, proto_dns_t);
+
+ fr_assert(buflen >= DNS_HDR_LEN);
+
+ opcode = packet->opcode;
+
+ /*
+ * Disallowed packet
+ */
+ if (!inst->priorities[opcode]) return 0;
+
+ if (!inst->allowed[opcode]) return -1;
+
+ /*
+ * @todo - if we cared, we could also return -1 for "this
+ * is a bad packet". But that's really only for
+ * mod_inject, as we assume that app_io->read() always
+ * returns good packets.
+ */
+
+ /*
+ * Return the configured priority.
+ */
+ return inst->priorities[opcode];
+
+}
+
+/** Open listen sockets/connect to external event source
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] sc to add our file descriptor to.
+ * @param[in] conf Listen section parsed to give us isntance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_open(void *instance, fr_schedule_t *sc, UNUSED CONF_SECTION *conf)
+{
+ proto_dns_t *inst = talloc_get_type_abort(instance, proto_dns_t);
+
+ inst->io.app = &proto_dns;
+ inst->io.app_instance = instance;
+
+ return fr_master_io_listen(inst, &inst->io, sc,
+ inst->max_packet_size, inst->num_messages);
+}
+
+/** Instantiate the application
+ *
+ * Instantiate I/O and type submodules.
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] conf Listen section parsed to give us instance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_instantiate(void *instance, CONF_SECTION *conf)
+{
+ proto_dns_t *inst = talloc_get_type_abort(instance, proto_dns_t);
+
+ /*
+ * No IO module, it's an empty listener.
+ */
+ if (!inst->io.submodule) return 0;
+
+ /*
+ * These configuration items are not printed by default,
+ * because normal people shouldn't be touching them.
+ */
+ if (!inst->max_packet_size && inst->io.app_io) inst->max_packet_size = inst->io.app_io->default_message_size;
+
+ if (!inst->num_messages) inst->num_messages = 256;
+
+ FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, >=, 32);
+ FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, <=, 65535);
+
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
+
+ /*
+ * Instantiate the master io submodule
+ */
+ return fr_master_app_io.instantiate(&inst->io, conf);
+}
+
+/** Bootstrap the application
+ *
+ * Bootstrap I/O and type submodules.
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] conf Listen section parsed to give us instance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
+{
+ proto_dns_t *inst = talloc_get_type_abort(instance, proto_dns_t);
+
+ /*
+ * Ensure that the server CONF_SECTION is always set.
+ */
+ inst->io.server_cs = cf_item_to_section(cf_parent(conf));
+
+ fr_assert(dict_dns != NULL);
+ fr_assert(attr_packet_type != NULL);
+
+ /*
+ * No IO module, it's an empty listener.
+ */
+ if (!inst->io.submodule) return 0;
+
+ /*
+ * These timers are usually protocol specific.
+ */
+ FR_TIME_DELTA_BOUND_CHECK("idle_timeout", inst->io.idle_timeout, >=, fr_time_delta_from_sec(1));
+ FR_TIME_DELTA_BOUND_CHECK("idle_timeout", inst->io.idle_timeout, <=, fr_time_delta_from_sec(600));
+
+ /*
+ * Tell the master handler about the main protocol instance.
+ */
+ inst->io.app = &proto_dns;
+ inst->io.app_instance = inst;
+
+ /*
+ * We will need this for dynamic clients and connected sockets.
+ */
+ inst->io.dl_inst = dl_module_instance_by_data(inst);
+ fr_assert(inst != NULL);
+
+ /*
+ * Bootstrap the master IO handler.
+ */
+ return fr_master_app_io.bootstrap(&inst->io, conf);
+}
+
+static int mod_load(void)
+{
+ if (fr_dns_global_init() < 0) {
+ PERROR("Failed initialising protocol library");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void mod_unload(void)
+{
+ fr_dns_global_free();
+}
+
+fr_app_t proto_dns = {
+ .magic = RLM_MODULE_INIT,
+ .name = "dns",
+ .config = proto_dns_config,
+ .inst_size = sizeof(proto_dns_t),
+ .dict = &dict_dns,
+
+ .onload = mod_load,
+ .unload = mod_unload,
+
+ .bootstrap = mod_bootstrap,
+ .instantiate = mod_instantiate,
+ .open = mod_open,
+ .decode = mod_decode,
+ .encode = mod_encode,
+ .priority = mod_priority_set
+};
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * $Id$
+ *
+ * @file proto_dns.h
+ * @brief Structures for the DNS protocol
+ *
+ * @copyright 2020 Network RADIUS SARL (legal@networkradius.com)
+ */
+#include <freeradius-devel/io/master.h>
+#include <freeradius-devel/dns/dns.h>
+
+/** An instance of a proto_dns listen section
+ *
+ */
+typedef struct {
+ fr_io_instance_t io; //!< wrapper for IO abstraction
+
+ char **allowed_types; //!< names for for 'type = ...'
+ bool allowed[FR_DNS_CODE_MAX]; //!< indexed by value
+
+ uint32_t max_packet_size; //!< for message ring buffer.
+ uint32_t num_messages; //!< for message ring buffer.
+
+ uint32_t priorities[FR_DNS_CODE_MAX]; //!< priorities for individual packets
+} proto_dns_t;
--- /dev/null
+TARGETNAME := proto_dns
+
+ifneq "$(TARGETNAME)" ""
+TARGET := $(TARGETNAME).a
+endif
+
+SOURCES := proto_dns.c
+
+TGT_PREREQS := libfreeradius-dns.a libfreeradius-io.a
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file proto_dns_udp.c
+ * @brief DHCPv6 handler for UDP.
+ *
+ * @copyright 2020 Network RADIUS SARL (legal@networkradius.com)
+ */
+#define LOG_PREFIX "proto_dns_udp - "
+
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/server/protocol.h>
+#include <freeradius-devel/util/udp.h>
+#include <freeradius-devel/util/trie.h>
+#include <freeradius-devel/io/base.h>
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/io/listen.h>
+#include <freeradius-devel/io/schedule.h>
+#include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/protocol/dns/freeradius.internal.h>
+#include "proto_dns.h"
+
+extern fr_app_io_t proto_dns_udp;
+
+typedef struct {
+ char const *name; //!< socket name
+ int sockfd;
+
+ fr_io_address_t *connection; //!< for connected sockets.
+
+ fr_stats_t stats; //!< statistics for this socket
+} proto_dns_udp_thread_t;
+
+typedef struct {
+ CONF_SECTION *cs; //!< our configuration
+
+ fr_ipaddr_t ipaddr; //!< IP address to listen on.
+
+ char const *interface; //!< Interface to bind to.
+
+ uint32_t recv_buff; //!< How big the kernel's receive buffer should be.
+
+ uint32_t max_packet_size; //!< for message ring buffer.
+ uint32_t max_attributes; //!< Limit maximum decodable attributes.
+
+ uint16_t port; //!< Port to listen on.
+
+ bool recv_buff_is_set; //!< Whether we were provided with a receive
+ //!< buffer value.
+
+ RADCLIENT_LIST *clients; //!< local clients
+ RADCLIENT *default_client; //!< default 0/0 client
+
+ fr_trie_t *trie; //!< for parsed networks
+ fr_ipaddr_t *allow; //!< allowed networks for dynamic clients
+ fr_ipaddr_t *deny; //!< denied networks for dynamic clients
+} proto_dns_udp_t;
+
+
+static const CONF_PARSER networks_config[] = {
+ { FR_CONF_OFFSET("allow", FR_TYPE_COMBO_IP_PREFIX | FR_TYPE_MULTI, proto_dns_udp_t, allow) },
+ { FR_CONF_OFFSET("deny", FR_TYPE_COMBO_IP_PREFIX | FR_TYPE_MULTI, proto_dns_udp_t, deny) },
+
+ CONF_PARSER_TERMINATOR
+};
+
+
+static const CONF_PARSER udp_listen_config[] = {
+ { FR_CONF_OFFSET("ipaddr", FR_TYPE_COMBO_IP_ADDR, proto_dns_udp_t, ipaddr) },
+ { FR_CONF_OFFSET("ipv6addr", FR_TYPE_IPV6_ADDR, proto_dns_udp_t, ipaddr) },
+ { FR_CONF_OFFSET("ipv4addr", FR_TYPE_IPV4_ADDR, proto_dns_udp_t, ipaddr) },
+
+ { FR_CONF_OFFSET("interface", FR_TYPE_STRING, proto_dns_udp_t, interface) },
+
+ { FR_CONF_OFFSET("port", FR_TYPE_UINT16, proto_dns_udp_t, port), .dflt = "547" },
+ { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, proto_dns_udp_t, recv_buff) },
+
+ { FR_CONF_POINTER("networks", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) networks_config },
+
+ { FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_dns_udp_t, max_packet_size), .dflt = "576" } ,
+ { FR_CONF_OFFSET("max_attributes", FR_TYPE_UINT32, proto_dns_udp_t, max_attributes), .dflt = STRINGIFY(DHCPV4_MAX_ATTRIBUTES) } ,
+
+ CONF_PARSER_TERMINATOR
+};
+
+static fr_dict_t const *dict_dns;
+
+extern fr_dict_autoload_t proto_dns_udp_dict[];
+fr_dict_autoload_t proto_dns_udp_dict[] = {
+ { .out = &dict_dns, .proto = "dns" },
+ { NULL }
+};
+
+static fr_dict_attr_t const *attr_packet_type;
+
+extern fr_dict_attr_autoload_t proto_dns_udp_dict_attr[];
+fr_dict_attr_autoload_t proto_dns_udp_dict_attr[] = {
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dns},
+
+ { NULL }
+};
+
+static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t *recv_time_p, uint8_t *buffer, size_t buffer_len,
+ size_t *leftover, UNUSED uint32_t *priority, UNUSED bool *is_dup)
+{
+// proto_dns_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dns_udp_t);
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+ fr_io_address_t *address, **address_p;
+
+ int flags;
+ ssize_t data_size;
+ size_t packet_len;
+ uint32_t xid;
+ fr_dns_packet_t *packet;
+
+ *leftover = 0; /* always for UDP */
+
+ /*
+ * Where the addresses should go. This is a special case
+ * for proto_dns.
+ */
+ address_p = (fr_io_address_t **)packet_ctx;
+ address = *address_p;
+
+ /*
+ * Tell udp_recv if we're connected or not.
+ */
+ flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL);
+
+ data_size = udp_recv(thread->sockfd, flags, &address->socket, buffer, buffer_len, recv_time_p);
+ if (data_size < 0) {
+ RATE_LIMIT_GLOBAL(PERROR, "Read error (%zd)", data_size);
+ return data_size;
+ }
+
+ if ((size_t) data_size < DNS_HDR_LEN) {
+ RATE_LIMIT_GLOBAL(WARN, "Insufficient data - ignoring");
+ return 0;
+ }
+
+ packet_len = data_size;
+
+ /*
+ * We've seen a server reply to this port, but the giaddr
+ * is *not* our address. Drop it.
+ */
+ packet = (fr_dns_packet_t *) buffer;
+
+ if (!fr_dns_packet_ok(buffer, packet_len, true)) {
+ RATE_LIMIT_GLOBAL(WARN, "Invalid DNS packet - ignoring");
+ return 0;
+ }
+
+ /*
+ * check packet code
+ */
+
+ /*
+ * proto_dns sets the priority
+ */
+
+ xid = fr_net_to_uint16(buffer);
+
+ /*
+ * Print out what we received.
+ */
+ DEBUG2("Received %s ID %04x length %d %s", fr_dns_packet_codes[packet->opcode], xid,
+ (int) packet_len, thread->name);
+
+ return packet_len;
+}
+
+static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t request_time,
+ uint8_t *buffer, size_t buffer_len, UNUSED size_t written)
+{
+// proto_dns_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dns_udp_t);
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+
+ fr_io_track_t *track = talloc_get_type_abort(packet_ctx, fr_io_track_t);
+ fr_socket_t socket;
+
+ int flags;
+ ssize_t data_size;
+
+ /*
+ * @todo - share a stats interface with the parent? or
+ * put the stats in the listener, so that proto_dns
+ * can update them, too.. <sigh>
+ */
+ thread->stats.total_responses++;
+
+ flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL);
+
+ /*
+ * Send packets to the originator.
+ */
+ fr_socket_addr_swap(&socket, &track->address->socket);
+
+ /*
+ * Figure out which kind of packet we're sending.
+ */
+ if (!thread->connection) {
+ // @todo - figure out where to send the packet
+ }
+
+ /*
+ * proto_dns takes care of suppressing do-not-respond, etc.
+ */
+ data_size = udp_send(&socket, flags, buffer, buffer_len);
+
+ /*
+ * This socket is dead. That's an error...
+ */
+ if (data_size <= 0) return data_size;
+
+ return data_size;
+}
+
+
+static int mod_connection_set(fr_listen_t *li, fr_io_address_t *connection)
+{
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+
+ thread->connection = connection;
+ return 0;
+}
+
+
+static void mod_network_get(void *instance, int *ipproto, bool *dynamic_clients, fr_trie_t const **trie)
+{
+ proto_dns_udp_t *inst = talloc_get_type_abort(instance, proto_dns_udp_t);
+
+ *ipproto = IPPROTO_UDP;
+ *dynamic_clients = false;
+ *trie = inst->trie;
+}
+
+
+/** Open a UDP listener for DHCPv6
+ *
+ */
+static int mod_open(fr_listen_t *li)
+{
+ proto_dns_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dns_udp_t);
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+
+ int sockfd, rcode;
+ uint16_t port = inst->port;
+
+ li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, "domain", true);
+ if (sockfd < 0) {
+ PERROR("Failed opening UDP socket");
+ error:
+ return -1;
+ }
+
+ li->app_io_addr = fr_socket_addr_alloc_inet_src(li, IPPROTO_UDP, 0, &inst->ipaddr, port);
+
+ /*
+ * Set SO_REUSEPORT before bind, so that all packets can
+ * listen on the same destination IP address.
+ */
+ if (1) {
+ int on = 1;
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
+ ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno));
+ close(sockfd);
+ return -1;
+ }
+ }
+
+ /*
+ * SUID up is really only needed if interface is set, OR port <1024.
+ */
+ rad_suid_up();
+ rcode = fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface);
+ rad_suid_down();
+ if (rcode < 0) {
+ PERROR("Failed binding socket");
+ close(sockfd);
+ goto error;
+ }
+
+ thread->sockfd = sockfd;
+
+ fr_assert((cf_parent(inst->cs) != NULL) && (cf_parent(cf_parent(inst->cs)) != NULL)); /* listen { ... } */
+
+ thread->name = fr_app_io_socket_name(thread, &proto_dns_udp,
+ NULL, 0,
+ &inst->ipaddr, inst->port,
+ inst->interface);
+ return 0;
+}
+
+
+/** Set the file descriptor for this socket.
+ *
+ */
+static int mod_fd_set(fr_listen_t *li, int fd)
+{
+ proto_dns_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dns_udp_t);
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+
+ thread->sockfd = fd;
+
+ thread->name = fr_app_io_socket_name(thread, &proto_dns_udp,
+ &thread->connection->socket.inet.src_ipaddr, thread->connection->socket.inet.src_port,
+ &inst->ipaddr, inst->port,
+ inst->interface);
+
+ return 0;
+}
+
+
+static char const *mod_name(fr_listen_t *li)
+{
+ proto_dns_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dns_udp_thread_t);
+
+ return thread->name;
+}
+
+
+static int mod_bootstrap(void *instance, CONF_SECTION *cs)
+{
+ proto_dns_udp_t *inst = talloc_get_type_abort(instance, proto_dns_udp_t);
+ size_t num;
+ CONF_ITEM *ci;
+ CONF_SECTION *server_cs;
+ RADCLIENT *client;
+
+ inst->cs = cs;
+
+ /*
+ * Complain if no "ipaddr" is set.
+ */
+ if (inst->ipaddr.af == AF_UNSPEC) {
+ if (!inst->interface) {
+ cf_log_err(cs, "No 'ipaddr' was specified in the 'udp' section");
+ return -1;
+ }
+
+ /*
+ * If there's a named interface, maybe we can
+ * find a link-local address for it. If so, just
+ * use that.
+ */
+ if (inst->interface &&
+ (fr_interface_to_ipaddr(inst->interface, &inst->ipaddr, AF_INET, true) < 0)) {
+ cf_log_err(cs, "No 'ipaddr' specified, and we cannot determine one for interface '%s'",
+ inst->interface);
+ return -1;
+ }
+ }
+
+ if (inst->recv_buff_is_set) {
+ FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, 32);
+ FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, INT_MAX);
+ }
+
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65536);
+
+ /*
+ * Parse and create the trie for dynamic clients, even if
+ * there's no dynamic clients.
+ */
+ num = talloc_array_length(inst->allow);
+ if (num) {
+ inst->trie = fr_master_io_network(inst, inst->ipaddr.af, inst->allow, inst->deny);
+ if (!inst->trie) {
+ cf_log_perr(cs, "Failed creating list of networks");
+ return -1;
+ }
+ }
+
+ ci = cf_parent(inst->cs); /* listen { ... } */
+ fr_assert(ci != NULL);
+ ci = cf_parent(ci);
+ fr_assert(ci != NULL);
+
+ server_cs = cf_item_to_section(ci);
+
+ /*
+ * Look up local clients, if they exist.
+ *
+ * @todo - ensure that we only parse clients which are
+ * for IPPROTO_UDP, and don't require a "secret".
+ */
+ if (cf_section_find_next(server_cs, NULL, "client", CF_IDENT_ANY)) {
+ inst->clients = client_list_parse_section(server_cs, IPPROTO_UDP, false);
+ if (!inst->clients) {
+ cf_log_err(cs, "Failed creating local clients");
+ return -1;
+ }
+ }
+
+ /*
+ * Create a fake client.
+ */
+ client = inst->default_client = talloc_zero(inst, RADCLIENT);
+ if (!inst->default_client) return 0;
+
+ client->ipaddr = (fr_ipaddr_t ) {
+ .af = AF_INET6,
+ };
+
+ client->src_ipaddr = client->ipaddr;
+
+ client->longname = client->shortname = client->secret = talloc_strdup(client, "default");
+ client->nas_type = talloc_strdup(client, "other");
+
+ return 0;
+}
+
+static RADCLIENT *mod_client_find(fr_listen_t *li, fr_ipaddr_t const *ipaddr, int ipproto)
+{
+ proto_dns_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dns_udp_t);
+
+ /*
+ * Prefer local clients.
+ */
+ if (inst->clients) {
+ RADCLIENT *client;
+
+ client = client_find(inst->clients, ipaddr, ipproto);
+ if (client) return client;
+ }
+
+ return inst->default_client;
+}
+
+fr_app_io_t proto_dns_udp = {
+ .magic = RLM_MODULE_INIT,
+ .name = "dns_udp",
+ .config = udp_listen_config,
+ .inst_size = sizeof(proto_dns_udp_t),
+ .thread_inst_size = sizeof(proto_dns_udp_thread_t),
+ .bootstrap = mod_bootstrap,
+
+ .default_message_size = 576,
+ .track_duplicates = false,
+
+ .open = mod_open,
+ .read = mod_read,
+ .write = mod_write,
+ .fd_set = mod_fd_set,
+ .connection_set = mod_connection_set,
+ .network_get = mod_network_get,
+ .client_find = mod_client_find,
+ .get_name = mod_name,
+};
--- /dev/null
+TARGETNAME := proto_dns_udp
+
+ifneq "$(TARGETNAME)" ""
+TARGET := $(TARGETNAME).a
+endif
+
+SOURCES := proto_dns_udp.c
+
+TGT_PREREQS := libfreeradius-dns.a
/* String types require decoding of dns format labels */
used = fr_dns_label_to_value_box(ur->out_ctx, vb, (uint8_t const *)packet, packet_len,
- (uint8_t const *)fr_dbuff_current(&dbuff), true);
+ (uint8_t const *)fr_dbuff_current(&dbuff), true, NULL);
if (used < 0) goto error;
fr_dbuff_advance(&dbuff, (size_t)used);
break;
--- /dev/null
+TARGETNAME := process_dns
+
+TARGET := $(TARGETNAME).a
+
+SOURCES := base.c
+TGT_PREREQS := libfreeradius-dns.a
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/process/dns/base.c
+ * @brief DNS processing.
+ *
+ * @copyright 2020 Network RADIUS SARL (legal@networkradius.com)
+ */
+#include <freeradius-devel/server/protocol.h>
+#include <freeradius-devel/unlang/base.h>
+#include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/dns/dns.h>
+#include <freeradius-devel/protocol/dns/rfc1034.h>
+
+static fr_dict_t const *dict_dns;
+
+extern fr_dict_autoload_t process_dns_dict[];
+fr_dict_autoload_t process_dns_dict[] = {
+ { .out = &dict_dns, .proto = "dns" },
+ { NULL }
+};
+
+static fr_dict_attr_t const *attr_packet_type;
+
+extern fr_dict_attr_autoload_t process_dns_dict_attr[];
+fr_dict_attr_autoload_t process_dns_dict_attr[] = {
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dns},
+ { NULL }
+};
+
+typedef struct {
+ uint64_t nothing; // so that the next field isn't at offset 0
+
+ CONF_SECTION *request;
+ CONF_SECTION *reply;
+ CONF_SECTION *recv_reply;
+ CONF_SECTION *reverse_request;
+ CONF_SECTION *reverse_reply;
+ CONF_SECTION *do_not_respond;
+} process_dns_sections_t;
+
+typedef struct {
+ bool test;
+
+ process_dns_sections_t sections;
+} process_dns_t;
+
+#define PROCESS_PACKET_TYPE fr_dns_packet_code_t
+#define PROCESS_CODE_MAX FR_DNS_CODE_MAX
+#define PROCESS_CODE_DO_NOT_RESPOND FR_DNS_DO_NOT_RESPOND
+#define PROCESS_PACKET_CODE_VALID FR_DNS_PACKET_CODE_VALID
+#define PROCESS_INST process_dns_t
+#include <freeradius-devel/server/process.h>
+
+static fr_process_state_t const process_state[] = {
+ [ FR_DNS_QUERY ] = {
+ .packet_type = {
+ [RLM_MODULE_NOOP] = FR_DNS_QUERY_RESPONSE,
+ [RLM_MODULE_OK] = FR_DNS_QUERY_RESPONSE,
+ [RLM_MODULE_UPDATED] = FR_DNS_QUERY_RESPONSE,
+
+ [RLM_MODULE_REJECT] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_FAIL] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_INVALID] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
+ },
+ .rcode = RLM_MODULE_NOOP,
+ .recv = recv_generic,
+ .resume = resume_recv_generic,
+ .section_offset = PROCESS_CONF_OFFSET(request),
+ },
+ [ FR_DNS_QUERY_RESPONSE ] = {
+ .packet_type = {
+ [RLM_MODULE_NOOP] = FR_DNS_QUERY_RESPONSE,
+ [RLM_MODULE_OK] = FR_DNS_QUERY_RESPONSE,
+ [RLM_MODULE_UPDATED] = FR_DNS_QUERY_RESPONSE,
+
+ [RLM_MODULE_REJECT] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_FAIL] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_INVALID] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
+ },
+ .rcode = RLM_MODULE_NOOP,
+ .send = send_generic,
+ .resume = resume_send_generic,
+ .section_offset = PROCESS_CONF_OFFSET(reply),
+ },
+
+
+ // @todo - recv reply, to look at other replies.
+
+ [ FR_DNS_DO_NOT_RESPOND ] = {
+ .packet_type = {
+ [RLM_MODULE_NOOP] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_OK] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_UPDATED] = FR_DNS_DO_NOT_RESPOND,
+
+ [RLM_MODULE_REJECT] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_FAIL] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_INVALID] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_DISALLOW] = FR_DNS_DO_NOT_RESPOND,
+ [RLM_MODULE_NOTFOUND] = FR_DNS_DO_NOT_RESPOND,
+ },
+ .rcode = RLM_MODULE_NOOP,
+ .send = send_generic,
+ .resume = resume_send_generic,
+ .section_offset = PROCESS_CONF_OFFSET(do_not_respond),
+ },
+};
+
+/*
+ * Debug the packet if requested.
+ */
+static void dns_packet_debug(request_t *request, fr_radius_packet_t const *packet, fr_pair_list_t const *list, bool received)
+{
+ if (!packet) return;
+ if (!RDEBUG_ENABLED) return;
+
+ log_request(L_DBG, L_DBG_LVL_1, request, __FILE__, __LINE__, "%s %s",
+ received ? "Received" : "Sending",
+ fr_dns_packet_codes[packet->code]);
+
+ if (received || request->parent) {
+ log_request_pair_list(L_DBG_LVL_1, request, NULL, list, NULL);
+ } else {
+ log_request_proto_pair_list(L_DBG_LVL_1, request, NULL, list, NULL);
+ }
+}
+
+static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+ fr_process_state_t const *state;
+
+ PROCESS_TRACE;
+
+ (void)talloc_get_type_abort_const(mctx->instance, process_dns_t);
+ fr_assert(PROCESS_PACKET_CODE_VALID(request->packet->code));
+
+ request->component = "dns";
+ request->module = NULL;
+ fr_assert(request->dict == dict_dns);
+
+ UPDATE_STATE(packet);
+
+ dns_packet_debug(request, request->packet, &request->request_pairs, true);
+
+ return state->recv(p_result, mctx, request);
+}
+
+
+static const virtual_server_compile_t compile_list[] = {
+ {
+ .name = "recv",
+ .name2 = "query",
+ .component = MOD_POST_AUTH,
+ .offset = PROCESS_CONF_OFFSET(request),
+ },
+ {
+ .name = "send",
+ .name2 = "query.response",
+ .component = MOD_POST_AUTH,
+ .offset = PROCESS_CONF_OFFSET(reply),
+ },
+ {
+ .name = "send",
+ .name2 = "Do-Not-Respond",
+ .component = MOD_POST_AUTH,
+ .offset = PROCESS_CONF_OFFSET(do_not_respond),
+ },
+
+ COMPILE_TERMINATOR
+};
+
+
+extern fr_process_module_t process_dns;
+fr_process_module_t process_dns = {
+ .magic = RLM_MODULE_INIT,
+ .name = "process_dns",
+ .inst_size = sizeof(process_dns_t),
+ .process = mod_process,
+ .compile_list = compile_list,
+ .dict = &dict_dns,
+};
*/
fr_pair_list_init(list);
fr_dcursor_init(&cursor, list);
- return fr_struct_from_network(ctx, &cursor, attr_arp_packet, packet, FR_ARP_PACKET_SIZE,
+ return fr_struct_from_network(ctx, &cursor, attr_arp_packet, packet, FR_ARP_PACKET_SIZE, false,
NULL, NULL, NULL);
}
break;
case FR_TYPE_STRUCT:
- slen = fr_struct_from_network(ctx, cursor, parent, data, data_len,
+ slen = fr_struct_from_network(ctx, cursor, parent, data, data_len, false,
decode_ctx, decode_value_trampoline, decode_tlv_trampoline);
if (slen < 0) return slen;
return data_len;
* types. It's just easier that way.
*/
if (!parent->flags.array) {
- slen = fr_dns_label_uncompressed_length(data, data_len, &next);
+ slen = fr_dns_label_uncompressed_length(data, data, data_len, &next, NULL);
if (slen <= 0) goto raw;
/*
* If any of the labels point outside of this
* area, OR they are otherwise invalid, then that's an error.
*/
- slen = fr_dns_labels_network_verify(data, data_len, data);
+ slen = fr_dns_labels_network_verify(data, data, data_len, data, NULL);
if (slen < 0) {
raw:
return decode_raw(ctx, cursor, dict, parent, data, data_len, decode_ctx);
* function should never fail unless there's a
* bug in the code.
*/
- slen = fr_dns_label_to_value_box(vp, &vp->data, data, labels_len, data + total, true);
+ slen = fr_dns_label_to_value_box(vp, &vp->data, data, labels_len, data + total, true, NULL);
if (slen <= 0) {
talloc_free(vp);
goto raw;
fr_dbuff_marker(&last_byte, &work_dbuff);
fr_dbuff_marker(&src, &work_dbuff);
- slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data);
+ slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL);
if (slen < 0) return slen;
/*
*
* https://tools.ietf.org/html/rfc8415#section-10
*/
- slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data);
+ slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, false, &vp->data, NULL);
if (slen <= 0) return PAIR_ENCODE_FATAL_ERROR;
vp = fr_dcursor_next(cursor);
--- /dev/null
+#
+# Makefile
+#
+# Version: $Id$
+#
+TARGET := libfreeradius-dns.a
+
+SOURCES := base.c decode.c encode.c
+
+SRC_CFLAGS := -I$(top_builddir)/src -DNO_ASSERT
+TGT_LDLIBS := $(PCAP_LIBS)
+TGT_LDFLAGS := $(PCAP_LDFLAGS)
+TGT_PREREQS := libfreeradius-util.a
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/dns/attrs.h
+ * @brief DHCP attributes
+ *
+ * @copyright 2018 The FreeRADIUS project
+ * @copyright 2018 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSIDH(dns_attrs_h, "$Id$")
+
+#include <freeradius-devel/util/dict.h>
+
+extern fr_dict_t const *dict_dns;
+
+extern fr_dict_attr_t const *attr_packet_type;
+extern fr_dict_attr_t const *attr_dns_packet;
+extern fr_dict_attr_t const *attr_dns_question;
+extern fr_dict_attr_t const *attr_dns_rr;
+extern fr_dict_attr_t const *attr_dns_ns;
+extern fr_dict_attr_t const *attr_dns_ar;
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/dns/base.c
+ * @brief Functions to send/receive dns packets.
+ *
+ * @copyright 2008 The FreeRADIUS server project
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/util/base.h>
+
+#include "dns.h"
+#include "attrs.h"
+
+static uint32_t instance_count = 0;
+
+typedef struct {
+ uint16_t code;
+ uint16_t length;
+} dns_option_t;
+
+fr_dict_t const *dict_dns;
+
+extern fr_dict_autoload_t dns_dict[];
+fr_dict_autoload_t dns_dict[] = {
+ { .out = &dict_dns, .proto = "dns" },
+ { NULL }
+};
+
+//fr_dict_attr_t const *attr_dns_packet_type;
+fr_dict_attr_t const *attr_dns_packet;
+fr_dict_attr_t const *attr_dns_question;
+fr_dict_attr_t const *attr_dns_rr;
+fr_dict_attr_t const *attr_dns_ns;
+fr_dict_attr_t const *attr_dns_ar;
+
+extern fr_dict_attr_autoload_t dns_dict_attr[];
+fr_dict_attr_autoload_t dns_dict_attr[] = {
+// { .out = &attr_dns_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT16, .dict = &dict_dns },
+ { .out = &attr_dns_packet, .name = "packet", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
+ { .out = &attr_dns_question, .name = "question", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
+ { .out = &attr_dns_rr, .name = "rr", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
+ { .out = &attr_dns_ns, .name = "ns", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
+ { .out = &attr_dns_ar, .name = "ar", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
+ { NULL }
+};
+
+ char const *fr_dns_packet_codes[FR_DNS_CODE_MAX] = {
+ [FR_DNS_QUERY] = "query",
+ [FR_DNS_IQUERY] = "iquery",
+ [FR_DNS_STATUS] = "status",
+ [FR_DNS_UPDATE] = "update",
+ [FR_DNS_STATEFUL_OP] = "stateful-operations",
+};
+
+bool fr_dns_packet_ok(uint8_t const *packet, size_t packet_len, bool query)
+{
+ if (packet_len <= DNS_HDR_LEN) {
+ return false;
+ }
+
+ /*
+ * query=0, response=1
+ */
+ if (((packet[2] & 0x80) == 0) != query) {
+ return false;
+ }
+
+ if (query) {
+ /*
+ * There should be at least one query, and no replies in the query!
+ */
+ if (fr_net_to_uint16(packet + 4) == 0) {
+ return false;
+ }
+ if (fr_net_to_uint16(packet + 6) != 0) {
+ return false;
+ }
+ if (fr_net_to_uint16(packet + 8) != 0) {
+ return false;
+ }
+
+ // additional records can exist!
+ } else {
+ /*
+ * Replies _usually_ copy the query. But not
+ * always And replies can have zero or more answers.
+ */
+ }
+
+ return true;
+}
+
+
+/** Return the on-the-wire length of an attribute value
+ *
+ * @param[in] vp to return the length of.
+ * @return the length of the attribute.
+ */
+size_t fr_dns_value_len(fr_pair_t const *vp)
+{
+ switch (vp->vp_type) {
+ case FR_TYPE_VARIABLE_SIZE:
+#ifndef NDEBUG
+ if (!vp->da->flags.extra && (vp->da->flags.subtype == FLAG_ENCODE_DNS_LABEL)) {
+ fr_assert_fail("DNS labels MUST be encoded/decoded with their own function, and not with generic 'string' functions");
+ return 0;
+ }
+#endif
+
+ if (vp->da->flags.length) return vp->da->flags.length; /* Variable type with fixed length */
+
+ /*
+ * Arrays get maxed at 2^16-1
+ */
+ if (vp->da->flags.array && ((vp->vp_type == FR_TYPE_STRING) || (vp->vp_type == FR_TYPE_OCTETS))) {
+ if (vp->vp_length > 65535) return 65535;
+ }
+
+ return vp->vp_length;
+
+ case FR_TYPE_STRUCTURAL:
+ fr_assert_fail(NULL);
+ return 0;
+
+ default:
+ return fr_value_box_network_length(&vp->data);
+ }
+}
+
+/** Resolve/cache attributes in the DNS dictionary
+ *
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+int fr_dns_global_init(void)
+{
+ if (instance_count > 0) {
+ instance_count++;
+ return 0;
+ }
+
+ if (fr_dict_autoload(dns_dict) < 0) return -1;
+ if (fr_dict_attr_autoload(dns_dict_attr) < 0) {
+ fr_dict_autofree(dns_dict);
+ return -1;
+ }
+
+ instance_count++;
+
+ return 0;
+}
+
+void fr_dns_global_free(void)
+{
+ if (--instance_count > 0) return;
+
+ fr_dict_autofree(dns_dict);
+}
+
+static fr_table_num_ordered_t const subtype_table[] = {
+ { L("dns_label"), FLAG_ENCODE_DNS_LABEL },
+};
+
+
+static bool attr_valid(UNUSED fr_dict_t *dict, UNUSED fr_dict_attr_t const *parent,
+ UNUSED char const *name, UNUSED int attr, fr_type_t type, fr_dict_attr_flags_t *flags)
+{
+ /*
+ * "arrays" of string/octets are encoded as a 16-bit
+ * length, followed by the actual data.
+ */
+ if (flags->array && ((type == FR_TYPE_STRING) || (type == FR_TYPE_OCTETS))) {
+ flags->is_known_width = true;
+ }
+
+ /*
+ * "extra" signifies that subtype is being used by the
+ * dictionaries itself.
+ */
+ if (flags->extra || !flags->subtype) return true;
+
+ if (type != FR_TYPE_STRING) {
+ fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'");
+ return false;
+ }
+
+ flags->is_known_width = true;
+
+ return true;
+}
+
+extern fr_dict_protocol_t libfreeradius_dns_dict_protocol;
+fr_dict_protocol_t libfreeradius_dns_dict_protocol = {
+ .name = "dns",
+ .default_type_size = 2,
+ .default_type_length = 2,
+ .subtype_table = subtype_table,
+ .subtype_table_len = NUM_ELEMENTS(subtype_table),
+ .attr_valid = attr_valid,
+};
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/dns/decode.c
+ * @brief Functions to decode DNS packets.
+ *
+ * @author Alan DeKok (aland@freeradius.org)
+ *
+ * @copyright 2021 The FreeRADIUS server project
+ * @copyright 2021 NetworkRADIUS SARL (legal@networkradius.com)
+ */
+#include <stdint.h>
+#include <stddef.h>
+
+#include <freeradius-devel/io/test_point.h>
+#include <freeradius-devel/util/dns.h>
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/util/struct.h>
+#include <freeradius-devel/util/talloc.h>
+#include <freeradius-devel/util/types.h>
+
+#include "dns.h"
+#include "attrs.h"
+
+static ssize_t decode_raw(TALLOC_CTX *ctx, fr_dcursor_t *cursor, UNUSED fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dict_attr_t *unknown;
+ fr_dict_attr_t const *da;
+ fr_dns_ctx_t *packet_ctx = decode_ctx;
+ ssize_t slen;
+
+#ifdef __clang_analyzer__
+ if (!packet_ctx || !packet_ctx->tmp_ctx || !parent->parent) return PAIR_DECODE_FATAL_ERROR;
+#endif
+
+ FR_PROTO_HEX_DUMP(data, data_len, "decode_raw");
+
+ /*
+ * Re-write the attribute to be "raw". It is
+ * therefore of type "octets", and will be
+ * handled below.
+ */
+ unknown = fr_dict_unknown_attr_afrom_da(packet_ctx->tmp_ctx, parent);
+ if (!unknown) {
+ fr_strerror_printf("%s: Internal sanity check %d", __FUNCTION__, __LINE__);
+ return PAIR_DECODE_OOM;
+ }
+ unknown->flags.is_raw = 1;
+
+ vp = fr_pair_afrom_da(ctx, unknown);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ slen = fr_value_box_from_network(vp, &vp->data, vp->da->type, vp->da,
+ &FR_DBUFF_TMP(data, data_len), data_len, true);
+ if (slen < 0) {
+ talloc_free(vp);
+ da = unknown;
+ fr_dict_unknown_free(&da);
+ return slen;
+ }
+
+ vp->type = VT_DATA;
+ vp->vp_tainted = true;
+ fr_dcursor_append(cursor, vp);
+ return data_len;
+}
+
+
+static ssize_t decode_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx);
+static ssize_t decode_array(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx);
+static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx);
+
+/** Handle arrays of DNS labels for fr_struct_from_network()
+ *
+ */
+static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx)
+{
+ if ((parent->type == FR_TYPE_STRING) && !parent->flags.extra && parent->flags.subtype) {
+ FR_PROTO_TRACE("decode DNS labels");
+ return decode_dns_labels(ctx, cursor, dict, parent, data, data_len, decode_ctx);
+ }
+
+ if (parent->flags.array) return decode_array(ctx, cursor, dict, parent, data, data_len, decode_ctx);
+
+ return decode_value(ctx, cursor, dict, parent, data, data_len, decode_ctx);
+}
+
+
+static ssize_t decode_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx)
+{
+ ssize_t slen;
+ fr_pair_t *vp;
+ uint8_t prefix_len;
+
+ FR_PROTO_HEX_DUMP(data, data_len, "decode_value");
+
+ switch (parent->type) {
+ /*
+ * Address MAY be shorter than 16 bytes.
+ */
+ case FR_TYPE_IPV6_PREFIX:
+ if ((data_len == 0) || (data_len > (1 + sizeof(vp->vp_ipv6addr)))) {
+ raw:
+ return decode_raw(ctx, cursor, dict, parent, data, data_len, decode_ctx);
+
+ };
+
+ /*
+ * Structs used fixed length fields
+ */
+ if (parent->parent->type == FR_TYPE_STRUCT) {
+ if (data_len != (1 + sizeof(vp->vp_ipv6addr))) goto raw;
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ vp->vp_ip.af = AF_INET6;
+ vp->vp_ip.scope_id = 0;
+ vp->vp_ip.prefix = data[0];
+ memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1);
+ break;
+ }
+
+ /*
+ * No address, the prefix length MUST be zero.
+ */
+ if (data_len == 1) {
+ if (data[0] != 0) goto raw;
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ vp->vp_ip.af = AF_INET6;
+ vp->vp_ip.scope_id = 0;
+ vp->vp_ip.prefix = 0;
+ memset(&vp->vp_ipv6addr, 0, sizeof(vp->vp_ipv6addr));
+ break;
+ }
+
+ prefix_len = data[0];
+
+ /*
+ * If we have a /64 prefix but only 7 bytes of
+ * address, that's an error.
+ */
+ if (fr_bytes_from_bits(prefix_len) > (data_len - 1)) goto raw;
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ vp->vp_ip.af = AF_INET6;
+ vp->vp_ip.scope_id = 0;
+ vp->vp_ip.prefix = prefix_len;
+ memset(&vp->vp_ipv6addr, 0, sizeof(vp->vp_ipv6addr));
+ memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1);
+ break;
+
+ /*
+ * A bool is encoded as an empty option if it's
+ * true. A bool is omitted entirely if it's
+ * false.
+ */
+ case FR_TYPE_BOOL:
+ if (data_len != 0) goto raw;
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+ vp->vp_bool = true;
+ break;
+
+ case FR_TYPE_STRUCT:
+ slen = fr_struct_from_network(ctx, cursor, parent, data, data_len, true,
+ decode_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) return slen;
+ return data_len;
+
+ case FR_TYPE_GROUP:
+ return PAIR_DECODE_FATAL_ERROR; /* not supported */
+
+#if 0
+ {
+ fr_dcursor_t child_cursor;
+ fr_pair_list_t head;
+ fr_pair_list_init(&head);
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ /*
+ * Child VPs go into the child group, not in the
+ * main parent list. We start decoding
+ * attributes from the dictionary root, not from
+ * this parent. We also don't decode an option
+ * header, as we're just decoding the values
+ * here.
+ */
+ fr_dcursor_init(&child_cursor, &head);
+ slen = decode_tlvs(vp, &child_cursor, dict, fr_dict_root(dict_dns), data, data_len, decode_ctx, false);
+ if (slen < 0) {
+ talloc_free(vp);
+ goto raw;
+ }
+ fr_pair_list_append(&vp->vp_group, &head);
+ break;
+ }
+#endif
+
+ default:
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ if (fr_value_box_from_network(vp, &vp->data, vp->da->type, vp->da,
+ &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) {
+ FR_PROTO_TRACE("failed decoding?");
+ talloc_free(vp);
+ goto raw;
+ }
+ break;
+ }
+
+ vp->type = VT_DATA;
+ vp->vp_tainted = true;
+ fr_dcursor_append(cursor, vp);
+ return data_len;
+}
+
+
+static ssize_t decode_array(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx)
+{
+ uint8_t const *p = data, *end = p + data_len;
+ ssize_t slen;
+ size_t element_len;
+
+ FR_PROTO_HEX_DUMP(data, data_len, "decode_array");
+
+ if (!fr_cond_assert_msg(parent->flags.array,
+ "%s: Internal sanity check failed, attribute \"%s\" does not have array bit set",
+ __FUNCTION__, parent->name)) return PAIR_DECODE_FATAL_ERROR;
+
+#if 0
+ /*
+ * Fixed-size fields get decoded with a simple decoder.
+ */
+ element_len = fr_dns_attr_sizes[parent->type][0];
+ if (element_len > 0) {
+ while (p < end) {
+ /*
+ * Not enough room for one more element,
+ * decode the last bit as raw data.
+ */
+ if ((size_t) (end - p) < element_len) {
+ slen = decode_raw(ctx, cursor, dict, parent, p, end - p , decode_ctx);
+ if (slen < 0) return slen;
+ break;
+ }
+
+ slen = decode_value(ctx, cursor, dict, parent, p, element_len, decode_ctx);
+ if (slen < 0) return slen;
+ if (!fr_cond_assert((size_t) slen == element_len)) return -(p - data);
+
+ p += slen;
+ }
+
+ /*
+ * We MUST have decoded the entire input. If
+ * not, we ignore the extra bits.
+ */
+ return data_len;
+ }
+#endif
+
+ /*
+ * If the data is variable length i.e. strings or octets
+ * there is a length field before each element.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+
+ * | text-len | String |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+
+ */
+ while (p < end) {
+ if ((end - p) < 2) {
+ raw:
+ slen = decode_raw(ctx, cursor, dict, parent, p, end - p , decode_ctx);
+ if (slen < 0) return slen;
+ break;
+ }
+
+ element_len = fr_net_to_uint16(p);
+ if ((p + 2 + element_len) > end) {
+ goto raw;
+ }
+
+ p += 2;
+ slen = decode_value(ctx, cursor, dict, parent, p, element_len, decode_ctx);
+ if (slen < 0) return slen;
+ p += slen;
+ }
+
+ return data_len;
+}
+
+
+static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t const data_len, void *decode_ctx)
+{
+ ssize_t slen;
+ size_t total, labels_len;
+ fr_pair_t *vp;
+ uint8_t const *next = data;
+ fr_dns_ctx_t *packet_ctx = decode_ctx;
+
+ FR_PROTO_HEX_DUMP(data, data_len, "decode_dns_labels");
+
+ /*
+ * This function handles both single-valued and array
+ * types. It's just easier that way.
+ */
+ if (!parent->flags.array) {
+ /*
+ * Decode starting at "NEXT", but allowing decodes from the start of the packet.
+ */
+ slen = fr_dns_label_uncompressed_length(packet_ctx->packet, data, data + data_len - packet_ctx->packet, &next, packet_ctx->lb);
+ if (slen <= 0) {
+ FR_PROTO_TRACE("length failed at %zd - %s", slen, fr_strerror());
+ goto raw;
+ }
+
+ labels_len = next - data; /* decode only what we've found */
+ } else {
+ /*
+ * Get the length of the entire set of labels, up
+ * to (and including) the final 0x00.
+ *
+ * If any of the labels point outside of this
+ * area, OR they are otherwise invalid, then that's an error.
+ */
+ slen = fr_dns_labels_network_verify(packet_ctx->packet, data, data + data_len - packet_ctx->packet, data, packet_ctx->lb);
+ if (slen < 0) {
+ FR_PROTO_TRACE("verify failed");
+ raw:
+ return decode_raw(ctx, cursor, dict, parent, data, data_len, decode_ctx);
+ }
+
+ labels_len = slen;
+ }
+
+ /*
+ * Loop over the input buffer, decoding the labels one by
+ * one.
+ */
+ for (total = 0; total < labels_len; total += slen) {
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (!vp) return PAIR_DECODE_OOM;
+
+ /*
+ * Having verified the input above, this next
+ * function should never fail unless there's a
+ * bug in the code.
+ */
+ slen = fr_dns_label_to_value_box(vp, &vp->data, data, labels_len, data + total, true, packet_ctx->lb);
+ if (slen <= 0) {
+ FR_PROTO_TRACE("Failed decoding label at %zd", slen);
+ talloc_free(vp);
+ goto raw;
+ }
+
+ vp->type = VT_DATA;
+ fr_dcursor_append(cursor, vp);
+ }
+
+ FR_PROTO_TRACE("decode_dns_labels - %zu", labels_len);
+ return labels_len;
+}
+
+/** Decode a DNS packet
+ *
+ */
+ssize_t fr_dns_decode(TALLOC_CTX *ctx, uint8_t const *packet, size_t packet_len, fr_dcursor_t *cursor, fr_dns_ctx_t *packet_ctx)
+{
+ ssize_t slen;
+ int i, count;
+ uint8_t const *p, *end;
+
+ FR_PROTO_TRACE("HERE %d", __LINE__);
+
+ if (packet_len < DNS_HDR_LEN) return 0;
+
+ /*
+ * @todo - synthesize Packet-Type from the various fields.
+ */
+
+ FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode");
+
+ /*
+ * Decode the header.
+ */
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_packet, packet, DNS_HDR_LEN, true,
+ packet_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) {
+ fail:
+ fr_strerror_const("Failed decoding DNS packet");
+ return slen;
+ }
+
+ p = packet + DNS_HDR_LEN;
+ end = packet + packet_len;
+
+ /*
+ * Decode questions.
+ */
+ count = fr_net_to_uint16(packet + 4);
+ FR_PROTO_TRACE("Decoding %u questions", count);
+ for (i = 0; i < count; i++) {
+ FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - question %d/%d", i, count);
+
+ if (p >= end) {
+ FR_PROTO_TRACE("question overflows packet at %d", i);
+ goto fail;
+ }
+
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_question, p, end - p, true,
+ packet_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) goto fail;
+ p += slen;
+ }
+
+ /*
+ * Decode answers
+ */
+ count = fr_net_to_uint16(packet + 6);
+ FR_PROTO_TRACE("Decoding %u answers", count);
+ for (i = 0; i < count; i++) {
+ FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - answer %d/%d", i, count);
+
+ if (p >= end) {
+ FR_PROTO_TRACE("answer overflows packet at %d", i);
+ goto fail;
+ }
+
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_rr, p, end - p, true,
+ packet_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) goto fail;
+ p += slen;
+ }
+
+ /*
+ * Decode NS
+ */
+ count = fr_net_to_uint16(packet + 8);
+ FR_PROTO_TRACE("Decoding %u NS records", count);
+ for (i = 0; i < count; i++) {
+ FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - NS %d/%d", i, count);
+
+ if (p >= end) {
+ FR_PROTO_TRACE("answer overflows packet at %d", i);
+ goto fail;
+ }
+
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_ns, p, end - p, true,
+ packet_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) goto fail;
+ p += slen;
+ }
+
+ /*
+ * Decode answers
+ */
+ count = fr_net_to_uint16(packet + 10);
+ FR_PROTO_TRACE("Decoding %u additional records", count);
+ for (i = 0; i < count; i++) {
+ FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - additional %d/%d", i, count);
+
+ if (p >= end) {
+ FR_PROTO_TRACE("answer overflows packet at %d", i);
+ goto fail;
+ }
+
+ /*
+ * @todo - allow for decoding TLVs here, for the OPT RR (41).
+ * That should only exist within the AR set.
+ */
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_ar, p, end - p, true,
+ packet_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) goto fail;
+ p += slen;
+ }
+
+ return packet_len;
+}
+
+/** Decode DNS RR
+ *
+ * @param[in] ctx context to alloc new attributes in.
+ * @param[in,out] cursor Where to write the decoded options.
+ * @param[in] dict to lookup attributes in.
+ * @param[in] data to parse.
+ * @param[in] data_len of data to parse.
+ * @param[in] decode_ctx Unused.
+ */
+static ssize_t fr_dns_decode_rr(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
+ UNUSED fr_dict_t const *dict, uint8_t const *data, size_t data_len, void *decode_ctx)
+{
+ ssize_t slen;
+ fr_dns_ctx_t *packet_ctx = (fr_dns_ctx_t *) decode_ctx;
+
+ FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len);
+
+ if (data_len == 0) return 0;
+
+ /*
+ * This function is only used for testing, so update decode_ctx
+ */
+ packet_ctx->packet = data;
+ packet_ctx->packet_len = data_len;
+
+ FR_PROTO_HEX_DUMP(data, data_len, NULL);
+
+ /*
+ * There should be at least room for the RR header
+ */
+ if (data_len < 9) {
+ fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
+ return -1;
+ }
+
+ slen = fr_struct_from_network(ctx, cursor, attr_dns_rr, data, data_len, true,
+ decode_ctx, decode_value_trampoline, NULL);
+ if (slen < 0) return slen;
+
+ FR_PROTO_TRACE("decoding option complete, returning %zd byte(s)", slen);
+ return slen;
+}
+
+/*
+ * Test points
+ */
+static int _decode_test_ctx(UNUSED fr_dns_ctx_t *test_ctx)
+{
+ fr_dns_global_free();
+
+ return 0;
+}
+
+static int decode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+ fr_dns_ctx_t *test_ctx;
+
+ if (fr_dns_global_init() < 0) return -1;
+
+ test_ctx = talloc_zero(ctx, fr_dns_ctx_t);
+ talloc_set_destructor(test_ctx, _decode_test_ctx);
+
+ test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
+ *out = test_ctx;
+
+ return 0;
+}
+
+static ssize_t fr_dns_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *list, uint8_t const *data, size_t data_len, void *proto_ctx)
+{
+ fr_dcursor_t cursor;
+ fr_dns_ctx_t *packet_ctx = proto_ctx;
+
+ /*
+ * Allow queries or answers
+ */
+#if 0
+ if (!fr_dns_packet_ok(data, data_len, true)) {
+ FR_PROTO_TRACE("FAIL %d", __LINE__);
+ if (!fr_dns_packet_ok(data, data_len, false)) {
+ FR_PROTO_TRACE("FAIL %d", __LINE__);
+ return -1;
+ }
+
+ FR_PROTO_TRACE("FAIL %d", __LINE__);
+ }
+#endif
+
+ fr_pair_list_init(list);
+ fr_dcursor_init(&cursor, list);
+
+ packet_ctx->packet = data;
+ packet_ctx->packet_len = data_len;
+
+ packet_ctx->lb = fr_dns_labels_init(packet_ctx, data, 256);
+ fr_assert(packet_ctx->lb != NULL);
+
+ if (fr_dns_decode(ctx, data, data_len, &cursor, proto_ctx) < 0) return -1;
+
+ return data_len;
+}
+
+/*
+ * Test points
+ */
+extern fr_test_point_pair_decode_t dns_tp_decode_pair;
+fr_test_point_pair_decode_t dns_tp_decode_pair = {
+ .test_ctx = decode_test_ctx,
+ .func = fr_dns_decode_rr
+};
+
+extern fr_test_point_proto_decode_t dns_tp_decode_proto;
+fr_test_point_proto_decode_t dns_tp_decode_proto = {
+ .test_ctx = decode_test_ctx,
+ .func = fr_dns_decode_proto
+};
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/dns/dns.h
+ * @brief Implementation of the DNS protocol.
+ *
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(dhcp_h, "$Id$")
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <freeradius-devel/util/packet.h>
+#include <freeradius-devel/util/dns.h>
+
+typedef struct {
+ uint16_t id;
+#ifdef WORDS_BIGENDIAN
+ unsigned int query : 1;
+ unsigned int opcode : 4;
+ unsigned int authoritative : 1;
+ unsigned int truncated : 1;
+ unsigned int recursion_desired : 1;
+#else
+ unsigned int recursion_desired : 1;
+ unsigned int truncated : 1;
+ unsigned int authoritative : 1;
+ unsigned int opcode : 4;
+ unsigned int query : 1;
+#endif
+
+#ifdef WORDS_BIGENDIAN
+ unsigned int recursion_available : 1;
+ unsigned int reserved : 1;
+ unsigned int authentic_data : 1;
+ unsigned int checking_disabled : 1;
+ unsigned int rcode : 4;
+#else
+ unsigned int rcode : 4;
+ unsigned int checking_disabled : 1;
+ unsigned int authentic_data : 1;
+ unsigned int reserved : 1;
+ unsigned int recursion_available : 1;
+#endif
+
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+} CC_HINT(__packed__) fr_dns_packet_t;
+
+/** subtype values for DHCPv4 and DHCPv6
+ *
+ */
+enum {
+ FLAG_ENCODE_NONE = 0, //!< no particular encoding for DNS strings
+ FLAG_ENCODE_DNS_LABEL, //!< encode as DNS label
+};
+
+typedef struct {
+ TALLOC_CTX *tmp_ctx; //!< for temporary things cleaned up during decoding
+ uint8_t const *packet; //!< DNS labels can point anywhere in the packet :(
+ size_t packet_len;
+ fr_dns_labels_t *lb;
+} fr_dns_ctx_t;
+
+int fr_dns_global_init(void);
+void fr_dns_global_free(void);
+
+typedef enum {
+ FR_DNS_QUERY = 0,
+ FR_DNS_IQUERY = 1,
+ FR_DNS_STATUS = 2,
+ FR_DNS_NOTIFY = 4,
+ FR_DNS_UPDATE = 5,
+ FR_DNS_STATEFUL_OP = 6,
+ FR_DNS_CODE_MAX = 7,
+
+ FR_DNS_QUERY_RESPONSE = 16,
+ FR_DNS_IQUERY_RESPONSE = 17,
+ FR_DNS_STATUS_RESPONSE = 18,
+ FR_DNS_NOTIFY_RESPONSE = 20,
+ FR_DNS_UPDATE_RESPONSE = 21,
+ FR_DNS_STATEFUL_OP_RESPONSE = 22,
+
+ FR_DNS_DO_NOT_RESPOND = 256,
+} fr_dns_packet_code_t;
+
+#define FR_DNS_PACKET_CODE_VALID(_code) (((_code) < FR_DNS_CODE_MAX) || (((_code & 0x10) != 0) && ((_code & ~0x10) < FR_DNS_CODE_MAX)))
+
+#define DNS_HDR_LEN (12)
+
+extern char const *fr_dns_packet_codes[FR_DNS_CODE_MAX];
+
+bool fr_dns_packet_ok(uint8_t const *packet, size_t packet_len, bool query);
+
+size_t fr_dns_value_len(fr_pair_t const *vp);
+
+ssize_t fr_dns_decode(TALLOC_CTX *ctx, uint8_t const *packet, size_t packet_len, fr_dcursor_t *cursor, fr_dns_ctx_t *packet_ctx);
+
+ssize_t fr_dns_encode(fr_dbuff_t *dbuff, fr_pair_list_t *vps, void *encode_ctx);
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/dns/encode.c
+ * @brief Functions to encode DNS packets
+ *
+ * @author Alan DeKok (aland@freeradius.org)
+ *
+ * @copyright 2021 NetworkRADIUS SARL (legal@networkradius.com)
+ */
+#include <stdint.h>
+#include <stddef.h>
+#include <freeradius-devel/io/test_point.h>
+#include <freeradius-devel/util/dbuff.h>
+#include <freeradius-devel/util/dns.h>
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/util/struct.h>
+#include <freeradius-devel/util/talloc.h>
+#include <freeradius-devel/util/types.h>
+
+#include "dns.h"
+#include "attrs.h"
+
+#define DNS_OPT_HDR_LEN (4)
+
+static ssize_t encode_value(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx);
+
+static ssize_t encode_rfc_hdr(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx);
+
+static ssize_t encode_tlv_hdr(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx);
+
+static ssize_t encode_tlv(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx);
+
+/** Macro-like function for encoding an option header
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | option-code | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * @param[out] m Where to write the 4 byte option header.
+ * @param[in] option The option number (host byte order).
+ * @param[in] data_len The length of the option (host byte order).
+ * @return
+ * - <0 How much data would have been required as a negative value.
+ * - 4 The length of data written.
+ */
+static inline ssize_t encode_option_hdr(fr_dbuff_marker_t *m, uint16_t option, size_t data_len)
+{
+ FR_DBUFF_IN_RETURN(m, option);
+ FR_DBUFF_IN_RETURN(m, (uint16_t) data_len);
+
+ return sizeof(option) + sizeof(uint16_t);
+}
+
+
+static inline ssize_t encode_array(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, int depth,
+ fr_dcursor_t *cursor, void *encode_ctx);
+
+static ssize_t encode_value_trampoline(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ fr_dict_attr_t const *da = da_stack->da[depth];
+
+ /*
+ * Write out the option's value
+ */
+ if (da->flags.array) {
+ return encode_array(dbuff, da_stack, depth, cursor, encode_ctx);
+ }
+
+ return encode_value(dbuff, da_stack, depth, cursor, encode_ctx);
+}
+
+static ssize_t encode_value(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ ssize_t slen;
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_pair_t const *vp = fr_dcursor_current(cursor);
+ fr_dict_attr_t const *da = da_stack->da[depth];
+ fr_dns_ctx_t *packet_ctx = encode_ctx;
+
+ VP_VERIFY(vp);
+ FR_PROTO_STACK_PRINT(da_stack, depth);
+
+ /*
+ * Nested structs
+ */
+ if (vp->da->type == FR_TYPE_STRUCT) {
+ fr_dcursor_t child_cursor;
+
+ fr_dcursor_init(&child_cursor, &vp->vp_group);
+
+ slen = fr_struct_to_network(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx, encode_value_trampoline, encode_tlv);
+ if (slen < 0) return slen;
+
+ /*
+ * Rebuild the da_stack for the next option.
+ */
+ vp = fr_dcursor_next(cursor);
+ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+ return fr_dbuff_set(dbuff, &work_dbuff);
+ }
+
+ /*
+ * Flat-list
+ */
+ if (da->type == FR_TYPE_STRUCT) {
+ slen = fr_struct_to_network(&work_dbuff, da_stack, depth, cursor, encode_ctx, encode_value_trampoline, encode_tlv);
+ if (slen <= 0) return slen;
+
+ /*
+ * Rebuild the da_stack for the next option.
+ */
+ vp = fr_dcursor_current(cursor);
+ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+ return fr_dbuff_set(dbuff, &work_dbuff);
+ }
+ /*
+ * If it's not a TLV, it should be a value type RFC
+ * attribute make sure that it is.
+ */
+ if (da_stack->da[depth + 1] != NULL) {
+ fr_strerror_printf("%s: Encoding value but not at top of stack", __FUNCTION__);
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ if (vp->da != da) {
+ fr_strerror_printf("%s: Top of stack does not match vp->da", __FUNCTION__);
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ switch (da->type) {
+ case FR_TYPE_TLV:
+ case FR_TYPE_VENDOR:
+ case FR_TYPE_VSA:
+ case FR_TYPE_GROUP:
+ fr_strerror_printf("%s: Called with structural type %s", __FUNCTION__,
+ fr_table_str_by_value(fr_value_box_type_table, da->type, "?Unknown?"));
+ return PAIR_ENCODE_FATAL_ERROR;
+
+ default:
+ break;
+ }
+
+
+ switch (da->type) {
+ case FR_TYPE_STRING:
+ /*
+ * DNS labels get a special encoder.
+ */
+ if (!da->flags.extra && (da->flags.subtype == FLAG_ENCODE_DNS_LABEL)) {
+ fr_dbuff_marker_t last_byte, src;
+
+ fr_dbuff_marker(&last_byte, &work_dbuff);
+ fr_dbuff_marker(&src, &work_dbuff);
+ FR_PROTO_TRACE("encode DNS label %s", vp->vp_strvalue);
+ slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, true, &vp->data, packet_ctx->lb);
+ if (slen < 0) return slen;
+ break;
+ }
+ goto to_network;
+
+ /*
+ * Common encoder might add scope byte, so we just copy the address portion
+ */
+ case FR_TYPE_IPV6_ADDR:
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
+ break;
+
+ case FR_TYPE_IPV4_PREFIX:
+ fr_strerror_const("invalid data type - ipv4prefix");
+ return PAIR_ENCODE_FATAL_ERROR;
+
+ case FR_TYPE_IPV6_PREFIX:
+ fr_strerror_const("invalid data type - ipv4prefix");
+ return PAIR_ENCODE_FATAL_ERROR;
+
+ case FR_TYPE_BOOL:
+ /*
+ * Don't encode anything! The mere existence of
+ * the attribute signifies a "true" value.
+ */
+ break;
+
+ case FR_TYPE_GROUP:
+ fr_strerror_const("invalid data type - group");
+ return PAIR_ENCODE_FATAL_ERROR;
+
+ /*
+ * The value_box functions will take care of fixed-width
+ * "string" and "octets" options.
+ */
+ to_network:
+ case FR_TYPE_OCTETS:
+ /*
+ * Hack until we find all places that don't set data.enumv
+ */
+ if (vp->da->flags.length && (vp->data.enumv != vp->da)) {
+ fr_dict_attr_t const * const *c = &vp->data.enumv;
+ fr_dict_attr_t **u;
+
+ memcpy(&u, &c, sizeof(c)); /* const issues */
+ memcpy(u, &vp->da, sizeof(vp->da));
+ }
+ FALL_THROUGH;
+
+ default:
+ slen = fr_value_box_to_network(&work_dbuff, &vp->data);
+ if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
+ break;
+ }
+
+ /*
+ * Rebuilds the TLV stack for encoding the next attribute
+ */
+ vp = fr_dcursor_next(cursor);
+ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "done value");
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static inline ssize_t encode_array(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ ssize_t slen;
+ size_t element_len;
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_pair_t *vp;
+ fr_dict_attr_t const *da = da_stack->da[depth];
+ fr_dns_ctx_t *packet_ctx = encode_ctx;
+
+ if (!fr_cond_assert_msg(da->flags.array,
+ "%s: Internal sanity check failed, attribute \"%s\" does not have array bit set",
+ __FUNCTION__, da->name)) return PAIR_ENCODE_FATAL_ERROR;
+
+ /*
+ * DNS labels have internalized length, so we don't need
+ * length headers.
+ */
+ if ((da->type == FR_TYPE_STRING) && !da->flags.extra && da->flags.subtype){
+ while (fr_dbuff_extend(&work_dbuff)) {
+ vp = fr_dcursor_current(cursor);
+
+ slen = fr_dns_label_from_value_box_dbuff(&work_dbuff, true, &vp->data, packet_ctx->lb);
+ if (slen <= 0) return PAIR_ENCODE_FATAL_ERROR;
+
+ vp = fr_dcursor_next(cursor);
+ if (!vp || (vp->da != da)) break; /* Stop if we have an attribute of a different type */
+ }
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+ }
+
+ while (fr_dbuff_extend(&work_dbuff)) {
+ bool len_field = false;
+ fr_dbuff_t element_dbuff = FR_DBUFF(&work_dbuff);
+
+ element_len = fr_dns_value_len(fr_dcursor_current(cursor));
+
+ /*
+ * If the data is variable length i.e. strings or octets
+ * we need to include a length field before each element.
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+
+ * | text-len | String |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+
+ */
+ if (!da->flags.length) {
+ len_field = true;
+ FR_DBUFF_ADVANCE_RETURN(&element_dbuff, sizeof(uint16_t)); /* Make room for the length field */
+ }
+
+ slen = encode_value(&element_dbuff, da_stack, depth, cursor, encode_ctx);
+ if (slen < 0) return slen;
+ if (!fr_cond_assert(slen < UINT16_MAX)) return PAIR_ENCODE_FATAL_ERROR;
+
+ /*
+ * Ensure we always create elements of the correct length.
+ * This is mainly for fixed length octets type attributes
+ * containing one or more keys.
+ */
+ if (da->flags.length) {
+ if ((size_t)slen < element_len) {
+ FR_DBUFF_MEMSET_RETURN(&element_dbuff, 0, element_len - slen);
+ slen = element_len;
+ } else if ((size_t)slen > element_len){
+ slen = element_len;
+ }
+ }
+
+ /*
+ * Populate the length field
+ */
+ if (len_field) fr_dbuff_in(&work_dbuff, (uint16_t) slen);
+ fr_dbuff_set(&work_dbuff, &element_dbuff);
+
+
+ vp = fr_dcursor_current(cursor);
+ if (!vp || (vp->da != da)) break; /* Stop if we have an attribute of a different type */
+ }
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static ssize_t encode_option_data(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ ssize_t len;
+ fr_pair_t *vp = fr_dcursor_current(cursor);
+ fr_dcursor_t child_cursor;
+ fr_dbuff_t work_dbuff;
+
+ if (da_stack->da[depth]) {
+ /*
+ * Determine the nested type and call the appropriate encoder
+ */
+ switch (da_stack->da[depth]->type) {
+ case FR_TYPE_TLV:
+ if (!da_stack->da[depth + 1]) goto do_child;
+
+ return encode_tlv_hdr(dbuff, da_stack, depth, cursor, encode_ctx);
+
+ case FR_TYPE_GROUP:
+ if (!da_stack->da[depth + 1]) goto do_child;
+ FALL_THROUGH;
+
+ default:
+ break;
+ }
+
+ return encode_rfc_hdr(dbuff, da_stack, depth, cursor, encode_ctx);
+ }
+
+ if (!da_stack->da[depth]) {
+ switch (vp->da->type) {
+ case FR_TYPE_STRUCTURAL:
+ break;
+
+ default:
+ fr_strerror_printf("Internal sanity check failed");
+ return -1;
+ }
+ }
+
+do_child:
+ fr_dcursor_init(&child_cursor, &vp->vp_group);
+ work_dbuff = FR_DBUFF(dbuff);
+
+ while ((vp = fr_dcursor_current(&child_cursor)) != NULL) {
+ fr_proto_da_stack_build(da_stack, vp->da);
+
+ switch (da_stack->da[depth]->type) {
+ case FR_TYPE_TLV:
+ len = encode_tlv_hdr(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
+ break;
+
+ default:
+ len = encode_rfc_hdr(&work_dbuff, da_stack, depth, &child_cursor, encode_ctx);
+ break;
+ }
+
+ if (len <= 0) return len;
+ }
+
+ /*
+ * Skip over the attribute we just encoded.
+ */
+ vp = fr_dcursor_next(cursor);
+ fr_proto_da_stack_build(da_stack, vp ? vp->da : NULL);
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static ssize_t encode_tlv(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_pair_t const *vp = fr_dcursor_current(cursor);
+ fr_dict_attr_t const *da = da_stack->da[depth];
+ ssize_t len;
+ fr_dbuff_extend_status_t status = FR_DBUFF_EXTENDABLE;
+
+ while (fr_dbuff_extend_lowat(&status, &work_dbuff, DNS_OPT_HDR_LEN) > DNS_OPT_HDR_LEN) {
+ FR_PROTO_STACK_PRINT(da_stack, depth);
+
+ len = encode_option_data(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx);
+ if (len < 0) return len;
+
+ /*
+ * If nothing updated the attribute, stop
+ */
+ if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) break;
+
+ /*
+ * We can encode multiple sub TLVs, if after
+ * rebuilding the TLV Stack, the attribute
+ * at this depth is the same.
+ */
+ if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) break;
+ vp = fr_dcursor_current(cursor);
+ }
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done TLV body");
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+/** Encode an RFC format TLV.
+ *
+ * This could be a standard attribute, or a TLV data type.
+ * If it's a standard attribute, then vp->da->attr == attribute.
+ * Otherwise, attribute may be something else.
+ */
+static ssize_t encode_rfc_hdr(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_dbuff_marker_t hdr;
+ fr_dict_attr_t const *da = da_stack->da[depth];
+ ssize_t len;
+
+ FR_PROTO_STACK_PRINT(da_stack, depth);
+ fr_dbuff_marker(&hdr, &work_dbuff);
+
+ /*
+ * Make space for the header...
+ */
+ FR_DBUFF_EXTEND_LOWAT_OR_RETURN(&work_dbuff, DNS_OPT_HDR_LEN);
+ fr_dbuff_advance(&work_dbuff, DNS_OPT_HDR_LEN);
+
+ /*
+ * Write out the option's value
+ */
+ if (da->flags.array) {
+ len = encode_array(&work_dbuff, da_stack, depth, cursor, encode_ctx);
+ } else {
+ len = encode_value(&work_dbuff, da_stack, depth, cursor, encode_ctx);
+ }
+ if (len < 0) return len;
+
+ /*
+ * Write out the option number and length (before the value we just wrote)
+ */
+ (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DNS_OPT_HDR_LEN));
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done RFC header");
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static ssize_t encode_tlv_hdr(fr_dbuff_t *dbuff,
+ fr_da_stack_t *da_stack, unsigned int depth,
+ fr_dcursor_t *cursor, void *encode_ctx)
+{
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_dbuff_marker_t hdr;
+ fr_dict_attr_t const *da = da_stack->da[depth];
+ ssize_t len;
+
+ fr_dbuff_marker(&hdr, &work_dbuff);
+ VP_VERIFY(fr_dcursor_current(cursor));
+ FR_PROTO_STACK_PRINT(da_stack, depth);
+
+ if (da_stack->da[depth]->type != FR_TYPE_TLV) {
+ fr_strerror_printf("%s: Expected type \"tlv\" got \"%s\"", __FUNCTION__,
+ fr_table_str_by_value(fr_value_box_type_table, da_stack->da[depth]->type, "?Unknown?"));
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ if (!da_stack->da[depth + 1]) {
+ fr_assert(0);
+ fr_strerror_printf("%s: Can't encode empty TLV", __FUNCTION__);
+ return PAIR_ENCODE_FATAL_ERROR;
+ }
+
+ FR_DBUFF_ADVANCE_RETURN(&work_dbuff, DNS_OPT_HDR_LEN); /* Make room for option header */
+
+ len = encode_tlv(&work_dbuff, da_stack, depth, cursor, encode_ctx);
+ if (len < 0) return len;
+
+ /*
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | option-code | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ (void) encode_option_hdr(&hdr, (uint16_t)da->attr, (uint16_t) (fr_dbuff_used(&work_dbuff) - DNS_OPT_HDR_LEN));
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Done TLV header");
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+
+/** Encode a Dns option and any sub-options.
+ *
+ * @param[out] dbuff Where to write encoded DHCP attributes.
+ * @param[in] cursor with current VP set to the option to be encoded.
+ * Will be advanced to the next option to encode.
+ * @param[in] encode_ctx containing parameters for the encoder.
+ * @return
+ * - > 0 length of data written.
+ * - < 0 error.
+ */
+static ssize_t fr_dns_encode_rr(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
+{
+ ssize_t slen;
+ fr_pair_t *vp;
+ fr_da_stack_t da_stack;
+ fr_dbuff_t work_dbuff = FR_DBUFF_MAX(dbuff, UINT16_MAX);
+
+ fr_proto_da_stack_build(&da_stack, attr_dns_rr);
+ FR_PROTO_STACK_PRINT(&da_stack, 0);
+
+ FR_PROTO_TRACE("encode_rr -- remaining %zd", fr_dbuff_remaining(&work_dbuff));
+
+ vp = fr_dcursor_current(cursor);
+ if (vp->da->type == FR_TYPE_STRUCT) {
+ fr_dcursor_t child_cursor;
+
+ fr_dcursor_init(&child_cursor, &vp->vp_group);
+
+ slen = fr_struct_to_network(&work_dbuff, &da_stack, 0, &child_cursor, encode_ctx, encode_value_trampoline, NULL);
+ if (slen <= 0) return slen;
+ (void) fr_dcursor_next(cursor);
+
+ } else {
+ slen = fr_struct_to_network(&work_dbuff, &da_stack, 0, cursor, encode_ctx, encode_value_trampoline, NULL);
+ if (slen <= 0) return slen;
+ }
+
+ FR_PROTO_TRACE("Complete rr is %zu byte(s)", fr_dbuff_used(&work_dbuff));
+ FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), NULL);
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static ssize_t encode_record(fr_dbuff_t *dbuff, fr_da_stack_t *da_stack, fr_pair_list_t *vps,
+ fr_dict_attr_t const *attr, fr_dns_ctx_t *packet_ctx, uint8_t *counter)
+{
+ int count;
+ fr_pair_t *vp;
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ fr_dcursor_t cursor;
+
+ vp = fr_dcursor_iter_by_da_init(&cursor, vps, attr);
+ if (!vp) {
+ FR_PROTO_TRACE(" %s not found in list", attr->name);
+ return 0;
+ }
+
+ fr_proto_da_stack_build(da_stack, attr);
+
+ count = 0;
+ while (count < 65535) {
+ ssize_t slen;
+ fr_dcursor_t child_cursor;
+
+ fr_dcursor_init(&child_cursor, &vp->vp_group);
+ slen = fr_struct_to_network(&work_dbuff, da_stack, 0, &child_cursor, packet_ctx, encode_value_trampoline, NULL);
+ if (slen <= 0) return slen;
+
+ count++;
+ vp = fr_dcursor_next(&cursor);
+ if (!vp) break;
+ }
+
+ fr_net_from_uint16(counter, count);
+ FR_PROTO_TRACE(" %s encoded %d records", attr->name, count);
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+/** Encode a DNS packet
+ *
+ */
+ssize_t fr_dns_encode(fr_dbuff_t *dbuff, fr_pair_list_t *vps, void *encode_ctx)
+{
+ fr_dbuff_t work_dbuff = FR_DBUFF(dbuff);
+ ssize_t slen;
+ uint8_t *packet;
+ fr_pair_t *vp;
+ fr_dns_ctx_t *packet_ctx = encode_ctx;
+ fr_dcursor_t cursor, child_cursor;
+ fr_da_stack_t da_stack;
+
+ packet = fr_dbuff_current(&work_dbuff);
+ fr_assert(packet == packet_ctx->packet);
+
+ /*
+ * @todo - find maximum packet length, and limit work_dbuff to that.
+ */
+ vp = fr_dcursor_iter_by_da_init(&cursor, vps, attr_dns_packet);
+ if (!vp) {
+ fr_pair_list_debug(vps);
+
+ fr_strerror_const("attribute list does not include DNS packet header");
+ return -1;
+ }
+
+ /*
+ * Encode the header.
+ */
+ fr_dcursor_init(&child_cursor, &vp->vp_group);
+ fr_proto_da_stack_build(&da_stack, attr_dns_packet);
+
+ slen = fr_struct_to_network(&work_dbuff, &da_stack, 0, &cursor, packet_ctx, encode_value_trampoline, NULL);
+ if (slen <= 0) return slen;
+
+ fr_assert(slen == DNS_HDR_LEN);
+
+ /*
+ * Encode questions
+ */
+ slen = encode_record(&work_dbuff, &da_stack, vps, attr_dns_question, packet_ctx, packet + 4);
+ if (slen < 0) return slen - (fr_dbuff_current(&work_dbuff) - packet);
+
+ /*
+ * Encode answers
+ */
+ slen = encode_record(&work_dbuff, &da_stack, vps, attr_dns_rr, packet_ctx, packet + 6);
+ if (slen < 0) return slen - (fr_dbuff_current(&work_dbuff) - packet);
+
+ /*
+ * Encode NS records
+ */
+ slen = encode_record(&work_dbuff, &da_stack, vps, attr_dns_ns, packet_ctx, packet + 8);
+ if (slen < 0) return slen - (fr_dbuff_current(&work_dbuff) - packet);
+
+ /*
+ * Encode additional records
+ */
+ slen = encode_record(&work_dbuff, &da_stack, vps, attr_dns_ar, packet_ctx, packet + 10);
+ if (slen < 0) return slen - (fr_dbuff_current(&work_dbuff) - packet);
+
+ return fr_dbuff_set(dbuff, &work_dbuff);
+}
+
+static int _test_ctx_free(UNUSED fr_dns_ctx_t *ctx)
+{
+ fr_dns_global_free();
+
+ return 0;
+}
+
+static int encode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+ fr_dns_ctx_t *test_ctx;
+
+ if (fr_dns_global_init() < 0) return -1;
+
+ test_ctx = talloc_zero(ctx, fr_dns_ctx_t);
+ if (!test_ctx) return -1;
+
+ talloc_set_destructor(test_ctx, _test_ctx_free);
+ test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
+
+ *out = test_ctx;
+
+ return 0;
+}
+
+static ssize_t fr_dns_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, void *proto_ctx)
+{
+ ssize_t slen;
+ fr_dns_ctx_t *packet_ctx = (fr_dns_ctx_t *) proto_ctx;
+
+ packet_ctx->packet = data;
+ packet_ctx->packet_len = data_len;
+
+ packet_ctx->lb = fr_dns_labels_init(packet_ctx, data, 256);
+ fr_assert(packet_ctx->lb != NULL);
+
+ slen = fr_dns_encode(&FR_DBUFF_TMP(data, data_len), vps, packet_ctx);
+
+#ifndef NDEBUG
+ if (slen <= 0) return slen;
+
+ if (fr_debug_lvl > 2) {
+// fr_dns_print_hex(stdout, data, slen);
+ }
+#endif
+
+ return slen;
+}
+
+/*
+ * Test points
+ */
+extern fr_test_point_pair_encode_t dns_tp_encode_pair;
+fr_test_point_pair_encode_t dns_tp_encode_pair = {
+ .test_ctx = encode_test_ctx,
+ .func = fr_dns_encode_rr,
+};
+
+extern fr_test_point_proto_encode_t dns_tp_encode_proto;
+fr_test_point_proto_encode_t dns_tp_encode_proto = {
+ .test_ctx = encode_test_ctx,
+ .func = fr_dns_encode_proto
+};
* attribute, OR it's already been grouped
* into a contiguous memory buffer.
*/
- ret = fr_struct_from_network(ctx, cursor, parent, p, attr_len,
+ ret = fr_struct_from_network(ctx, cursor, parent, p, attr_len, false,
packet_ctx, decode_value, decode_tlv);
if (ret < 0) goto raw;
return attr_len;
/*
* Call the struct encoder to do the actual work.
*/
- if (fr_struct_from_network(ctx, cursor, attr_tacacs_packet, buffer, buffer_len, NULL, NULL, NULL) < 0) {
+ if (fr_struct_from_network(ctx, cursor, attr_tacacs_packet, buffer, buffer_len, false, NULL, NULL, NULL) < 0) {
fr_strerror_printf("Problems to decode %s using fr_struct_from_network()", attr_tacacs_packet->name);
return -1;
}
--- /dev/null
+#
+# Test vectors for DHCP attributes
+#
+proto dns
+proto-dictionary dns
+
+# 16 bits of ID 0
+# Query, all other bits are clear
+# 0 query
+# 1 answer
+# 0 nscount
+# 0 arcount
+#
+# A record of '.', class Internet, TTL 16
+# length 4, with 127.0.0.1 as the IP address
+
+# Z type class ttl
+decode-proto 00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+match packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 1, nscount = 0, arcount = 0 }, rr = { name = ".", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+
+encode-proto -
+match 00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+
+# Really "decode RR".
+# Z type class ttl length IPaddr
+decode-pair 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+match rr = { name = ".", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+
+encode-pair -
+match 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+
+#
+# And a complex label
+#
+encode-proto packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 1, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+match 00 00 00 00 00 00 00 01 00 00 00 00 03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+
+decode-proto -
+match packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 1, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+
+#
+# multiple labels (2)
+#
+encode-proto packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 2, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ftp.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+match 00 00 00 00 00 00 00 02 00 00 00 00 03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01 03 66 74 70 c0 10 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+
+#
+# We encode "www.example.com"
+# and then "ftp" with a pointer c010 to "example.com"
+#
+decode-proto -
+match packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 2, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ftp.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+
+#
+# multiple labels (3), but with all counts removed. The counts will
+# be calculated dynamically.
+#
+# Note that this isn't a valid DNS reply packet, as it should really
+# contain the questions which we're replying to. But we don't care
+# about every bit of RFC correctness here, we just care to test the
+# encoders and decoders for formatting.
+#
+encode-proto packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 3, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ftp.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ns.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+match 00 00 00 00 00 00 00 03 00 00 00 00 03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01 00 00 00 10 00 04 7f 00 00 01 03 66 74 70 c0 10 00 01 00 01 00 00 00 10 00 04 7f 00 00 01 02 6e 73 c0 10 00 01 00 01 00 00 00 10 00 04 7f 00 00 01
+
+#
+# We encode "www.example.com"
+# and then "ftp" with a pointer c010 to "example.com"
+#
+decode-proto -
+match packet = { id = 0, query = query, opcode = query, authoritative = no, truncated-response = no, recursion-desired = no, recursion-available = no, reserved = no, authentic-data = no, checking-disabled = no, rcode = no-error, qdcount = 0, ancount = 3, nscount = 0, arcount = 0 }, rr = { name = "www.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ftp.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }, rr = { name = "ns.example.com", type = a, class = 1, ttl = 16, type.a = { ip-address = 127.0.0.1 } }
+
+count
+match 22