]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ndisc: implement ndisc_option_build_encrypted_dns
authorRonan Pigott <ronan@rjp.ie>
Thu, 4 Apr 2024 02:04:33 +0000 (19:04 -0700)
committerRonan Pigott <ronan@rjp.ie>
Mon, 21 Oct 2024 16:10:20 +0000 (09:10 -0700)
This is only used by the fuzzer so far.

src/libsystemd-network/ndisc-option.c

index afa07c92c53b58260a038c266683316dd5f72e41..1071d98b19077a29a624a6199c0506055536cbca 100644 (file)
@@ -1423,6 +1423,142 @@ static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t
         return ndisc_option_add_encrypted_dns(options, offset, new_res, lifetime);
 }
 
+static int ndisc_option_build_encrypted_dns(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+        int r;
+
+        assert(option);
+        assert(option->type == SD_NDISC_OPTION_ENCRYPTED_DNS);
+        assert(ret);
+
+        size_t off, len, ilen, plen, poff;
+
+        /* Everything up to adn field is required, so we need at least 2*8 bytes */
+        _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8);
+        if (!buf)
+                return -ENOMEM;
+
+        _cleanup_strv_free_ char **alpns = NULL;
+        const sd_dns_resolver *res = option->encrypted_dns.resolver;
+        be32_t lifetime = usec_to_be32_sec(MIN(option->encrypted_dns.lifetime,
+                                               usec_sub_unsigned(option->encrypted_dns.valid_until, timestamp)));
+
+        /* Type (Length field filled in last) */
+        buf[0] = option->type;
+
+        /* Priority */
+        off = 2;
+        unaligned_write_be16(buf + off, res->priority);
+        off += sizeof(be16_t);
+
+        /* Lifetime */
+        memcpy(buf + off, &lifetime, sizeof(be32_t));
+        off += sizeof(be32_t);
+
+        /* ADN */
+        //FIXME can the wire format be longer than this?
+        ilen = strlen(res->auth_name) + 2;
+
+        /* From now on, there isn't guaranteed to be enough space to put each field */
+        if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen))
+                return -ENOMEM;
+
+        r = dns_name_to_wire_format(res->auth_name, buf + off + sizeof(uint16_t), ilen, /* canonical = */ false);
+        if (r < 0)
+                return r;
+        unaligned_write_be16(buf + off, (uint16_t) r);
+        off += sizeof(uint16_t) + r;
+
+        /* ADN-only mode */
+        if (res->n_addrs == 0)
+                goto padding;
+
+        /* addrs */
+        if (size_multiply_overflow(sizeof(struct in6_addr), res->n_addrs))
+                return -ENOMEM;
+
+        ilen = res->n_addrs * sizeof(struct in6_addr);
+        if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen))
+                return -ENOMEM;
+
+        unaligned_write_be16(buf + off, ilen);
+        off += sizeof(uint16_t);
+
+        FOREACH_ARRAY(addr, res->addrs, res->n_addrs) {
+                memcpy(buf + off, &addr->in6, sizeof(struct in6_addr));
+                off += sizeof(struct in6_addr);
+        }
+
+        /* SvcParam, MUST appear in order */
+        poff = off + sizeof(uint16_t);
+
+        /* ALPN */
+        dns_resolver_transports_to_strv(res->transports, &alpns);
+
+        /* res needs to have at least one valid transport */
+        if (strv_isempty(alpns))
+                return -EINVAL;
+
+        plen = 0;
+        STRV_FOREACH(alpn, alpns)
+                plen += sizeof(uint8_t) + strlen(*alpn);
+
+        if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
+                return -ENOMEM;
+
+        unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_ALPN);
+        poff += sizeof(uint16_t);
+        unaligned_write_be16(buf + poff, plen);
+        poff += sizeof(uint16_t);
+
+        STRV_FOREACH(alpn, alpns) {
+                size_t alen = strlen(*alpn);
+                buf[poff++] = alen;
+                memcpy(buf + poff, *alpn, alen);
+                poff += alen;
+        }
+
+        /* port */
+        if (res->port > 0) {
+                plen = 2;
+                if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
+                        return -ENOMEM;
+
+                unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_PORT);
+                poff += sizeof(uint16_t);
+                unaligned_write_be16(buf + poff, plen);
+                poff += sizeof(uint16_t);
+                unaligned_write_be16(buf + poff, res->port);
+                poff += sizeof(uint16_t);
+        }
+
+        /* dohpath */
+        if (res->dohpath) {
+                plen = strlen(res->dohpath);
+                if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
+                        return -ENOMEM;
+
+                unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_DOHPATH);
+                poff += sizeof(uint16_t);
+                unaligned_write_be16(buf + poff, plen);
+                poff += sizeof(uint16_t);
+                memcpy(buf + poff, res->dohpath, plen);
+                poff += plen;
+        }
+
+        unaligned_write_be16(buf + off, LESS_BY(poff, off));
+        off = poff;
+
+padding:
+        len = DIV_ROUND_UP(off, 8);
+        if (!GREEDY_REALLOC(buf, 8*len))
+                return -ENOMEM;
+        memzero(buf + off, 8*len - off);
+
+        buf[1] = len;
+        *ret = TAKE_PTR(buf);
+        return 0;
+}
+
 static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
         assert(options);
         assert(opt);
@@ -1644,6 +1780,10 @@ int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr,
                         r = ndisc_option_build_prefix64(option, timestamp, &buf);
                         break;
 
+                case SD_NDISC_OPTION_ENCRYPTED_DNS:
+                        r = ndisc_option_build_encrypted_dns(option, timestamp, &buf);
+                        break;
+
                 default:
                         continue;
                 }