]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31807 from yuwata/sd-ndisc-send
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 25 Mar 2024 17:55:33 +0000 (02:55 +0900)
committerGitHub <noreply@github.com>
Mon, 25 Mar 2024 17:55:33 +0000 (02:55 +0900)
sd-ndisc: introduce sd_ndisc_send()

src/libsystemd-network/fuzz-ndisc-rs.c
src/libsystemd-network/icmp6-packet.c
src/libsystemd-network/icmp6-packet.h
src/libsystemd-network/icmp6-util-unix.c
src/libsystemd-network/icmp6-util-unix.h
src/libsystemd-network/icmp6-util.c
src/libsystemd-network/icmp6-util.h
src/libsystemd-network/ndisc-option.c
src/libsystemd-network/ndisc-option.h
src/libsystemd-network/sd-ndisc.c
src/libsystemd-network/test-ndisc-rs.c

index a89e2b0ed7c874ee169cd1490d5af9c6b43b83bb..eed670fb6f017180c09784f02be677aa3ecdfa39 100644 (file)
@@ -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;
 }
index a0b0e84f5498cb61389c0708cced56fb645a399e..35459d11a29003a81c18ed0347880b4889e4703f 100644 (file)
@@ -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))
index d77d2e8537a7a53ca8e9c294ca4514ad137b2719..7d02534a8c27c84042b7f2960149e063ae906d41 100644 (file)
@@ -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);
index 5eac4a0e5357ef27a88b030067998ac613acb6b0..c400e4205ec24456b2a6fdfea09774f431f21cef 100644 (file)
@@ -1,12 +1,12 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <netinet/icmp6.h>
 #include <netinet/ip6.h>
 #include <unistd.h>
 
 #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(
index a9cb05a96e39eaa6e498b145f10ebc5901eca0cb..d7b0cc84b366666a9d803dd50a2f2a0ef45b6ed0 100644 (file)
@@ -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];
index 559167847f0980e34833c06786e6290bf370b0da..7b3786e518e60a23816a323becfecf37b2385bfa 100644 (file)
@@ -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;
index 72db688b0b9e8c6ab21f905a4ecb973d9d615048..49cdcba3ff711f89d80f8eff57f58af98fc02e47 100644 (file)
@@ -6,7 +6,9 @@
 ***/
 
 #include <net/ethernet.h>
+#include <netinet/in.h>
 #include <stdbool.h>
+#include <sys/uio.h>
 
 #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,
index 380e71e76433870d1a7a0f85efb7b0d9d8dc78a3..9b4f48700d30faa3cfd4fd5628a202ae715890a3 100644 (file)
@@ -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);
+}
index 45108ee1aa58f93a2ef4521c9ee7d9ccdc9330ba..2669d5d5c91157ca8c3a5837892aa788c1e119b7 100644 (file)
@@ -3,8 +3,10 @@
 
 #include <inttypes.h>
 #include <net/ethernet.h>
+#include <netinet/icmp6.h>
 #include <netinet/in.h>
 #include <netinet/ip6.h>
+#include <sys/uio.h>
 
 #include "sd-ndisc-protocol.h"
 
 #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);
index 939fa87d302355aef2edaac891c0696d9ca1c60e..4dbf1b95d6e17f161e93cb87de11202a1baa4888 100644 (file)
@@ -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));
index 4541605940f4c7c4d5aff876dbc37055ca526048..5bf3e84db99ee31873b13bd1c367195019cc74fa 100644 (file)
@@ -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);