diff options
Diffstat (limited to 'src/libsystemd-network/sd-icmp6-nd.c')
-rw-r--r-- | src/libsystemd-network/sd-icmp6-nd.c | 722 |
1 files changed, 0 insertions, 722 deletions
diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c deleted file mode 100644 index bedcac8d9e..0000000000 --- a/src/libsystemd-network/sd-icmp6-nd.c +++ /dev/null @@ -1,722 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - 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 <http://www.gnu.org/licenses/>. -***/ - -#include <netinet/icmp6.h> -#include <netinet/ip6.h> -#include <string.h> -#include <stdbool.h> -#include <netinet/in.h> -#include <sys/ioctl.h> - -#include "socket-util.h" -#include "async.h" - -#include "dhcp6-internal.h" -#include "sd-icmp6-nd.h" - -#define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC -#define ICMP6_MAX_ROUTER_SOLICITATIONS 3 - -enum icmp6_nd_state { - ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0, - ICMP6_ROUTER_SOLICITATION_SENT = 10, - ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11, -}; - -#define IP6_MIN_MTU (unsigned)1280 -#define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr)) -#define ICMP6_OPT_LEN_UNITS 8 - -typedef struct ICMP6Prefix ICMP6Prefix; - -struct ICMP6Prefix { - unsigned n_ref; - - LIST_FIELDS(ICMP6Prefix, prefixes); - - uint8_t len; - sd_event_source *timeout_valid; - struct in6_addr addr; -}; - -struct sd_icmp6_nd { - unsigned n_ref; - - enum icmp6_nd_state state; - sd_event *event; - int event_priority; - int index; - struct ether_addr mac_addr; - uint32_t mtu; - ICMP6Prefix *expired_prefix; - LIST_HEAD(ICMP6Prefix, prefixes); - int fd; - sd_event_source *recv; - sd_event_source *timeout; - int nd_sent; - sd_icmp6_nd_callback_t callback; - void *userdata; -}; - -#define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__) - -static ICMP6Prefix *icmp6_prefix_unref(ICMP6Prefix *prefix) { - - if (!prefix) - return NULL; - - assert(prefix->n_ref > 0); - prefix->n_ref--; - - if (prefix->n_ref > 0) - return NULL; - - prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid); - free(prefix); - return NULL; -} - -static int icmp6_prefix_new(ICMP6Prefix **ret) { - _cleanup_free_ ICMP6Prefix *prefix = NULL; - - assert(ret); - - prefix = new0(ICMP6Prefix, 1); - if (!prefix) - return -ENOMEM; - - prefix->n_ref = 1; - LIST_INIT(prefixes, prefix); - - *ret = prefix; - prefix = NULL; - - return 0; -} - -static void icmp6_nd_notify(sd_icmp6_nd *nd, int event) { - if (nd->callback) - nd->callback(nd, event, nd->userdata); -} - -int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback, - void *userdata) { - assert(nd); - - nd->callback = callback; - nd->userdata = userdata; - - return 0; -} - -int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) { - assert(nd); - assert(interface_index >= -1); - - nd->index = interface_index; - - return 0; -} - -int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) { - assert(nd); - - if (mac_addr) - memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr)); - else - zero(nd->mac_addr); - - return 0; - -} - -int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) { - int r; - - assert_return(nd, -EINVAL); - assert_return(!nd->event, -EBUSY); - - if (event) - nd->event = sd_event_ref(event); - else { - r = sd_event_default(&nd->event); - if (r < 0) - return 0; - } - - nd->event_priority = priority; - - return 0; -} - -int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) { - assert_return(nd, -EINVAL); - - nd->event = sd_event_unref(nd->event); - - return 0; -} - -sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) { - assert(nd); - - return nd->event; -} - -sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) { - - if (!nd) - return NULL; - - assert(nd->n_ref > 0); - nd->n_ref++; - - return nd; -} - -static int icmp6_nd_init(sd_icmp6_nd *nd) { - assert(nd); - - nd->recv = sd_event_source_unref(nd->recv); - nd->fd = asynchronous_close(nd->fd); - nd->timeout = sd_event_source_unref(nd->timeout); - - return 0; -} - -sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) { - ICMP6Prefix *prefix, *p; - - if (!nd) - return NULL; - - assert(nd->n_ref > 0); - nd->n_ref--; - - if (nd->n_ref > 0) - return NULL; - - icmp6_nd_init(nd); - sd_icmp6_nd_detach_event(nd); - - LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { - LIST_REMOVE(prefixes, nd->prefixes, prefix); - - prefix = icmp6_prefix_unref(prefix); - } - - free(nd); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref); -#define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp) - -int sd_icmp6_nd_new(sd_icmp6_nd **ret) { - _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL; - - assert(ret); - - nd = new0(sd_icmp6_nd, 1); - if (!nd) - return -ENOMEM; - - nd->n_ref = 1; - - nd->index = -1; - nd->fd = -1; - - LIST_HEAD_INIT(nd->prefixes); - - *ret = nd; - nd = NULL; - - return 0; -} - -int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) { - assert_return(nd, -EINVAL); - assert_return(mtu, -EINVAL); - - if (nd->mtu == 0) - return -ENOMSG; - - *mtu = nd->mtu; - - return 0; -} - -static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec, - void *userdata) { - sd_icmp6_nd *nd = userdata; - ICMP6Prefix *prefix, *p; - - assert(nd); - - LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { - if (prefix->timeout_valid != s) - continue; - - log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d", - SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len); - - LIST_REMOVE(prefixes, nd->prefixes, prefix); - - nd->expired_prefix = prefix; - icmp6_nd_notify(nd, - SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED); - nd->expired_prefix = NULL; - - prefix = icmp6_prefix_unref(prefix); - - break; - } - - return 0; -} - -static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd, - ICMP6Prefix *prefix, - usec_t valid) { - usec_t time_now; - int r; - - assert_return(prefix, -EINVAL); - - r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - return r; - - prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid); - - r = sd_event_add_time(nd->event, &prefix->timeout_valid, - clock_boottime_or_monotonic(), time_now + valid, - USEC_PER_SEC, icmp6_ra_prefix_timeout, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(prefix->timeout_valid, - nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(prefix->timeout_valid, - "icmp6-prefix-timeout"); - -error: - if (r < 0) - prefix->timeout_valid = - sd_event_source_unref(prefix->timeout_valid); - - return r; -} - -static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen, - const struct in6_addr *addr, - uint8_t addr_prefixlen) { - uint8_t bytes, mask, len; - - assert_return(prefix, -EINVAL); - assert_return(addr, -EINVAL); - - len = MIN(prefixlen, addr_prefixlen); - - bytes = len / 8; - mask = 0xff << (8 - len % 8); - - if (memcmp(prefix, addr, bytes) != 0 || - (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask)) - return -EADDRNOTAVAIL; - - return 0; -} - -static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr, - uint8_t addr_len, ICMP6Prefix **result) { - ICMP6Prefix *prefix; - - LIST_FOREACH(prefixes, prefix, head) { - if (icmp6_prefix_match(&prefix->addr, prefix->len, addr, - addr_len) >= 0) { - *result = prefix; - return 0; - } - } - - return -EADDRNOTAVAIL; -} - -int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen, - struct in6_addr *addr) { - return icmp6_prefix_match(prefix, prefixlen, addr, - sizeof(addr->s6_addr) * 8); -} - -int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr, - uint8_t *prefixlen) { - int r; - ICMP6Prefix *prefix; - - assert_return(nd, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(prefixlen, -EINVAL); - - r = icmp6_ra_prefix_match(nd->prefixes, addr, - sizeof(addr->s6_addr) * 8, &prefix); - if (r < 0) - return r; - - *prefixlen = prefix->len; - - return 0; -} - -int sd_icmp6_ra_get_expired_prefix(sd_icmp6_nd *nd, struct in6_addr **addr, uint8_t *prefixlen) { - assert_return(nd, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(prefixlen, -EINVAL); - - if (!nd->expired_prefix) - return -EADDRNOTAVAIL; - - *addr = &nd->expired_prefix->addr; - *prefixlen = nd->expired_prefix->len; - - return 0; -} - -static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len, - const struct nd_opt_prefix_info *prefix_opt) { - int r; - ICMP6Prefix *prefix; - uint32_t lifetime; - char time_string[FORMAT_TIMESPAN_MAX]; - - assert_return(nd, -EINVAL); - assert_return(prefix_opt, -EINVAL); - - if (len < prefix_opt->nd_opt_pi_len) - return -ENOMSG; - - if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)) - return 0; - - lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time); - - r = icmp6_ra_prefix_match(nd->prefixes, - &prefix_opt->nd_opt_pi_prefix, - prefix_opt->nd_opt_pi_prefix_len, &prefix); - - if (r < 0 && r != -EADDRNOTAVAIL) - return r; - - /* if router advertisment prefix valid timeout is zero, the timeout - callback will be called immediately to clean up the prefix */ - - if (r == -EADDRNOTAVAIL) { - r = icmp6_prefix_new(&prefix); - if (r < 0) - return r; - - prefix->len = prefix_opt->nd_opt_pi_prefix_len; - - memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix, - sizeof(prefix->addr)); - - log_icmp6_nd(nd, "New prefix "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - lifetime * USEC_PER_SEC, 0)); - - LIST_PREPEND(prefixes, nd->prefixes, prefix); - - } else { - if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) { - uint8_t prefixlen; - - prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len); - - log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d", - prefix->len, - prefix_opt->nd_opt_pi_prefix_len, - prefixlen); - - prefix->len = prefixlen; - } - - log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_ICMP6_ND_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - lifetime * USEC_PER_SEC, 0)); - } - - r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC); - - return r; -} - -static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra, - ssize_t len) { - void *opt; - struct nd_opt_hdr *opt_hdr; - - assert_return(nd, -EINVAL); - assert_return(ra, -EINVAL); - - len -= sizeof(*ra); - if (len < ICMP6_OPT_LEN_UNITS) { - log_icmp6_nd(nd, "Router Advertisement below minimum length"); - - return -ENOMSG; - } - - opt = ra + 1; - opt_hdr = opt; - - while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) { - struct nd_opt_mtu *opt_mtu; - uint32_t mtu; - struct nd_opt_prefix_info *opt_prefix; - - if (opt_hdr->nd_opt_len == 0) - return -ENOMSG; - - switch (opt_hdr->nd_opt_type) { - case ND_OPT_MTU: - opt_mtu = opt; - - mtu = be32toh(opt_mtu->nd_opt_mtu_mtu); - - if (mtu != nd->mtu) { - nd->mtu = MAX(mtu, IP6_MIN_MTU); - - log_icmp6_nd(nd, "Router Advertisement link MTU %d using %d", - mtu, nd->mtu); - } - - break; - - case ND_OPT_PREFIX_INFORMATION: - opt_prefix = opt; - - icmp6_ra_prefix_update(nd, len, opt_prefix); - - break; - } - - len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS; - opt = (void *)((char *)opt + - opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS); - opt_hdr = opt; - } - - if (len > 0) - log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len); - - return 0; -} - -static int icmp6_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_icmp6_nd *nd = userdata; - int r, buflen = 0; - ssize_t len; - _cleanup_free_ struct nd_router_advert *ra = NULL; - int event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_NONE; - - assert(s); - assert(nd); - assert(nd->event); - - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = ICMP6_ND_RECV_SIZE; - - ra = malloc(buflen); - if (!ra) - return -ENOMEM; - - len = read(fd, ra, buflen); - if (len < 0) { - log_icmp6_nd(nd, "Could not receive message from UDP socket: %m"); - return 0; - } - - if (ra->nd_ra_type != ND_ROUTER_ADVERT) - return 0; - - if (ra->nd_ra_code != 0) - return 0; - - nd->timeout = sd_event_source_unref(nd->timeout); - - nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN; - - if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER ) - event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_OTHER; - - if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) - event = SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_MANAGED; - - log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s", - ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none", - ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none"); - - if (event != SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_NONE) { - r = icmp6_ra_parse(nd, ra, len); - if (r < 0) { - log_icmp6_nd(nd, "Could not parse Router Advertisement: %s", - strerror(-r)); - return 0; - } - } - - icmp6_nd_notify(nd, event); - - return 0; -} - -static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - sd_icmp6_nd *nd = userdata; - uint64_t time_now, next_timeout; - struct ether_addr unset = { }; - struct ether_addr *addr = NULL; - int r; - - assert(s); - assert(nd); - assert(nd->event); - - nd->timeout = sd_event_source_unref(nd->timeout); - - if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) { - icmp6_nd_notify(nd, SD_ICMP6_ND_EVENT_ROUTER_ADVERTISMENT_TIMEOUT); - nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN; - } else { - if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr))) - addr = &nd->mac_addr; - - r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr); - if (r < 0) - log_icmp6_nd(nd, "Error sending Router Solicitation"); - else { - nd->state = ICMP6_ROUTER_SOLICITATION_SENT; - log_icmp6_nd(nd, "Sent Router Solicitation"); - } - - nd->nd_sent++; - - r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL; - - r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - next_timeout, 0, - icmp6_router_solicitation_timeout, nd); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - r = sd_event_source_set_priority(nd->timeout, - nd->event_priority); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - r = sd_event_source_set_description(nd->timeout, "icmp6-timeout"); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - } - - return 0; -} - -int sd_icmp6_nd_stop(sd_icmp6_nd *nd) { - assert_return(nd, -EINVAL); - assert_return(nd->event, -EINVAL); - - log_icmp6_nd(client, "Stop ICMPv6"); - - icmp6_nd_init(nd); - - nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE; - - return 0; -} - -int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) { - int r; - - assert(nd); - assert(nd->event); - - if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE) - return -EINVAL; - - if (nd->index < 1) - return -EINVAL; - - r = dhcp_network_icmp6_bind_router_solicitation(nd->index); - if (r < 0) - return r; - - nd->fd = r; - - r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN, - icmp6_router_advertisment_recv, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(nd->recv, nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(nd->recv, "icmp6-receive-message"); - if (r < 0) - goto error; - - r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - 0, 0, icmp6_router_solicitation_timeout, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(nd->timeout, nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(nd->timeout, "icmp6-timeout"); -error: - if (r < 0) - icmp6_nd_init(nd); - else - log_icmp6_nd(client, "Start Router Solicitation"); - - return r; -} |