]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Miracles and magic.
authorAlan T. DeKok <aland@freeradius.org>
Tue, 5 Oct 2021 18:39:52 +0000 (14:39 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 5 Oct 2021 18:45:10 +0000 (14:45 -0400)
36 files changed:
Makefile
raddb/sites-available/dns [new file with mode: 0644]
share/dictionary/README.md
share/dictionary/dns/dictionary [new file with mode: 0644]
share/dictionary/dns/dictionary.freeradius.internal [new file with mode: 0644]
share/dictionary/dns/dictionary.rfc1034 [new file with mode: 0644]
src/bin/unit_test_attribute.c
src/include/all.mk
src/lib/util/dns.c
src/lib/util/dns.h
src/lib/util/struct.c
src/lib/util/struct.h
src/listen/dhcpv6/proto_dhcpv6.c
src/listen/dhcpv6/proto_dhcpv6_udp.c
src/listen/dns/README.md [new file with mode: 0644]
src/listen/dns/all.mk [new file with mode: 0644]
src/listen/dns/proto_dns.c [new file with mode: 0644]
src/listen/dns/proto_dns.h [new file with mode: 0644]
src/listen/dns/proto_dns.mk [new file with mode: 0644]
src/listen/dns/proto_dns_udp.c [new file with mode: 0644]
src/listen/dns/proto_dns_udp.mk [new file with mode: 0644]
src/modules/rlm_unbound/rlm_unbound.c
src/process/dns/all.mk [new file with mode: 0644]
src/process/dns/base.c [new file with mode: 0644]
src/protocols/arp/base.c
src/protocols/dhcpv6/decode.c
src/protocols/dhcpv6/encode.c
src/protocols/dns/all.mk [new file with mode: 0644]
src/protocols/dns/attrs.h [new file with mode: 0644]
src/protocols/dns/base.c [new file with mode: 0644]
src/protocols/dns/decode.c [new file with mode: 0644]
src/protocols/dns/dns.h [new file with mode: 0644]
src/protocols/dns/encode.c [new file with mode: 0644]
src/protocols/radius/decode.c
src/protocols/tacacs/decode.c
src/tests/unit/protocols/dns/base.txt [new file with mode: 0644]

index e762b96f402d9eec0bf9dbfc61b917ab977ed2f4..d7f8247adece464222db692db56470ec48eb9693 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -71,6 +71,7 @@ PROTOCOLS    := \
        arp \
        dhcpv4 \
        dhcpv6 \
+       dns \
        eap/aka-sim \
        ethernet \
        freeradius \
diff --git a/raddb/sites-available/dns b/raddb/sites-available/dns
new file mode 100644 (file)
index 0000000..42a7ee6
--- /dev/null
@@ -0,0 +1,56 @@
+#  -*- 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
+}
+
+}
index b8dcdc987287f44cc4e1aec1ef413d7f24f4f4e9..5c884950738428b1fbfd72c97fd020af70a949ac 100644 (file)
@@ -13,6 +13,9 @@ PROTOCOL        VMPS            6       format=2
 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
diff --git a/share/dictionary/dns/dictionary b/share/dictionary/dns/dictionary
new file mode 100644 (file)
index 0000000..ca1026b
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- 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
diff --git a/share/dictionary/dns/dictionary.freeradius.internal b/share/dictionary/dns/dictionary.freeradius.internal
new file mode 100644 (file)
index 0000000..d5f7253
--- /dev/null
@@ -0,0 +1,16 @@
+# -*- 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
diff --git a/share/dictionary/dns/dictionary.rfc1034 b/share/dictionary/dns/dictionary.rfc1034
new file mode 100644 (file)
index 0000000..13706ca
--- /dev/null
@@ -0,0 +1,167 @@
+# -*- 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
index eb473df39f5cf6532b657bc62d6c3ce7bdaac0df..0a3148bf1b6a7191e81155565216810513ed9aef 100644 (file)
@@ -1525,7 +1525,7 @@ static size_t command_encode_dns_label(command_result_t *result, command_file_ct
                }
 
                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();
@@ -1564,7 +1564,7 @@ static size_t command_decode_dns_label(command_result_t *result, command_file_ct
        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);
index 0debbcd08d0e1e6c97d4a1fb5c16dc499136f986..f2682d9ac10e3dfdced0483f1df9c7383cc71351 100644 (file)
@@ -62,7 +62,7 @@ src/include/$(1): $(2)
        ${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))))
index 21efd63d509d8ba565af89e4fe1f4b779517fb67..0359083eda04edd1ea523e5eec9e504b93c170fa 100644 (file)
@@ -28,6 +28,106 @@ RCSID("$Id$")
 #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.
  *
@@ -67,7 +167,7 @@ static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
        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
@@ -123,6 +223,9 @@ static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
  *  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
@@ -147,6 +250,7 @@ static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
  *  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()
@@ -157,12 +261,13 @@ static bool labelcmp(uint8_t const *a, uint8_t const *b, size_t len)
  *     - 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.
@@ -291,7 +396,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
                         *      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
@@ -311,7 +416,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
                         *      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;
@@ -327,11 +432,12 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
         *      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;
        }
 
        /*
@@ -340,13 +446,13 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
         *      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
@@ -398,7 +504,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
                 *      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.
@@ -446,7 +552,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
                 *      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
@@ -466,7 +572,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
                 *      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;
@@ -477,7 +583,7 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
        /*
         *      Who knows what it is, we couldn't compress it.
         */
-       return false;
+       return compressed;
 }
 
 
@@ -492,12 +598,12 @@ static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t
  *     - 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;
@@ -521,13 +627,14 @@ ssize_t fr_dns_label_from_value_box_dbuff(fr_dbuff_t *dbuff, bool compression, f
  * @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;
@@ -690,17 +797,67 @@ ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t 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;
 }
@@ -712,28 +869,36 @@ done:
  *
  *  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;
@@ -814,13 +979,23 @@ ssize_t fr_dns_label_uncompressed_length(uint8_t const *buf, size_t buf_len, uin
                         *      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
@@ -925,19 +1100,23 @@ ssize_t fr_dns_label_uncompressed_length(uint8_t const *buf, size_t buf_len, uin
         */
        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;
@@ -949,7 +1128,7 @@ ssize_t fr_dns_labels_network_verify(uint8_t const *buf, size_t buf_len, uint8_t
                        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' */
        }
 
@@ -1002,26 +1181,33 @@ static ssize_t dns_label_decode(uint8_t const *buf, uint8_t const **start, uint8
  * @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);
 
@@ -1053,13 +1239,15 @@ ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst,
                 *      only returns 0 when the current byte is 0x00,
                 *      which it can't be.
                 */
-               slen = dns_label_decode(src, &current, &next);
+               slen = dns_label_decode(packet, &current, &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;
@@ -1091,6 +1279,7 @@ ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst,
         *      buffer exactly.
         */
        if (p != (uint8_t *) q) {
+               FR_PROTO_TRACE("dns_label_to_value_box - Failed at %d", __LINE__);
                goto fail;
        }
 
@@ -1102,47 +1291,3 @@ ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst,
         */
        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;
-}
index 7dbbd9575d8c2c545a792aa193f9a2bf99d7e667..31be362fbc89ea43ea323e0b6d891a2946a8c5a2 100644 (file)
@@ -27,19 +27,31 @@ RCSIDH(dns_h, "$Id$")
 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
 }
index db1735a78a3b12b07ca1cbe92e2d050dcbc1af47..11154be4c8033bdd80357597b3a8437cbffca0fc 100644 (file)
@@ -65,7 +65,7 @@ fr_pair_t *fr_raw_from_network(TALLOC_CTX *ctx, fr_dict_attr_t const *parent, ui
  */
 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;
@@ -73,18 +73,30 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
        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;
 
@@ -95,7 +107,10 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                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;
@@ -122,7 +137,10 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                        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));
@@ -134,7 +152,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                        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;
@@ -162,6 +180,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                                        break;
 
                                default:
+                                       FR_PROTO_TRACE("Can't decode unknown type?");
                                        goto unknown;
                        }
 
@@ -192,8 +211,11 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                         *      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;
                        }
 
@@ -222,8 +244,11 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                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 */
@@ -244,7 +269,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                        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;
@@ -263,7 +288,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                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;
 
                        /*
@@ -302,7 +327,10 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                /*
                 *      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];
@@ -315,12 +343,16 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                         *      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);
                        }
@@ -330,31 +362,36 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_dcursor_t *cursor,
                } 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;
 }
 
index 9d79afbf5d16d40349fb6d8fb686161054b853f8..b8ef0b7469e966a4ba60084ac0c15ffb43e72056 100644 (file)
@@ -38,7 +38,7 @@ typedef ssize_t (*fr_decode_value_t)(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_d
 
 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,
index 9ab971a05228a39beeaf3a06ffec0ed2675d3d5a..4ad0c526ad55c6f4ba9d58f5587ed845e1c0513d 100644 (file)
@@ -129,7 +129,7 @@ static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
 
        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;
        }
 
index 17d350fc8173a85cfc7db5c19f8a1b1b6c4343c4..54dd1e36497339740657acd4baa76ea50a4c7c01 100644 (file)
@@ -106,7 +106,7 @@ static const CONF_PARSER udp_listen_config[] = {
        { 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
 };
diff --git a/src/listen/dns/README.md b/src/listen/dns/README.md
new file mode 100644 (file)
index 0000000..f354e58
--- /dev/null
@@ -0,0 +1,8 @@
+# proto_dhcp
+## Metadata
+<dl>
+  <dt>category</dt><dd>protocols</dd>
+</dl>
+
+## Summary
+Implements DNS (Domain Name System).
diff --git a/src/listen/dns/all.mk b/src/listen/dns/all.mk
new file mode 100644 (file)
index 0000000..61c7979
--- /dev/null
@@ -0,0 +1 @@
+SUBMAKEFILES := proto_dns.mk proto_dns_udp.mk
diff --git a/src/listen/dns/proto_dns.c b/src/listen/dns/proto_dns.c
new file mode 100644 (file)
index 0000000..b9df7fc
--- /dev/null
@@ -0,0 +1,469 @@
+/*
+ *   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
+};
diff --git a/src/listen/dns/proto_dns.h b/src/listen/dns/proto_dns.h
new file mode 100644 (file)
index 0000000..6fcd749
--- /dev/null
@@ -0,0 +1,42 @@
+#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;
diff --git a/src/listen/dns/proto_dns.mk b/src/listen/dns/proto_dns.mk
new file mode 100644 (file)
index 0000000..2d9b1f9
--- /dev/null
@@ -0,0 +1,9 @@
+TARGETNAME     := proto_dns
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := proto_dns.c
+
+TGT_PREREQS    := libfreeradius-dns.a libfreeradius-io.a
diff --git a/src/listen/dns/proto_dns_udp.c b/src/listen/dns/proto_dns_udp.c
new file mode 100644 (file)
index 0000000..7063565
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ *   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,
+};
diff --git a/src/listen/dns/proto_dns_udp.mk b/src/listen/dns/proto_dns_udp.mk
new file mode 100644 (file)
index 0000000..71a0ee0
--- /dev/null
@@ -0,0 +1,9 @@
+TARGETNAME     := proto_dns_udp
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := proto_dns_udp.c
+
+TGT_PREREQS    := libfreeradius-dns.a
index 9eaab5d6013288840d8a647cb61febd41d70a4ea..dc4699fa7477af758722e6b38390e1c36b941a4c 100644 (file)
@@ -234,7 +234,7 @@ static void xlat_unbound_callback(void *mydata, int rcode, void *packet, int pac
 
                        /*      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;
diff --git a/src/process/dns/all.mk b/src/process/dns/all.mk
new file mode 100644 (file)
index 0000000..ec2c9ee
--- /dev/null
@@ -0,0 +1,6 @@
+TARGETNAME := process_dns
+
+TARGET         := $(TARGETNAME).a
+
+SOURCES                := base.c
+TGT_PREREQS    := libfreeradius-dns.a
diff --git a/src/process/dns/base.c b/src/process/dns/base.c
new file mode 100644 (file)
index 0000000..4a94961
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ *   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,
+};
index ac9211958829acb17f7a214cf315c3c543ae24e7..38e1bb7691e1b927e05850d2602bea1544d535e6 100644 (file)
@@ -261,7 +261,7 @@ ssize_t fr_arp_decode(TALLOC_CTX *ctx, uint8_t const *packet, size_t packet_len,
         */
        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);
 }
 
index 997a4000ffa261c8fa0e32ff81eb1f4687a3f3d1..88b8460b42c485f910ccf5bd29946f85693690ff 100644 (file)
@@ -233,7 +233,7 @@ static ssize_t decode_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t con
                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;
@@ -374,7 +374,7 @@ static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_
         *      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;
 
                /*
@@ -393,7 +393,7 @@ static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_
                 *      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);
@@ -415,7 +415,7 @@ static ssize_t decode_dns_labels(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_
                 *      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;
index ca0438587925ffc02eba73d2b46f288d60952324..b2ee28c36ac06c71b42b7046181cfecda19fa082 100644 (file)
@@ -178,7 +178,7 @@ static ssize_t encode_value(fr_dbuff_t *dbuff,
 
                        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;
 
                        /*
@@ -421,7 +421,7 @@ static inline ssize_t encode_array(fr_dbuff_t *dbuff,
                         *
                         *      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);
diff --git a/src/protocols/dns/all.mk b/src/protocols/dns/all.mk
new file mode 100644 (file)
index 0000000..7b27565
--- /dev/null
@@ -0,0 +1,13 @@
+#
+# 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
diff --git a/src/protocols/dns/attrs.h b/src/protocols/dns/attrs.h
new file mode 100644 (file)
index 0000000..ff951eb
--- /dev/null
@@ -0,0 +1,37 @@
+#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;
diff --git a/src/protocols/dns/base.c b/src/protocols/dns/base.c
new file mode 100644 (file)
index 0000000..27abe69
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ *   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,
+};
diff --git a/src/protocols/dns/decode.c b/src/protocols/dns/decode.c
new file mode 100644 (file)
index 0000000..a313247
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+ *   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
+};
diff --git a/src/protocols/dns/dns.h b/src/protocols/dns/dns.h
new file mode 100644 (file)
index 0000000..502b071
--- /dev/null
@@ -0,0 +1,124 @@
+#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
diff --git a/src/protocols/dns/encode.c b/src/protocols/dns/encode.c
new file mode 100644 (file)
index 0000000..661d44c
--- /dev/null
@@ -0,0 +1,737 @@
+/*
+ *   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
+};
index ccf6978e1f5fb2d97062edb061b040db15a1fdb5..3d87d621f46065c56ef46f59d45a3603d2e5da45 100644 (file)
@@ -1566,7 +1566,7 @@ ssize_t fr_radius_decode_pair_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_di
                 *      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;
index a3bfdd49104c6b61a8e8092f6068772b070501ff..26488c53275528689a80528743ecca7b3c9bb4d4 100644 (file)
@@ -293,7 +293,7 @@ ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, uint8_t const *buffer, size_t buffer_l
        /*
         *      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;
        }
diff --git a/src/tests/unit/protocols/dns/base.txt b/src/tests/unit/protocols/dns/base.txt
new file mode 100644 (file)
index 0000000..e6ceebb
--- /dev/null
@@ -0,0 +1,74 @@
+#
+#  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