diff --git a/src/libsystemd-network/fuzz-ndisc-rs.c b/src/libsystemd-network/fuzz-ndisc-rs.c index a89e2b0ed7..eed670fb6f 100644 --- a/src/libsystemd-network/fuzz-ndisc-rs.c +++ b/src/libsystemd-network/fuzz-ndisc-rs.c @@ -9,22 +9,19 @@ #include "alloc-util.h" #include "fd-util.h" #include "fuzz.h" +#include "icmp6-packet.h" #include "icmp6-util-unix.h" #include "ndisc-internal.h" +#include "ndisc-option.h" #include "socket-util.h" -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { +static void test_with_sd_ndisc(const uint8_t *data, size_t size) { struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} }; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - if (outside_size_range(size, 0, 2048)) - return 0; - - fuzz_setup_logging(); - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); @@ -35,6 +32,46 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { (void) sd_event_run(e, UINT64_MAX); assert_se(sd_ndisc_stop(nd) >= 0); close(test_fd[1]); +} +static void test_with_icmp6_packet(const uint8_t *data, size_t size) { + static const struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + }; + + _cleanup_close_pair_ int fd_pair[2] = EBADF_PAIR; + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + _cleanup_set_free_ Set *options = NULL; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd_pair) >= 0); + assert_se(write(fd_pair[1], data, size) == (ssize_t) size); + + if (icmp6_packet_receive(fd_pair[0], &packet) < 0) + return; + + if (ndisc_parse_options(packet, &options) < 0) + return; + + if (ndisc_send(fd_pair[1], &dst, icmp6_packet_get_header(packet), options) < 0) + return; + + packet = icmp6_packet_unref(packet); + options = set_free(options); + + if (icmp6_packet_receive(fd_pair[0], &packet) < 0) + return; + + (void) ndisc_parse_options(packet, &options); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (outside_size_range(size, 0, 2048)) + return 0; + + fuzz_setup_logging(); + + test_with_sd_ndisc(data, size); + test_with_icmp6_packet(data, size); return 0; } diff --git a/src/libsystemd-network/icmp6-packet.c b/src/libsystemd-network/icmp6-packet.c index a0b0e84f54..35459d11a2 100644 --- a/src/libsystemd-network/icmp6-packet.c +++ b/src/libsystemd-network/icmp6-packet.c @@ -52,7 +52,7 @@ int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret) { return 0; } -static const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) { +const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) { assert(p); if (p->raw_size < sizeof(struct icmp6_hdr)) diff --git a/src/libsystemd-network/icmp6-packet.h b/src/libsystemd-network/icmp6-packet.h index d77d2e8537..7d02534a8c 100644 --- a/src/libsystemd-network/icmp6-packet.h +++ b/src/libsystemd-network/icmp6-packet.h @@ -23,6 +23,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ICMP6Packet*, icmp6_packet_unref); int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret); int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret); +const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p); int icmp6_packet_get_type(ICMP6Packet *p); int icmp6_packet_receive(int fd, ICMP6Packet **ret); diff --git a/src/libsystemd-network/icmp6-util-unix.c b/src/libsystemd-network/icmp6-util-unix.c index 5eac4a0e53..c400e4205e 100644 --- a/src/libsystemd-network/icmp6-util-unix.c +++ b/src/libsystemd-network/icmp6-util-unix.c @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include "fd-util.h" #include "icmp6-util-unix.h" -send_ra_t send_ra_function = NULL; int test_fd[2] = EBADF_PAIR; static struct in6_addr dummy_link_local = { @@ -23,11 +23,8 @@ int icmp6_bind(int ifindex, bool is_router) { return test_fd[is_router]; } -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - if (!send_ra_function) - return 0; - - return send_ra_function(0); +int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) { + return writev(fd, iov, n_iov); } int icmp6_receive( diff --git a/src/libsystemd-network/icmp6-util-unix.h b/src/libsystemd-network/icmp6-util-unix.h index a9cb05a96e..d7b0cc84b3 100644 --- a/src/libsystemd-network/icmp6-util-unix.h +++ b/src/libsystemd-network/icmp6-util-unix.h @@ -3,7 +3,4 @@ #include "icmp6-util.h" -typedef int (*send_ra_t)(uint8_t flags); - -extern send_ra_t send_ra_function; extern int test_fd[2]; diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 559167847f..7b3786e518 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -88,37 +88,15 @@ int icmp6_bind(int ifindex, bool is_router) { return TAKE_FD(s); } -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - struct sockaddr_in6 dst = { - .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, - }; - struct { - struct nd_router_solicit rs; - struct nd_opt_hdr rs_opt; - struct ether_addr rs_opt_mac; - } _packed_ rs = { - .rs.nd_rs_type = ND_ROUTER_SOLICIT, - .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, - .rs_opt.nd_opt_len = 1, - }; - struct iovec iov = { - .iov_base = &rs, - .iov_len = sizeof(rs), - }; +int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) { struct msghdr msg = { - .msg_name = &dst, - .msg_namelen = sizeof(dst), - .msg_iov = &iov, - .msg_iovlen = 1, + .msg_name = (struct sockaddr_in6*) dst, + .msg_namelen = sizeof(struct sockaddr_in6), + .msg_iov = (struct iovec*) iov, + .msg_iovlen = n_iov, }; - assert(s >= 0); - assert(ether_addr); - - rs.rs_opt_mac = *ether_addr; - - if (sendmsg(s, &msg, 0) < 0) + if (sendmsg(fd, &msg, 0) < 0) return -errno; return 0; diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 72db688b0b..49cdcba3ff 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -6,7 +6,9 @@ ***/ #include +#include #include +#include #include "time-util.h" @@ -19,7 +21,7 @@ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } int icmp6_bind(int ifindex, bool is_router); -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); +int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov); int icmp6_receive( int fd, void *buffer, diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index 380e71e764..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" @@ -65,6 +67,13 @@ static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) { return p; } +static void ndisc_raw_done(sd_ndisc_raw *raw) { + if (!raw) + return; + + free(raw->bytes); +} + static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) { if (!rdnss) return; @@ -84,6 +93,10 @@ sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { return NULL; switch (option->type) { + case 0: + ndisc_raw_done(&option->raw); + break; + case SD_NDISC_OPTION_RDNSS: ndisc_rdnss_done(&option->rdnss); break; @@ -111,6 +124,9 @@ static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_op return r; switch (x->type) { + case 0: + return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length); + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: case SD_NDISC_OPTION_TARGET_LL_ADDRESS: case SD_NDISC_OPTION_REDIRECTED_HEADER: @@ -155,6 +171,10 @@ static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash siphash24_compress_typesafe(option->type, state); switch (option->type) { + case 0: + siphash24_compress(option->raw.bytes, option->raw.length, state); + break; + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: case SD_NDISC_OPTION_TARGET_LL_ADDRESS: case SD_NDISC_OPTION_REDIRECTED_HEADER: @@ -199,6 +219,44 @@ static int ndisc_option_consume(Set **options, sd_ndisc_option *p) { return set_ensure_consume(options, &ndisc_option_hash_ops, p); } +int ndisc_option_add_raw(Set **options, size_t offset, size_t length, const uint8_t *bytes) { + _cleanup_free_ uint8_t *copy = NULL; + + assert(options); + assert(bytes); + + if (length == 0) + return -EINVAL; + + copy = newdup(uint8_t, bytes, length); + if (!copy) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(/* type = */ 0, offset); + if (!p) + return -ENOMEM; + + p->raw = (sd_ndisc_raw) { + .bytes = TAKE_PTR(copy), + .length = length, + }; + + 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)); @@ -232,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, @@ -292,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); @@ -319,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); @@ -348,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, @@ -413,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, @@ -459,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); @@ -488,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; @@ -592,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); @@ -640,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, @@ -731,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); @@ -788,6 +1100,10 @@ int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) { return log_debug_errno(r, "Failed to parse NDisc option header: %m"); switch (type) { + case 0: + r = -EBADMSG; + break; + 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); @@ -853,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); +} diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h index 45108ee1aa..2669d5d5c9 100644 --- a/src/libsystemd-network/ndisc-option.h +++ b/src/libsystemd-network/ndisc-option.h @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include "sd-ndisc-protocol.h" @@ -13,6 +15,11 @@ #include "set.h" #include "time-util.h" +typedef struct sd_ndisc_raw { + uint8_t *bytes; + size_t length; +} sd_ndisc_raw; + /* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */ typedef struct sd_ndisc_prefix { uint8_t flags; @@ -51,6 +58,7 @@ typedef struct sd_ndisc_option { size_t offset; union { + sd_ndisc_raw raw; /* for testing or unsupported options */ 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 */ @@ -107,6 +115,11 @@ static inline sd_ndisc_option* ndisc_option_get(Set *options, uint8_t type) { int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret); +int ndisc_option_add_raw( + Set **options, + size_t offset, + size_t length, + const uint8_t *bytes); int ndisc_option_add_link_layer_address( Set **options, uint8_t opt, @@ -160,3 +173,5 @@ int ndisc_option_add_prefix64( uint8_t prefixlen, const struct in6_addr *prefix, usec_t lifetime); + +int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options); diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 939fa87d30..4dbf1b95d6 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -9,6 +9,7 @@ #include "sd-ndisc.h" #include "alloc-util.h" +#include "ether-addr-util.h" #include "event-util.h" #include "fd-util.h" #include "icmp6-util.h" @@ -268,6 +269,29 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda return 0; } +static int ndisc_send_router_solicitation(sd_ndisc *nd) { + static const struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + }; + static const struct nd_router_solicit header = { + .nd_rs_type = ND_ROUTER_SOLICIT, + }; + + _cleanup_set_free_ Set *options = NULL; + int r; + + assert(nd); + + if (!ether_addr_is_null(&nd->mac_addr)) { + r = ndisc_option_add_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, 0, &nd->mac_addr); + if (r < 0) + return r; + } + + return ndisc_send(nd->fd, &dst, &header.nd_rs_hdr, options); +} + static usec_t ndisc_timeout_compute_random(usec_t val) { /* compute a time that is random within ±10% of the given value */ return val - val / 10 + @@ -301,7 +325,7 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { if (r < 0) goto fail; - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); + r = ndisc_send_router_solicitation(nd); if (r < 0) log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m", FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 4541605940..5bf3e84db9 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "fd-util.h" #include "hexdecoct.h" +#include "icmp6-packet.h" #include "icmp6-util-unix.h" #include "socket-util.h" #include "strv.h" @@ -23,7 +24,6 @@ static struct ether_addr mac_addr = { }; static bool verbose = false; -static sd_ndisc *test_timeout_nd; static void router_dump(sd_ndisc_router *rt) { struct in6_addr addr; @@ -232,12 +232,18 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, void *message, v sd_event_exit(e, 0); } +static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + assert_se(icmp6_packet_receive(fd, &packet) >= 0); + + return send_ra(0); +} + TEST(rs) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - send_ra_function = send_ra; - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); @@ -261,9 +267,12 @@ TEST(rs) { assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_loop(e) >= 0); - test_fd[1] = safe_close(test_fd[1]); + test_fd[1] = -EBADF; } static int send_ra_invalid_domain(uint8_t flags) { @@ -312,12 +321,18 @@ static int send_ra_invalid_domain(uint8_t flags) { return 0; } +static int on_recv_rs_invalid_domain(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + assert_se(icmp6_packet_receive(fd, &packet) >= 0); + + return send_ra_invalid_domain(0); +} + TEST(invalid_domain) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - send_ra_function = send_ra_invalid_domain; - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); @@ -335,19 +350,22 @@ TEST(invalid_domain) { assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_invalid_domain, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_loop(e) >= 0); - test_fd[1] = safe_close(test_fd[1]); + test_fd[1] = -EBADF; } -static int test_timeout_value(uint8_t flags) { +static int on_recv_rs_timeout(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + sd_ndisc *nd = ASSERT_PTR(userdata); static int count = 0; static usec_t last = 0; - sd_ndisc *nd = test_timeout_nd; usec_t min, max; - assert_se(nd); - assert_se(nd->event); + assert_se(icmp6_packet_receive(fd, &packet) >= 0); if (++count >= 20) sd_event_exit(nd->event, 0); @@ -391,17 +409,14 @@ static int test_timeout_value(uint8_t flags) { TEST(timeout) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - send_ra_function = test_timeout_value; - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); assert_se(nd); - test_timeout_nd = nd; - assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); @@ -413,9 +428,12 @@ TEST(timeout) { assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_timeout, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_loop(e) >= 0); - test_fd[1] = safe_close(test_fd[1]); + test_fd[1] = -EBADF; } DEFINE_TEST_MAIN(LOG_DEBUG);