diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index a2e5106b42..e4340f9957 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -12,7 +12,7 @@ sources = files( 'icmp6-util.c', 'lldp-neighbor.c', 'lldp-network.c', - 'ndisc-protocol.c', + 'ndisc-option.c', 'network-common.c', 'network-internal.c', 'sd-dhcp-client-id.c', diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c new file mode 100644 index 0000000000..380e71e764 --- /dev/null +++ b/src/libsystemd-network/ndisc-option.c @@ -0,0 +1,855 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "dns-domain.h" +#include "ether-addr-util.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "missing_network.h" +#include "ndisc-option.h" +#include "network-common.h" +#include "strv.h" +#include "unaligned.h" + +/* RFC does not say anything about the maximum number of options, but let's limit the number of options for + * safety. Typically, the number of options in an ICMPv6 message should be only a few. */ +#define MAX_OPTIONS 128 + +int ndisc_option_parse( + ICMP6Packet *p, + size_t offset, + uint8_t *ret_type, + size_t *ret_len, + const uint8_t **ret_opt) { + + assert(p); + + if (offset == p->raw_size) + return -ESPIPE; /* end of the packet */ + + if (offset > p->raw_size) + return -EBADMSG; + + if (p->raw_size - offset < sizeof(struct nd_opt_hdr)) + return -EBADMSG; + + assert_cc(alignof(struct nd_opt_hdr) == 1); + const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset); + if (hdr->nd_opt_len == 0) + return -EBADMSG; + + size_t len = hdr->nd_opt_len * 8; + if (p->raw_size - offset < len) + return -EBADMSG; + + if (ret_type) + *ret_type = hdr->nd_opt_type; + if (ret_len) + *ret_len = len; + if (ret_opt) + *ret_opt = p->raw_packet + offset; + + return 0; +} + +static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) { + sd_ndisc_option *p = new0(sd_ndisc_option, 1); /* use new0() here to make the fuzzers silent. */ + if (!p) + return NULL; + + /* As the same reason in the above, do not use the structured initializer here. */ + p->type = type; + p->offset = offset; + + return p; +} + +static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) { + if (!rdnss) + return; + + free(rdnss->addresses); +} + +static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) { + if (!dnssl) + return; + + strv_free(dnssl->domains); +} + +sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { + if (!option) + return NULL; + + switch (option->type) { + case SD_NDISC_OPTION_RDNSS: + ndisc_rdnss_done(&option->rdnss); + break; + + case SD_NDISC_OPTION_DNSSL: + ndisc_dnssl_done(&option->dnssl); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + free(option->captive_portal); + break; + } + + return mfree(option); +} + +static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_option *y) { + int r; + + assert(x); + assert(y); + + r = CMP(x->type, y->type); + if (r != 0) + return r; + + switch (x->type) { + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + case SD_NDISC_OPTION_REDIRECTED_HEADER: + case SD_NDISC_OPTION_MTU: + case SD_NDISC_OPTION_FLAGS_EXTENSION: + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + /* These options cannot be specified multiple times. */ + return 0; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + /* Should not specify the same prefix multiple times. */ + r = CMP(x->prefix.prefixlen, y->prefix.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->prefix.address, &y->prefix.address, sizeof(struct in6_addr)); + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = CMP(x->route.prefixlen, y->route.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->route.address, &y->route.address, sizeof(struct in6_addr)); + + case SD_NDISC_OPTION_PREF64: + r = CMP(x->prefix64.prefixlen, y->prefix64.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->prefix64.prefix, &y->prefix64.prefix, sizeof(struct in6_addr)); + + default: + /* DNSSL, RDNSS, and other unsupported options can be specified multiple times. */ + return trivial_compare_func(x, y); + } +} + +static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash *state) { + assert(option); + assert(state); + + siphash24_compress_typesafe(option->type, state); + + switch (option->type) { + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + case SD_NDISC_OPTION_REDIRECTED_HEADER: + case SD_NDISC_OPTION_MTU: + case SD_NDISC_OPTION_FLAGS_EXTENSION: + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + siphash24_compress_typesafe(option->prefix.prefixlen, state); + siphash24_compress_typesafe(option->prefix.address, state); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + siphash24_compress_typesafe(option->route.prefixlen, state); + siphash24_compress_typesafe(option->route.address, state); + break; + + case SD_NDISC_OPTION_PREF64: + siphash24_compress_typesafe(option->prefix64.prefixlen, state); + siphash24_compress_typesafe(option->prefix64.prefix, state); + break; + + default: + trivial_hash_func(option, state); + } +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_option_hash_ops, + sd_ndisc_option, + ndisc_option_hash_func, + ndisc_option_compare_func, + ndisc_option_free); + +static int ndisc_option_consume(Set **options, sd_ndisc_option *p) { + if (set_size(*options) >= MAX_OPTIONS) { + ndisc_option_free(p); + return -ETOOMANYREFS; /* recognizable error code */ + } + + return set_ensure_consume(options, &ndisc_option_hash_ops, p); +} + +int ndisc_option_add_link_layer_address(Set **options, uint8_t opt, size_t offset, const struct ether_addr *mac) { + assert(options); + assert(IN_SET(opt, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + assert(mac); + + if (ether_addr_is_null(mac)) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(opt, offset); + if (!p) + return -ENOMEM; + + p->mac = *mac; + + return set_ensure_consume(options, &ndisc_option_hash_ops, p); +} + +static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len != sizeof(struct ether_addr) + 2) + return -EBADMSG; + + if (!IN_SET(opt[0], SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)) + return -EBADMSG; + + struct ether_addr mac; + memcpy(&mac, opt + 2, sizeof(struct ether_addr)); + + return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac); +} + +int ndisc_option_add_prefix( + Set **options, + size_t offset, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime) { + + assert(options); + assert(address); + + if (prefixlen > 128) + return -EINVAL; + + if (in6_addr_is_link_local(address)) + return -EINVAL; + + if (preferred_lifetime > valid_lifetime) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_PREFIX_INFORMATION, offset); + if (!p) + return -ENOMEM; + + p->prefix = (sd_ndisc_prefix) { + .flags = flags, + .prefixlen = prefixlen, + .address = *address, + .valid_lifetime = valid_lifetime, + .preferred_lifetime = preferred_lifetime, + }; + + in6_addr_mask(&p->prefix.address, p->prefix.prefixlen); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, const uint8_t *opt) { + const struct nd_opt_prefix_info *pi = (const struct nd_opt_prefix_info*) ASSERT_PTR(opt); + + assert(options); + + if (len != sizeof(struct nd_opt_prefix_info)) + return -EBADMSG; + + if (pi->nd_opt_pi_type != SD_NDISC_OPTION_PREFIX_INFORMATION) + return -EBADMSG; + + usec_t valid = be32_sec_to_usec(pi->nd_opt_pi_valid_time, /* max_as_infinity = */ true); + usec_t pref = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true); + + /* We only support 64 bits interface identifier for addrconf. */ + uint8_t flags = pi->nd_opt_pi_flags_reserved; + if (FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO) && pi->nd_opt_pi_prefix_len != 64) + flags &= ~ND_OPT_PI_FLAG_AUTO; + + return ndisc_option_add_prefix(options, offset, flags, pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, valid, pref); +} + +int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) { + assert(options); + assert(hdr); + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_REDIRECTED_HEADER, offset); + if (!p) + return -ENOMEM; + + /* For safety, here we copy only IPv6 header. */ + memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr)); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_redirected_header(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_REDIRECTED_HEADER) + return -EBADMSG; + + return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr))); +} + +int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) { + assert(options); + + if (mtu < IPV6_MIN_MTU) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_MTU, offset); + if (!p) + return -ENOMEM; + + p->mtu = mtu; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, const uint8_t *opt) { + const struct nd_opt_mtu *pm = (const struct nd_opt_mtu*) ASSERT_PTR(opt); + + assert(options); + + if (len != sizeof(struct nd_opt_mtu)) + return -EBADMSG; + + if (pm->nd_opt_mtu_type != SD_NDISC_OPTION_MTU) + return -EBADMSG; + + return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu)); +} + +int ndisc_option_add_route( + Set **options, + size_t offset, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime) { + + assert(options); + assert(prefix); + + if (prefixlen > 128) + return -EINVAL; + + /* RFC 4191 section 2.3 + * Prf (Route Preference) + * 2-bit signed integer. The Route Preference indicates whether to prefer the router associated with + * this prefix over others, when multiple identical prefixes (for different routers) have been + * received. If the Reserved (10) value is received, the Route Information Option MUST be ignored. */ + if (!IN_SET(preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH)) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_ROUTE_INFORMATION, offset); + if (!p) + return -ENOMEM; + + p->route = (sd_ndisc_route) { + .preference = preference, + .prefixlen = prefixlen, + .address = *prefix, + .lifetime = lifetime, + }; + + in6_addr_mask(&p->route.address, p->route.prefixlen); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (!IN_SET(len, 1*8, 2*8, 3*8)) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_ROUTE_INFORMATION) + return -EBADMSG; + + uint8_t prefixlen = opt[2]; + if (prefixlen > 128) + return -EBADMSG; + + if (len < (size_t) (DIV_ROUND_UP(prefixlen, 64) + 1) * 8) + return -EBADMSG; + + uint8_t preference = (opt[3] >> 3) & 0x03; + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + + struct in6_addr prefix; + memcpy(&prefix, opt + 8, len - 8); + in6_addr_mask(&prefix, prefixlen); + + return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime); +} + +int ndisc_option_add_rdnss( + Set **options, + size_t offset, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime) { + + assert(options); + assert(addresses); + + if (n_addresses == 0) + return -EINVAL; + + _cleanup_free_ struct in6_addr *addrs = newdup(struct in6_addr, addresses, n_addresses); + if (!addrs) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_RDNSS, offset); + if (!p) + return -ENOMEM; + + p->rdnss = (sd_ndisc_rdnss) { + .n_addresses = n_addresses, + .addresses = TAKE_PTR(addrs), + .lifetime = lifetime, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < 8 + sizeof(struct in6_addr) || (len % sizeof(struct in6_addr)) != 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_RDNSS) + return -EBADMSG; + + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + size_t n_addrs = len / sizeof(struct in6_addr); + + return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime); +} + +int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) { + assert(options); + + if ((flags & UINT64_C(0x00ffffffffffff00)) != flags) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_FLAGS_EXTENSION, offset); + if (!p) + return -ENOMEM; + + p->extended_flags = flags; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len != 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_FLAGS_EXTENSION) + return -EBADMSG; + + uint64_t flags = (unaligned_read_be64(opt) & UINT64_C(0xffffffffffff0000)) >> 8; + return ndisc_option_add_flags_extension(options, offset, flags); +} + +int ndisc_option_add_dnssl(Set **options, size_t offset, char * const *domains, usec_t lifetime) { + int r; + + assert(options); + + if (strv_isempty(domains)) + return -EINVAL; + + STRV_FOREACH(s, domains) { + r = dns_name_is_valid(*s); + if (r < 0) + return r; + + if (is_localhost(*s) || dns_name_is_root(*s)) + return -EINVAL; + } + + _cleanup_strv_free_ char **copy = strv_copy(domains); + if (!copy) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_DNSSL, offset); + if (!p) + return -ENOMEM; + + p->dnssl = (sd_ndisc_dnssl) { + .domains = TAKE_PTR(copy), + .lifetime = lifetime, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, const uint8_t *opt) { + int r; + + assert(options); + assert(opt); + + if (len < 2*8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_DNSSL) + return -EBADMSG; + + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *e = NULL; + size_t n = 0; + for (size_t c, pos = 8; pos < len; pos += c) { + + c = opt[pos]; + pos++; + + if (c == 0) { + /* Found NUL termination */ + + if (n > 0) { + _cleanup_free_ char *normalized = NULL; + + e[n] = 0; + r = dns_name_normalize(e, 0, &normalized); + if (r < 0) + return r; + + /* Ignore the root domain name or "localhost" and friends */ + if (!is_localhost(normalized) && !dns_name_is_root(normalized)) { + r = strv_consume(&l, TAKE_PTR(normalized)); + if (r < 0) + return r; + } + } + + n = 0; + continue; + } + + /* Check for compression (which is not allowed) */ + if (c > 63) + return -EBADMSG; + + if (pos + c >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(e, n + (n != 0) + DNS_LABEL_ESCAPED_MAX + 1U)) + return -ENOMEM; + + if (n != 0) + e[n++] = '.'; + + r = dns_label_escape((const char*) (opt + pos), c, e + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (n > 0) /* Not properly NUL terminated */ + return -EBADMSG; + + return ndisc_option_add_dnssl(options, offset, l, lifetime); +} + +int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) { + assert(options); + + if (isempty(portal)) + return -EINVAL; + + if (!in_charset(portal, URI_VALID)) + return -EINVAL; + + _cleanup_free_ char *copy = strdup(portal); + if (!copy) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_CAPTIVE_PORTAL, offset); + if (!p) + return -ENOMEM; + + p->captive_portal = TAKE_PTR(copy); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_CAPTIVE_PORTAL) + return -EBADMSG; + + _cleanup_free_ char *portal = memdup_suffix0(opt + 2, len - 2); + if (!portal) + return -ENOMEM; + + size_t size = strlen(portal); + if (size == 0) + return -EBADMSG; + + /* Check that the message is not truncated by an embedded NUL. + * NUL padding to a multiple of 8 is expected. */ + if (DIV_ROUND_UP(size + 2, 8) * 8 != len && DIV_ROUND_UP(size + 3, 8) * 8 != len) + return -EBADMSG; + + return ndisc_option_add_captive_portal(options, offset, portal); +} + +static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { + [PREFIX_LENGTH_CODE_96] = 96, + [PREFIX_LENGTH_CODE_64] = 64, + [PREFIX_LENGTH_CODE_56] = 56, + [PREFIX_LENGTH_CODE_48] = 48, + [PREFIX_LENGTH_CODE_40] = 40, + [PREFIX_LENGTH_CODE_32] = 32, +}; + +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { + for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) + if (prefix_length_code_to_prefix_length[i] == prefixlen) { + if (ret) + *ret = i; + return 0; + } + + return -EINVAL; +} + +static int pref64_lifetime_and_plc_parse(uint16_t lifetime_and_plc, uint8_t *ret_prefixlen, usec_t *ret_lifetime) { + uint16_t plc = lifetime_and_plc & PREF64_PLC_MASK; + if (plc >= _PREFIX_LENGTH_CODE_MAX) + return -EINVAL; + + if (ret_prefixlen) + *ret_prefixlen = prefix_length_code_to_prefix_length[plc]; + if (ret_lifetime) + *ret_lifetime = (lifetime_and_plc & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC; + return 0; +} + +int ndisc_option_add_prefix64( + Set **options, + size_t offset, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime) { + + int r; + + assert(options); + assert(prefix); + + r = pref64_prefix_length_to_plc(prefixlen, NULL); + if (r < 0) + return r; + + if (lifetime > PREF64_MAX_LIFETIME_USEC) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_PREF64, offset); + if (!p) + return -ENOMEM; + + p->prefix64 = (sd_ndisc_prefix64) { + .prefixlen = prefixlen, + .prefix = *prefix, + .lifetime = lifetime, + }; + + in6_addr_mask(&p->prefix64.prefix, p->prefix64.prefixlen); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len, const uint8_t *opt) { + int r; + + assert(options); + assert(opt); + + if (len != 2*8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_PREF64) + return -EBADMSG; + + uint8_t prefixlen; + usec_t lifetime; + r = pref64_lifetime_and_plc_parse(unaligned_read_be16(opt + 2), &prefixlen, &lifetime); + if (r < 0) + return r; + + struct in6_addr prefix; + memcpy(&prefix, opt + 4, len - 4); + in6_addr_mask(&prefix, prefixlen); + + return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime); +} + +static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + assert(len > 0); + + sd_ndisc_option *p = ndisc_option_new(opt[0], offset); + if (!p) + return -ENOMEM; + + return ndisc_option_consume(options, p); +} + +static int ndisc_header_size(uint8_t icmp6_type) { + switch (icmp6_type) { + case ND_ROUTER_SOLICIT: + return sizeof(struct nd_router_solicit); + case ND_ROUTER_ADVERT: + return sizeof(struct nd_router_advert); + case ND_NEIGHBOR_SOLICIT: + return sizeof(struct nd_neighbor_solicit); + case ND_NEIGHBOR_ADVERT: + return sizeof(struct nd_neighbor_advert); + case ND_REDIRECT: + return sizeof(struct nd_redirect); + default: + return -EINVAL; + } +} + +int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) { + _cleanup_set_free_ Set *options = NULL; + int r; + + assert(packet); + assert(ret_options); + + r = icmp6_packet_get_type(packet); + if (r < 0) + return r; + + r = ndisc_header_size(r); + if (r < 0) + return -EBADMSG; + size_t header_size = r; + + if (packet->raw_size < header_size) + return -EBADMSG; + + for (size_t length, offset = header_size; offset < packet->raw_size; offset += length) { + uint8_t type; + const uint8_t *opt; + + r = ndisc_option_parse(packet, offset, &type, &length, &opt); + if (r < 0) + return log_debug_errno(r, "Failed to parse NDisc option header: %m"); + + switch (type) { + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + r = ndisc_option_parse_link_layer_address(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + r = ndisc_option_parse_prefix(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_REDIRECTED_HEADER: + r = ndisc_option_parse_redirected_header(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_MTU: + r = ndisc_option_parse_mtu(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = ndisc_option_parse_route(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_RDNSS: + r = ndisc_option_parse_rdnss(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + r = ndisc_option_parse_flags_extension(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_DNSSL: + r = ndisc_option_parse_dnssl(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + r = ndisc_option_parse_captive_portal(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_PREF64: + r = ndisc_option_parse_prefix64(&options, offset, length, opt); + break; + + default: + r = ndisc_option_parse_default(&options, offset, length, opt); + } + if (r == -ENOMEM) + return log_oom_debug(); + if (r < 0) + log_debug_errno(r, "Failed to parse NDisc option %u, ignoring: %m", type); + } + + *ret_options = TAKE_PTR(options); + return 0; +} + +int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) { + assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + + sd_ndisc_option *p = ndisc_option_get(options, type); + if (!p) + return -ENODATA; + + if (ret) + *ret = p->mac; + return 0; +} diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h new file mode 100644 index 0000000000..45108ee1aa --- /dev/null +++ b/src/libsystemd-network/ndisc-option.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include + +#include "sd-ndisc-protocol.h" + +#include "icmp6-packet.h" +#include "macro.h" +#include "set.h" +#include "time-util.h" + +/* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */ +typedef struct sd_ndisc_prefix { + uint8_t flags; + uint8_t prefixlen; + struct in6_addr address; + usec_t valid_lifetime; + usec_t preferred_lifetime; +} sd_ndisc_prefix; + +typedef struct sd_ndisc_route { + uint8_t preference; + uint8_t prefixlen; + struct in6_addr address; + usec_t lifetime; +} sd_ndisc_route; + +typedef struct sd_ndisc_rdnss { + size_t n_addresses; + struct in6_addr *addresses; + usec_t lifetime; +} sd_ndisc_rdnss; + +typedef struct sd_ndisc_dnssl { + char **domains; + usec_t lifetime; +} sd_ndisc_dnssl; + +typedef struct sd_ndisc_prefix64 { + uint8_t prefixlen; + struct in6_addr prefix; + usec_t lifetime; +} sd_ndisc_prefix64; + +typedef struct sd_ndisc_option { + uint8_t type; + size_t offset; + + union { + struct ether_addr mac; /* SD_NDISC_OPTION_SOURCE_LL_ADDRESS or SD_NDISC_OPTION_TARGET_LL_ADDRESS */ + sd_ndisc_prefix prefix; /* SD_NDISC_OPTION_PREFIX_INFORMATION */ + struct ip6_hdr hdr; /* SD_NDISC_OPTION_REDIRECTED_HEADER */ + uint32_t mtu; /* SD_NDISC_OPTION_MTU */ + sd_ndisc_route route; /* SD_NDISC_OPTION_ROUTE_INFORMATION */ + sd_ndisc_rdnss rdnss; /* SD_NDISC_OPTION_RDNSS */ + uint64_t extended_flags; /* SD_NDISC_OPTION_FLAGS_EXTENSION */ + sd_ndisc_dnssl dnssl; /* SD_NDISC_OPTION_DNSSL */ + char *captive_portal; /* SD_NDISC_OPTION_CAPTIVE_PORTAL */ + sd_ndisc_prefix64 prefix64; /* SD_NDISC_OPTION_PREF64 */ + }; +} sd_ndisc_option; + +/* RFC 8781: PREF64 or (NAT64 prefix) */ +#define PREF64_SCALED_LIFETIME_MASK 0xfff8 +#define PREF64_PLC_MASK 0x0007 +#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC) + +typedef enum PrefixLengthCode { + PREFIX_LENGTH_CODE_96, + PREFIX_LENGTH_CODE_64, + PREFIX_LENGTH_CODE_56, + PREFIX_LENGTH_CODE_48, + PREFIX_LENGTH_CODE_40, + PREFIX_LENGTH_CODE_32, + _PREFIX_LENGTH_CODE_MAX, + _PREFIX_LENGTH_CODE_INVALID = -EINVAL, +} PrefixLengthCode; + +/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */ +struct nd_opt_prefix64_info { + uint8_t type; + uint8_t length; + uint16_t lifetime_and_plc; + uint8_t prefix[12]; +} _packed_; + +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret); + +sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option); + +int ndisc_option_parse( + ICMP6Packet *p, + size_t offset, + uint8_t *ret_type, + size_t *ret_len, + const uint8_t **ret_opt); + +int ndisc_parse_options(ICMP6Packet *p, Set **ret_options); + +static inline sd_ndisc_option* ndisc_option_get(Set *options, uint8_t type) { + return set_get(options, &(sd_ndisc_option) { .type = type, }); +} + +int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret); + +int ndisc_option_add_link_layer_address( + Set **options, + uint8_t opt, + size_t offset, + const struct ether_addr *mac); +int ndisc_option_add_prefix( + Set **options, + size_t offset, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime); +int ndisc_option_add_redirected_header( + Set **options, + size_t offset, + const struct ip6_hdr *hdr); +int ndisc_option_add_mtu( + Set **options, + size_t offset, + uint32_t mtu); +int ndisc_option_add_route( + Set **options, + size_t offset, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime); +int ndisc_option_add_rdnss( + Set **options, + size_t offset, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime); +int ndisc_option_add_flags_extension( + Set **options, + size_t offset, + uint64_t flags); +int ndisc_option_add_dnssl( + Set **options, + size_t offset, + char * const *domains, + usec_t lifetime); +int ndisc_option_add_captive_portal( + Set **options, + size_t offset, + const char *portal); +int ndisc_option_add_prefix64( + Set **options, + size_t offset, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime); diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c deleted file mode 100644 index d1f0819e14..0000000000 --- a/src/libsystemd-network/ndisc-protocol.c +++ /dev/null @@ -1,73 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "ndisc-protocol.h" - -int ndisc_option_parse( - ICMP6Packet *p, - size_t offset, - uint8_t *ret_type, - size_t *ret_len, - const uint8_t **ret_opt) { - - assert(p); - - if (offset == p->raw_size) - return -ESPIPE; /* end of the packet */ - - if (offset > p->raw_size) - return -EBADMSG; - - if (p->raw_size - offset < sizeof(struct nd_opt_hdr)) - return -EBADMSG; - - assert_cc(alignof(struct nd_opt_hdr) == 1); - const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset); - if (hdr->nd_opt_len == 0) - return -EBADMSG; - - size_t len = hdr->nd_opt_len * 8; - if (p->raw_size - offset < len) - return -EBADMSG; - - if (ret_type) - *ret_type = hdr->nd_opt_type; - if (ret_len) - *ret_len = len; - if (ret_opt) - *ret_opt = p->raw_packet + offset; - - return 0; -} - -static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { - [PREFIX_LENGTH_CODE_96] = 96, - [PREFIX_LENGTH_CODE_64] = 64, - [PREFIX_LENGTH_CODE_56] = 56, - [PREFIX_LENGTH_CODE_48] = 48, - [PREFIX_LENGTH_CODE_40] = 40, - [PREFIX_LENGTH_CODE_32] = 32, -}; - -int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) { - plc &= PREF64_PLC_MASK; - if (plc >= _PREFIX_LENGTH_CODE_MAX) - return -EINVAL; - - if (ret) - *ret = prefix_length_code_to_prefix_length[plc]; - return 0; -} - -int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { - assert(ret); - - for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) - if (prefix_length_code_to_prefix_length[i] == prefixlen) { - *ret = i; - return 0; - } - - return -EINVAL; -} diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h deleted file mode 100644 index dcb1b191c7..0000000000 --- a/src/libsystemd-network/ndisc-protocol.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "icmp6-packet.h" -#include "time-util.h" - -/* RFC 8781: PREF64 or (NAT64 prefix) */ -#define PREF64_SCALED_LIFETIME_MASK 0xfff8 -#define PREF64_PLC_MASK 0x0007 -#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC) - -typedef enum PrefixLengthCode { - PREFIX_LENGTH_CODE_96, - PREFIX_LENGTH_CODE_64, - PREFIX_LENGTH_CODE_56, - PREFIX_LENGTH_CODE_48, - PREFIX_LENGTH_CODE_40, - PREFIX_LENGTH_CODE_32, - _PREFIX_LENGTH_CODE_MAX, - _PREFIX_LENGTH_CODE_INVALID = -EINVAL, -} PrefixLengthCode; - -/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */ -struct nd_opt_prefix64_info { - uint8_t type; - uint8_t length; - uint16_t lifetime_and_plc; - uint8_t prefix[12]; -} _packed_; - -int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret); -int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret); - -int ndisc_option_parse( - ICMP6Packet *p, - size_t offset, - uint8_t *ret_type, - size_t *ret_len, - const uint8_t **ret_opt); diff --git a/src/libsystemd-network/ndisc-router-internal.h b/src/libsystemd-network/ndisc-router-internal.h index f1a03bc0ab..6f13350835 100644 --- a/src/libsystemd-network/ndisc-router-internal.h +++ b/src/libsystemd-network/ndisc-router-internal.h @@ -8,6 +8,7 @@ #include "sd-ndisc.h" #include "icmp6-packet.h" +#include "ndisc-option.h" #include "time-util.h" struct sd_ndisc_router { @@ -15,33 +16,19 @@ struct sd_ndisc_router { ICMP6Packet *packet; - /* The current read index for the iterative option interface */ - size_t rindex; - - uint64_t flags; - unsigned preference; - uint64_t lifetime_usec; + /* From RA header */ + uint8_t hop_limit; + uint8_t flags; + uint8_t preference; + usec_t lifetime_usec; usec_t reachable_time_usec; usec_t retransmission_time_usec; - uint8_t hop_limit; - uint32_t mtu; + /* Options */ + Set *options; + Iterator iterator; + sd_ndisc_option *current_option; }; -static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) { - return ASSERT_PTR(ASSERT_PTR(rt)->packet)->raw_packet; -} - -static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex; -} - -static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0]; -} -static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8; -} - sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet); int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt); diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index d6cec904b0..8091a5d882 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -10,7 +10,7 @@ #include "sd-radv.h" #include "list.h" -#include "ndisc-protocol.h" +#include "ndisc-option.h" #include "network-common.h" #include "sparse-endian.h" #include "time-util.h" diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c index 4a95d7dba7..bf7e185ee6 100644 --- a/src/libsystemd-network/sd-ndisc-router.c +++ b/src/libsystemd-network/sd-ndisc-router.c @@ -8,21 +8,15 @@ #include "sd-ndisc.h" #include "alloc-util.h" -#include "dns-domain.h" -#include "escape.h" -#include "hostname-util.h" -#include "memory-util.h" -#include "missing_network.h" #include "ndisc-internal.h" -#include "ndisc-protocol.h" #include "ndisc-router-internal.h" -#include "strv.h" static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) { if (!rt) return NULL; icmp6_packet_unref(rt->packet); + set_free(rt->options); return mfree(rt); } @@ -40,12 +34,13 @@ sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) { *rt = (sd_ndisc_router) { .n_ref = 1, .packet = icmp6_packet_ref(packet), + .iterator = ITERATOR_FIRST, }; return rt; } -int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { +int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret) { assert_return(rt, -EINVAL); return icmp6_packet_get_sender_address(rt->packet, ret); @@ -90,24 +85,8 @@ DEFINE_GET_TIMESTAMP(rdnss_get_lifetime); DEFINE_GET_TIMESTAMP(dnssl_get_lifetime); DEFINE_GET_TIMESTAMP(prefix64_get_lifetime); -static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) { - uint16_t lifetime_and_plc; - - assert(p); - - if (length != sizeof(struct nd_opt_prefix64_info)) - return false; - - lifetime_and_plc = be16toh(p->lifetime_and_plc); - if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0) - return false; - - return true; -} - int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { const struct nd_router_advert *a; - bool has_mtu = false, has_flag_extension = false; int r; assert(rt); @@ -127,107 +106,20 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { rt->reachable_time_usec = be32_msec_to_usec(a->nd_ra_reachable, /* mas_as_infinity = */ false); rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); + /* RFC 4191 section 2.2 + * Prf (Default Router Preference) + * 2-bit signed integer. Indicates whether to prefer this router over other default routers. If the + * Router Lifetime is zero, the preference value MUST be set to (00) by the sender and MUST be + * ignored by the receiver. If the Reserved (10) value is received, the receiver MUST treat the value + * as if it were (00). */ rt->preference = (rt->flags >> 3) & 3; - if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) + if (rt->preference == SD_NDISC_PREFERENCE_RESERVED) rt->preference = SD_NDISC_PREFERENCE_MEDIUM; - for (size_t offset = sizeof(struct nd_router_advert), length; offset < rt->packet->raw_size; offset += length) { - uint8_t type; - const uint8_t *p; + r = ndisc_parse_options(rt->packet, &rt->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in router advertisement message, ignoring: %m"); - r = ndisc_option_parse(rt->packet, offset, &type, &length, &p); - if (r < 0) - return log_ndisc_errno(nd, r, "Failed to parse NDisc option header, ignoring: %m"); - - switch (type) { - - case SD_NDISC_OPTION_PREFIX_INFORMATION: - - if (length != 4*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Prefix option of invalid size, ignoring datagram."); - - if (p[2] > 128) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Bad prefix length, ignoring datagram."); - - break; - - case SD_NDISC_OPTION_MTU: { - uint32_t m; - - if (has_mtu) { - log_ndisc(nd, "MTU option specified twice, ignoring."); - break; - } - - if (length != 8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "MTU option of invalid size, ignoring datagram."); - - m = be32toh(*(uint32_t*) (p + 4)); - if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */ - rt->mtu = m; - - has_mtu = true; - break; - } - - case SD_NDISC_OPTION_ROUTE_INFORMATION: - if (length < 1*8 || length > 3*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Route information option of invalid size, ignoring datagram."); - - if (p[2] > 128) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Bad route prefix length, ignoring datagram."); - - break; - - case SD_NDISC_OPTION_RDNSS: - if (length < 3*8 || (length % (2*8)) != 1*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), "RDNSS option has invalid size."); - - break; - - case SD_NDISC_OPTION_FLAGS_EXTENSION: - - if (has_flag_extension) { - log_ndisc(nd, "Flags extension option specified twice, ignoring."); - break; - } - - if (length < 1*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Flags extension option has invalid size."); - - /* Add in the additional flags bits */ - rt->flags |= - ((uint64_t) p[2] << 8) | - ((uint64_t) p[3] << 16) | - ((uint64_t) p[4] << 24) | - ((uint64_t) p[5] << 32) | - ((uint64_t) p[6] << 40) | - ((uint64_t) p[7] << 48); - - has_flag_extension = true; - break; - - case SD_NDISC_OPTION_DNSSL: - if (length < 2*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "DNSSL option has invalid size."); - - break; - case SD_NDISC_OPTION_PREF64: { - if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length)) - log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "PREF64 prefix has invalid prefix length."); - break; - }} - } - - rt->rindex = sizeof(struct nd_router_advert); return 0; } @@ -259,7 +151,9 @@ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); - *ret = rt->flags; + sd_ndisc_option *p = ndisc_option_get(rt->options, SD_NDISC_OPTION_FLAGS_EXTENSION); + + *ret = rt->flags | (p ? p->extended_flags : 0); return 0; } @@ -272,7 +166,7 @@ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { return rt->lifetime_usec > 0; /* Indicate if the router is still valid or not. */ } -int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); @@ -280,602 +174,134 @@ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { return 0; } +int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret) { + assert_return(rt, -EINVAL); + + return ndisc_option_get_mac(rt->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret); +} + int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); - if (rt->mtu <= 0) + sd_ndisc_option *p = ndisc_option_get(rt->options, SD_NDISC_OPTION_MTU); + if (!p) return -ENODATA; - *ret = rt->mtu; + *ret = p->mtu; + return 0; +} + +int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get(rt->options, SD_NDISC_OPTION_CAPTIVE_PORTAL); + if (!p) + return -ENODATA; + + *ret = p->captive_portal; return 0; } int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { assert_return(rt, -EINVAL); - assert(rt->packet); - assert(rt->packet->raw_size >= sizeof(struct nd_router_advert)); - - rt->rindex = sizeof(struct nd_router_advert); - return rt->rindex < rt->packet->raw_size; + rt->iterator = ITERATOR_FIRST; + return sd_ndisc_router_option_next(rt); } int sd_ndisc_router_option_next(sd_ndisc_router *rt) { - size_t length; - int r; - assert_return(rt, -EINVAL); - r = ndisc_option_parse(rt->packet, rt->rindex, NULL, &length, NULL); - if (r < 0) - return r; - - rt->rindex += length; - return rt->rindex < rt->packet->raw_size; + return set_iterate(rt->options, &rt->iterator, (void**) &rt->current_option); } int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { assert_return(rt, -EINVAL); - return ndisc_option_parse(rt->packet, rt->rindex, ret, NULL, NULL); + assert_return(ret, -EINVAL); + + if (!rt->current_option) + return -ENODATA; + + *ret = rt->current_option->type; + return 0; } int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { - uint8_t k; + uint8_t t; int r; assert_return(rt, -EINVAL); - r = sd_ndisc_router_option_get_type(rt, &k); + r = sd_ndisc_router_option_get_type(rt, &t); if (r < 0) return r; - return type == k; + return t == type; } -int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) { - size_t length; - +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size) { assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(ret_size, -EINVAL); - /* Note that this returns the full option, including the option header */ + if (!rt->current_option) + return -ENODATA; - if (rt->rindex + 2 > rt->packet->raw_size) - return -EBADMSG; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (rt->rindex + length > rt->packet->raw_size) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - *ret_size = length; - - return 0; + return ndisc_option_parse(rt->packet, rt->current_option->offset, NULL, ret_size, ret); } -static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) { - struct nd_opt_prefix_info *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length != sizeof(struct nd_opt_prefix_info)) - return -EBADMSG; - - ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); - if (ri->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix_info *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &ri); - if (r < 0) - return r; - - *ret = be32_sec_to_usec(ri->nd_opt_pi_valid_time, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) { - struct nd_opt_prefix_info *pi; - uint8_t flags; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - flags = pi->nd_opt_pi_flags_reserved; - - if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) { - log_ndisc(NULL, "Invalid prefix length, ignoring prefix for stateless autoconfiguration."); - flags &= ~ND_OPT_PI_FLAG_AUTO; +#define DEFINE_GETTER(name, type, element, element_type) \ + int sd_ndisc_router_##name##_get_##element( \ + sd_ndisc_router *rt, \ + element_type *ret) { \ + \ + int r; \ + \ + assert_return(rt, -EINVAL); \ + assert_return(ret, -EINVAL); \ + \ + r = sd_ndisc_router_option_is_type(rt, type); \ + if (r < 0) \ + return r; \ + if (r == 0) \ + return -EMEDIUMTYPE; \ + \ + *ret = rt->current_option->name.element; \ + return 0; \ } - *ret = flags; - return 0; -} +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, flags, uint8_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, prefixlen, uint8_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, address, struct in6_addr); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, valid_lifetime, uint64_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, preferred_lifetime, uint64_t); -int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { - struct nd_opt_prefix_info *pi; +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, preference, uint8_t); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, prefixlen, uint8_t); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, address, struct in6_addr); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, lifetime, uint64_t); + +DEFINE_GETTER(rdnss, SD_NDISC_OPTION_RDNSS, lifetime, uint64_t); + +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { int r; assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = pi->nd_opt_pi_prefix; - return 0; -} - -int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - if (pi->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = pi->nd_opt_pi_prefix_len; - return 0; -} - -static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) { - uint8_t *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 1*8 || length > 3*8) - return -EBADMSG; - - ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - - if (ri[2] > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - zero(*ret); - memcpy(ret, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8); - - return 0; -} - -int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = ri[2]; - return 0; -} - -int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - if (!IN_SET((ri[3] >> 3) & 3, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH)) - return -EOPNOTSUPP; - - *ret = (ri[3] >> 3) & 3; - return 0; -} - -static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); if (r < 0) return r; if (r == 0) return -EMEDIUMTYPE; - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 3*8 || (length % (2*8)) != 1*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; + *ret = rt->current_option->rdnss.addresses; + return (int) rt->current_option->rdnss.n_addresses; } -int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { - uint8_t *ri; - int r; +DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, domains, char**); +DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t); - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = (const struct in6_addr*) (ri + 8); - return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16; -} - -int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 2*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; -} - -int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - _cleanup_free_ char *e = NULL; - size_t n = 0, left; - uint8_t *ri, *p; - bool first = true; - int r; - unsigned k = 0; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - p = ri + 8; - left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8; - - for (;;) { - if (left == 0) { - - if (n > 0) /* Not properly NUL terminated */ - return -EBADMSG; - - break; - } - - if (*p == 0) { - /* Found NUL termination */ - - if (n > 0) { - _cleanup_free_ char *normalized = NULL; - - e[n] = 0; - r = dns_name_normalize(e, 0, &normalized); - if (r < 0) { - _cleanup_free_ char *escaped = cescape(e); - log_debug_errno(r, "Failed to normalize advertised domain name \"%s\": %m", strna(escaped)); - /* Here, do not propagate error code from dns_name_normalize() except for ENOMEM. */ - return r == -ENOMEM ? -ENOMEM : -EBADMSG; - } - - /* Ignore the root domain name or "localhost" and friends */ - if (!is_localhost(normalized) && - !dns_name_is_root(normalized)) { - - if (strv_push(&l, normalized) < 0) - return -ENOMEM; - - normalized = NULL; - k++; - } - } - - n = 0; - first = true; - p++, left--; - continue; - } - - /* Check for compression (which is not allowed) */ - if (*p > 63) - return -EBADMSG; - - if (1U + *p + 1U > left) - return -EBADMSG; - - if (!GREEDY_REALLOC(e, n + !first + DNS_LABEL_ESCAPED_MAX + 1U)) - return -ENOMEM; - - if (first) - first = false; - else - e[n++] = '.'; - - r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) { - _cleanup_free_ char *escaped = cescape_length((const char*) p+1, *p); - log_debug_errno(r, "Failed to escape advertised domain name \"%s\": %m", strna(escaped)); - /* Here, do not propagate error code from dns_label_escape() except for ENOMEM. */ - return r == -ENOMEM ? -ENOMEM : -EBADMSG; - } - - n += r; - - left -= 1 + *p; - p += 1 + *p; - } - - if (strv_isempty(l)) { - *ret = NULL; - return 0; - } - - *ret = TAKE_PTR(l); - - return k; -} - -int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size) { - int r; - const char *nd_opt_captive_portal; - size_t length; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(ret_size, -EINVAL); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_CAPTIVE_PORTAL); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - r = sd_ndisc_router_option_get_raw(rt, (void *)&nd_opt_captive_portal, &length); - if (r < 0) - return r; - - /* The length field has units of 8 octets */ - assert(length % 8 == 0); - if (length == 0) - return -EBADMSG; - - /* Check that the message is not truncated by an embedded NUL. - * NUL padding to a multiple of 8 is expected. */ - size_t size = strnlen(nd_opt_captive_portal + 2, length - 2); - if (DIV_ROUND_UP(size + 2, 8) != length / 8) - return -EBADMSG; - - /* Let's not return an empty buffer */ - if (size == 0) { - *ret = NULL; - *ret_size = 0; - return 0; - } - - *ret = nd_opt_captive_portal + 2; - *ret_size = size; - - return 0; -} - -static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) { - struct nd_opt_prefix64_info *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length != sizeof(struct nd_opt_prefix64_info)) - return -EBADMSG; - - ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); - if (!pref64_option_verify(ri, length)) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret) { - struct nd_opt_prefix64_info *pi; - struct in6_addr a = {}; - unsigned prefixlen; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen); - if (r < 0) - return r; - - memcpy(&a, pi->prefix, sizeof(pi->prefix)); - in6_addr_mask(&a, prefixlen); - /* extra safety check for refusing malformed prefix. */ - if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0) - return -EBADMSG; - - *ret = a; - return 0; -} - -int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - struct nd_opt_prefix64_info *pi; - uint16_t lifetime_prefix_len; - uint8_t prefix_len; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - lifetime_prefix_len = be16toh(pi->lifetime_and_plc); - pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len); - - *ret = prefix_len; - return 0; -} - -int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix64_info *pi; - uint16_t lifetime_prefix_len; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - lifetime_prefix_len = be16toh(pi->lifetime_and_plc); - - *ret = (lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC; - return 0; -} +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t); +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr); +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t); diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index e37dfeeded..4541605940 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -31,13 +31,13 @@ static void router_dump(sd_ndisc_router *rt) { usec_t t, lifetime, retrans_time; uint64_t flags; uint32_t mtu; - unsigned preference; + uint8_t preference; int r; assert_se(rt); log_info("--"); - assert_se(sd_ndisc_router_get_address(rt, &addr) >= 0); + assert_se(sd_ndisc_router_get_sender_address(rt, &addr) >= 0); log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr)); assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0); @@ -91,20 +91,19 @@ static void router_dump(sd_ndisc_router *rt) { case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: case SD_NDISC_OPTION_TARGET_LL_ADDRESS: { _cleanup_free_ char *c = NULL; - const void *p; + const uint8_t *p; size_t n; assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0); assert_se(n > 2); - assert_se(c = hexmem((uint8_t*) p + 2, n - 2)); + assert_se(c = hexmem(p + 2, n - 2)); log_info("Address: %s", c); break; } case SD_NDISC_OPTION_PREFIX_INFORMATION: { - unsigned prefix_len; - uint8_t pfl; + uint8_t prefix_len, pfl; struct in6_addr a; assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime) >= 0); @@ -146,18 +145,12 @@ static void router_dump(sd_ndisc_router *rt) { } case SD_NDISC_OPTION_DNSSL: { - _cleanup_strv_free_ char **l = NULL; - int n, i; + char **l; - n = sd_ndisc_router_dnssl_get_domains(rt, &l); - if (n == -EBADMSG) { - log_info("Invalid domain(s)."); - break; - } - assert_se(n > 0); + assert_se(sd_ndisc_router_dnssl_get_domains(rt, &l) >= 0); - for (i = 0; i < n; i++) - log_info("Domain: %s", l[i]); + STRV_FOREACH(s, l) + log_info("Domain: %s", *s); assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime) >= 0); assert_se(sd_ndisc_router_dnssl_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index b3da0c2a31..61d591a2af 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -190,7 +190,7 @@ static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return r; @@ -336,7 +336,7 @@ static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router * assert(link); assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return r; @@ -387,7 +387,7 @@ static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -430,7 +430,7 @@ static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { usec_t lifetime_usec; struct in6_addr gateway; - unsigned preference; + uint8_t preference; int r; assert(link); @@ -452,7 +452,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m"); - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); @@ -625,7 +625,7 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r usec_t lifetime_valid_usec, lifetime_preferred_usec; _cleanup_set_free_ Set *addresses = NULL; struct in6_addr prefix, *a; - unsigned prefixlen; + uint8_t prefixlen; int r; assert(link); @@ -703,7 +703,7 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { _cleanup_(route_unrefp) Route *route = NULL; - unsigned prefixlen, preference; + uint8_t prefixlen, preference; usec_t lifetime_usec; struct in6_addr prefix; int r; @@ -751,7 +751,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { static int ndisc_router_drop_onlink_prefix(Link *link, sd_ndisc_router *rt) { _cleanup_(route_unrefp) Route *route = NULL; - unsigned prefixlen; + uint8_t prefixlen; struct in6_addr prefix; usec_t lifetime_usec; int r; @@ -800,9 +800,8 @@ static int ndisc_router_drop_onlink_prefix(Link *link, sd_ndisc_router *rt) { } static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { - unsigned prefixlen; + uint8_t flags, prefixlen; struct in6_addr a; - uint8_t flags; int r; assert(link); @@ -856,7 +855,7 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { _cleanup_(route_unrefp) Route *route = NULL; - unsigned preference, prefixlen; + uint8_t preference, prefixlen; struct in6_addr gateway, dst; usec_t lifetime_usec; int r; @@ -895,7 +894,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { return 0; } - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); @@ -962,7 +961,7 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { if (!link->network->ndisc_use_dns) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1041,7 +1040,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( free); static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { - _cleanup_strv_free_ char **l = NULL; + char **l; usec_t lifetime_usec; struct in6_addr router; bool updated = false, logged_about_too_many = false; @@ -1054,7 +1053,7 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { if (link->network->ndisc_use_domains == DHCP_USE_DOMAINS_NO) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1145,11 +1144,10 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { _cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL; _cleanup_free_ char *captive_portal = NULL; + const char *uri; usec_t lifetime_usec; NDiscCaptivePortal *exist; struct in6_addr router; - const char *uri; - size_t len; int r; assert(link); @@ -1159,7 +1157,7 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) if (!link->network->ndisc_use_captive_portal) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1170,31 +1168,25 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) if (r < 0) return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); - r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); + r = sd_ndisc_router_get_captive_portal(rt, &uri); if (r < 0) return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m"); - if (len == 0) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received empty captive portal, ignoring."); - - r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m"); - - if (!in_charset(captive_portal, URI_VALID)) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring."); + captive_portal = strdup(uri); + if (!captive_portal) + return log_oom(); if (lifetime_usec == 0) { /* Drop the portal with zero lifetime. */ ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, })); return 0; } exist = set_get(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, }); if (exist) { @@ -1268,7 +1260,7 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { _cleanup_free_ NDiscPREF64 *new_entry = NULL; usec_t lifetime_usec; struct in6_addr a, router; - unsigned prefix_len; + uint8_t prefix_len; NDiscPREF64 *exist; int r; @@ -1279,7 +1271,7 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { if (!link->network->ndisc_use_pref64) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1617,7 +1609,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { assert(link->manager); assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r == -ENODATA) { log_link_debug(link, "Received RA without router address, ignoring."); return 0; diff --git a/src/systemd/sd-ndisc-protocol.h b/src/systemd/sd-ndisc-protocol.h index c6064b6795..3104ad42a3 100644 --- a/src/systemd/sd-ndisc-protocol.h +++ b/src/systemd/sd-ndisc-protocol.h @@ -27,6 +27,7 @@ enum { SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, SD_NDISC_OPTION_PREFIX_INFORMATION = 3, + SD_NDISC_OPTION_REDIRECTED_HEADER = 4, SD_NDISC_OPTION_MTU = 5, SD_NDISC_OPTION_ROUTE_INFORMATION = 24, SD_NDISC_OPTION_RDNSS = 25, @@ -38,9 +39,10 @@ enum { /* Route preference, RFC 4191, Section 2.1 */ enum { - SD_NDISC_PREFERENCE_LOW = 3U, - SD_NDISC_PREFERENCE_MEDIUM = 0U, - SD_NDISC_PREFERENCE_HIGH = 1U + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, + SD_NDISC_PREFERENCE_RESERVED = 2U, + SD_NDISC_PREFERENCE_LOW = 3U }; _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-ndisc-router.h b/src/systemd/sd-ndisc-router.h index 57dbb12817..32ee5796e6 100644 --- a/src/systemd/sd-ndisc-router.h +++ b/src/systemd/sd-ndisc-router.h @@ -18,6 +18,7 @@ ***/ #include +#include #include #include #include @@ -32,24 +33,26 @@ sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); -int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret); +int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret); int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); int sd_ndisc_router_get_reachable_time(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret); int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret); /* Generic option access */ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); int sd_ndisc_router_option_next(sd_ndisc_router *rt); int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); -int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size); /* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret); @@ -58,14 +61,14 @@ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t int sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); /* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_route_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, uint8_t *ret); /* Specific option access: SD_NDISC_OPTION_RDNSS */ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); @@ -77,12 +80,9 @@ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_dnssl_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -/* Specific option access: SD_NDISC_OPTION_CAPTIVE_PORTAL */ -int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size); - /* Specific option access: SD_NDISC_OPTION_PREF64 */ int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);