]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: introduce a test executable to send NDisc message
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 5 Mar 2024 05:24:19 +0000 (14:24 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 4 Apr 2024 21:22:19 +0000 (06:22 +0900)
src/libsystemd-network/meson.build
src/libsystemd-network/test-ndisc-send.c [new file with mode: 0644]

index 27922591cc0c237e41f1cf054c39c508bc4a58d3..409c893458452f6302522162061e7961bc0f1f20 100644 (file)
@@ -99,6 +99,10 @@ executables += [
                         'icmp6-util-unix.c',
                 ),
         },
+        network_test_template + {
+                'sources' : files('test-ndisc-send.c'),
+                'type' : 'manual',
+        },
         network_test_template + {
                 'sources' : files('test-sd-dhcp-lease.c'),
         },
diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c
new file mode 100644 (file)
index 0000000..71e445c
--- /dev/null
@@ -0,0 +1,454 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+
+#include "build.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "main-func.h"
+#include "ndisc-option.h"
+#include "netlink-util.h"
+#include "network-common.h"
+#include "parse-util.h"
+#include "socket-util.h"
+#include "strv.h"
+#include "time-util.h"
+
+static int arg_ifindex = 0;
+static int arg_icmp6_type = 0;
+static union in_addr_union arg_dest = IN_ADDR_NULL;
+static uint8_t arg_hop_limit = 0;
+static uint8_t arg_ra_flags = 0;
+static uint8_t arg_preference = false;
+static usec_t arg_lifetime = 0;
+static usec_t arg_reachable = 0;
+static usec_t arg_retransmit = 0;
+static uint32_t arg_na_flags = 0;
+static union in_addr_union arg_target_address = IN_ADDR_NULL;
+static union in_addr_union arg_redirect_destination = IN_ADDR_NULL;
+static bool arg_set_source_mac = false;
+static struct ether_addr arg_source_mac = {};
+static bool arg_set_target_mac = false;
+static struct ether_addr arg_target_mac = {};
+static struct ip6_hdr *arg_redirected_header = NULL;
+static bool arg_set_mtu = false;
+static uint32_t arg_mtu = 0;
+
+STATIC_DESTRUCTOR_REGISTER(arg_redirected_header, freep);
+
+static int parse_icmp6_type(const char *str) {
+        if (STR_IN_SET(str, "router-solicit", "rs", "RS"))
+                return ND_ROUTER_SOLICIT;
+        if (STR_IN_SET(str, "router-advertisement", "ra", "RA"))
+                return ND_ROUTER_ADVERT;
+        if (STR_IN_SET(str, "neighbor-solicit", "ns", "NS"))
+                return ND_NEIGHBOR_SOLICIT;
+        if (STR_IN_SET(str, "neighbor-advertisement", "na", "NA"))
+                return ND_NEIGHBOR_ADVERT;
+        if (STR_IN_SET(str, "redirect", "rd", "RD"))
+                return ND_REDIRECT;
+        return -EINVAL;
+}
+
+static int parse_preference(const char *str) {
+        if (streq(str, "low"))
+                return SD_NDISC_PREFERENCE_LOW;
+        if (streq(str, "medium"))
+                return SD_NDISC_PREFERENCE_MEDIUM;
+        if (streq(str, "high"))
+                return SD_NDISC_PREFERENCE_HIGH;
+        if (streq(str, "reserved"))
+                return SD_NDISC_PREFERENCE_RESERVED;
+        return -EINVAL;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_RA_HOP_LIMIT,
+                ARG_RA_MANAGED,
+                ARG_RA_OTHER,
+                ARG_RA_HOME_AGENT,
+                ARG_RA_PREFERENCE,
+                ARG_RA_LIFETIME,
+                ARG_RA_REACHABLE,
+                ARG_RA_RETRANSMIT,
+                ARG_NA_ROUTER,
+                ARG_NA_SOLICITED,
+                ARG_NA_OVERRIDE,
+                ARG_TARGET_ADDRESS,
+                ARG_REDIRECT_DESTINATION,
+                ARG_OPTION_SOURCE_LL,
+                ARG_OPTION_TARGET_LL,
+                ARG_OPTION_REDIRECTED_HEADER,
+                ARG_OPTION_MTU,
+        };
+
+        static const struct option options[] = {
+                { "version",              no_argument,       NULL, ARG_VERSION                  },
+                { "interface",            required_argument, NULL, 'i'                          },
+                { "type",                 required_argument, NULL, 't'                          },
+                { "dest",                 required_argument, NULL, 'd'                          },
+                /* For Router Advertisement */
+                { "hop-limit",            required_argument, NULL, ARG_RA_HOP_LIMIT             },
+                { "managed",              required_argument, NULL, ARG_RA_MANAGED               },
+                { "other",                required_argument, NULL, ARG_RA_OTHER                 },
+                { "home-agent",           required_argument, NULL, ARG_RA_HOME_AGENT            },
+                { "preference",           required_argument, NULL, ARG_RA_PREFERENCE            },
+                { "lifetime",             required_argument, NULL, ARG_RA_LIFETIME              },
+                { "reachable-time",       required_argument, NULL, ARG_RA_REACHABLE             },
+                { "retransmit-timer",     required_argument, NULL, ARG_RA_RETRANSMIT            },
+                /* For Neighbor Advertisement */
+                { "is-router",            required_argument, NULL, ARG_NA_ROUTER                },
+                { "is-solicited",         required_argument, NULL, ARG_NA_SOLICITED             },
+                { "is-override",          required_argument, NULL, ARG_NA_OVERRIDE              },
+                /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */
+                { "target-address",       required_argument, NULL, ARG_TARGET_ADDRESS           },
+                /* For Redirect */
+                { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION     },
+                /* Options */
+                { "source-ll-address",    required_argument, NULL, ARG_OPTION_SOURCE_LL         },
+                { "target-ll-address",    required_argument, NULL, ARG_OPTION_TARGET_LL         },
+                { "redirected-header",    required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER },
+                { "mtu",                  required_argument, NULL, ARG_OPTION_MTU               },
+                {}
+        };
+
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        int c, r;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case ARG_VERSION:
+                        return version();
+
+                case 'i':
+                        r = rtnl_resolve_interface_or_warn(&rtnl, optarg);
+                        if (r < 0)
+                                return r;
+                        arg_ifindex = r;
+                        break;
+
+                case 't':
+                        r = parse_icmp6_type(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse message type: %m");
+                        arg_icmp6_type = r;
+                        break;
+
+                case 'd':
+                        r = in_addr_from_string(AF_INET6, optarg, &arg_dest);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse destination address: %m");
+                        if (!in6_addr_is_link_local(&arg_dest.in6))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "The destination address %s is not a link-local address.", optarg);
+                        break;
+
+                case ARG_RA_HOP_LIMIT:
+                        r = safe_atou8(optarg, &arg_hop_limit);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse hop limit: %m");
+                        break;
+
+                case ARG_RA_MANAGED:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse managed flag: %m");
+                        SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r);
+                        break;
+
+                case ARG_RA_OTHER:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse other flag: %m");
+                        SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r);
+                        break;
+
+                case ARG_RA_HOME_AGENT:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse home-agent flag: %m");
+                        SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r);
+                        break;
+
+                case ARG_RA_PREFERENCE:
+                        r = parse_preference(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse preference: %m");
+                        arg_preference = r;
+                        break;
+
+                case ARG_RA_LIFETIME:
+                        r = parse_sec(optarg, &arg_lifetime);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse lifetime: %m");
+                        break;
+
+                case ARG_RA_REACHABLE:
+                        r = parse_sec(optarg, &arg_reachable);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse reachable time: %m");
+                        break;
+
+                case ARG_RA_RETRANSMIT:
+                        r = parse_sec(optarg, &arg_retransmit);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse retransmit timer: %m");
+                        break;
+
+                case ARG_NA_ROUTER:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse is-router flag: %m");
+                        SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r);
+                        break;
+
+                case ARG_NA_SOLICITED:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse is-solicited flag: %m");
+                        SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r);
+                        break;
+
+                case ARG_NA_OVERRIDE:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse is-override flag: %m");
+                        SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r);
+                        break;
+
+                case ARG_TARGET_ADDRESS:
+                        r = in_addr_from_string(AF_INET6, optarg, &arg_target_address);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse target address: %m");
+                        break;
+
+                case ARG_REDIRECT_DESTINATION:
+                        r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse destination address: %m");
+                        break;
+
+                case ARG_OPTION_SOURCE_LL:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse source LL address option: %m");
+                        arg_set_source_mac = r;
+                        break;
+
+                case ARG_OPTION_TARGET_LL:
+                        r = parse_ether_addr(optarg, &arg_target_mac);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse target LL address option: %m");
+                        arg_set_target_mac = true;
+                        break;
+
+                case ARG_OPTION_REDIRECTED_HEADER: {
+                        _cleanup_free_ void *p = NULL;
+                        size_t len;
+
+                        r = unbase64mem(optarg, &p, &len);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse redirected header: %m");
+
+                        if (len < sizeof(struct ip6_hdr))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid redirected header.");
+
+                        arg_redirected_header = TAKE_PTR(p);
+                        break;
+                }
+                case ARG_OPTION_MTU:
+                        r = safe_atou32(optarg, &arg_mtu);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse MTU: %m");
+                        arg_set_mtu = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached();
+                }
+        }
+
+        if (arg_ifindex <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory.");
+
+        if (arg_icmp6_type <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--type/-t option is mandatory.");
+
+        if (in6_addr_is_null(&arg_dest.in6)) {
+                if (IN_SET(arg_icmp6_type, ND_ROUTER_ADVERT, ND_NEIGHBOR_ADVERT, ND_REDIRECT))
+                        arg_dest.in6 = (struct in6_addr) IN6ADDR_ALL_NODES_MULTICAST_INIT;
+                else
+                        arg_dest.in6 = (struct in6_addr) IN6ADDR_ALL_ROUTERS_MULTICAST_INIT;
+        }
+
+        if (arg_set_source_mac) {
+                struct hw_addr_data hw_addr;
+
+                r = rtnl_get_link_info(&rtnl, arg_ifindex,
+                                       /* ret_iftype = */ NULL,
+                                       /* ret_flags = */ NULL,
+                                       /* ret_kind = */ NULL,
+                                       &hw_addr,
+                                       /* ret_permanent_hw_addr = */ NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get the source link-layer address: %m");
+
+                if (hw_addr.length != sizeof(struct ether_addr))
+                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "Unsupported hardware address length %zu: %m",
+                                               hw_addr.length);
+
+                arg_source_mac = hw_addr.ether;
+        }
+
+        return 1;
+}
+
+static int send_icmp6(int fd, const struct icmp6_hdr *hdr) {
+        _cleanup_set_free_ Set *options = NULL;
+        int r;
+
+        assert(fd >= 0);
+        assert(hdr);
+
+        if (arg_set_source_mac) {
+                r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &arg_source_mac);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_set_target_mac) {
+                r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_TARGET_LL_ADDRESS, &arg_target_mac);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_redirected_header) {
+                r = ndisc_option_add_redirected_header(&options, 0, arg_redirected_header);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_set_mtu) {
+                r = ndisc_option_add_mtu(&options, 0, arg_mtu);
+                if (r < 0)
+                        return r;
+        }
+
+        struct sockaddr_in6 dst_sockaddr = {
+                .sin6_family = AF_INET6,
+                .sin6_addr = arg_dest.in6,
+        };
+
+        return ndisc_send(fd, &dst_sockaddr, hdr, options, now(CLOCK_BOOTTIME));
+}
+
+static int send_router_solicit(int fd) {
+        struct nd_router_solicit hdr = {
+                .nd_rs_type = ND_ROUTER_SOLICIT,
+        };
+
+        assert(fd >= 0);
+
+        return send_icmp6(fd, &hdr.nd_rs_hdr);
+}
+
+static int send_router_advertisement(int fd) {
+        struct nd_router_advert hdr = {
+                .nd_ra_type = ND_ROUTER_ADVERT,
+                .nd_ra_router_lifetime = usec_to_be16_sec(arg_lifetime),
+                .nd_ra_reachable = usec_to_be32_msec(arg_reachable),
+                .nd_ra_retransmit = usec_to_be32_msec(arg_retransmit),
+        };
+
+        assert(fd >= 0);
+
+        /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
+         * simultaneously in the structured initializer in the above. */
+        hdr.nd_ra_curhoplimit = arg_hop_limit;
+        hdr.nd_ra_flags_reserved = arg_ra_flags;
+
+        return send_icmp6(fd, &hdr.nd_ra_hdr);
+}
+
+static int send_neighbor_solicit(int fd) {
+        struct nd_neighbor_solicit hdr = {
+                .nd_ns_type = ND_NEIGHBOR_SOLICIT,
+                .nd_ns_target = arg_target_address.in6,
+        };
+
+        assert(fd >= 0);
+
+        return send_icmp6(fd, &hdr.nd_ns_hdr);
+}
+
+static int send_neighbor_advertisement(int fd) {
+        struct nd_neighbor_advert hdr = {
+                .nd_na_type = ND_NEIGHBOR_ADVERT,
+                .nd_na_flags_reserved = arg_na_flags,
+                .nd_na_target = arg_target_address.in6,
+        };
+
+        assert(fd >= 0);
+
+        return send_icmp6(fd, &hdr.nd_na_hdr);
+}
+
+static int send_redirect(int fd) {
+        struct nd_redirect hdr = {
+                .nd_rd_type = ND_REDIRECT,
+                .nd_rd_target = arg_target_address.in6,
+                .nd_rd_dst = arg_redirect_destination.in6,
+        };
+
+        assert(fd >= 0);
+
+        return send_icmp6(fd, &hdr.nd_rd_hdr);
+}
+
+static int run(int argc, char *argv[]) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        log_setup();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        fd = icmp6_bind(arg_ifindex, /* is_router = */ false);
+        if (fd < 0)
+                return log_error_errno(fd, "Failed to bind socket to interface: %m");
+
+        switch (arg_icmp6_type) {
+        case ND_ROUTER_SOLICIT:
+                return send_router_solicit(fd);
+        case ND_ROUTER_ADVERT:
+                return send_router_advertisement(fd);
+        case ND_NEIGHBOR_SOLICIT:
+                return send_neighbor_solicit(fd);
+        case ND_NEIGHBOR_ADVERT:
+                return send_neighbor_advertisement(fd);
+        case ND_REDIRECT:
+                return send_redirect(fd);
+        default:
+                assert_not_reached();
+        }
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);