]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add DNS label compression.
authorAlan T. DeKok <aland@freeradius.org>
Sat, 19 Oct 2019 15:51:51 +0000 (11:51 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sat, 19 Oct 2019 15:52:12 +0000 (11:52 -0400)
It's a little brute-force right now, but it seems to work.

src/lib/util/value.c
src/tests/unit/data_types.txt

index 71ba6242df5e7b21920ee2f398b1709b60351fa1..e514d424432aa7ddf4dbc984a5665875149ec73c 100644 (file)
@@ -1296,6 +1296,118 @@ ssize_t fr_value_box_to_network(size_t *need, uint8_t *dst, size_t dst_len, fr_v
        return min;
 }
 
+/** Match two DNS labels
+ *
+ *  Both 'a' and 'b' MUST start with a length byte.
+ *
+ *  Both 'a' and 'b' MUST be valid labels.  This function does NOT
+ *  validate the input at all!
+ *
+ *  'a' MAY contain compressed pointers.  'b' MUST NOT contain
+ *  compressed pointers.
+ */
+static bool dns_label_match(uint8_t const *start, uint8_t const *a, uint8_t const *b)
+{
+       /*
+        *      Lengths don't match, we're done.
+        */
+       if (*a != *b) return false;
+
+       /*
+        *      Prefixes don't match, we're done.
+        */
+       if (memcmp(a + 1, b + 1, *a) != 0) return false;
+
+       /*
+        *      Skip the matching label.
+        */
+       b += *a + 1;
+       a += *a + 1;
+
+       /*
+        *      If both labels are done, then it's a match.
+        */
+       if (!*a && !*b) return true;
+
+       /*
+        *      If 'a' is now a compressed pointer, jump to the actual
+        *      label.  Note that we don't do bounds checking on the
+        *      offset.  It's always positive, and it's always valid.
+        */
+       if (*a > 63) {
+               uint16_t offset;
+
+               offset = start + a[1];
+               offset += ((*a & ~0xc0) << 8);
+
+               a = start + offset;
+
+               /*
+                *      Compressed pointers can't point to compressed
+                *      pointers.
+                */
+               if (*a > 63) return false;
+       }
+
+       return dns_label_match(start, a, b);
+}
+
+/** Compress "label" by looking at buffer in start..end
+ *
+ * "label" MUST be uncompressed.
+ */
+static bool dns_label_compress(uint8_t const *start, uint8_t const *end, uint8_t *label)
+{
+       uint8_t const *p;
+       uint8_t *q;
+       size_t offset;
+
+       p = start;
+       q = label;
+
+       while (p < end) {
+               /*
+                *      Skip compressed pointers.  We know they don't
+                *      match.
+                */
+               if (*p > 63) {
+                       p += 2;
+                       continue;
+               }
+
+               /*
+                *      Skip end bytes, which clearly don't match.
+                */
+               if (*p == 0) {
+                       p++;
+                       continue;
+               }
+
+               /*
+                *      We know have something which MIGHT match.  Do
+                *      a full check on it, including dereferencing
+                *      compressed pointers.
+                */
+               if (!dns_label_match(start, p, q)) {
+                       p += *p + 1;
+                       continue;
+               }
+
+               /*
+                *      We do have a match, encode the offset.
+                */
+               offset = p - start;
+               if (offset > (1 << 14)) return false;
+
+               label[1] = offset & 0xff;
+               label[0] = ((offset >> 8) & 0xff) | 0xc0;
+               return true;
+       }
+
+       return false;
+}
+
+
 /** Encode a single value box of type string, serializing its contents to a dns label
  *
  * This functions takes a large buffer and encodes the label in part
@@ -1369,6 +1481,11 @@ ssize_t fr_value_box_to_dns_label(size_t *need, uint8_t *buf, size_t buf_len, ui
        *label = 0;
        data = label + 1;
 
+       /*
+        *      @todo - encode into a local buffer, and then try to
+        *      compress it into the output buffer.  This means that
+        *      the output buffer can be a little bit smaller.
+        */
        while (q < strend) {
                /*
                 *      Just for pairanoia
@@ -1437,10 +1554,27 @@ ssize_t fr_value_box_to_dns_label(size_t *need, uint8_t *buf, size_t buf_len, ui
        *(data++) = 0;          /* end of label */
 
        /*
-        *      @todo - do label compression on suffixes, by scanning
-        *      backwards for zero bytes, and doing comparisons.
+        *      Only one label, don't compress it.  Or, the label is
+        *      already compressed.
+        */
+       if ((buf == where) || ((data - where) <= 2)) goto done;
+
+       /*
+        *      Do label compression on the resulting string, starting
+        *      from the beginning of the input buffer.
+        *
+        *      @todo - this is pretty brute-force.  It works for
+        *      small number of labels and small buffers.  It's
+        *      inefficient for anything large.
         */
+       for (label = where; *label != 0; label += *label + 1) {
+               if (dns_label_compress(buf, where, label)) {
+                       data = label + 2;
+                       break;
+               }
+       }
 
+done:
        if (need) *need = 0;
        return data - where;
 }
index ab65dbbbc027c286df0f9b7a319499329df05f57..0ddcf40aacb135776a704dd7a09bf29b33111a73 100644 (file)
@@ -105,5 +105,9 @@ match 03 66 6f 6f 03 63 6f 6d 00 01 62 02 63 61 00
 encode-dns-label .
 match 00
 
+# From RFC 1035 Section 4.1.4
+encode-dns-label F.ISI.ARPA,FOO.F.ISI.ARPA, ARPA, .
+match 01 46 03 49 53 49 04 41 52 50 41 00 03 46 4f 4f c0 00 c0 06 00
+
 count
-match 48
+match 50