diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index b436287aee..bc411a4f36 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -12,12 +12,6 @@ #include "sd-forward.h" #include "list.h" -struct sd_dhcp_route { - struct in_addr dst_addr; - struct in_addr gw_addr; - unsigned char dst_prefixlen; -}; - struct sd_dhcp_raw_option { LIST_FIELDS(struct sd_dhcp_raw_option, options); diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 92f7d5ed8c..e0ef4f06fb 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -6,10 +6,14 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#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 "in-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" @@ -17,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) @@ -190,6 +195,24 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, if (size_multiply_overflow(sizeof(struct in_addr), n_addr)) return -ENOBUFS; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (dhcp_message_has_option(message, SD_DHCP_OPTION_SIP_SERVER)) + return -EEXIST; + + size_t len = size_add(1, sizeof(struct in_addr) * n_addr); + if (len == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + buf[0] = 1; /* 'enc' field, 0: domains, 1: addresses */ + memcpy(buf + 1, addr, sizeof(struct in_addr) * n_addr); + + return dhcp_message_append_option(message, code, len, buf); + } + return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); } @@ -208,6 +231,138 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co return dhcp_message_append_option(message, code, strlen(data), data); } +static int dhcp_message_append_option_static_routes(sd_dhcp_message *message, size_t n_routes, const sd_dhcp_route *routes) { + int r; + + assert(message); + assert(routes || n_routes == 0); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ struct in_addr *buf = new(struct in_addr, 2 * n_routes); + if (!buf) + return -ENOMEM; + + size_t count = 0; + FOREACH_ARRAY(route, routes, n_routes) { + uint8_t prefixlen; + r = in4_addr_default_prefixlen(&route->dst_addr, &prefixlen); + if (r < 0) + return r; + + if (prefixlen != route->dst_prefixlen) + return -EINVAL; + + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, prefixlen); + + buf[count++] = dst; + buf[count++] = route->gw_addr; + } + + assert(count == 2 * n_routes); + + return dhcp_message_append_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, 2 * n_routes, buf); +} + +static int dhcp_message_append_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + assert(message); + assert(routes || n_routes == 0); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(1 + 2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, (1 + 2 * sizeof(struct in_addr)) * n_routes); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(route, routes, n_routes) { + if (route->dst_prefixlen > sizeof(struct in_addr) * 8) + return -EINVAL; + + *p++ = route->dst_prefixlen; + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, route->dst_prefixlen); + p = mempcpy(p, &dst, DIV_ROUND_UP(route->dst_prefixlen, 8)); + p = mempcpy(p, &route->gw_addr, sizeof(struct in_addr)); + } + + return dhcp_message_append_option(message, code, p - buf, buf); +} + +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_append_option_static_routes(message, n_routes, routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_append_option_classless_static_routes(message, code, n_routes, routes); + default: + return -EINVAL; + } +} + +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses) { + + assert(message); + assert(prefix); + assert(n_br_addresses == 0 || br_addresses); + + /* See RFC 5969 Section 7.1.1 and dhcp_message_get_option_6rd() below. */ + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_6RD)) + return -EEXIST; + + if (ipv4masklen > 32) + return -EINVAL; + + if (32 - ipv4masklen + prefixlen > 128) + return -EINVAL; + + if (n_br_addresses == 0) + return -EINVAL; + + if (size_multiply_overflow(sizeof(struct in_addr), n_br_addresses)) + return -ENOBUFS; + + size_t buflen = size_add(2 + sizeof(struct in6_addr), sizeof(struct in_addr) * n_br_addresses); + if (buflen == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, buflen); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + *p++ = ipv4masklen; + *p++ = prefixlen; + + struct in6_addr masked = *prefix; + (void) in6_addr_mask(&masked, prefixlen); + p = mempcpy(p, &masked, sizeof(struct in6_addr)); + + memcpy(p, br_addresses, n_br_addresses * sizeof(struct in_addr)); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_6RD, buflen, buf); +} + int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { assert(message); assert(id); @@ -306,6 +461,46 @@ int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, return dhcp_message_append_option_fqdn(message, flags, is_client, hostname); } +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv) { + int r; + + assert(message); + + if (tlv_isempty(tlv)) + return 0; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(tlv, &iov); + if (r < 0) + return r; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + +int dhcp_message_append_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + const struct iovec_wrapper *iovw) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = iovw_merge(iovw, length_size, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return 0; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -382,11 +577,22 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si assert(message); assert(ret_n_addr || !ret_addr); - _cleanup_(iovec_done) struct iovec iov = {}; - r = dhcp_message_get_option_alloc(message, code, &iov); + _cleanup_(iovec_done) struct iovec iov_free = {}; + r = dhcp_message_get_option_alloc(message, code, &iov_free); if (r < 0) return r; + struct iovec iov = iov_free; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (!iovec_is_set(&iov)) + return -EBADMSG; + + if (*(uint8_t*) iov.iov_base != 1) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + iovec_inc(&iov, 1); + } + if (iov.iov_len % sizeof(struct in_addr) != 0) return -EBADMSG; @@ -394,8 +600,17 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si if (n == 0) return -ENODATA; - if (ret_addr) - *ret_addr = (struct in_addr*) TAKE_PTR(iov.iov_base); + if (ret_addr) { + if (code == SD_DHCP_OPTION_SIP_SERVER) { + struct in_addr *addr = newdup(struct in_addr, iov.iov_base, n); + if (!addr) + return -ENOMEM; + *ret_addr = addr; + } else { + *ret_addr = iov.iov_base; + TAKE_STRUCT(iov_free); + } + } if (ret_n_addr) *ret_n_addr = n; return 0; @@ -433,6 +648,210 @@ int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char return 0; } +static int dhcp_message_get_option_static_routes(sd_dhcp_message *message, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + r = dhcp_message_get_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, &n, &addrs); + if (r < 0) + return r; + + if (n % 2 != 0) + return -EBADMSG; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (size_t i = 0; i < n; i += 2) { + struct in_addr dst = addrs[i]; + + uint8_t prefixlen; + if (in4_addr_default_prefixlen(&dst, &prefixlen) < 0) + continue; + + (void) in4_addr_mask(&dst, prefixlen); + + /* RFC 2132 section 5.8: + * The default route (0.0.0.0) is an illegal destination for a static route.*/ + if (in4_addr_is_null(&dst)) + continue; + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = addrs[i + 1], + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +static int dhcp_message_get_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (struct iovec i = iov; iovec_is_set(&i);) { + uint8_t prefixlen = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + + if (prefixlen > 32) + return -EBADMSG; + + size_t n = DIV_ROUND_UP(prefixlen, 8); + if (n > i.iov_len) + return -EBADMSG; + + struct in_addr dst = {}; + memcpy_safe(&dst, i.iov_base, n); + (void) in4_addr_mask(&dst, prefixlen); + iovec_inc(&i, n); + + if (i.iov_len < sizeof(struct in_addr)) + return -EBADMSG; + + struct in_addr gw; + memcpy(&gw, i.iov_base, sizeof(struct in_addr)); + iovec_inc(&i, sizeof(struct in_addr)); + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = gw, + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_get_option_static_routes(message, ret_n_routes, ret_routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_get_option_classless_static_routes(message, code, ret_n_routes, ret_routes); + default: + return -EINVAL; + } +} + +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses) { + + int r; + + assert(message); + assert(ret_n_br_addresses || !ret_br_addresses); + + /* See RFC 5969 Section 7.1.1 */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_6RD, &iov); + if (r < 0) + return r; + + /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ + if (iov.iov_len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || + (iov.iov_len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n_br_addresses = (iov.iov_len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); + assert(n_br_addresses > 0); /* We have already checked that in the above. */ + + const uint8_t *p = iov.iov_base; + + /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses + * within a given 6rd domain. This may be any value between 0 and 32. Any value + * greater than 32 is invalid. */ + uint8_t ipv4masklen = *p++; + if (ipv4masklen > 32) + return -EBADMSG; + + /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the + * purpose of bounds checking by DHCP option processing, the sum of + * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ + uint8_t prefixlen = *p++; + if (32 - ipv4masklen + prefixlen > 128) + return -EBADMSG; + + /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. + * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and + * MUST be initialized to zero by the sender and ignored by the receiver. */ + struct in6_addr prefix; + memcpy(&prefix, p, sizeof(struct in6_addr)); + (void) in6_addr_mask(&prefix, prefixlen); + p += sizeof(struct in6_addr); + + /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */ + if (ret_br_addresses) { + struct in_addr *br_addresses = newdup(struct in_addr, p, n_br_addresses); + if (!br_addresses) + return -ENOMEM; + + *ret_br_addresses = br_addresses; + } + + if (ret_ipv4masklen) + *ret_ipv4masklen = ipv4masklen; + if (ret_prefixlen) + *ret_prefixlen = prefixlen; + if (ret_prefix) + *ret_prefix = prefix; + if (ret_n_br_addresses) + *ret_n_br_addresses = n_br_addresses; + return 0; +} + int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { int r; @@ -579,6 +998,331 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); } +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) { + int r; + + assert(message); + + /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH and SD_DHCP_OPTION_SIP_SERVER. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + const uint8_t *buf = iov.iov_base; + size_t len = iov.iov_len; + + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (len == 0) + return -EBADMSG; + + if (buf[0] != 0) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + len--; + buf++; + } + + _cleanup_strv_free_ char **names = NULL; + size_t n_names = 0; + + _cleanup_free_ char *name = NULL; + size_t n = 0; + + for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) { + uint8_t c = buf[pos++]; + + if (c == 0) { + /* End of name */ + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized)); + if (r < 0) + return r; + + if (next_chunk != 0) + pos = next_chunk; + + next_chunk = 0; + jump_barrier = pos; + + name = mfree(name); + n = 0; + + } else if (c <= 63) { + /* Literal label */ + + const char *label = (const char*) (buf + pos); + pos += c; + + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */ + return -EBADMSG; + + /* Save the current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos + 1; + + pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]); + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (pos >= jump_barrier) + return -EBADMSG; + + jump_barrier = pos; + + } else + return -EBADMSG; + } + + if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */ + return -EBADMSG; + + if (strv_isempty(names)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(names); + return 0; +} + +int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) { + int r; + + assert(message); + assert(!FLAGS_SET(flags, TLV_TEMPORARY)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(flags); + if (!tlv) + return -ENOMEM; + + r = tlv_parse(tlv, &iov); + if (r < 0) + return r; + + if (tlv_isempty(tlv)) + return -ENODATA; + + if (ret) + *ret = TAKE_PTR(tlv); + return 0; +} + +int dhcp_message_get_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + struct iovec_wrapper *ret) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovec_split(&iov, length_size, &iovw); + if (r < 0) + return r; + + if (iovw_isempty(&iovw)) + return -ENODATA; + + if (ret) + *ret = TAKE_STRUCT(iovw); + 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, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index ad16883892..e003d9cc5b 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -39,9 +39,19 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes); +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses); int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv); +int dhcp_message_append_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, const struct iovec_wrapper *iovw); int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -53,11 +63,23 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes); +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses); int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); 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, diff --git a/src/libsystemd-network/dhcp-route.c b/src/libsystemd-network/dhcp-route.c new file mode 100644 index 0000000000..33828276ac --- /dev/null +++ b/src/libsystemd-network/dhcp-route.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-lease.h" + +#include "dhcp-route.h" /* IWYU pragma: keep */ + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->gw_addr; + return 0; +} diff --git a/src/libsystemd-network/dhcp-route.h b/src/libsystemd-network/dhcp-route.h new file mode 100644 index 0000000000..e313c08a58 --- /dev/null +++ b/src/libsystemd-network/dhcp-route.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "in-addr-util.h" /* IWYU pragma: keep */ + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + uint8_t dst_prefixlen; +}; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 500882319e..74e09fac84 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -8,6 +8,7 @@ libsystemd_network_sources = files( 'dhcp-option.c', 'dhcp-packet.c', 'dhcp-protocol.c', + 'dhcp-route.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index c1eaa361af..cc29d9fd1b 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -3,8 +3,10 @@ #include #include +#include "sd-dhcp-lease.h" + #include "alloc-util.h" -#include "dhcp-lease-internal.h" +#include "dhcp-route.h" #include "dns-resolver-internal.h" #include "extract-word.h" #include "hexdecoct.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index bfe4874be5..8003f37fd3 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "dhcp-lease-internal.h" #include "dhcp-option.h" +#include "dhcp-route.h" #include "dns-def.h" #include "dns-domain.h" #include "dns-resolver-internal.h" @@ -1802,27 +1803,3 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { *ret = lease->timezone; return 0; } - -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { - assert_return(route, -EINVAL); - assert_return(destination, -EINVAL); - - *destination = route->dst_addr; - return 0; -} - -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { - assert_return(route, -EINVAL); - assert_return(length, -EINVAL); - - *length = route->dst_prefixlen; - return 0; -} - -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { - assert_return(route, -EINVAL); - assert_return(gateway, -EINVAL); - - *gateway = route->gw_addr; - return 0; -} diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 816edc9fc6..7f5d6669a9 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -6,6 +6,9 @@ #include "dhcp-client-id-internal.h" #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" @@ -68,7 +71,8 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { static void verify_addresses( sd_dhcp_message *m, - size_t n_ntp, const struct in_addr *ntp) { + size_t n_ntp, const struct in_addr *ntp, + size_t n_sip, const struct in_addr *sip) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr)); @@ -81,6 +85,14 @@ static void verify_addresses( ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs)); ASSERT_EQ(n, n_ntp); ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); + + ASSERT_ERROR(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + ASSERT_ERROR(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + + addrs = mfree(addrs); + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_sip); + ASSERT_EQ(memcmp(addrs, sip, sizeof(struct in_addr) * n), 0); } static void verify_string(sd_dhcp_message *m, const char *expected) { @@ -96,6 +108,47 @@ static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) ASSERT_STREQ(s, joined); } +static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_route *expected) { + uint8_t code; + FOREACH_ARGUMENT(code, + SD_DHCP_OPTION_STATIC_ROUTE, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE) { + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n; + ASSERT_OK(dhcp_message_get_option_routes(m, code, &n, &routes)); + ASSERT_EQ(n, n_expected); + for (size_t i = 0; i < n; i++) { + ASSERT_EQ(routes[i].dst_addr.s_addr, expected[i].dst_addr.s_addr); + ASSERT_EQ(routes[i].gw_addr.s_addr, expected[i].gw_addr.s_addr); + ASSERT_EQ(routes[i].dst_prefixlen, expected[i].dst_prefixlen); + } + } +} + +static void verify_6rd( + sd_dhcp_message *m, + uint8_t expected_ipv4masklen, + uint8_t expected_prefixlen, + const struct in6_addr *expected_prefix, + size_t expected_n_br_addresses, + const struct in_addr *expected_br_addresses) { + + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + size_t n_br_addresses; + _cleanup_free_ struct in_addr *br_addresses = NULL; + + ASSERT_OK(dhcp_message_get_option_6rd(m, NULL, NULL, NULL, NULL, NULL)); + ASSERT_OK(dhcp_message_get_option_6rd(m, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses)); + ASSERT_EQ(ipv4masklen, expected_ipv4masklen); + ASSERT_EQ(prefixlen, expected_prefixlen); + ASSERT_TRUE(in6_addr_equal(&prefix, expected_prefix)); + ASSERT_EQ(n_br_addresses, expected_n_br_addresses); + ASSERT_EQ(memcmp(br_addresses, expected_br_addresses, sizeof(struct in_addr) * n_br_addresses), 0); +} + static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) { sd_dhcp_client_id id = {}; ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); @@ -114,6 +167,26 @@ static void verify_hostname(sd_dhcp_message *m, const char *expected) { ASSERT_STREQ(s, expected); } +static void verify_sub_tlv(sd_dhcp_message *m, TLV *expected) { + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + TLV_DHCP4_SUBOPTION, + &tlv)); + + _cleanup_(iovec_done) struct iovec iov = {}, iov_expected = {}; + ASSERT_OK(tlv_build(tlv, &iov)); + ASSERT_OK(tlv_build(expected, &iov_expected)); + ASSERT_TRUE(iovec_equal(&iov, &iov_expected)); +} + +static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_wrapper *expected) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &iovw)); + ASSERT_TRUE(iovw_equal(&iovw, expected)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -140,6 +213,43 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + /* 192.0.2.17 - 20 */ + struct in_addr sip[4] = { + { .s_addr = htobe32(0xC0000211) }, + { .s_addr = htobe32(0xC0000212) }, + { .s_addr = htobe32(0xC0000213) }, + { .s_addr = htobe32(0xC0000214) }, + }; + + struct sd_dhcp_route routes[3] = { + { /* class A: 10.0.0.0/8 -> 192.0.2.33 */ + .dst_addr = { .s_addr = htobe32(0x0A000000) }, + .gw_addr = { .s_addr = htobe32(0xC0000221) }, + .dst_prefixlen = 8, + }, + { /* class B: 172.16.0.0/16 -> 192.0.2.34 */ + .dst_addr = { .s_addr = htobe32(0xAC100000) }, + .gw_addr = { .s_addr = htobe32(0xC0000222) }, + .dst_prefixlen = 16, + }, + { /* class C: 192.168.0.0/24 -> 192.0.2.35 */ + .dst_addr = { .s_addr = htobe32(0xC0A80000) }, + .gw_addr = { .s_addr = htobe32(0xC0000223) }, + .dst_prefixlen = 24, + }, + }; + + uint8_t sixrd_ipv4masklen = 24; + uint8_t sixrd_prefixlen = 64; + struct in6_addr sixrd_prefix = { + .s6_addr = { 0x20, 0x01, 0x0d, 0xb8, }, + }; + struct in_addr sixrd_br_addresses[3] = { + { .s_addr = htobe32(0xC0000231) }, + { .s_addr = htobe32(0xC0000232) }, + { .s_addr = htobe32(0xC0000233) }, + }; + sd_dhcp_client_id id = { .raw = { 1, 3, 3, 3, 3, 3, 3, }, .size = 7, @@ -153,6 +263,23 @@ TEST(dhcp_message) { const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + _cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}, user_class_1 = {}, user_class_2 = {}; + FOREACH_STRING(s, "hoge", "foo", "bar") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_1, s, strlen(s))); + } + FOREACH_STRING(s, "aaa", "bbb", "ccc") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_2, s, strlen(s))); + } + + _cleanup_(tlv_done) TLV vendor = TLV_INIT(TLV_DHCP4_SUBOPTION); + for (unsigned i = 0; i < 3; i++) { + uint8_t buf[255]; + memset(buf, 42 + i, sizeof(buf)); + ASSERT_OK(tlv_append(&vendor, i + 1, 255, buf)); + } + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -205,7 +332,10 @@ TEST(dhcp_message) { /* multiple addresses */ ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0])); ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); - verify_addresses(m, ELEMENTSOF(ntp), ntp); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip)); + ASSERT_ERROR(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, 0, NULL)); + verify_addresses(m, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); /* string */ ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); @@ -220,6 +350,22 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); verify_string(m, vendor_class); + /* routes */ + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + verify_routes(m, ELEMENTSOF(routes), routes); + + /* 6rd */ + ASSERT_ERROR(dhcp_message_append_option_6rd(m, 33, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, 127, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 0, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, SIZE_MAX, sixrd_br_addresses), ENOBUFS); + ASSERT_OK(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses)); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_6RD, ELEMENTSOF(sixrd_br_addresses) - 1, sixrd_br_addresses + 1)); + verify_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); + /* client ID */ ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); verify_client_id(m, &id); @@ -243,6 +389,16 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname)); verify_hostname(m, hostname); + /* vendor specific */ + ASSERT_OK(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor)); + ASSERT_ERROR(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor), EEXIST); + verify_sub_tlv(m, &vendor); + + /* user class */ + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_1)); + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_2)); + verify_length_prefixed_data(m, &user_class); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -269,11 +425,15 @@ TEST(dhcp_message) { verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); - verify_addresses(m2, ELEMENTSOF(ntp), ntp); + verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); verify_string(m2, vendor_class); + verify_routes(m2, ELEMENTSOF(routes), routes); + verify_6rd(m2, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); verify_client_id(m2, &id); verify_prl(m2, prl); verify_hostname(m2, hostname); + verify_sub_tlv(m2, &vendor); + verify_length_prefixed_data(m2, &user_class); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; @@ -281,4 +441,332 @@ TEST(dhcp_message) { ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); } +static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + strv = strv_free(strv); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_SIP_SERVER); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, (len + 1) / 2, sip)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1 - (len + 1) / 2, sip + (len + 1) / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); +} + +static void test_domains_fail(size_t len, const uint8_t *data) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG); +} + +TEST(domains) { + /* simple */ + static uint8_t simple[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 4, 'h', 'o', 'g', 'e', + 3, 'f', 'o', 'o', + 0, + }; + test_domains_one(ELEMENTSOF(simple), simple, + STRV_MAKE("example.com", "hoge.foo")); + + /* compressed */ + static uint8_t compressed[] = { + 4, 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 5, + 3, 'b', 'a', 'r', + 0xc0, 18, + 3, 'b', 'a', 'z', + 0xc0, 0, + }; + test_domains_one(ELEMENTSOF(compressed), compressed, + STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com")); + + /* invalid pointer */ + static uint8_t invalid[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 0xff, + }; + test_domains_fail(ELEMENTSOF(invalid), invalid); + + /* forward pointer */ + static uint8_t forward[] = { + 4, 'h', 'o', 'g', 'e', + 0xc0, 11, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + }; + test_domains_fail(ELEMENTSOF(forward), forward); + + /* infinite loop */ + static uint8_t loop1[] = { + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop1), loop1); + + static uint8_t loop2[] = { + 0xc0, 0x02, + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop2), loop2); + + static uint8_t loop3[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop3), loop3); + + /* unterminated */ + static uint8_t unterminated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + }; + test_domains_fail(ELEMENTSOF(unterminated), unterminated); + + /* truncated label */ + static uint8_t truncated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + }; + 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); diff --git a/src/network/test-network.c b/src/network/test-network.c index ab3a86e10b..c78c50e3a7 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -3,7 +3,7 @@ #include #include "alloc-util.h" -#include "dhcp-lease-internal.h" +#include "dhcp-route.h" #include "hashmap.h" #include "hostname-setup.h" #include "network-internal.h" diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 3aa67c3fa5..2dbee5450d 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -86,9 +86,9 @@ int sd_dhcp_lease_get_6rd( size_t *ret_n_br_addresses); int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease); -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref);