summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network/sd-ndisc.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libsystemd-network/sd-ndisc.c329
1 files changed, 253 insertions, 76 deletions
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index 1beed5d..ca15f94 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -9,13 +9,16 @@
#include "sd-ndisc.h"
#include "alloc-util.h"
+#include "ether-addr-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
#include "memory-util.h"
#include "ndisc-internal.h"
-#include "ndisc-router.h"
+#include "ndisc-neighbor-internal.h"
+#include "ndisc-redirect-internal.h"
+#include "ndisc-router-internal.h"
#include "network-common.h"
#include "random-util.h"
#include "socket-util.h"
@@ -25,13 +28,15 @@
#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",
+ [SD_NDISC_EVENT_REDIRECT] = "redirect",
};
DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
-static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) {
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) {
assert(ndisc);
assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
@@ -39,7 +44,14 @@ static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_rou
return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
- ndisc->callback(ndisc, event, rt, ndisc->userdata);
+ ndisc->callback(ndisc, event, message, ndisc->userdata);
+}
+
+int sd_ndisc_is_running(sd_ndisc *nd) {
+ if (!nd)
+ return false;
+
+ return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0;
}
int sd_ndisc_set_callback(
@@ -58,7 +70,7 @@ int sd_ndisc_set_callback(
int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
assert_return(nd, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
- assert_return(nd->fd < 0, -EBUSY);
+ assert_return(!sd_ndisc_is_running(nd), -EBUSY);
nd->ifindex = ifindex;
return 0;
@@ -89,6 +101,18 @@ int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
return 0;
}
+int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) {
+ assert_return(nd, -EINVAL);
+ assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
+
+ if (addr)
+ nd->link_local_addr = *addr;
+ else
+ zero(nd->link_local_addr);
+
+ return 0;
+}
+
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
@@ -104,7 +128,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int r;
assert_return(nd, -EINVAL);
- assert_return(nd->fd < 0, -EBUSY);
+ assert_return(!sd_ndisc_is_running(nd), -EBUSY);
assert_return(!nd->event, -EBUSY);
if (event)
@@ -123,7 +147,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int sd_ndisc_detach_event(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
- assert_return(nd->fd < 0, -EBUSY);
+ assert_return(!sd_ndisc_is_running(nd), -EBUSY);
nd->event = sd_event_unref(nd->event);
return 0;
@@ -179,78 +203,207 @@ int sd_ndisc_new(sd_ndisc **ret) {
return 0;
}
-static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
+static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
int r;
assert(nd);
- assert(rt);
+ assert(packet);
+
+ rt = ndisc_router_new(packet);
+ if (!rt)
+ return -ENOMEM;
r = ndisc_router_parse(nd, rt);
if (r < 0)
return r;
- log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s",
- rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
- rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
- FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC));
+ (void) event_source_disable(nd->timeout_event_source);
+ (void) event_source_disable(nd->timeout_no_ra);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *s = NULL;
+ struct in6_addr a;
+ uint64_t flags;
+ uint8_t pref;
+ usec_t lifetime;
+
+ r = sd_ndisc_router_get_sender_address(rt, &a);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_flags(rt, &flags);
+ if (r < 0)
+ return r;
+
+ r = ndisc_router_flags_to_string(flags, &s);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_preference(rt, &pref);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_lifetime(rt, &lifetime);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Router Advertisement from %s: flags=0x%0*"PRIx64"(%s), preference=%s, lifetime=%s",
+ IN6_ADDR_TO_STRING(&a),
+ flags & UINT64_C(0x00ffffffffffff00) ? 14 : 2, flags, /* suppress too many zeros if no extension */
+ s ?: "none",
+ ndisc_router_preference_to_string(pref),
+ FORMAT_TIMESPAN(lifetime, USEC_PER_SEC));
+ }
ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
return 0;
}
+static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL;
+ 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;
+
+ if (DEBUG_LOGGING) {
+ struct in6_addr a;
+
+ 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_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL;
+ int r;
+
+ assert(nd);
+ assert(packet);
+
+ rd = ndisc_redirect_new(packet);
+ if (!rd)
+ return -ENOMEM;
+
+ r = ndisc_redirect_parse(nd, rd);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ struct in6_addr sender, target, dest;
+
+ r = sd_ndisc_redirect_get_sender_address(rd, &sender);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_target_address(rd, &target);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_destination_address(rd, &dest);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s",
+ IN6_ADDR_TO_STRING(&sender),
+ IN6_ADDR_TO_STRING(&target),
+ IN6_ADDR_TO_STRING(&dest));
+ }
+
+ ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd);
+ return 0;
+}
+
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
sd_ndisc *nd = ASSERT_PTR(userdata);
- ssize_t buflen;
int r;
assert(s);
assert(nd->event);
- buflen = next_datagram_size_fd(fd);
- if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ r = icmp6_packet_receive(fd, &packet);
+ if (r < 0) {
+ log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m");
return 0;
- if (buflen < 0) {
- log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ }
+
+ /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
+ * that hosts MUST discard messages with the null source address. */
+ if (in6_addr_is_null(&packet->sender_address)) {
+ log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
return 0;
}
- rt = ndisc_router_new(buflen);
- if (!rt)
- return -ENOMEM;
+ if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) {
+ log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring.");
+ return 0;
+ }
- r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
- if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
+ r = icmp6_packet_get_type(packet);
+ if (r < 0) {
+ log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m");
return 0;
- if (r < 0)
- switch (r) {
- case -EADDRNOTAVAIL:
- log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring.");
- return 0;
+ }
- case -EMULTIHOP:
- log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
- return 0;
+ switch (r) {
+ case ND_ROUTER_ADVERT:
+ (void) ndisc_handle_router(nd, packet);
+ break;
- case -EPFNOSUPPORT:
- log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
- return 0;
+ case ND_NEIGHBOR_ADVERT:
+ (void) ndisc_handle_neighbor(nd, packet);
+ break;
- default:
- log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
- return 0;
- }
+ case ND_REDIRECT:
+ (void) ndisc_handle_redirect(nd, packet);
+ break;
- /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
- * that hosts MUST discard messages with the null source address. */
- if (in6_addr_is_null(&rt->address))
- log_ndisc(nd, "Received RA from null address. Ignoring.");
+ default:
+ log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
+ }
- (void) event_source_disable(nd->timeout_event_source);
- (void) ndisc_handle_datagram(nd, rt);
return 0;
}
+static int ndisc_send_router_solicitation(sd_ndisc *nd) {
+ static const struct nd_router_solicit header = {
+ .nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+
+ _cleanup_set_free_ Set *options = NULL;
+ int r;
+
+ assert(nd);
+
+ if (!ether_addr_is_null(&nd->mac_addr)) {
+ r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(nd->fd, &IN6_ADDR_ALL_ROUTERS_MULTICAST, &header.nd_rs_hdr, options, USEC_INFINITY);
+}
+
static usec_t ndisc_timeout_compute_random(usec_t val) {
/* compute a time that is random within ±10% of the given value */
return val - val / 10 +
@@ -284,7 +437,7 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
if (r < 0)
goto fail;
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ r = ndisc_send_router_solicitation(nd);
if (r < 0)
log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
@@ -316,7 +469,7 @@ int sd_ndisc_stop(sd_ndisc *nd) {
if (!nd)
return 0;
- if (nd->fd < 0)
+ if (!sd_ndisc_is_running(nd))
return 0;
log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
@@ -325,50 +478,74 @@ int sd_ndisc_stop(sd_ndisc *nd) {
return 1;
}
-int sd_ndisc_start(sd_ndisc *nd) {
+static int ndisc_setup_recv_event(sd_ndisc *nd) {
int r;
- usec_t time_now;
- assert_return(nd, -EINVAL);
- assert_return(nd->event, -EINVAL);
- assert_return(nd->ifindex > 0, -EINVAL);
+ assert(nd);
+ assert(nd->event);
+ assert(nd->ifindex > 0);
- if (nd->fd >= 0)
- return 0;
+ _cleanup_close_ int fd = -EBADF;
+ fd = icmp6_bind(nd->ifindex, /* is_router = */ false);
+ if (fd < 0)
+ return fd;
- assert(!nd->recv_event_source);
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ r = sd_event_add_io(nd->event, &s, fd, EPOLLIN, ndisc_recv, nd);
+ if (r < 0)
+ return r;
- r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
+ r = sd_event_source_set_priority(s, nd->event_priority);
if (r < 0)
- goto fail;
+ return r;
+
+ (void) sd_event_source_set_description(s, "ndisc-receive-router-message");
- nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
- if (nd->fd < 0)
- return nd->fd;
+ nd->fd = TAKE_FD(fd);
+ nd->recv_event_source = TAKE_PTR(s);
+ return 1;
+}
- r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
+static int ndisc_setup_timer(sd_ndisc *nd) {
+ int r;
+
+ assert(nd);
+ assert(nd->event);
+
+ r = event_reset_time_relative(nd->event, &nd->timeout_event_source,
+ CLOCK_BOOTTIME,
+ USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
+ ndisc_timeout, nd,
+ nd->event_priority, "ndisc-timeout", true);
if (r < 0)
- goto fail;
+ return r;
- r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
+ r = event_reset_time_relative(nd->event, &nd->timeout_no_ra,
+ CLOCK_BOOTTIME,
+ NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
+ ndisc_timeout_no_ra, nd,
+ nd->event_priority, "ndisc-timeout-no-ra", true);
if (r < 0)
- goto fail;
+ return r;
- (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
+ return 0;
+}
- r = event_reset_time(nd->event, &nd->timeout_event_source,
- CLOCK_BOOTTIME,
- time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
- ndisc_timeout, nd,
- nd->event_priority, "ndisc-timeout", true);
+int sd_ndisc_start(sd_ndisc *nd) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+ assert_return(nd->event, -EINVAL);
+ assert_return(nd->ifindex > 0, -EINVAL);
+
+ if (sd_ndisc_is_running(nd))
+ return 0;
+
+ r = ndisc_setup_recv_event(nd);
if (r < 0)
goto fail;
- r = event_reset_time(nd->event, &nd->timeout_no_ra,
- CLOCK_BOOTTIME,
- time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
- ndisc_timeout_no_ra, nd,
- nd->event_priority, "ndisc-timeout-no-ra", true);
+ r = ndisc_setup_timer(nd);
if (r < 0)
goto fail;