diff options
author | Tom Gundersen <teg@jklm.no> | 2015-08-21 12:50:31 +0200 |
---|---|---|
committer | Tom Gundersen <teg@jklm.no> | 2015-09-18 15:14:43 +0200 |
commit | e3dca0089b7b50e2ec21409d1292727921d06102 (patch) | |
tree | 7b437df24b2d8b3c5f4fcf215729efdd15eb656e /src/libsystemd-network/sd-ipv4ll.c | |
parent | 6af9144f5ff65cb9f6ae9999e7e0a9edc4841b2b (diff) |
sd-ipv4acd: introduce new library split out from sd-ipv4ll
This splits the Address Conflict Detection out of the Link Local
library so that we can reuse it for DHCP and static addresses in
the future.
Implements RFC5227.
Diffstat (limited to 'src/libsystemd-network/sd-ipv4ll.c')
-rw-r--r-- | src/libsystemd-network/sd-ipv4ll.c | 568 |
1 files changed, 145 insertions, 423 deletions
diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 03a9b3f4f4..f0230b919c 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -24,65 +24,33 @@ #include <stdio.h> #include <arpa/inet.h> -#include "util.h" -#include "siphash24.h" +#include "event-util.h" #include "list.h" #include "random-util.h" -#include "event-util.h" +#include "refcnt.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "util.h" -#include "arp-util.h" +#include "sd-ipv4acd.h" #include "sd-ipv4ll.h" -/* Constants from the RFC */ -#define PROBE_WAIT 1 -#define PROBE_NUM 3 -#define PROBE_MIN 1 -#define PROBE_MAX 2 -#define ANNOUNCE_WAIT 2 -#define ANNOUNCE_NUM 2 -#define ANNOUNCE_INTERVAL 2 -#define MAX_CONFLICTS 10 -#define RATE_LIMIT_INTERVAL 60 -#define DEFEND_INTERVAL 10 - #define IPV4LL_NETWORK 0xA9FE0000L #define IPV4LL_NETMASK 0xFFFF0000L -#define log_ipv4ll(ll, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "IPv4LL: " fmt, ##__VA_ARGS__) - #define IPV4LL_DONT_DESTROY(ll) \ _cleanup_ipv4ll_unref_ _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) -typedef enum IPv4LLState { - IPV4LL_STATE_INIT, - IPV4LL_STATE_WAITING_PROBE, - IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE, - IPV4LL_STATE_ANNOUNCING, - IPV4LL_STATE_RUNNING, - _IPV4LL_STATE_MAX, - _IPV4LL_STATE_INVALID = -1 -} IPv4LLState; - struct sd_ipv4ll { unsigned n_ref; - IPv4LLState state; - int index; - int fd; - int iteration; - int conflict; - sd_event_source *receive_message; - sd_event_source *timer; - usec_t defend_window; - be32_t address; + sd_ipv4acd *acd; + be32_t address; /* the address pushed to ACD */ struct random_data *random_data; char *random_data_state; + /* External */ be32_t claimed_address; - struct ether_addr mac_addr; - sd_event *event; - int event_priority; sd_ipv4ll_cb_t cb; void* userdata; }; @@ -107,12 +75,7 @@ sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { if (ll->n_ref > 0) return NULL; - ll->receive_message = sd_event_source_unref(ll->receive_message); - ll->fd = safe_close(ll->fd); - - ll->timer = sd_event_source_unref(ll->timer); - - sd_ipv4ll_detach_event(ll); + sd_ipv4acd_unref(ll->acd); free(ll->random_data); free(ll->random_data_state); @@ -124,8 +87,11 @@ sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_unref); #define _cleanup_ipv4ll_unref_ _cleanup_(sd_ipv4ll_unrefp) +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); + int sd_ipv4ll_new(sd_ipv4ll **ret) { _cleanup_ipv4ll_unref_ sd_ipv4ll *ll = NULL; + int r; assert_return(ret, -EINVAL); @@ -133,10 +99,15 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) { if (!ll) return -ENOMEM; + r = sd_ipv4acd_new(&ll->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); + if (r < 0) + return r; + ll->n_ref = 1; - ll->state = IPV4LL_STATE_INIT; - ll->index = -1; - ll->fd = -1; *ret = ll; ll = NULL; @@ -144,347 +115,62 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) { return 0; } -static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, bool reset_counter) { - - assert(ll); - assert(st < _IPV4LL_STATE_MAX); - - if (st == ll->state && !reset_counter) { - ll->iteration++; - } else { - ll->state = st; - ll->iteration = 0; - } -} - -static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { - assert(ll); - - if (ll->cb) - ll->cb(ll, event, ll->userdata); -} - -static void ipv4ll_stop(sd_ipv4ll *ll) { - IPV4LL_DONT_DESTROY(ll); - - assert(ll); - - ll->receive_message = sd_event_source_unref(ll->receive_message); - ll->fd = safe_close(ll->fd); - - ll->timer = sd_event_source_unref(ll->timer); - - log_ipv4ll(ll, "STOPPED"); - - ll->claimed_address = 0; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); -} - int sd_ipv4ll_stop(sd_ipv4ll *ll) { - IPV4LL_DONT_DESTROY(ll); - - assert_return(ll, -EINVAL); - - ipv4ll_stop(ll); - - ipv4ll_client_notify(ll, IPV4LL_EVENT_STOP); - - return 0; -} - -static int ipv4ll_pick_address(sd_ipv4ll *ll, be32_t *address) { - be32_t addr; - int r; - int32_t random; - - assert(ll); - assert(address); - assert(ll->random_data); - - do { - r = random_r(ll->random_data, &random); - if (r < 0) - return r; - addr = htonl((random & 0x0000FFFF) | IPV4LL_NETWORK); - } while (addr == ll->address || - (ntohl(addr) & IPV4LL_NETMASK) != IPV4LL_NETWORK || - (ntohl(addr) & 0x0000FF00) == 0x0000 || - (ntohl(addr) & 0x0000FF00) == 0xFF00); - - *address = addr; - return 0; -} - -static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); - -static int ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { - _cleanup_event_source_unref_ sd_event_source *timer = NULL; - usec_t next_timeout; - usec_t time_now; int r; - assert(sec >= 0); - assert(random_sec >= 0); - assert(ll); - - next_timeout = sec * USEC_PER_SEC; - - if (random_sec) - next_timeout += random_u32() % (random_sec * USEC_PER_SEC); - - assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now) >= 0); - - r = sd_event_add_time(ll->event, &timer, clock_boottime_or_monotonic(), - time_now + next_timeout, 0, ipv4ll_on_timeout, ll); - if (r < 0) - return r; - - r = sd_event_source_set_priority(timer, ll->event_priority); - if (r < 0) - return r; + assert_return(ll, -EINVAL); - r = sd_event_source_set_description(timer, "ipv4ll-timer"); + r = sd_ipv4acd_stop(ll->acd); if (r < 0) return r; - ll->timer = sd_event_source_unref(ll->timer); - ll->timer = timer; - timer = NULL; - return 0; } -static bool ipv4ll_arp_conflict(sd_ipv4ll *ll, struct ether_arp *arp) { - assert(ll); - assert(arp); - - /* see the BPF */ - if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->address)) == 0) - return true; +int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { + assert_return(ll, -EINVAL); - /* the TPA matched instead of the SPA, this is not a conflict */ - return false; + return sd_ipv4acd_set_index(ll->acd, interface_index); } -static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - sd_ipv4ll *ll = userdata; - int r = 0; - - assert(ll); - - switch (ll->state) { - case IPV4LL_STATE_INIT: - - log_ipv4ll(ll, "PROBE"); - - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); - - if (ll->conflict >= MAX_CONFLICTS) { - log_ipv4ll(ll, "MAX_CONFLICTS"); - r = ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); - if (r < 0) - return r; - } else { - r = ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - if (r < 0) - return r; - } - - break; - case IPV4LL_STATE_WAITING_PROBE: - case IPV4LL_STATE_PROBING: - /* Send a probe */ - r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); - if (r < 0) { - log_ipv4ll(ll, "Failed to send ARP probe."); - goto out; - } - - if (ll->iteration < PROBE_NUM - 2) { - ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, false); - - r = ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); - if (r < 0) - goto out; - } else { - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, true); - - r = ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); - if (r < 0) - goto out; - } - - break; - - case IPV4LL_STATE_ANNOUNCING: - if (ll->iteration >= ANNOUNCE_NUM - 1) { - ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, false); - - break; - } - case IPV4LL_STATE_WAITING_ANNOUNCE: - /* Send announcement packet */ - r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); - if (r < 0) { - log_ipv4ll(ll, "Failed to send ARP announcement."); - goto out; - } - - ipv4ll_set_state(ll, IPV4LL_STATE_ANNOUNCING, false); - - r = ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); - if (r < 0) - goto out; - - if (ll->iteration == 0) { - log_ipv4ll(ll, "ANNOUNCE"); - ll->claimed_address = ll->address; - ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND); - ll->conflict = 0; - } - - break; - default: - assert_not_reached("Invalid state."); - } - -out: - if (r < 0 && ll) - sd_ipv4ll_stop(ll); - - return 1; -} +#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) -static int ipv4ll_on_conflict(sd_ipv4ll *ll) { - IPV4LL_DONT_DESTROY(ll); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { int r; - assert(ll); - - log_ipv4ll(ll, "CONFLICT"); - - ll->conflict++; - - ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - - sd_ipv4ll_stop(ll); - - /* Pick a new address */ - r = ipv4ll_pick_address(ll, &ll->address); - if (r < 0) - return r; - - r = sd_ipv4ll_start(ll); - if (r < 0) - return r; + assert_return(ll, -EINVAL); - return 0; -} + if (!ll->random_data) { + uint8_t seed[8]; -static int ipv4ll_on_packet(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { - sd_ipv4ll *ll = userdata; - struct ether_arp packet; - int r; + /* If no random data is set, generate some from the MAC */ + siphash24(seed, &addr->ether_addr_octet, + ETH_ALEN, HASH_KEY.bytes); - assert(ll); - assert(fd >= 0); - - r = read(fd, &packet, sizeof(struct ether_arp)); - if (r < (int) sizeof(struct ether_arp)) - goto out; - - switch (ll->state) { - case IPV4LL_STATE_ANNOUNCING: - case IPV4LL_STATE_RUNNING: - if (ipv4ll_arp_conflict(ll, &packet)) { - usec_t ts; - - assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &ts) >= 0); - - /* Defend address */ - if (ts > ll->defend_window) { - ll->defend_window = ts + DEFEND_INTERVAL * USEC_PER_SEC; - r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); - if (r < 0) { - log_ipv4ll(ll, "Failed to send ARP announcement."); - goto out; - } - - } else { - r = ipv4ll_on_conflict(ll); - if (r < 0) - goto out; - } - } + assert_cc(sizeof(unsigned) <= 8); - break; - case IPV4LL_STATE_WAITING_PROBE: - case IPV4LL_STATE_PROBING: - case IPV4LL_STATE_WAITING_ANNOUNCE: - /* BPF ensures this packet indicates a conflict */ - r = ipv4ll_on_conflict(ll); + r = sd_ipv4ll_set_address_seed(ll, *(unsigned*)seed); if (r < 0) - goto out; - - break; - default: - assert_not_reached("Invalid state."); + return r; } -out: - if (r < 0 && ll) - sd_ipv4ll_stop(ll); - - return 1; -} - -int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { - assert_return(ll, -EINVAL); - assert_return(interface_index > 0, -EINVAL); - assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); - - ll->index = interface_index; - - return 0; -} - -int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { - assert_return(ll, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); - - if (memcmp(&ll->mac_addr, addr, ETH_ALEN) == 0) - return 0; - - memcpy(&ll->mac_addr, addr, ETH_ALEN); - - return 0; + return sd_ipv4acd_set_mac(ll->acd, addr); } int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { assert_return(ll, -EINVAL); - ll->event = sd_event_unref(ll->event); - - return 0; + return sd_ipv4acd_detach_event(ll->acd); } int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority) { int r; assert_return(ll, -EINVAL); - assert_return(!ll->event, -EBUSY); - - if (event) - ll->event = sd_event_ref(event); - else { - r = sd_event_default(&ll->event); - if (r < 0) - return r; - } - ll->event_priority = priority; + r = sd_ipv4acd_attach_event(ll->acd, event, priority); + if (r < 0) + return r; return 0; } @@ -506,111 +192,147 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address){ return -ENOENT; address->s_addr = ll->claimed_address; + return 0; } -int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint8_t seed[8]) { - unsigned int entropy; +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed) { + _cleanup_free_ struct random_data *random_data = NULL; + _cleanup_free_ char *random_data_state = NULL; int r; assert_return(ll, -EINVAL); assert_return(seed, -EINVAL); - entropy = *seed; + random_data = new0(struct random_data, 1); + if (!random_data) + return -ENOMEM; - free(ll->random_data); - free(ll->random_data_state); + random_data_state = new0(char, 128); + if (!random_data_state) + return -ENOMEM; - ll->random_data = new0(struct random_data, 1); - ll->random_data_state = new0(char, 128); + r = initstate_r(seed, random_data_state, 128, random_data); + if (r < 0) + return r; - if (!ll->random_data || !ll->random_data_state) { - r = -ENOMEM; - goto error; - } + free(ll->random_data); + ll->random_data = random_data; + random_data = NULL; - r = initstate_r((unsigned int)entropy, ll->random_data_state, 128, ll->random_data); - if (r < 0) - goto error; + free(ll->random_data_state); + ll->random_data_state = random_data_state; + random_data_state = NULL; -error: - if (r < 0){ - free(ll->random_data); - free(ll->random_data_state); - ll->random_data = NULL; - ll->random_data_state = NULL; - } - return r; + return 0; } bool sd_ipv4ll_is_running(sd_ipv4ll *ll) { assert_return(ll, false); - return ll->state != IPV4LL_STATE_INIT; + return sd_ipv4acd_is_running(ll->acd); } -#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) - -int sd_ipv4ll_start(sd_ipv4ll *ll) { +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + struct in_addr in_addr; + be32_t addr; int r; + int32_t random; - assert_return(ll, -EINVAL); - assert_return(ll->event, -EINVAL); - assert_return(ll->index > 0, -EINVAL); - assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); + assert(ll); + assert(ll->random_data); - ll->state = IPV4LL_STATE_INIT; + do { + r = random_r(ll->random_data, &random); + if (r < 0) + return r; + addr = htonl((random & 0x0000FFFF) | IPV4LL_NETWORK); + } while (addr == ll->address || + (ntohl(addr) & IPV4LL_NETMASK) != IPV4LL_NETWORK || + (ntohl(addr) & 0x0000FF00) == 0x0000 || + (ntohl(addr) & 0x0000FF00) == 0xFF00); - ll->conflict = 0; - ll->defend_window = 0; - ll->claimed_address = 0; + in_addr.s_addr = addr; - if (!ll->random_data) { - uint8_t seed[8]; + r = sd_ipv4acd_set_address(ll->acd, &in_addr); + if (r < 0) + return r; - /* Fallback to mac */ - siphash24(seed, &ll->mac_addr.ether_addr_octet, - ETH_ALEN, HASH_KEY.bytes); + ll->address = addr; - r = sd_ipv4ll_set_address_seed(ll, seed); - if (r < 0) - goto out; - } + return 0; +} + +int sd_ipv4ll_start(sd_ipv4ll *ll) { + int r; + + assert_return(ll, -EINVAL); + assert_return(ll->random_data, -EINVAL); if (ll->address == 0) { - r = ipv4ll_pick_address(ll, &ll->address); + r = ipv4ll_pick_address(ll); if (r < 0) - goto out; + return r; } - r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + r = sd_ipv4acd_start(ll->acd); if (r < 0) - goto out; + return r; - safe_close(ll->fd); - ll->fd = r; + return 0; +} - ipv4ll_set_state(ll, IPV4LL_STATE_INIT, true); +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); - r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, - EPOLLIN, ipv4ll_on_packet, ll); - if (r < 0) - goto out; + if (ll->cb) + ll->cb(ll, event, ll->userdata); +} - r = sd_event_source_set_priority(ll->receive_message, ll->event_priority); - if (r < 0) - goto out; +void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + sd_ipv4ll *ll = userdata; + IPV4LL_DONT_DESTROY(ll); + int r; - r = sd_event_source_set_description(ll->receive_message, "ipv4ll-receive-message"); - if (r < 0) - goto out; + assert(acd); + assert(ll); - r = ipv4ll_set_next_wakeup(ll, 0, 0); - if (r < 0) - goto out; -out: - if (r < 0) - ipv4ll_stop(ll); + switch (event) { + case IPV4ACD_EVENT_STOP: + ipv4ll_client_notify(ll, IPV4LL_EVENT_STOP); - return 0; + ll->claimed_address = 0; + + break; + case IPV4ACD_EVENT_BIND: + ll->claimed_address = ll->address; + ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND); + + break; + case IPV4ACD_EVENT_CONFLICT: + /* if an address was already bound we must call up to the + user to handle this, otherwise we just try again */ + if (ll->claimed_address != 0) { + ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); + + ll->claimed_address = 0; + } else { + r = ipv4ll_pick_address(ll); + if (r < 0) + goto error; + + r = sd_ipv4acd_start(ll->acd); + if (r < 0) + goto error; + } + + break; + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + return; + +error: + ipv4ll_client_notify(ll, IPV4LL_EVENT_STOP); } |