diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 7b3786e518..44c15f9044 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -42,6 +42,7 @@ int icmp6_bind(int ifindex, bool is_router) { .ipv6mr_interface = ifindex, }; ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); } s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index e4340f9957..46a6119c6b 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -28,6 +28,7 @@ sources = files( 'sd-lldp-rx.c', 'sd-lldp-tx.c', 'sd-ndisc.c', + 'sd-ndisc-neighbor.c', 'sd-ndisc-router.c', 'sd-radv.c', ) diff --git a/src/libsystemd-network/ndisc-neighbor-internal.h b/src/libsystemd-network/ndisc-neighbor-internal.h new file mode 100644 index 0000000000..aee655665e --- /dev/null +++ b/src/libsystemd-network/ndisc-neighbor-internal.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-ndisc.h" + +#include "icmp6-packet.h" +#include "set.h" + +struct sd_ndisc_neighbor { + unsigned n_ref; + + ICMP6Packet *packet; + + uint32_t flags; + struct in6_addr target_address; + + Set *options; +}; + +sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet); +int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na); diff --git a/src/libsystemd-network/sd-ndisc-neighbor.c b/src/libsystemd-network/sd-ndisc-neighbor.c new file mode 100644 index 0000000000..1bb6ebf610 --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-neighbor.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "ndisc-internal.h" +#include "ndisc-neighbor-internal.h" +#include "ndisc-option.h" + +static sd_ndisc_neighbor* ndisc_neighbor_free(sd_ndisc_neighbor *na) { + if (!na) + return NULL; + + icmp6_packet_unref(na->packet); + set_free(na->options); + return mfree(na); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor, ndisc_neighbor_free); + +sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet) { + sd_ndisc_neighbor *na; + + assert(packet); + + na = new(sd_ndisc_neighbor, 1); + if (!na) + return NULL; + + *na = (sd_ndisc_neighbor) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + }; + + return na; +} + +int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na) { + int r; + + assert(na); + + if (na->packet->raw_size < sizeof(struct nd_neighbor_advert)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a neighbor advertisement, ignoring datagram."); + + /* Neighbor advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */ + const struct nd_neighbor_advert *a = (const struct nd_neighbor_advert*) na->packet->raw_packet; + assert(a->nd_na_type == ND_NEIGHBOR_ADVERT); + assert(a->nd_na_code == 0); + + na->flags = a->nd_na_flags_reserved; /* the first 3 bits */ + na->target_address = a->nd_na_target; + + /* RFC 4861 section 4.4: + * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that + * prompted this advertisement. For an unsolicited advertisement, the address whose link-layer + * address has changed. The Target Address MUST NOT be a multicast address. + * + * Here, we only check if the target address is a link-layer address (or a null address, for safety) + * when the message is an unsolicited neighbor advertisement. */ + if (!FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED)) + if (!in6_addr_is_link_local(&na->target_address) && !in6_addr_is_null(&na->target_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received ND packet with an invalid target address (%s), ignoring datagram.", + IN6_ADDR_TO_STRING(&na->target_address)); + + r = ndisc_parse_options(na->packet, &na->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in neighbor advertisement message, ignoring: %m"); + + return 0; +} + +int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret) { + assert_return(na, -EINVAL); + + return icmp6_packet_get_sender_address(na->packet, ret); +} + +int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret) { + assert_return(na, -EINVAL); + + if (in6_addr_is_null(&na->target_address)) + /* fall back to the sender address, for safety. */ + return sd_ndisc_neighbor_get_sender_address(na, ret); + + if (ret) + *ret = na->target_address; + return 0; +} + +int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret) { + assert_return(na, -EINVAL); + + return ndisc_option_get_mac(na->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret); +} + +int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret) { + assert_return(na, -EINVAL); + + if (ret) + *ret = na->flags; + return 0; +} + +int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_ROUTER); +} + +int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED); +} + +int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_OVERRIDE); +} diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 18752a94e7..373e7a45ce 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -16,6 +16,7 @@ #include "in-addr-util.h" #include "memory-util.h" #include "ndisc-internal.h" +#include "ndisc-neighbor-internal.h" #include "ndisc-router-internal.h" #include "network-common.h" #include "random-util.h" @@ -26,8 +27,9 @@ #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { - [SD_NDISC_EVENT_TIMEOUT] = "timeout", - [SD_NDISC_EVENT_ROUTER] = "router", + [SD_NDISC_EVENT_TIMEOUT] = "timeout", + [SD_NDISC_EVENT_ROUTER] = "router", + [SD_NDISC_EVENT_NEIGHBOR] = "neighbor", }; DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t); @@ -225,6 +227,36 @@ static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) { return 0; } +static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) { + _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL; + struct in6_addr a; + int r; + + assert(nd); + assert(packet); + + na = ndisc_neighbor_new(packet); + if (!na) + return -ENOMEM; + + r = ndisc_neighbor_parse(nd, na); + if (r < 0) + return r; + + r = sd_ndisc_neighbor_get_sender_address(na, &a); + if (r < 0) + return r; + + log_ndisc(nd, "Received Neighbor Advertisement from %s: Router=%s, Solicited=%s, Override=%s", + IN6_ADDR_TO_STRING(&a), + yes_no(sd_ndisc_neighbor_is_router(na) > 0), + yes_no(sd_ndisc_neighbor_is_solicited(na) > 0), + yes_no(sd_ndisc_neighbor_is_override(na) > 0)); + + ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na); + return 0; +} + static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; sd_ndisc *nd = ASSERT_PTR(userdata); @@ -262,6 +294,10 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda (void) ndisc_handle_router(nd, packet); break; + case ND_NEIGHBOR_ADVERT: + (void) ndisc_handle_neighbor(nd, packet); + break; + default: log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r); } diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 5bf3e84db9..5ad2c92b49 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -195,7 +195,7 @@ static int send_ra(uint8_t flags) { return 0; } -static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { +static void test_callback_ra(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { sd_event *e = userdata; static unsigned idx = 0; uint64_t flags_array[] = { @@ -253,7 +253,7 @@ TEST(rs) { assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_ra, e) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, 30 * USEC_PER_SEC, 0, @@ -342,7 +342,7 @@ TEST(invalid_domain) { assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_ra, e) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, 30 * USEC_PER_SEC, 0, @@ -358,6 +358,120 @@ TEST(invalid_domain) { test_fd[1] = -EBADF; } +static void neighbor_dump(sd_ndisc_neighbor *na) { + struct in6_addr addr; + uint32_t flags; + + assert_se(na); + + log_info("--"); + assert_se(sd_ndisc_neighbor_get_sender_address(na, &addr) >= 0); + log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr)); + + assert_se(sd_ndisc_neighbor_get_flags(na, &flags) >= 0); + log_info("Flags: Router:%s, Solicited:%s, Override: %s", + yes_no(flags & ND_NA_FLAG_ROUTER), + yes_no(flags & ND_NA_FLAG_SOLICITED), + yes_no(flags & ND_NA_FLAG_OVERRIDE)); + + assert_se(sd_ndisc_neighbor_is_router(na) == FLAGS_SET(flags, ND_NA_FLAG_ROUTER)); + assert_se(sd_ndisc_neighbor_is_solicited(na) == FLAGS_SET(flags, ND_NA_FLAG_SOLICITED)); + assert_se(sd_ndisc_neighbor_is_override(na) == FLAGS_SET(flags, ND_NA_FLAG_OVERRIDE)); +} + +static int send_na(uint32_t flags) { + uint8_t advertisement[] = { + /* struct nd_neighbor_advert */ + 0x88, 0x00, 0xde, 0x83, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* type = 0x02 (SD_NDISC_OPTION_TARGET_LL_ADDRESS), length = 8 */ + 0x01, 0x01, 'A', 'B', 'C', '1', '2', '3', + }; + + ((struct nd_neighbor_advert*) advertisement)->nd_na_flags_reserved = flags; + + assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == sizeof(advertisement)); + if (verbose) + printf(" sent NA with flag 0x%02x\n", flags); + + return 0; +} + +static void test_callback_na(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { + sd_event *e = userdata; + static unsigned idx = 0; + uint32_t flags_array[] = { + 0, + 0, + ND_NA_FLAG_ROUTER, + ND_NA_FLAG_SOLICITED, + ND_NA_FLAG_SOLICITED | ND_NA_FLAG_OVERRIDE, + }; + uint32_t flags; + + assert_se(nd); + + if (event != SD_NDISC_EVENT_NEIGHBOR) + return; + + sd_ndisc_neighbor *rt = ASSERT_PTR(message); + + neighbor_dump(rt); + + assert_se(sd_ndisc_neighbor_get_flags(rt, &flags) >= 0); + assert_se(flags == flags_array[idx]); + idx++; + + if (verbose) + printf(" got event 0x%02" PRIx32 "\n", flags); + + if (idx < ELEMENTSOF(flags_array)) { + send_na(flags_array[idx]); + return; + } + + idx = 0; + sd_event_exit(e, 0); +} + +static int on_recv_rs_na(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_na(0); +} + +TEST(na) { + _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; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_na, e) >= 0); + + assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, + 30 * USEC_PER_SEC, 0, + NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_na, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + test_fd[1] = -EBADF; +} + 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); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 61d591a2af..71e3c30727 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1391,7 +1391,7 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { } } -static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { +static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec) { bool updated = false; NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; @@ -1420,6 +1420,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (route->lifetime_usec > timestamp_usec) continue; /* the route is still valid */ + if (router && !in6_addr_equal(&route->provider.in6, router)) + continue; + r = route_remove_and_cancel(route, link->manager); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m")); @@ -1432,6 +1435,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (address->lifetime_valid_usec > timestamp_usec) continue; /* the address is still valid */ + if (router && !in6_addr_equal(&address->provider.in6, router)) + continue; + r = address_remove_and_cancel(address, link); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC address, ignoring: %m")); @@ -1441,6 +1447,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (rdnss->lifetime_usec > timestamp_usec) continue; /* the DNS server is still valid */ + if (router && !in6_addr_equal(&rdnss->router, router)) + continue; + free(set_remove(link->ndisc_rdnss, rdnss)); updated = true; } @@ -1449,6 +1458,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (dnssl->lifetime_usec > timestamp_usec) continue; /* the DNS domain is still valid */ + if (router && !in6_addr_equal(&dnssl->router, router)) + continue; + free(set_remove(link->ndisc_dnssl, dnssl)); updated = true; } @@ -1457,6 +1469,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (cp->lifetime_usec > timestamp_usec) continue; /* the captive portal is still valid */ + if (router && !in6_addr_equal(&cp->router, router)) + continue; + ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, cp)); updated = true; } @@ -1465,6 +1480,9 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (p64->lifetime_usec > timestamp_usec) continue; /* the pref64 prefix is still valid */ + if (router && !in6_addr_equal(&p64->router, router)) + continue; + free(set_remove(link->ndisc_pref64, p64)); /* The pref64 prefix is not exported through the state file, hence it is not necessary to set * the 'updated' flag. */ @@ -1486,7 +1504,7 @@ static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdat assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); - (void) ndisc_drop_outdated(link, now_usec); + (void) ndisc_drop_outdated(link, /* router = */ NULL, now_usec); (void) ndisc_setup_expire(link); return 0; } @@ -1635,7 +1653,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; - r = ndisc_drop_outdated(link, timestamp_usec); + r = ndisc_drop_outdated(link, /* router = */ NULL, timestamp_usec); if (r < 0) return r; @@ -1679,6 +1697,131 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { return 0; } +static int ndisc_neighbor_handle_non_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr address; + int r; + + assert(link); + assert(na); + + /* Received Neighbor Advertisement message without Router flag. The node might have been a router, + * and now it is not. Let's drop all configurations based on RAs sent from the node. */ + + r = sd_ndisc_neighbor_get_target_address(na, &address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + (void) ndisc_drop_outdated(link, /* router = */ &address, /* timestamp_usec = */ USEC_INFINITY); + return 0; +} + +static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr current_address, original_address; + int r; + + assert(link); + assert(link->manager); + assert(na); + + /* Received Neighbor Advertisement message with Router flag. If the router address is changed, update + * the provider field of configurations. */ + + r = sd_ndisc_neighbor_get_sender_address(na, ¤t_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + r = sd_ndisc_neighbor_get_target_address(na, &original_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + if (in6_addr_equal(¤t_address, &original_address)) + return 0; /* the router address is not changed */ + + Route *route; + SET_FOREACH(route, link->manager->routes) { + if (route->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (route->nexthop.ifindex != link->ifindex) + continue; + + if (!in6_addr_equal(&route->provider.in6, &original_address)) + continue; + + route->provider.in6 = current_address; + } + + Address *address; + SET_FOREACH(address, link->addresses) { + if (address->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (!in6_addr_equal(&address->provider.in6, &original_address)) + continue; + + address->provider.in6 = current_address; + } + + NDiscRDNSS *rdnss; + SET_FOREACH(rdnss, link->ndisc_rdnss) { + if (!in6_addr_equal(&rdnss->router, &original_address)) + continue; + + rdnss->router = current_address; + } + + NDiscDNSSL *dnssl; + SET_FOREACH(dnssl, link->ndisc_dnssl) { + if (!in6_addr_equal(&dnssl->router, &original_address)) + continue; + + dnssl->router = current_address; + } + + NDiscCaptivePortal *cp; + SET_FOREACH(cp, link->ndisc_captive_portals) { + if (!in6_addr_equal(&cp->router, &original_address)) + continue; + + cp->router = current_address; + } + + NDiscPREF64 *p64; + SET_FOREACH(p64, link->ndisc_pref64) { + if (!in6_addr_equal(&p64->router, &original_address)) + continue; + + p64->router = current_address; + } + + return 0; +} + +static int ndisc_neighbor_handler(Link *link, sd_ndisc_neighbor *na) { + int r; + + assert(link); + assert(na); + + r = sd_ndisc_neighbor_is_router(na); + if (r < 0) + return r; + if (r == 0) + r = ndisc_neighbor_handle_non_router_message(link, na); + else + r = ndisc_neighbor_handle_router_message(link, na); + if (r < 0) + return r; + + return 0; +} + static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { Link *link = ASSERT_PTR(userdata); int r; @@ -1696,6 +1839,14 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, v } break; + case SD_NDISC_EVENT_NEIGHBOR: + r = ndisc_neighbor_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + link_enter_failed(link); + return; + } + break; + case SD_NDISC_EVENT_TIMEOUT: log_link_debug(link, "NDisc handler get timeout event"); if (link->ndisc_messages == 0) { @@ -1825,7 +1976,7 @@ void ndisc_flush(Link *link) { assert(link); /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */ - (void) ndisc_drop_outdated(link, /* timestamp_usec = */ USEC_INFINITY); + (void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY); link->ndisc_rdnss = set_free(link->ndisc_rdnss); link->ndisc_dnssl = set_free(link->ndisc_dnssl); diff --git a/src/systemd/meson.build b/src/systemd/meson.build index a91d7064bb..389f32908c 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -38,6 +38,7 @@ _not_installed_headers = [ 'sd-lldp-tx.h', 'sd-lldp.h', 'sd-ndisc.h', + 'sd-ndisc-neighbor.h', 'sd-ndisc-protocol.h', 'sd-ndisc-router.h', 'sd-netlink.h', diff --git a/src/systemd/sd-ndisc-neighbor.h b/src/systemd/sd-ndisc-neighbor.h new file mode 100644 index 0000000000..2ea033741f --- /dev/null +++ b/src/systemd/sd-ndisc-neighbor.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscneighborfoo +#define foosdndiscneighborfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_neighbor sd_ndisc_neighbor; + +sd_ndisc_neighbor *sd_ndisc_neighbor_ref(sd_ndisc_neighbor *na); +sd_ndisc_neighbor *sd_ndisc_neighbor_unref(sd_ndisc_neighbor *na); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor_unref); + +int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret); +/* RFC 4861 section 4.4: + * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that prompted + * this advertisement. For an unsolicited advertisement, the address whose link-layer address has changed. + * The Target Address MUST NOT be a multicast address. */ +int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret); +int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret); +int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret); +int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na); +int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na); +int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index 5f4f6caf8d..185e0c6fd8 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -26,6 +26,7 @@ #include #include "sd-event.h" +#include "sd-ndisc-neighbor.h" #include "sd-ndisc-protocol.h" #include "sd-ndisc-router.h" @@ -38,6 +39,7 @@ typedef struct sd_ndisc sd_ndisc; __extension__ typedef enum sd_ndisc_event_t { SD_NDISC_EVENT_TIMEOUT, SD_NDISC_EVENT_ROUTER, + SD_NDISC_EVENT_NEIGHBOR, _SD_NDISC_EVENT_MAX, _SD_NDISC_EVENT_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(NDISC_EVENT) diff --git a/test/test-network/conf/25-dhcp-server-veth-peer.network b/test/test-network/conf/25-dhcp-server-veth-peer.network index d5cc6d3334..abe3fa2596 100644 --- a/test/test-network/conf/25-dhcp-server-veth-peer.network +++ b/test/test-network/conf/25-dhcp-server-veth-peer.network @@ -6,3 +6,5 @@ Name=veth-peer IPv6AcceptRA=no Address=2600::1/0 Address=192.168.5.1/24 +# To make the kernel send NA with IsRouter flag. +IPv6Forwarding=yes