diff options
Diffstat (limited to '')
-rw-r--r-- | src/libsystemd-network/sd-ndisc.c | 329 |
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; |