diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2024-03-08 07:52:18 +0100 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2024-03-18 14:52:29 +0100 |
commit | ca114462177263aba4577c9628ae75612ada3762 (patch) | |
tree | ea47b800947e69661462bd8185bcf46c0e6fee60 /src/libsystemd-network/ndisc-option.c | |
parent | ndisc-option: add a way to specify invalid or unsupported options (diff) | |
download | systemd-ca114462177263aba4577c9628ae75612ada3762.tar.xz systemd-ca114462177263aba4577c9628ae75612ada3762.zip |
ndisc-option: introduce ndisc_send()
This will be used when sending Router Solicitation and Advertisement.
Diffstat (limited to 'src/libsystemd-network/ndisc-option.c')
-rw-r--r-- | src/libsystemd-network/ndisc-option.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index 862148a712..9b4f48700d 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -5,7 +5,9 @@ #include "dns-domain.h" #include "ether-addr-util.h" #include "hostname-util.h" +#include "icmp6-util.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "missing_network.h" #include "ndisc-option.h" #include "network-common.h" @@ -242,6 +244,19 @@ int ndisc_option_add_raw(Set **options, size_t offset, size_t length, const uint return ndisc_option_consume(options, p); } +static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == 0); + assert(ret); + + _cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; +} + 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)); @@ -275,6 +290,25 @@ static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, s return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac); } +static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + assert(ret); + + assert_cc(2 + sizeof(struct ether_addr) == 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr)); + if (!buf) + return -ENOMEM; + + buf[0] = option->type; + buf[1] = 1; + memcpy(buf + 2, &option->mac, sizeof(struct ether_addr)); + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_prefix( Set **options, size_t offset, @@ -335,6 +369,31 @@ static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, c return ndisc_option_add_prefix(options, offset, flags, pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, valid, pref); } +static int ndisc_option_build_prefix(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION); + assert(ret); + + assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0); + + _cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1); + if (!buf) + return -ENOMEM; + + *buf = (struct nd_opt_prefix_info) { + .nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION, + .nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8, + .nd_opt_pi_prefix_len = option->prefix.prefixlen, + .nd_opt_pi_flags_reserved = option->prefix.flags, + .nd_opt_pi_valid_time = usec_to_be32_sec(option->prefix.valid_lifetime), + .nd_opt_pi_preferred_time = usec_to_be32_sec(option->prefix.preferred_lifetime), + .nd_opt_pi_prefix = option->prefix.address, + }; + + *ret = (uint8_t*) TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) { assert(options); assert(hdr); @@ -362,6 +421,32 @@ static int ndisc_option_parse_redirected_header(Set **options, size_t offset, si return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr))); } +static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER); + assert(ret); + + assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0); + + size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + uint8_t *p; + p = mempcpy(buf, + &(const struct nd_opt_rd_hdr) { + .nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER, + .nd_opt_rh_len = len, + }, + sizeof(struct nd_opt_rd_hdr)); + memcpy(p, &option->hdr, sizeof(struct ip6_hdr)); + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) { assert(options); @@ -391,6 +476,27 @@ static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, cons return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu)); } +static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_MTU); + assert(ret); + + assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0); + + _cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1); + if (!buf) + return -ENOMEM; + + *buf = (struct nd_opt_mtu) { + .nd_opt_mtu_type = SD_NDISC_OPTION_MTU, + .nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8, + .nd_opt_mtu_mtu = htobe32(option->mtu), + }; + + *ret = (uint8_t*) TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_route( Set **options, size_t offset, @@ -456,6 +562,30 @@ static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, co return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime); } +static int ndisc_option_build_route(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION); + assert(option->route.prefixlen <= 128); + assert(ret); + + size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64); + be32_t lifetime = usec_to_be32_sec(option->route.lifetime); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION; + buf[1] = len; + buf[2] = option->route.prefixlen; + buf[3] = option->route.preference << 3; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8); + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_rdnss( Set **options, size_t offset, @@ -502,6 +632,29 @@ static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, co return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime); } +static int ndisc_option_build_rdnss(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_RDNSS); + assert(ret); + + size_t len = option->rdnss.n_addresses * 2 + 1; + be32_t lifetime = usec_to_be32_sec(option->rdnss.lifetime); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_RDNSS; + buf[1] = len; + buf[2] = 0; + buf[3] = 0; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses); + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) { assert(options); @@ -531,6 +684,23 @@ static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size return ndisc_option_add_flags_extension(options, offset, flags); } +static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION); + assert(ret); + + _cleanup_free_ uint8_t *buf = new(uint8_t, 8); + if (!buf) + return 0; + + unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8); + buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION; + buf[1] = 1; + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_dnssl(Set **options, size_t offset, char * const *domains, usec_t lifetime) { int r; @@ -635,6 +805,50 @@ static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, co return ndisc_option_add_dnssl(options, offset, l, lifetime); } +static int ndisc_option_build_dnssl(const sd_ndisc_option *option, uint8_t **ret) { + int r; + + assert(option); + assert(option->type == SD_NDISC_OPTION_DNSSL); + assert(ret); + + size_t len = 8; + STRV_FOREACH(s, option->dnssl.domains) + len += strlen(*s) + 2; + len = DIV_ROUND_UP(len, 8); + + be32_t lifetime = usec_to_be32_sec(option->dnssl.lifetime); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_DNSSL; + buf[1] = len; + buf[2] = 0; + buf[3] = 0; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + + size_t remaining = len * 8 - 8; + uint8_t *p = buf + 8; + + STRV_FOREACH(s, option->dnssl.domains) { + r = dns_name_to_wire_format(*s, p, remaining, /* canonical = */ false); + if (r < 0) + return r; + + assert(remaining >= (size_t) r); + p += r; + remaining -= r; + } + + if (remaining > 0) + memset(p, 0, remaining); + + *ret = TAKE_PTR(buf); + return 0; +} + int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) { assert(options); @@ -683,6 +897,30 @@ static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_ return ndisc_option_add_captive_portal(options, offset, portal); } +static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL); + assert(ret); + + size_t len_portal = strlen(option->captive_portal); + size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL; + buf[1] = len; + + uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal); + size_t remaining = len * 8 - 2 - len_portal; + if (remaining > 0) + memset(p, 0, remaining); + + *ret = TAKE_PTR(buf); + 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, @@ -774,6 +1012,37 @@ static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len, return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime); } +static int ndisc_option_build_prefix64(const sd_ndisc_option *option, uint8_t **ret) { + int r; + + assert(option); + assert(option->type == SD_NDISC_OPTION_PREF64); + assert(ret); + + uint8_t code; + r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code); + if (r < 0) + return r; + + uint16_t lifetime; + if (option->prefix64.lifetime >= PREF64_SCALED_LIFETIME_MASK * USEC_PER_SEC) + lifetime = PREF64_SCALED_LIFETIME_MASK; + else + lifetime = (uint16_t) DIV_ROUND_UP(option->prefix64.lifetime, USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK; + + _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_PREF64; + buf[1] = 2; + unaligned_write_be16(buf + 2, lifetime | code); + memcpy(buf + 4, &option->prefix64.prefix, 12); + + *ret = TAKE_PTR(buf); + return 0; +} + static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { assert(options); assert(opt); @@ -900,3 +1169,94 @@ int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) { *ret = p->mac; return 0; } + +int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options) { + int r; + + assert(fd >= 0); + assert(dst); + assert(hdr); + + struct iovec *iov = NULL; + size_t n_iov = 0; + CLEANUP_ARRAY(iov, n_iov, iovec_array_free); + + iov = new(struct iovec, 1 + set_size(options)); + if (!iov) + return -ENOMEM; + + r = ndisc_header_size(hdr->icmp6_type); + if (r < 0) + return r; + size_t hdr_size = r; + + _cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size); + if (!copy) + return -ENOMEM; + + iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size); + + const sd_ndisc_option *option; + SET_FOREACH(option, options) { + _cleanup_free_ uint8_t *buf = NULL; + + switch (option->type) { + case 0: + r = ndisc_option_build_raw(option, &buf); + break; + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + r = ndisc_option_build_link_layer_address(option, &buf); + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + r = ndisc_option_build_prefix(option, &buf); + break; + + case SD_NDISC_OPTION_REDIRECTED_HEADER: + r = ndisc_option_build_redirected_header(option, &buf); + break; + + case SD_NDISC_OPTION_MTU: + r = ndisc_option_build_mtu(option, &buf); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = ndisc_option_build_route(option, &buf); + break; + + case SD_NDISC_OPTION_RDNSS: + r = ndisc_option_build_rdnss(option, &buf); + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + r = ndisc_option_build_flags_extension(option, &buf); + break; + + case SD_NDISC_OPTION_DNSSL: + r = ndisc_option_build_dnssl(option, &buf); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + r = ndisc_option_build_captive_portal(option, &buf); + break; + + case SD_NDISC_OPTION_PREF64: + r = ndisc_option_build_prefix64(option, &buf); + break; + + default: + continue; + } + if (r == -ENOMEM) + return log_oom_debug(); + if (r < 0) + log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type); + + iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8); + TAKE_PTR(buf); + } + + return icmp6_send(fd, dst, iov, n_iov); +} |