diff options
-rw-r--r-- | src/libsystemd-network/icmp6-util.c | 1 | ||||
-rw-r--r-- | src/libsystemd-network/meson.build | 1 | ||||
-rw-r--r-- | src/libsystemd-network/ndisc-neighbor-internal.h | 21 | ||||
-rw-r--r-- | src/libsystemd-network/sd-ndisc-neighbor.c | 126 | ||||
-rw-r--r-- | src/libsystemd-network/sd-ndisc.c | 40 | ||||
-rw-r--r-- | src/systemd/meson.build | 1 | ||||
-rw-r--r-- | src/systemd/sd-ndisc-neighbor.h | 50 | ||||
-rw-r--r-- | src/systemd/sd-ndisc.h | 2 |
8 files changed, 240 insertions, 2 deletions
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 <netinet/icmp6.h> + +#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/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 <https://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <time.h> + +#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 <sys/types.h> #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) |