dhcp-message: introduce dhcp_message_get_option_dnr()
This is for DHCP option 162 (DNR).
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#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"
|
||||
@@ -20,6 +21,7 @@
|
||||
#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)
|
||||
@@ -1164,6 +1166,163 @@ int dhcp_message_get_option_length_prefixed_data(
|
||||
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,
|
||||
|
||||
@@ -79,6 +79,7 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret);
|
||||
int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret);
|
||||
int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret);
|
||||
int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret);
|
||||
int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers);
|
||||
|
||||
int dhcp_message_parse(
|
||||
const struct iovec *iov,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#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"
|
||||
@@ -575,4 +577,196 @@ TEST(domains) {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user