sd-ndisc: add basic support of Neighbor Advertisement message

This adds basic support of receiving and parsing Neighbor Advertisement
message defined in RFC 4861.
This commit is contained in:
Yu Watanabe 2024-02-14 20:39:50 +09:00
parent ce18410a54
commit 696eb2b8de
8 changed files with 240 additions and 2 deletions

View 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);

View 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',
)

View file

@ -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);

View file

@ -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);
}

View 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);
}

View 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',

View file

@ -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

View 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)