#include "dhcp-route.h"
#include "dns-def.h"
#include "dns-domain.h"
+#include "dns-resolver-internal.h"
#include "errno-util.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
#include "set.h"
#include "sort-util.h"
#include "string-util.h"
+#include "unaligned.h"
static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) {
if (!message)
return 0;
}
+static int parse_dnr_one(const struct iovec *iov, sd_dns_resolver *ret) {
+ int r;
+
+ assert(iovec_is_set(iov));
+ assert(ret);
+
+ _cleanup_(sd_dns_resolver_done) sd_dns_resolver resolver = {};
+ struct iovec i = *iov;
+
+ /* service priority */
+ if (i.iov_len < sizeof(be16_t))
+ return -EBADMSG;
+
+ resolver.priority = unaligned_read_be16(i.iov_base);
+ iovec_inc(&i, sizeof(be16_t));
+
+ /* RFC 9460 section 2.4.1:
+ * When SvcPriority is 0, the SVCB record is in AliasMode.
+ *
+ * We do not support the alias mode. But the entry itself is not invalid. */
+ if (resolver.priority == 0) {
+ *ret = (sd_dns_resolver) {};
+ return 0;
+ }
+
+ /* authentication domain name */
+ if (!iovec_is_set(&i))
+ return -EBADMSG;
+
+ size_t name_len = *(uint8_t*) i.iov_base;
+ iovec_inc(&i, 1);
+ if (i.iov_len < name_len)
+ return -EBADMSG;
+
+ const uint8_t *name_buf = i.iov_base;
+ iovec_inc(&i, name_len);
+
+ r = dns_name_from_wire_format(&name_buf, &name_len, &resolver.auth_name);
+ if (r < 0)
+ return r;
+ if (r == 0 || name_len != 0)
+ return -EBADMSG;
+
+ r = dns_name_is_valid_ldh(resolver.auth_name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EBADMSG;
+
+ if (dns_name_is_root(resolver.auth_name))
+ return -EBADMSG;
+
+ /* RFC9463 section 3.1.6: In ADN-only mode, server omits everything after the ADN.
+ *
+ * We don't support these, but they are not invalid. */
+ if (!iovec_is_set(&i)) {
+ *ret = (sd_dns_resolver) {};
+ return 0;
+ }
+
+ /* IPv4 addresses */
+ size_t n = *(uint8_t*) i.iov_base;
+ iovec_inc(&i, 1);
+
+ if (n % sizeof(struct in_addr) != 0)
+ return -EBADMSG;
+
+ n /= sizeof(struct in_addr);
+
+ /* RFC9463 section 3.1.8: option MUST include at least one valid IP addr */
+ if (n == 0)
+ return -EBADMSG;
+
+ resolver.family = AF_INET;
+ resolver.n_addrs = n;
+ resolver.addrs = new(union in_addr_union, n);
+ if (!resolver.addrs)
+ return -ENOMEM;
+
+ for (size_t j = 0; j < n; j++) {
+ if (i.iov_len < sizeof(struct in_addr))
+ return -EBADMSG;
+
+ struct in_addr a;
+ memcpy(&a, i.iov_base, sizeof(struct in_addr));
+ iovec_inc(&i, sizeof(struct in_addr));
+
+ /* RFC9463 section 5.2: client MUST discard multicast and host loopback addresses */
+ if (in4_addr_is_multicast(&a) || in4_addr_is_localhost(&a))
+ return -EBADMSG;
+
+ resolver.addrs[j] = (union in_addr_union) { .in = a };
+ }
+
+ /* service params */
+ r = dnr_parse_svc_params(i.iov_base, i.iov_len, &resolver);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* We can't use this record, but it is not invalid. */
+ *ret = (sd_dns_resolver) {};
+ return 0;
+ }
+
+ *ret = TAKE_STRUCT(resolver);
+ return 1;
+}
+
+int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers) {
+ int r;
+
+ assert(message);
+ assert(ret_n_resolvers || !ret_resolvers);
+
+ /* See RFC 9463 section 5.1 */
+
+ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
+ r = dhcp_message_get_option_length_prefixed_data(message, SD_DHCP_OPTION_V4_DNR, /* length_size= */ 2, &iovw);
+ if (r < 0)
+ return r;
+
+ sd_dns_resolver *resolvers = NULL;
+ size_t n_resolvers = 0;
+ CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array);
+ FOREACH_ARRAY(i, iovw.iovec, iovw.count) {
+ _cleanup_(sd_dns_resolver_done) sd_dns_resolver dnr = {};
+ r = parse_dnr_one(i, &dnr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (!ret_resolvers) {
+ n_resolvers++;
+ continue;
+ }
+
+ if (!GREEDY_REALLOC(resolvers, n_resolvers + 1))
+ return -ENOMEM;
+
+ resolvers[n_resolvers++] = TAKE_STRUCT(dnr);
+ }
+
+ if (n_resolvers == 0) /* no supported resolver */
+ return -ENODATA;
+
+ if (ret_resolvers) {
+ /* Sort the resolvers with their priorities. */
+ typesafe_qsort(resolvers, n_resolvers, dns_resolver_prio_compare);
+ *ret_resolvers = TAKE_PTR(resolvers);
+ }
+ if (ret_n_resolvers)
+ *ret_n_resolvers = n_resolvers;
+
+ return 0;
+}
+
static int dhcp_message_verify_header(
const struct iovec *iov,
uint8_t op,
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "dhcp-route.h"
+#include "dns-packet.h"
+#include "dns-resolver-internal.h"
#include "ether-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
test_domains_fail(ELEMENTSOF(truncated), truncated);
}
+TEST(dnr) {
+ _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
+ ASSERT_OK(dhcp_message_new(&m));
+
+ sd_dns_resolver *resolvers = NULL;
+ size_t n_resolvers = 0;
+ CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array);
+
+ static uint8_t data[] = {
+ /* Instance 1 */
+ /* length */
+ 0, 78,
+ /* priority */
+ 0, 1,
+ /* authentication domain name */
+ 22,
+ 8, 'r', 'e', 's', 'o', 'l', 'v', 'e', 'r',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ /* addresses */
+ 8,
+ 192, 0, 2, 1,
+ 192, 0, 2, 2,
+ /* service parameters */
+ /* ALPN */
+ 0, DNS_SVC_PARAM_KEY_ALPN,
+ 0, 14,
+ 2, 'h', '2',
+ 2, 'h', '3',
+ 3, 'd', 'o', 't',
+ 3, 'd', 'o', 'q',
+ /* port */
+ 0, DNS_SVC_PARAM_KEY_PORT,
+ 0, 2,
+ 0, 42,
+ /* DoH path*/
+ 0, DNS_SVC_PARAM_KEY_DOHPATH,
+ 0, 16,
+ '/', 'd', 'n', 's', '-', 'q', 'u', 'e', 'r', 'y', '{', '?', 'd', 'n', 's', '}',
+
+ /* Instance 2 */
+ /* length */
+ 0, 44,
+ /* priority */
+ 0, 2,
+ /* authentication domain name */
+ 22,
+ 8, 'h', 'o', 'g', 'e', 'h', 'o', 'g', 'e',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ /* addresses */
+ 4,
+ 192, 0, 2, 3,
+ /* service parameters */
+ /* ALPN */
+ 0, DNS_SVC_PARAM_KEY_ALPN,
+ 0, 4,
+ 3, 'd', 'o', 't',
+ /* port */
+ 0, DNS_SVC_PARAM_KEY_PORT,
+ 0, 2,
+ 0, 33,
+
+ /* Instance 3 (no address, ignored) */
+ /* length */
+ 0, 20,
+ /* priority */
+ 0, 3,
+ /* authentication domain name */
+ 17,
+ 3, 'f', 'o', 'o',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+
+ /* Instance 4 (unknown alpn, ignored) */
+ /* length */
+ 0, 37,
+ /* priority */
+ 0, 4,
+ /* authentication domain name */
+ 20,
+ 6, 'b', 'a', 'r', 'b', 'a', 'z',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ /* addresses */
+ 4,
+ 192, 0, 2, 4,
+ /* service parameters */
+ /* ALPN */
+ 0, DNS_SVC_PARAM_KEY_ALPN,
+ 0, 5,
+ 4, 'h', 'o', 'g', 'e',
+ };
+
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(data), data));
+ ASSERT_OK(dhcp_message_get_option_dnr(m, &n_resolvers, &resolvers));
+ ASSERT_EQ(n_resolvers, 2u);
+
+ ASSERT_EQ(resolvers[0].priority, 1u);
+ ASSERT_STREQ(resolvers[0].auth_name, "resolver.example.com");
+ ASSERT_EQ(resolvers[0].family, AF_INET);
+ ASSERT_EQ(resolvers[0].n_addrs, 2u);
+ ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[0]), "192.0.2.1");
+ ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[1]), "192.0.2.2");
+ ASSERT_EQ(resolvers[0].transports, SD_DNS_ALPN_HTTP_2_TLS | SD_DNS_ALPN_HTTP_3 | SD_DNS_ALPN_DOT | SD_DNS_ALPN_DOQ);
+ ASSERT_EQ(resolvers[0].port, 42u);
+ ASSERT_STREQ(resolvers[0].dohpath, "/dns-query{?dns}");
+
+ ASSERT_EQ(resolvers[1].priority, 2u);
+ ASSERT_STREQ(resolvers[1].auth_name, "hogehoge.example.com");
+ ASSERT_EQ(resolvers[1].family, AF_INET);
+ ASSERT_EQ(resolvers[1].n_addrs, 1u);
+ ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[1].family, &resolvers[1].addrs[0]), "192.0.2.3");
+ ASSERT_EQ(resolvers[1].transports, SD_DNS_ALPN_DOT);
+ ASSERT_EQ(resolvers[1].port, 33u);
+ ASSERT_NULL(resolvers[1].dohpath);
+
+ /* missing DoH path */
+ static uint8_t invalid[] = {
+ /* length */
+ 0, 35,
+ /* priority */
+ 0, 5,
+ /* authentication domain name */
+ 20,
+ 6, 'b', 'a', 'r', 'b', 'a', 'z',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ /* addresses */
+ 4,
+ 192, 0, 2, 5,
+ /* service parameters */
+ /* ALPN */
+ 0, DNS_SVC_PARAM_KEY_ALPN,
+ 0, 3,
+ 2, 'h', '2',
+ };
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid), invalid));
+ ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG);
+
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR);
+
+ /* missing ALPN */
+ static uint8_t invalid2[] = {
+ /* length */
+ 0, 28,
+ /* priority */
+ 0, 6,
+ /* authentication domain name */
+ 20,
+ 6, 'b', 'a', 'r', 'b', 'a', 'z',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0,
+ /* addresses */
+ 4,
+ 192, 0, 2, 6,
+ };
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid2), invalid2));
+ ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG);
+
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR);
+
+ /* truncated domain name */
+ static uint8_t invalid3[] = {
+ /* length */
+ 0, 34,
+ /* priority */
+ 0, 7,
+ /* authentication domain name */
+ 18,
+ 6, 'b', 'a', 'r', 'b', 'a', 'z',
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'c', 'o',
+ /* addresses */
+ 4,
+ 192, 0, 2, 7,
+ /* service parameters */
+ /* ALPN */
+ 0, DNS_SVC_PARAM_KEY_ALPN,
+ 0, 4,
+ 3, 'd', 'o', 't',
+ };
+ ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid3), invalid3));
+ ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EMSGSIZE);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);