summaryrefslogtreecommitdiffstats
path: root/plugins/shared/edns0_ecs.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/shared/edns0_ecs.c')
-rw-r--r--plugins/shared/edns0_ecs.c222
1 files changed, 222 insertions, 0 deletions
diff --git a/plugins/shared/edns0_ecs.c b/plugins/shared/edns0_ecs.c
new file mode 100644
index 0000000..2b2e1fa
--- /dev/null
+++ b/plugins/shared/edns0_ecs.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2018-2023, OARC, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define DNS_MSG_HDR_SZ 12
+#define RFC1035_MAXLABELSZ 63
+#define nptohs(p) ((((uint8_t*)(p))[0] << 8) | ((uint8_t*)(p))[1])
+
+static int rfc1035NameSkip(const u_char* buf, size_t sz, off_t* off)
+{
+ unsigned char c;
+ size_t len;
+ /*
+ * loop_detect[] tracks which position in the DNS message it has
+ * jumped to so it can't jump to the same twice, aka loop
+ */
+ static unsigned char loop_detect[0x3FFF] = { 0 };
+ do {
+ if ((*off) >= sz)
+ break;
+ c = *(buf + (*off));
+ if (c > 191) {
+ /* blasted compression */
+ int rc;
+ unsigned short s;
+ off_t ptr, loop_ptr;
+ s = nptohs(buf + (*off));
+ (*off) += sizeof(s);
+ /* Sanity check */
+ if ((*off) >= sz)
+ return 1; /* message too short */
+ ptr = s & 0x3FFF;
+ /* Make sure the pointer is inside this message */
+ if (ptr >= sz)
+ return 2; /* bad compression ptr */
+ if (ptr < DNS_MSG_HDR_SZ)
+ return 2; /* bad compression ptr */
+ if (loop_detect[ptr])
+ return 4; /* compression loop */
+ loop_detect[(loop_ptr = ptr)] = 1;
+
+ rc = rfc1035NameSkip(buf, sz, &ptr);
+
+ loop_detect[loop_ptr] = 0;
+ return rc;
+ } else if (c > RFC1035_MAXLABELSZ) {
+ /*
+ * "(The 10 and 01 combinations are reserved for future use.)"
+ */
+ return 3; /* reserved label/compression flags */
+ } else {
+ (*off)++;
+ len = (size_t)c;
+ if (len == 0)
+ break;
+ if ((*off) + len > sz)
+ return 4; /* message is too short */
+ (*off) += len;
+ }
+ } while (c > 0);
+ return 0;
+}
+
+static off_t skip_question(const u_char* buf, int len, off_t offset)
+{
+ if (rfc1035NameSkip(buf, len, &offset))
+ return 0;
+ if (offset + 4 > len)
+ return 0;
+ offset += 4;
+ return offset;
+}
+
+static off_t skip_rr(const u_char* buf, int len, off_t offset)
+{
+ if (rfc1035NameSkip(buf, len, &offset))
+ return 0;
+ if (offset + 10 > len)
+ return 0;
+ unsigned short us = nptohs(buf + offset + 8);
+ offset += 10;
+ if (offset + us > len)
+ return 0;
+ offset += us;
+ return offset;
+}
+
+#define EDNS0_TYPE_ECS 8
+
+typedef void (*edns0_ecs_cb)(int family, u_char* buf, size_t len);
+
+static void process_edns0_options(u_char* buf, int len, edns0_ecs_cb cb)
+{
+ unsigned short edns0_type;
+ unsigned short edns0_len;
+ off_t offset = 0;
+
+ while (len >= 4) {
+ edns0_type = nptohs(buf + offset);
+ edns0_len = nptohs(buf + offset + 2);
+ if (len < 4 + edns0_len)
+ break;
+ if (edns0_type == EDNS0_TYPE_ECS) {
+ if (edns0_len < 5)
+ break;
+ if (cb)
+ cb(nptohs(buf + offset + 4), buf + offset + 8, edns0_len - 4);
+ }
+ offset += 4 + edns0_len;
+ len -= 4 + edns0_len;
+ }
+}
+
+#define T_OPT 41
+
+static off_t grok_additional_for_opt_rr(u_char* buf, int len, off_t offset, edns0_ecs_cb cb)
+{
+ unsigned short us;
+ /*
+ * OPT RR for EDNS0 MUST be 0 (root domain), so if the first byte of
+ * the name is anything it can't be a valid EDNS0 record.
+ */
+ if (*(buf + offset)) {
+ if (rfc1035NameSkip(buf, len, &offset))
+ return 0;
+ if (offset + 10 > len)
+ return 0;
+ } else {
+ offset++;
+ if (offset + 10 > len)
+ return 0;
+ if (nptohs(buf + offset) == T_OPT) {
+ u_char version = *(buf + offset + 5);
+ us = nptohs(buf + offset + 8); // rd len
+ offset += 10;
+ if (offset + us > len)
+ return 0;
+ if (!version && us > 0)
+ process_edns0_options(buf + offset, us, cb);
+ offset += us;
+ return offset;
+ }
+ }
+ /* get rdlength */
+ us = nptohs(buf + offset + 8);
+ offset += 10;
+ if (offset + us > len)
+ return 0;
+ offset += us;
+ return offset;
+}
+
+static void parse_for_edns0_ecs(u_char* payload, size_t payloadlen, edns0_ecs_cb cb)
+{
+ off_t offset;
+ int qdcount, ancount, nscount, arcount;
+
+ qdcount = nptohs(payload + 4);
+ ancount = nptohs(payload + 6);
+ nscount = nptohs(payload + 8);
+ arcount = nptohs(payload + 10);
+
+ offset = DNS_MSG_HDR_SZ;
+
+ while (qdcount > 0 && offset < payloadlen) {
+ if (!(offset = skip_question(payload, payloadlen, offset))) {
+ return;
+ }
+ qdcount--;
+ }
+
+ while (ancount > 0 && offset < payloadlen) {
+ if (!(offset = skip_rr(payload, payloadlen, offset))) {
+ return;
+ }
+ ancount--;
+ }
+
+ while (nscount > 0 && offset < payloadlen) {
+ if (!(offset = skip_rr(payload, payloadlen, offset))) {
+ return;
+ }
+ nscount--;
+ }
+
+ while (arcount > 0 && offset < payloadlen) {
+ if (!(offset = grok_additional_for_opt_rr(payload, payloadlen, offset, cb))) {
+ return;
+ }
+ arcount--;
+ }
+} \ No newline at end of file