]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-ndisc: add basic support of Neighbor Advertisement message
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 14 Feb 2024 11:39:50 +0000 (20:39 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 4 Apr 2024 17:16:03 +0000 (02:16 +0900)
This adds basic support of receiving and parsing Neighbor Advertisement
message defined in RFC 4861.

src/libsystemd-network/icmp6-util.c
src/libsystemd-network/meson.build
src/libsystemd-network/ndisc-neighbor-internal.h [new file with mode: 0644]
src/libsystemd-network/sd-ndisc-neighbor.c [new file with mode: 0644]
src/libsystemd-network/sd-ndisc.c
src/systemd/meson.build
src/systemd/sd-ndisc-neighbor.h [new file with mode: 0644]
src/systemd/sd-ndisc.h

index 7b3786e518e60a23816a323becfecf37b2385bfa..44c15f904469dae3ec0edba7d3595082fab7411b 100644 (file)
@@ -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);
index e4340f995794756f0a098b4afdf0c5b7000e8ad5..46a6119c6b6c8e66b0184c6060a82013a75a38e0 100644 (file)
@@ -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 (file)
index 0000000..aee6556
--- /dev/null
@@ -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 (file)
index 0000000..1bb6ebf
--- /dev/null
@@ -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);
+}
index 18752a94e7af9698b20b4344cd81c52083254723..373e7a45ce61f568ca668d5e85984521bf5f0d7e 100644 (file)
@@ -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);
         }
index a91d7064bbef970cb2bbf776f683ec3ff92777f3..389f32908c673a285a623891ed7ed1762bc43f1e 100644 (file)
@@ -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 (file)
index 0000000..2ea0337
--- /dev/null
@@ -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
index 5f4f6caf8d2e5259c260dc5ef9f2c09947c4b0ee..185e0c6fd8217debad53b6b015ad917fbac451a9 100644 (file)
@@ -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)