From 996d16975b4d802335188a3be2bbc3635c1287f3 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Tue, 18 Aug 2015 15:37:43 +0200 Subject: sd-ipv4ll: filter out unwanted ARP packets in the kernel We currently process every ARP packet, but we should only care about the ones relating to our IP address. Also rename ipv4ll helpers to apr-utils.[ch], and rework the helpers a bit. --- Makefile.am | 7 +- src/libsystemd-network/arp-util.c | 153 +++++++++++++++++++++++++++++++ src/libsystemd-network/arp-util.h | 34 +++++++ src/libsystemd-network/ipv4ll-internal.h | 38 -------- src/libsystemd-network/ipv4ll-network.c | 91 ------------------ src/libsystemd-network/ipv4ll-packet.c | 71 -------------- src/libsystemd-network/sd-ipv4ll.c | 80 +++++++++------- src/libsystemd-network/test-ipv4ll.c | 69 +++++--------- 8 files changed, 259 insertions(+), 284 deletions(-) create mode 100644 src/libsystemd-network/arp-util.c create mode 100644 src/libsystemd-network/arp-util.h delete mode 100644 src/libsystemd-network/ipv4ll-internal.h delete mode 100644 src/libsystemd-network/ipv4ll-network.c delete mode 100644 src/libsystemd-network/ipv4ll-packet.c diff --git a/Makefile.am b/Makefile.am index c395840759..3930647aa0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3214,9 +3214,8 @@ libsystemd_network_la_SOURCES = \ src/libsystemd-network/dhcp-lease-internal.h \ src/libsystemd-network/sd-dhcp-lease.c \ src/libsystemd-network/sd-ipv4ll.c \ - src/libsystemd-network/ipv4ll-network.c \ - src/libsystemd-network/ipv4ll-packet.c \ - src/libsystemd-network/ipv4ll-internal.h \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/arp-util.c \ src/libsystemd-network/sd-pppoe.c \ src/libsystemd-network/network-internal.c \ src/libsystemd-network/network-internal.h \ @@ -3273,7 +3272,7 @@ test_dhcp_server_LDADD = \ test_ipv4ll_SOURCES = \ src/systemd/sd-ipv4ll.h \ - src/libsystemd-network/ipv4ll-internal.h \ + src/libsystemd-network/arp-util.h \ src/libsystemd-network/test-ipv4ll.c test_ipv4ll_LDADD = \ diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c new file mode 100644 index 0000000000..2f5b9b3731 --- /dev/null +++ b/src/libsystemd-network/arp-util.c @@ -0,0 +1,153 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include "util.h" +#include "arp-util.h" + +int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Hardware Address must be different from our own */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about*/ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter + }; + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htons(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + + s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} + +static int arp_send_packet(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htons(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htons(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htons(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htons(ARPOP_REQUEST), /* REQUEST */ + }; + int r; + + assert(fd >= 0); + assert(pa != 0); + assert(ha); + + memcpy(&arp.arp_sha, ha, ETH_ALEN); + memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + + if (announce) + memcpy(&arp.arp_spa, &pa, sizeof(pa)); + + r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/libsystemd-network/arp-util.h b/src/libsystemd-network/arp-util.h new file mode 100644 index 0000000000..44e5c893a7 --- /dev/null +++ b/src/libsystemd-network/arp-util.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. 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 . +***/ + +#include + +#include "sparse-endian.h" +#include "socket-util.h" + +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); diff --git a/src/libsystemd-network/ipv4ll-internal.h b/src/libsystemd-network/ipv4ll-internal.h deleted file mode 100644 index ae0ce43985..0000000000 --- a/src/libsystemd-network/ipv4ll-internal.h +++ /dev/null @@ -1,38 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. 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 . -***/ - -#include - -#include "sparse-endian.h" -#include "socket-util.h" - -int arp_network_bind_raw_socket(int index, union sockaddr_union *link); -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp); - -void arp_packet_init(struct ether_arp *arp); -void arp_packet_probe(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha); -void arp_packet_announcement(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha); -int arp_packet_verify_headers(struct ether_arp *arp); - -#define log_ipv4ll(ll, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "IPv4LL: " fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/ipv4ll-network.c b/src/libsystemd-network/ipv4ll-network.c deleted file mode 100644 index 93ffed408f..0000000000 --- a/src/libsystemd-network/ipv4ll-network.c +++ /dev/null @@ -1,91 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. 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 . -***/ - -#include - -#include "util.h" -#include "ipv4ll-internal.h" - -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp) { - int r; - - assert(arp); - assert(link); - assert(fd >= 0); - - r = sendto(fd, arp, sizeof(struct ether_arp), 0, &link->sa, sizeof(link->ll)); - if (r < 0) - return -errno; - - return 0; -} - -int arp_network_bind_raw_socket(int ifindex, union sockaddr_union *link) { - - static const struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 0, 1), /* protocol == request ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), /* protocol == reply ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - }; - struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = (struct sock_filter*) filter - }; - _cleanup_close_ int s = -1; - int r; - - assert(ifindex > 0); - assert(link); - - s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - link->ll.sll_family = AF_PACKET; - link->ll.sll_protocol = htons(ETH_P_ARP); - link->ll.sll_ifindex = ifindex; - link->ll.sll_halen = ETH_ALEN; - memset(link->ll.sll_addr, 0xff, ETH_ALEN); - - r = bind(s, &link->sa, sizeof(link->ll)); - if (r < 0) - return -errno; - - r = s; - s = -1; - - return r; -} diff --git a/src/libsystemd-network/ipv4ll-packet.c b/src/libsystemd-network/ipv4ll-packet.c deleted file mode 100644 index 2b6c73ab4b..0000000000 --- a/src/libsystemd-network/ipv4ll-packet.c +++ /dev/null @@ -1,71 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. 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 . -***/ -#include - -#include "util.h" -#include "ipv4ll-internal.h" - -void arp_packet_init(struct ether_arp *arp) { - assert(arp); - - memzero(arp, sizeof(struct ether_arp)); - /* Header */ - arp->ea_hdr.ar_hrd = htons(ARPHRD_ETHER); /* HTYPE */ - arp->ea_hdr.ar_pro = htons(ETHERTYPE_IP); /* PTYPE */ - arp->ea_hdr.ar_hln = ETH_ALEN; /* HLEN */ - arp->ea_hdr.ar_pln = sizeof arp->arp_spa; /* PLEN */ - arp->ea_hdr.ar_op = htons(ARPOP_REQUEST); /* REQUEST */ -} - -void arp_packet_probe(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha) { - assert(ha); - - arp_packet_init(arp); - memcpy(arp->arp_sha, ha, ETH_ALEN); - memcpy(arp->arp_tpa, &pa, sizeof(pa)); -} - -void arp_packet_announcement(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha) { - assert(ha); - - arp_packet_init(arp); - memcpy(arp->arp_sha, ha, ETH_ALEN); - memcpy(arp->arp_tpa, &pa, sizeof(pa)); - memcpy(arp->arp_spa, &pa, sizeof(pa)); -} - -int arp_packet_verify_headers(struct ether_arp *arp) { - assert(arp); - - if (arp->ea_hdr.ar_hrd != htons(ARPHRD_ETHER)) { - log_ipv4ll(NULL, "ignoring packet: header is not ARPHRD_ETHER"); - return -EINVAL; - } - if (arp->ea_hdr.ar_pro != htons(ETHERTYPE_IP)) { - log_ipv4ll(NULL, "ignoring packet: protocol is not ETHERTYPE_IP"); - return -EINVAL; - } - if (arp->ea_hdr.ar_op != htons(ARPOP_REQUEST) && - arp->ea_hdr.ar_op != htons(ARPOP_REPLY)) { - log_ipv4ll(NULL, "ignoring packet: operation is not ARPOP_REQUEST or ARPOP_REPLY"); - return -EINVAL; - } - - return 0; -} diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 14b9444dab..f292a9dba9 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -28,7 +28,7 @@ #include "list.h" #include "random-util.h" -#include "ipv4ll-internal.h" +#include "arp-util.h" #include "sd-ipv4ll.h" /* Constants from the RFC */ @@ -46,6 +46,8 @@ #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__) + typedef enum IPv4LLTrigger{ IPV4LL_TRIGGER_NULL, IPV4LL_TRIGGER_PACKET, @@ -72,7 +74,6 @@ struct sd_ipv4ll { IPv4LLState state; int index; int fd; - union sockaddr_union link; int iteration; int conflict; sd_event_source *receive_message; @@ -196,8 +197,7 @@ static bool ipv4ll_arp_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { assert(ll); assert(arp); - if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->address)) == 0 && - memcmp(arp->arp_sha, &ll->mac_addr, ETH_ALEN) != 0) + if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->address)) == 0) return true; return false; @@ -210,16 +210,13 @@ static bool ipv4ll_arp_probe_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { if (ipv4ll_arp_conflict(ll, arp)) return true; - if (memcmp(arp->arp_tpa, &ll->address, sizeof(ll->address)) == 0 && - memcmp(arp->arp_sha, &ll->mac_addr, ETH_ALEN)) + if (memcmp(arp->arp_tpa, &ll->address, sizeof(ll->address)) == 0) return true; return false; } static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void *trigger_data) { - struct ether_arp out_packet; - int out_packet_ready = 0; int r = 0; assert(ll); @@ -235,8 +232,12 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < PROBE_NUM-2)) { /* Send a probe */ - arp_packet_probe(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; + 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; + } + ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, 0); ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); @@ -244,8 +245,12 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void } else if (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration >= PROBE_NUM-2) { /* Send the last probe */ - arp_packet_probe(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; + 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; + } + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, 1); ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); @@ -254,8 +259,12 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void (ll->state == IPV4LL_STATE_ANNOUNCING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < ANNOUNCE_NUM-1)) { /* Send announcement packet */ - arp_packet_announcement(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; + 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, 0); ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); @@ -295,8 +304,12 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void /* Defend address */ if (time_now > ll->defend_window) { ll->defend_window = time_now + DEFEND_INTERVAL * USEC_PER_SEC; - arp_packet_announcement(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; + 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 conflicted = 1; } @@ -320,6 +333,15 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void r = ipv4ll_pick_address(ll, &ll->address); if (r < 0) goto out; + + ll->fd = safe_close(ll->fd); + + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + if (r < 0) + goto out; + + ll->fd = r; + ll->conflict++; ll->defend_window = 0; ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); @@ -329,15 +351,6 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); } else ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - - } - } - - if (out_packet_ready) { - r = arp_network_send_raw_socket(ll->fd, &ll->link, &out_packet); - if (r < 0) { - log_ipv4ll(ll, "failed to send arp packet out"); - goto out; } } @@ -374,10 +387,6 @@ static int ipv4ll_receive_message(sd_event_source *s, int fd, if (r < (int) sizeof(struct ether_arp)) return 0; - r = arp_packet_verify_headers(&arp); - if (r < 0) - return 0; - ipv4ll_run_state_machine(ll, IPV4LL_TRIGGER_PACKET, &arp); return 0; @@ -523,12 +532,6 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { ll->state = IPV4LL_STATE_INIT; - r = arp_network_bind_raw_socket(ll->index, &ll->link); - - if (r < 0) - goto out; - - ll->fd = r; ll->conflict = 0; ll->defend_window = 0; ll->claimed_address = 0; @@ -551,6 +554,13 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { goto out; } + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + if (r < 0) + goto out; + + safe_close(ll->fd); + ll->fd = r; + ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index d60ee98b25..e7447d322f 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -31,7 +31,7 @@ #include "event-util.h" #include "sd-ipv4ll.h" -#include "ipv4ll-internal.h" +#include "arp-util.h" static bool verbose = false; static bool extended = false; @@ -56,10 +56,10 @@ static void basic_request_handler(sd_ipv4ll *ll, int event, void *userdata) { } } -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp) { +static int arp_network_send_raw_socket(int fd, int ifindex, + const struct ether_arp *arp) { assert_se(arp); - assert_se(link); + assert_se(ifindex > 0); assert_se(fd >= 0); if (send(fd, arp, sizeof(struct ether_arp), 0) < 0) @@ -68,51 +68,35 @@ int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, return 0; } -int arp_network_bind_raw_socket(int index, union sockaddr_union *link) { - if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; - return test_fd[0]; -} + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); -static void test_arp_header(struct ether_arp *arp) { - assert_se(arp); - assert_se(arp->ea_hdr.ar_hrd == htons(ARPHRD_ETHER)); /* HTYPE */ - assert_se(arp->ea_hdr.ar_pro == htons(ETHERTYPE_IP)); /* PTYPE */ - assert_se(arp->ea_hdr.ar_hln == ETH_ALEN); /* HLEN */ - assert_se(arp->ea_hdr.ar_pln == sizeof arp->arp_spa); /* PLEN */ - assert_se(arp->ea_hdr.ar_op == htons(ARPOP_REQUEST)); /* REQUEST */ + return arp_network_send_raw_socket(fd, ifindex, &ea); } -static void test_arp_probe(void) { - struct ether_arp arp; - struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; - be32_t pa = 0x3030; +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; - if (verbose) - printf("* %s\n", __FUNCTION__); + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); - arp_packet_probe(&arp, pa, &mac_addr); - test_arp_header(&arp); - assert_se(memcmp(arp.arp_sha, &mac_addr, ETH_ALEN) == 0); - assert_se(memcmp(arp.arp_tpa, &pa, sizeof(pa)) == 0); + return arp_network_send_raw_socket(fd, ifindex, &ea); } -static void test_arp_announce(void) { - struct ether_arp arp; - struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; - be32_t pa = 0x3131; - - if (verbose) - printf("* %s\n", __FUNCTION__); +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac) { + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; - arp_packet_announcement(&arp, pa, &mac_addr); - test_arp_header(&arp); - assert_se(memcmp(arp.arp_sha, &mac_addr, ETH_ALEN) == 0); - assert_se(memcmp(arp.arp_tpa, &pa, sizeof(pa)) == 0); - assert_se(memcmp(arp.arp_spa, &pa, sizeof(pa)) == 0); + return test_fd[0]; } static void test_public_api_setters(sd_event *e) { @@ -187,18 +171,15 @@ static void test_basic_request(sd_event *e) { /* PROBE */ sd_event_run(e, (uint64_t) -1); assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); if (extended) { /* PROBE */ sd_event_run(e, (uint64_t) -1); assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); /* PROBE */ sd_event_run(e, (uint64_t) -1); assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); sd_event_run(e, (uint64_t) -1); assert_se(basic_request_handler_bind == 1); @@ -218,8 +199,6 @@ int main(int argc, char *argv[]) { assert_se(sd_event_new(&e) >= 0); test_public_api_setters(e); - test_arp_probe(); - test_arp_announce(); test_basic_request(e); return 0; -- cgit v1.2.3-54-g00ecf From 8e5787b5e045e40c961ce97003f440090f545c1c Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Tue, 18 Aug 2015 17:59:45 +0200 Subject: sd-ipv4ll: split run_state_machine() into on_packet() and on_timeout() Simplify the code a bit, no functional change. --- src/libsystemd-network/sd-ipv4ll.c | 188 ++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 106 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index f292a9dba9..0d5e46dd49 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -48,14 +48,6 @@ #define log_ipv4ll(ll, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "IPv4LL: " fmt, ##__VA_ARGS__) -typedef enum IPv4LLTrigger{ - IPV4LL_TRIGGER_NULL, - IPV4LL_TRIGGER_PACKET, - IPV4LL_TRIGGER_TIMEOUT, - _IPV4LL_TRIGGER_MAX, - _IPV4LL_TRIGGER_INVALID = -1 -} IPv4LLTrigger; - typedef enum IPv4LLState { IPV4LL_STATE_INIT, IPV4LL_STATE_WAITING_PROBE, @@ -93,8 +85,6 @@ struct sd_ipv4ll { void* userdata; }; -static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void *trigger_data); - static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, int reset_counter) { assert(ll); @@ -163,17 +153,6 @@ static int ipv4ll_pick_address(sd_ipv4ll *ll, be32_t *address) { return 0; } -static int ipv4ll_timer(sd_event_source *s, uint64_t usec, void *userdata) { - sd_ipv4ll *ll = (sd_ipv4ll*)userdata; - - assert(ll); - - ll->next_wakeup_valid = 0; - ipv4ll_run_state_machine(ll, IPV4LL_TRIGGER_TIMEOUT, NULL); - - return 0; -} - static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { usec_t next_timeout = 0; usec_t time_now = 0; @@ -216,11 +195,11 @@ static bool ipv4ll_arp_probe_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { return false; } -static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void *trigger_data) { +static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4ll *ll = userdata; int r = 0; assert(ll); - assert(trigger < _IPV4LL_TRIGGER_MAX); if (ll->state == IPV4LL_STATE_INIT) { @@ -228,8 +207,8 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - } else if ((ll->state == IPV4LL_STATE_WAITING_PROBE && trigger == IPV4LL_TRIGGER_TIMEOUT) || - (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < PROBE_NUM-2)) { + } else if (ll->state == IPV4LL_STATE_WAITING_PROBE || + (ll->state == IPV4LL_STATE_PROBING && ll->iteration < PROBE_NUM-2)) { /* Send a probe */ r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); @@ -242,7 +221,7 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); - } else if (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration >= PROBE_NUM-2) { + } else if (ll->state == IPV4LL_STATE_PROBING && ll->iteration >= PROBE_NUM-2) { /* Send the last probe */ r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); @@ -255,8 +234,8 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); - } else if ((ll->state == IPV4LL_STATE_WAITING_ANNOUNCE && trigger == IPV4LL_TRIGGER_TIMEOUT) || - (ll->state == IPV4LL_STATE_ANNOUNCING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < ANNOUNCE_NUM-1)) { + } else if (ll->state == IPV4LL_STATE_WAITING_ANNOUNCE || + (ll->state == IPV4LL_STATE_ANNOUNCING && ll->iteration < ANNOUNCE_NUM-1)) { /* Send announcement packet */ r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); @@ -279,117 +258,114 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void ll->conflict = 0; } - } else if ((ll->state == IPV4LL_STATE_ANNOUNCING && trigger == IPV4LL_TRIGGER_TIMEOUT && + } else if ((ll->state == IPV4LL_STATE_ANNOUNCING && ll->iteration >= ANNOUNCE_NUM-1)) { ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, 0); ll->next_wakeup_valid = 0; + } - } else if (trigger == IPV4LL_TRIGGER_PACKET) { - - int conflicted = 0; - usec_t time_now; - struct ether_arp* in_packet = (struct ether_arp*)trigger_data; - - assert(in_packet); - - if (IN_SET(ll->state, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING)) { - - if (ipv4ll_arp_conflict(ll, in_packet)) { + if (ll->next_wakeup_valid) { + ll->timer = sd_event_source_unref(ll->timer); + r = sd_event_add_time(ll->event, &ll->timer, clock_boottime_or_monotonic(), + ll->next_wakeup, 0, ipv4ll_on_timeout, ll); + if (r < 0) + goto out; - r = sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - goto out; + r = sd_event_source_set_priority(ll->timer, ll->event_priority); + if (r < 0) + goto out; - /* Defend address */ - if (time_now > ll->defend_window) { - ll->defend_window = time_now + 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; - } + r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); + if (r < 0) + goto out; + } - } else - conflicted = 1; - } +out: + if (r < 0 && ll) + ipv4ll_stop(ll, r); - } else if (IN_SET(ll->state, IPV4LL_STATE_WAITING_PROBE, - IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE)) { + return 1; +} - conflicted = ipv4ll_arp_probe_conflict(ll, in_packet); - } +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 conflicted = 0; + usec_t time_now; + int r; - if (conflicted) { - log_ipv4ll(ll, "CONFLICT"); - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - goto out; + assert(ll); + assert(fd >= 0); - ll->claimed_address = 0; + r = read(fd, &packet, sizeof(struct ether_arp)); + if (r < (int) sizeof(struct ether_arp)) + goto out; - /* Pick a new address */ - r = ipv4ll_pick_address(ll, &ll->address); - if (r < 0) - goto out; + if (IN_SET(ll->state, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING)) { - ll->fd = safe_close(ll->fd); + if (ipv4ll_arp_conflict(ll, &packet)) { - r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + r = sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now); if (r < 0) goto out; - ll->fd = r; - - ll->conflict++; - ll->defend_window = 0; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); + /* Defend address */ + if (time_now > ll->defend_window) { + ll->defend_window = time_now + 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; + } - if (ll->conflict >= MAX_CONFLICTS) { - log_ipv4ll(ll, "MAX_CONFLICTS"); - ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); } else - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); + conflicted = 1; } - } - if (ll->next_wakeup_valid) { - ll->timer = sd_event_source_unref(ll->timer); - r = sd_event_add_time(ll->event, &ll->timer, clock_boottime_or_monotonic(), - ll->next_wakeup, 0, ipv4ll_timer, ll); - if (r < 0) + } else if (IN_SET(ll->state, IPV4LL_STATE_WAITING_PROBE, + IPV4LL_STATE_PROBING, + IPV4LL_STATE_WAITING_ANNOUNCE)) + conflicted = ipv4ll_arp_probe_conflict(ll, &packet); + + if (conflicted) { + log_ipv4ll(ll, "CONFLICT"); + ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); + if (!ll || ll->state == IPV4LL_STATE_STOPPED) goto out; - r = sd_event_source_set_priority(ll->timer, ll->event_priority); + ll->claimed_address = 0; + + /* Pick a new address */ + r = ipv4ll_pick_address(ll, &ll->address); if (r < 0) goto out; - r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); + ll->fd = safe_close(ll->fd); + + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); if (r < 0) goto out; + + ll->fd = r; + + ll->conflict++; + ll->defend_window = 0; + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); + + if (ll->conflict >= MAX_CONFLICTS) { + log_ipv4ll(ll, "MAX_CONFLICTS"); + ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); + } else + ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); } out: if (r < 0 && ll) ipv4ll_stop(ll, r); -} -static int ipv4ll_receive_message(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { - int r; - struct ether_arp arp; - sd_ipv4ll *ll = (sd_ipv4ll*)userdata; - - assert(ll); - - r = read(fd, &arp, sizeof(struct ether_arp)); - if (r < (int) sizeof(struct ether_arp)) - return 0; - - ipv4ll_run_state_machine(ll, IPV4LL_TRIGGER_PACKET, &arp); - - return 0; + return 1; } int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { @@ -564,7 +540,7 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, - EPOLLIN, ipv4ll_receive_message, ll); + EPOLLIN, ipv4ll_on_packet, ll); if (r < 0) goto out; @@ -580,7 +556,7 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { &ll->timer, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()), 0, - ipv4ll_timer, ll); + ipv4ll_on_timeout, ll); if (r < 0) goto out; -- cgit v1.2.3-54-g00ecf From b26f7e8ec922f0d55733ab8d5b6c41212e5a4575 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Tue, 18 Aug 2015 23:58:58 +0200 Subject: sd-ipv4ll: split out on_conflict() from on_packet() --- src/libsystemd-network/sd-ipv4ll.c | 96 +++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 0d5e46dd49..8f75b585e1 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -288,12 +288,49 @@ out: return 1; } +static int ipv4ll_on_conflict(sd_ipv4ll *ll) { + int r; + + assert(ll); + + log_ipv4ll(ll, "CONFLICT"); + + ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); + if (!ll || ll->state == IPV4LL_STATE_STOPPED) + return 0; + + ll->claimed_address = 0; + + /* Pick a new address */ + r = ipv4ll_pick_address(ll, &ll->address); + if (r < 0) + return r; + + ll->fd = safe_close(ll->fd); + + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + if (r < 0) + return r; + + ll->fd = r; + + ll->conflict++; + ll->defend_window = 0; + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); + + if (ll->conflict >= MAX_CONFLICTS) { + log_ipv4ll(ll, "MAX_CONFLICTS"); + ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); + } else + ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); + + return 0; +} + 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 conflicted = 0; - usec_t time_now; int r; assert(ll); @@ -306,59 +343,34 @@ static int ipv4ll_on_packet(sd_event_source *s, int fd, if (IN_SET(ll->state, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING)) { if (ipv4ll_arp_conflict(ll, &packet)) { + usec_t ts; - r = sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - goto out; + assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &ts) >= 0); /* Defend address */ - if (time_now > ll->defend_window) { - ll->defend_window = time_now + DEFEND_INTERVAL * USEC_PER_SEC; + 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 - conflicted = 1; + } else { + r = ipv4ll_on_conflict(ll); + if (r < 0) + goto out; + } } } else if (IN_SET(ll->state, IPV4LL_STATE_WAITING_PROBE, IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE)) - conflicted = ipv4ll_arp_probe_conflict(ll, &packet); - - if (conflicted) { - log_ipv4ll(ll, "CONFLICT"); - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - goto out; - - ll->claimed_address = 0; - - /* Pick a new address */ - r = ipv4ll_pick_address(ll, &ll->address); - if (r < 0) - goto out; - - ll->fd = safe_close(ll->fd); - - r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); - if (r < 0) - goto out; - - ll->fd = r; - - ll->conflict++; - ll->defend_window = 0; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); - - if (ll->conflict >= MAX_CONFLICTS) { - log_ipv4ll(ll, "MAX_CONFLICTS"); - ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); - } else - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); + IPV4LL_STATE_WAITING_ANNOUNCE)) { + if (ipv4ll_arp_probe_conflict(ll, &packet)) { + r = ipv4ll_on_conflict(ll); + if (r < 0) + goto out; + } } out: -- cgit v1.2.3-54-g00ecf From 25d6213b4331e9cbf355f4e851fc5b4ee2ae4c0f Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Wed, 19 Aug 2015 17:02:33 +0200 Subject: sd-ipv4ll: minor cleanups --- src/libsystemd-network/sd-ipv4ll.c | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 8f75b585e1..9736e4d12a 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -85,7 +85,7 @@ struct sd_ipv4ll { void* userdata; }; -static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, int reset_counter) { +static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, bool reset_counter) { assert(ll); assert(st < _IPV4LL_STATE_MAX); @@ -124,7 +124,7 @@ static sd_ipv4ll *ipv4ll_stop(sd_ipv4ll *ll, int event) { if (ll) { ll->claimed_address = 0; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); + ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); } return ll; @@ -204,7 +204,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) if (ll->state == IPV4LL_STATE_INIT) { log_ipv4ll(ll, "PROBE"); - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); } else if (ll->state == IPV4LL_STATE_WAITING_PROBE || @@ -217,7 +217,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) goto out; } - ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, 0); + ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, false); ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); @@ -230,7 +230,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) goto out; } - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, 1); + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, true); ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); @@ -244,7 +244,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) goto out; } - ipv4ll_set_state(ll, IPV4LL_STATE_ANNOUNCING, 0); + ipv4ll_set_state(ll, IPV4LL_STATE_ANNOUNCING, false); ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); @@ -261,7 +261,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) } else if ((ll->state == IPV4LL_STATE_ANNOUNCING && ll->iteration >= ANNOUNCE_NUM-1)) { - ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, 0); + ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, false); ll->next_wakeup_valid = 0; } @@ -316,7 +316,7 @@ static int ipv4ll_on_conflict(sd_ipv4ll *ll) { ll->conflict++; ll->defend_window = 0; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); if (ll->conflict >= MAX_CONFLICTS) { log_ipv4ll(ll, "MAX_CONFLICTS"); @@ -340,8 +340,9 @@ static int ipv4ll_on_packet(sd_event_source *s, int fd, if (r < (int) sizeof(struct ether_arp)) goto out; - if (IN_SET(ll->state, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING)) { - + switch (ll->state) { + case IPV4LL_STATE_ANNOUNCING: + case IPV4LL_STATE_RUNNING: if (ipv4ll_arp_conflict(ll, &packet)) { usec_t ts; @@ -363,14 +364,19 @@ static int ipv4ll_on_packet(sd_event_source *s, int fd, } } - } else if (IN_SET(ll->state, IPV4LL_STATE_WAITING_PROBE, - IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE)) { - if (ipv4ll_arp_probe_conflict(ll, &packet)) { - r = ipv4ll_on_conflict(ll); - if (r < 0) - goto out; - } + break; + case IPV4LL_STATE_WAITING_PROBE: + case IPV4LL_STATE_PROBING: + case IPV4LL_STATE_WAITING_ANNOUNCE: + if (ipv4ll_arp_probe_conflict(ll, &packet)) { + r = ipv4ll_on_conflict(ll); + if (r < 0) + goto out; + } + + break; + default: + assert_not_reached("Invalid state."); } out: @@ -549,7 +555,7 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { safe_close(ll->fd); ll->fd = r; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); + ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, EPOLLIN, ipv4ll_on_packet, ll); @@ -588,7 +594,7 @@ out: int sd_ipv4ll_stop(sd_ipv4ll *ll) { ipv4ll_stop(ll, IPV4LL_EVENT_STOP); if (ll) - ipv4ll_set_state(ll, IPV4LL_STATE_STOPPED, 1); + ipv4ll_set_state(ll, IPV4LL_STATE_STOPPED, true); return 0; } -- cgit v1.2.3-54-g00ecf From 028e0b20563cbe5a7adb350e670181c77f4c9050 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Wed, 19 Aug 2015 19:42:38 +0200 Subject: sd-ipv4ll: remove duplicate packet verification Most packets are filtered out by the BPF, so only check for the parts that may actually differ. --- src/libsystemd-network/sd-ipv4ll.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 9736e4d12a..b6aad2888d 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -172,26 +172,15 @@ static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { ll->next_wakeup_valid = 1; } -static bool ipv4ll_arp_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { +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; - return false; -} - -static bool ipv4ll_arp_probe_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { - assert(ll); - assert(arp); - - if (ipv4ll_arp_conflict(ll, arp)) - return true; - - if (memcmp(arp->arp_tpa, &ll->address, sizeof(ll->address)) == 0) - return true; - + /* the TPA matched instead of the SPA, this is not a conflict */ return false; } @@ -368,11 +357,10 @@ static int ipv4ll_on_packet(sd_event_source *s, int fd, case IPV4LL_STATE_WAITING_PROBE: case IPV4LL_STATE_PROBING: case IPV4LL_STATE_WAITING_ANNOUNCE: - if (ipv4ll_arp_probe_conflict(ll, &packet)) { - r = ipv4ll_on_conflict(ll); - if (r < 0) - goto out; - } + /* BPF ensures this packet indicates a conflict */ + r = ipv4ll_on_conflict(ll); + if (r < 0) + goto out; break; default: -- cgit v1.2.3-54-g00ecf From 94a355a1306ef51ab6c7f5264aaac35a54d5b54a Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Thu, 20 Aug 2015 00:09:08 +0200 Subject: sd-ipv4ll: code cleanups Simplify timeout handling. --- src/libsystemd-network/sd-ipv4ll.c | 118 ++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index b6aad2888d..6bb9b8c82d 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -27,6 +27,7 @@ #include "siphash24.h" #include "list.h" #include "random-util.h" +#include "event-util.h" #include "arp-util.h" #include "sd-ipv4ll.h" @@ -70,9 +71,7 @@ struct sd_ipv4ll { int conflict; sd_event_source *receive_message; sd_event_source *timer; - usec_t next_wakeup; usec_t defend_window; - int next_wakeup_valid; be32_t address; struct random_data *random_data; char *random_data_state; @@ -153,9 +152,13 @@ static int ipv4ll_pick_address(sd_ipv4ll *ll, be32_t *address) { return 0; } -static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { - usec_t next_timeout = 0; - usec_t time_now = 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); @@ -168,8 +171,24 @@ static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now) >= 0); - ll->next_wakeup = time_now + next_timeout; - ll->next_wakeup_valid = 1; + 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; + + r = sd_event_source_set_description(timer, "ipv4ll-timer"); + 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) { @@ -190,15 +209,18 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) assert(ll); - if (ll->state == IPV4LL_STATE_INIT) { + switch (ll->state) { + case IPV4LL_STATE_INIT: log_ipv4ll(ll, "PROBE"); ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - - } else if (ll->state == IPV4LL_STATE_WAITING_PROBE || - (ll->state == IPV4LL_STATE_PROBING && ll->iteration < PROBE_NUM-2)) { + r = ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); + if (r < 0) + goto out; + 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) { @@ -206,26 +228,29 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) goto out; } - ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, false); + if (ll->iteration < PROBE_NUM - 2) { + ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, false); - ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); - - } else if (ll->state == IPV4LL_STATE_PROBING && ll->iteration >= PROBE_NUM-2) { + 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); - /* Send the last 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; + r = ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); + if (r < 0) + goto out; } - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, true); - - ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); + break; - } else if (ll->state == IPV4LL_STATE_WAITING_ANNOUNCE || - (ll->state == IPV4LL_STATE_ANNOUNCING && ll->iteration < ANNOUNCE_NUM-1)) { + 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) { @@ -235,7 +260,9 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) ipv4ll_set_state(ll, IPV4LL_STATE_ANNOUNCING, false); - ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); + r = ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); + if (r < 0) + goto out; if (ll->iteration == 0) { log_ipv4ll(ll, "ANNOUNCE"); @@ -247,27 +274,9 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) ll->conflict = 0; } - } else if ((ll->state == IPV4LL_STATE_ANNOUNCING && - ll->iteration >= ANNOUNCE_NUM-1)) { - - ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, false); - ll->next_wakeup_valid = 0; - } - - if (ll->next_wakeup_valid) { - ll->timer = sd_event_source_unref(ll->timer); - r = sd_event_add_time(ll->event, &ll->timer, clock_boottime_or_monotonic(), - ll->next_wakeup, 0, ipv4ll_on_timeout, ll); - if (r < 0) - goto out; - - r = sd_event_source_set_priority(ll->timer, ll->event_priority); - if (r < 0) - goto out; - - r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); - if (r < 0) - goto out; + break; + default: + assert_not_reached("Invalid state."); } out: @@ -558,20 +567,9 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { if (r < 0) goto out; - r = sd_event_add_time(ll->event, - &ll->timer, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()), 0, - ipv4ll_on_timeout, ll); - - if (r < 0) - goto out; - - r = sd_event_source_set_priority(ll->timer, ll->event_priority); + r = ipv4ll_set_next_wakeup(ll, 0, 0); if (r < 0) goto out; - - r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); out: if (r < 0) ipv4ll_stop(ll, IPV4LL_EVENT_STOP); -- cgit v1.2.3-54-g00ecf From 5707940ed31272d44ba2dcf0a74a3024256d2875 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Thu, 20 Aug 2015 11:26:57 +0200 Subject: sd-ipv4ll: don't allow changing MAC address whilst running This requires the caller to stop and restart the statemachine if they want to change the MAC address. --- src/libsystemd-network/sd-ipv4ll.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 6bb9b8c82d..2345e1aabb 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -2,6 +2,7 @@ This file is part of systemd. Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen 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 @@ -395,29 +396,15 @@ int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { } int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { - bool need_restart = false; - assert_return(ll, -EINVAL); assert_return(addr, -EINVAL); + assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED), -EBUSY); if (memcmp(&ll->mac_addr, addr, ETH_ALEN) == 0) return 0; - if (!IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED)) { - log_ipv4ll(ll, "Changing MAC address on running IPv4LL " - "client, restarting"); - ll = ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - need_restart = true; - } - - if (!ll) - return 0; - memcpy(&ll->mac_addr, addr, ETH_ALEN); - if (need_restart) - sd_ipv4ll_start(ll); - return 0; } -- cgit v1.2.3-54-g00ecf From b45e4eb679ad0c9a77c4fe6e404c8842d4097fdb Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Thu, 20 Aug 2015 11:40:10 +0200 Subject: sd-ipv4ll: rework callbacks Firstly, no longer distinguish between STOP and INIT states. Secondly, do not trigger STOP events when calls to sd_ipv4ll_*() fail. The caller is the one who would receive the event and will already know that the call to sd_ipv4ll_*() has failed, so it is redundant. STOP events will now only be triggered by calling sd_ipv4ll_stop() explicitly or by some internal error in the library triggered by receiving a packet or an expiring timeout (i.e., any error that would otherwise not be reported back to the consumer of the library). Lastly, follow CODING_STYLE and always return NULL on unref. Protect from objects being destroyed in callbacks accordingly. --- src/libsystemd-network/sd-ipv4ll.c | 197 ++++++++++++++++------------------- src/libsystemd-network/test-ipv4ll.c | 2 +- src/network/networkd-ipv4ll.c | 5 +- 3 files changed, 94 insertions(+), 110 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 2345e1aabb..05ce5c073a 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -50,6 +50,9 @@ #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, @@ -57,7 +60,6 @@ typedef enum IPv4LLState { IPV4LL_STATE_WAITING_ANNOUNCE, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING, - IPV4LL_STATE_STOPPED, _IPV4LL_STATE_MAX, _IPV4LL_STATE_INVALID = -1 } IPv4LLState; @@ -85,6 +87,63 @@ struct sd_ipv4ll { void* userdata; }; +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + assert(ll->n_ref >= 1); + ll->n_ref++; + + return ll; +} + +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + assert(ll->n_ref >= 1); + ll->n_ref--; + + 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); + + free(ll->random_data); + free(ll->random_data_state); + free(ll); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_unref); +#define _cleanup_ipv4ll_unref_ _cleanup_(sd_ipv4ll_unrefp) + +int sd_ipv4ll_new(sd_ipv4ll **ret) { + _cleanup_ipv4ll_unref_ sd_ipv4ll *ll = NULL; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4ll, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = 1; + ll->state = IPV4LL_STATE_INIT; + ll->index = -1; + ll->fd = -1; + + *ret = ll; + ll = NULL; + + return 0; +} + static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, bool reset_counter) { assert(ll); @@ -98,19 +157,16 @@ static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, bool reset_counter) } } -static sd_ipv4ll *ipv4ll_client_notify(sd_ipv4ll *ll, int event) { +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { assert(ll); - if (ll->cb) { - ll = sd_ipv4ll_ref(ll); + if (ll->cb) ll->cb(ll, event, ll->userdata); - ll = sd_ipv4ll_unref(ll); - } - - return ll; } -static sd_ipv4ll *ipv4ll_stop(sd_ipv4ll *ll, int event) { +static void ipv4ll_stop(sd_ipv4ll *ll) { + IPV4LL_DONT_DESTROY(ll); + assert(ll); ll->receive_message = sd_event_source_unref(ll->receive_message); @@ -120,14 +176,20 @@ static sd_ipv4ll *ipv4ll_stop(sd_ipv4ll *ll, int event) { log_ipv4ll(ll, "STOPPED"); - ll = ipv4ll_client_notify(ll, event); + ll->claimed_address = 0; + ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); +} - if (ll) { - ll->claimed_address = 0; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); - } +int sd_ipv4ll_stop(sd_ipv4ll *ll) { + IPV4LL_DONT_DESTROY(ll); - return 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) { @@ -268,10 +330,7 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) if (ll->iteration == 0) { log_ipv4ll(ll, "ANNOUNCE"); ll->claimed_address = ll->address; - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - goto out; - + ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND); ll->conflict = 0; } @@ -282,21 +341,20 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) out: if (r < 0 && ll) - ipv4ll_stop(ll, r); + sd_ipv4ll_stop(ll); return 1; } static int ipv4ll_on_conflict(sd_ipv4ll *ll) { + IPV4LL_DONT_DESTROY(ll); int r; assert(ll); log_ipv4ll(ll, "CONFLICT"); - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - return 0; + ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); ll->claimed_address = 0; @@ -321,7 +379,7 @@ static int ipv4ll_on_conflict(sd_ipv4ll *ll) { log_ipv4ll(ll, "MAX_CONFLICTS"); ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); } else - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); + ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); return 0; } @@ -379,7 +437,7 @@ static int ipv4ll_on_packet(sd_event_source *s, int fd, out: if (r < 0 && ll) - ipv4ll_stop(ll, r); + sd_ipv4ll_stop(ll); return 1; } @@ -387,8 +445,7 @@ out: int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { assert_return(ll, -EINVAL); assert_return(interface_index > 0, -EINVAL); - assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, - IPV4LL_STATE_STOPPED), -EBUSY); + assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); ll->index = interface_index; @@ -398,7 +455,7 @@ int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { assert_return(ll, -EINVAL); assert_return(addr, -EINVAL); - assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED), -EBUSY); + assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); if (memcmp(&ll->mac_addr, addr, ETH_ALEN) == 0) return 0; @@ -426,10 +483,8 @@ int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority) { ll->event = sd_event_ref(event); else { r = sd_event_default(&ll->event); - if (r < 0) { - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); + if (r < 0) return r; - } } ll->event_priority = priority; @@ -457,7 +512,7 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address){ return 0; } -int sd_ipv4ll_set_address_seed (sd_ipv4ll *ll, uint8_t seed[8]) { +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint8_t seed[8]) { unsigned int entropy; int r; @@ -494,19 +549,18 @@ error: bool sd_ipv4ll_is_running(sd_ipv4ll *ll) { assert_return(ll, false); - return !IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED); + return ll->state != IPV4LL_STATE_INIT; } #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) { +int sd_ipv4ll_start(sd_ipv4ll *ll) { int r; assert_return(ll, -EINVAL); assert_return(ll->event, -EINVAL); assert_return(ll->index > 0, -EINVAL); - assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, - IPV4LL_STATE_STOPPED), -EBUSY); + assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY); ll->state = IPV4LL_STATE_INIT; @@ -539,7 +593,7 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { safe_close(ll->fd); ll->fd = r; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, true); + ipv4ll_set_state(ll, IPV4LL_STATE_INIT, true); r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, EPOLLIN, ipv4ll_on_packet, ll); @@ -559,74 +613,7 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) { goto out; out: if (r < 0) - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - - return 0; -} - -int sd_ipv4ll_stop(sd_ipv4ll *ll) { - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - if (ll) - ipv4ll_set_state(ll, IPV4LL_STATE_STOPPED, true); - - return 0; -} - -sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) { - - if (!ll) - return NULL; - - assert(ll->n_ref >= 1); - ll->n_ref++; - - return ll; -} - -sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { - - if (!ll) - return NULL; - - assert(ll->n_ref >= 1); - ll->n_ref--; - - if (ll->n_ref > 0) - return 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); - - sd_ipv4ll_detach_event(ll); - - free(ll->random_data); - free(ll->random_data_state); - free(ll); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_unref); -#define _cleanup_ipv4ll_free_ _cleanup_(sd_ipv4ll_unrefp) - -int sd_ipv4ll_new(sd_ipv4ll **ret) { - _cleanup_ipv4ll_free_ sd_ipv4ll *ll = NULL; - - assert_return(ret, -EINVAL); - - ll = new0(sd_ipv4ll, 1); - if (!ll) - return -ENOMEM; - - ll->n_ref = 1; - ll->state = IPV4LL_STATE_INIT; - ll->index = -1; - ll->fd = -1; - - *ret = ll; - ll = NULL; + ipv4ll_stop(ll); return 0; } diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index e7447d322f..55ec2f3972 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -133,7 +133,7 @@ static void test_public_api_setters(sd_event *e) { assert_se(sd_ipv4ll_set_index(ll, 99) == 0); assert_se(sd_ipv4ll_ref(ll) == ll); - assert_se(sd_ipv4ll_unref(ll) == ll); + assert_se(sd_ipv4ll_unref(ll) == NULL); /* Cleanup */ assert_se(sd_ipv4ll_unref(ll) == NULL); diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 0a27a30278..43aaa749ff 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -195,10 +195,7 @@ static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata){ } break; default: - if (event < 0) - log_link_warning(link, "IPv4 link-local error: %s", strerror(-event)); - else - log_link_warning(link, "IPv4 link-local unknown event: %d", event); + log_link_warning(link, "IPv4 link-local unknown event: %d", event); break; } } -- cgit v1.2.3-54-g00ecf From 6af9144f5ff65cb9f6ae9999e7e0a9edc4841b2b Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 21 Aug 2015 01:02:59 +0200 Subject: sd-ipv4ll: simplify conflict handling Use stop() and start() to drop some pulicate code. --- src/libsystemd-network/sd-ipv4ll.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 05ce5c073a..03a9b3f4f4 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -276,10 +276,19 @@ static int ipv4ll_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) case IPV4LL_STATE_INIT: log_ipv4ll(ll, "PROBE"); + ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); - r = ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - if (r < 0) - goto out; + + 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: @@ -354,33 +363,21 @@ static int ipv4ll_on_conflict(sd_ipv4ll *ll) { log_ipv4ll(ll, "CONFLICT"); + ll->conflict++; + ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - ll->claimed_address = 0; + sd_ipv4ll_stop(ll); /* Pick a new address */ r = ipv4ll_pick_address(ll, &ll->address); if (r < 0) return r; - ll->fd = safe_close(ll->fd); - - r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + r = sd_ipv4ll_start(ll); if (r < 0) return r; - ll->fd = r; - - ll->conflict++; - ll->defend_window = 0; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, true); - - if (ll->conflict >= MAX_CONFLICTS) { - log_ipv4ll(ll, "MAX_CONFLICTS"); - ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); - } else - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - return 0; } -- cgit v1.2.3-54-g00ecf From e3dca0089b7b50e2ec21409d1292727921d06102 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 21 Aug 2015 12:50:31 +0200 Subject: 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. --- Makefile.am | 2 + src/libsystemd-network/sd-ipv4acd.c | 529 ++++++++++++++++++++++++++++++++ src/libsystemd-network/sd-ipv4ll.c | 568 +++++++++-------------------------- src/libsystemd-network/test-ipv4ll.c | 11 +- src/network/networkd-ipv4ll.c | 4 +- src/systemd/sd-ipv4acd.h | 55 ++++ src/systemd/sd-ipv4ll.h | 2 +- 7 files changed, 743 insertions(+), 428 deletions(-) create mode 100644 src/libsystemd-network/sd-ipv4acd.c create mode 100644 src/systemd/sd-ipv4acd.h diff --git a/Makefile.am b/Makefile.am index 3930647aa0..7b73ba24e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3198,6 +3198,7 @@ libsystemd_network_la_SOURCES = \ src/systemd/sd-dhcp-server.h \ src/systemd/sd-dhcp-lease.h \ src/systemd/sd-ipv4ll.h \ + src/systemd/sd-ipv4acd.h \ src/systemd/sd-icmp6-nd.h \ src/systemd/sd-dhcp6-client.h \ src/systemd/sd-dhcp6-lease.h \ @@ -3214,6 +3215,7 @@ libsystemd_network_la_SOURCES = \ src/libsystemd-network/dhcp-lease-internal.h \ src/libsystemd-network/sd-dhcp-lease.c \ src/libsystemd-network/sd-ipv4ll.c \ + src/libsystemd-network/sd-ipv4acd.c \ src/libsystemd-network/arp-util.h \ src/libsystemd-network/arp-util.c \ src/libsystemd-network/sd-pppoe.c \ diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c new file mode 100644 index 0000000000..ee5d13c030 --- /dev/null +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -0,0 +1,529 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "event-util.h" +#include "in-addr-util.h" +#include "list.h" +#include "refcnt.h" +#include "random-util.h" +#include "siphash24.h" +#include "util.h" + +#include "arp-util.h" +#include "sd-ipv4acd.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 IPV4ACD_NETWORK 0xA9FE0000L +#define IPV4ACD_NETMASK 0xFFFF0000L + +#define log_ipv4acd_full(ll, level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "ACD: " fmt, ##__VA_ARGS__) + +#define log_ipv4acd_debug(ll, ...) log_ipv4acd_full(ll, LOG_DEBUG, 0, ##__VA_ARGS__) +#define log_ipv4acd_info(ll, ...) log_ipv4acd_full(ll, LOG_INFO, 0, ##__VA_ARGS__) +#define log_ipv4acd_notice(ll, ...) log_ipv4acd_full(ll, LOG_NOTICE, 0, ##__VA_ARGS__) +#define log_ipv4acd_warning(ll, ...) log_ipv4acd_full(ll, LOG_WARNING, 0, ##__VA_ARGS__) +#define log_ipv4acd_error(ll, ...) log_ipv4acd_full(ll, LOG_ERR, 0, ##__VA_ARGS__) + +#define log_ipv4acd_debug_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_DEBUG, error, ##__VA_ARGS__) +#define log_ipv4acd_info_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_INFO, error, ##__VA_ARGS__) +#define log_ipv4acd_notice_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_NOTICE, error, ##__VA_ARGS__) +#define log_ipv4acd_warning_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_WARNING, error, ##__VA_ARGS__) +#define log_ipv4acd_error_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_ERR, error, ##__VA_ARGS__) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + IPV4ACD_STATE_WAITING_PROBE, + IPV4ACD_STATE_PROBING, + IPV4ACD_STATE_WAITING_ANNOUNCE, + IPV4ACD_STATE_ANNOUNCING, + IPV4ACD_STATE_RUNNING, + _IPV4ACD_STATE_MAX, + _IPV4ACD_STATE_INVALID = -1 +} IPv4ACDState; + +struct sd_ipv4acd { + RefCount n_ref; + + IPv4ACDState 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; + /* External */ + struct ether_addr mac_addr; + sd_event *event; + int event_priority; + sd_ipv4acd_cb_t cb; + void* userdata; +}; + +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll) { + if (ll) + assert_se(REFCNT_INC(ll->n_ref) >= 2); + + return ll; +} + +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll) { + if (!ll || REFCNT_DEC(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_ipv4acd_detach_event(ll); + + free(ll); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4acd*, sd_ipv4acd_unref); +#define _cleanup_ipv4acd_unref_ _cleanup_(sd_ipv4acd_unrefp) + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_ipv4acd_unref_ sd_ipv4acd *ll = NULL; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4acd, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = REFCNT_INIT; + ll->state = IPV4ACD_STATE_INIT; + ll->index = -1; + ll->fd = -1; + + *ret = ll; + ll = NULL; + + return 0; +} + +static void ipv4acd_set_state(sd_ipv4acd *ll, IPv4ACDState st, bool reset_counter) { + + assert(ll); + assert(st < _IPV4ACD_STATE_MAX); + + if (st == ll->state && !reset_counter) + ll->iteration++; + else { + ll->state = st; + ll->iteration = 0; + } +} + +static void ipv4acd_client_notify(sd_ipv4acd *ll, int event) { + assert(ll); + + if (ll->cb) + ll->cb(ll, event, ll->userdata); +} + +static void ipv4acd_stop(sd_ipv4acd *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_ipv4acd_debug(ll, "STOPPED"); + + ipv4acd_set_state (ll, IPV4ACD_STATE_INIT, true); +} + +int sd_ipv4acd_stop(sd_ipv4acd *ll) { + assert_return(ll, -EINVAL); + + ipv4acd_stop(ll); + + ipv4acd_client_notify(ll, IPV4ACD_EVENT_STOP); + + return 0; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int ipv4acd_set_next_wakeup(sd_ipv4acd *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, ipv4acd_on_timeout, ll); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timer, ll->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(timer, "ipv4acd-timer"); + if (r < 0) + return r; + + ll->timer = sd_event_source_unref(ll->timer); + ll->timer = timer; + timer = NULL; + + return 0; +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *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; + + /* the TPA matched instead of the SPA, this is not a conflict */ + return false; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4acd *ll = userdata; + int r = 0; + + assert(ll); + + switch (ll->state) { + case IPV4ACD_STATE_INIT: + + ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_PROBE, true); + + if (ll->conflict >= MAX_CONFLICTS) { + log_ipv4acd_notice(ll, "Max conflicts reached, delaying by %us", RATE_LIMIT_INTERVAL); + r = ipv4acd_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); + if (r < 0) + goto out; + + ll->conflict = 0; + } else { + r = ipv4acd_set_next_wakeup(ll, 0, PROBE_WAIT); + if (r < 0) + goto out; + } + + break; + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); + if (r < 0) { + log_ipv4acd_error_errno(ll, r, "Failed to send ARP probe: %m"); + goto out; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = ll->address }; + + r = in_addr_to_string(AF_INET, &addr, &address); + if (r >= 0) + log_ipv4acd_debug(ll, "Probing %s", address); + } + + if (ll->iteration < PROBE_NUM - 2) { + ipv4acd_set_state(ll, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); + if (r < 0) + goto out; + } else { + ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); + if (r < 0) + goto out; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (ll->iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(ll, IPV4ACD_STATE_RUNNING, false); + + break; + } + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); + if (r < 0) { + log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); + goto out; + } else + log_ipv4acd_debug(ll, "ANNOUNCE"); + + ipv4acd_set_state(ll, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); + if (r < 0) + goto out; + + if (ll->iteration == 0) { + ll->conflict = 0; + ipv4acd_client_notify(ll, IPV4ACD_EVENT_BIND); + } + + break; + default: + assert_not_reached("Invalid state."); + } + +out: + if (r < 0) + sd_ipv4acd_stop(ll); + + return 1; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *ll) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = ll->address }; + int r; + + assert(ll); + + ll->conflict++; + + r = in_addr_to_string(AF_INET, &addr, &address); + if (r >= 0) + log_ipv4acd_debug(ll, "Conflict on %s (%u)", address, ll->conflict); + + ipv4acd_stop(ll); + + ipv4acd_client_notify(ll, IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet(sd_event_source *s, int fd, + uint32_t revents, void *userdata) { + sd_ipv4acd *ll = userdata; + struct ether_arp packet; + int r; + + 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 IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + if (ipv4acd_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_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); + goto out; + } else + log_ipv4acd_debug(ll, "DEFEND"); + + } else + ipv4acd_on_conflict(ll); + } + + break; + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* BPF ensures this packet indicates a conflict */ + ipv4acd_on_conflict(ll); + + break; + default: + assert_not_reached("Invalid state."); + } + +out: + if (r < 0) + sd_ipv4acd_stop(ll); + + return 1; +} + +int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index) { + assert_return(ll, -EINVAL); + assert_return(interface_index > 0, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->index = interface_index; + + return 0; +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr) { + assert_return(ll, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + memcpy(&ll->mac_addr, addr, ETH_ALEN); + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *ll) { + assert_return(ll, -EINVAL); + + ll->event = sd_event_unref(ll->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *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; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->cb = cb; + ll->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address){ + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->address = address->s_addr; + + return 0; +} + +bool sd_ipv4acd_is_running(sd_ipv4acd *ll) { + assert_return(ll, false); + + return ll->state != IPV4ACD_STATE_INIT; +} + +static bool ether_addr_is_nul(const struct ether_addr *addr) { + const struct ether_addr nul_addr = {}; + + assert(addr); + + return memcmp(addr, &nul_addr, sizeof(struct ether_addr)) == 0; +} + +#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +int sd_ipv4acd_start(sd_ipv4acd *ll) { + int r; + + assert_return(ll, -EINVAL); + assert_return(ll->event, -EINVAL); + assert_return(ll->index > 0, -EINVAL); + assert_return(ll->address != 0, -EINVAL); + assert_return(!ether_addr_is_nul(&ll->mac_addr), -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->defend_window = 0; + + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + if (r < 0) + goto out; + + ll->fd = safe_close(ll->fd); + ll->fd = r; + + r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, + EPOLLIN, ipv4acd_on_packet, ll); + if (r < 0) + goto out; + + r = sd_event_source_set_priority(ll->receive_message, ll->event_priority); + if (r < 0) + goto out; + + r = sd_event_source_set_description(ll->receive_message, "ipv4acd-receive-message"); + if (r < 0) + goto out; + + r = ipv4acd_set_next_wakeup(ll, 0, 0); + if (r < 0) + goto out; +out: + if (r < 0) { + ipv4acd_stop(ll); + return r; + } + + return 0; +} 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 #include -#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); } diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index 55ec2f3972..f0e26bda07 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -118,9 +118,8 @@ static void test_public_api_setters(sd_event *e) { assert_se(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_callback(ll, NULL, NULL) == 0); - assert_se(sd_ipv4ll_set_address_seed(NULL, NULL) == -EINVAL); - assert_se(sd_ipv4ll_set_address_seed(ll, NULL) == -EINVAL); - assert_se(sd_ipv4ll_set_address_seed(ll, seed) == 0); + assert_se(sd_ipv4ll_set_address_seed(NULL, *(unsigned *) seed) == -EINVAL); + assert_se(sd_ipv4ll_set_address_seed(ll, *(unsigned *) seed) == 0); assert_se(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_mac(ll, NULL) == -EINVAL); @@ -168,6 +167,8 @@ static void test_basic_request(sd_event *e) { sd_event_run(e, (uint64_t) -1); assert_se(sd_ipv4ll_start(ll) == -EBUSY); + assert_se(sd_ipv4ll_is_running(ll)); + /* PROBE */ sd_event_run(e, (uint64_t) -1); assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); @@ -196,6 +197,10 @@ static void test_basic_request(sd_event *e) { int main(int argc, char *argv[]) { _cleanup_event_unref_ sd_event *e = NULL; + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + assert_se(sd_event_new(&e) >= 0); test_public_api_setters(e); diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 43aaa749ff..1c34f55b4b 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -215,7 +215,9 @@ int ipv4ll_configure(Link *link) { if (link->udev_device) { r = net_get_unique_predictable_data(link->udev_device, seed); if (r >= 0) { - r = sd_ipv4ll_set_address_seed(link->ipv4ll, seed); + assert_cc(sizeof(unsigned) <= 8); + + r = sd_ipv4ll_set_address_seed(link->ipv4ll, *(unsigned *)seed); if (r < 0) return r; } diff --git a/src/systemd/sd-ipv4acd.h b/src/systemd/sd-ipv4acd.h new file mode 100644 index 0000000000..8844ae848d --- /dev/null +++ b/src/systemd/sd-ipv4acd.h @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include "sd-event.h" + +enum { + IPV4ACD_EVENT_STOP = 0, + IPV4ACD_EVENT_BIND = 1, + IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_cb_t)(sd_ipv4acd *ll, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *ll); +int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority); +int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr); +int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index); +int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address); +bool sd_ipv4acd_is_running(sd_ipv4acd *ll); +int sd_ipv4acd_start(sd_ipv4acd *ll); +int sd_ipv4acd_stop(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll); +int sd_ipv4acd_new (sd_ipv4acd **ret); + +#endif diff --git a/src/systemd/sd-ipv4ll.h b/src/systemd/sd-ipv4ll.h index d017158154..9581e99d31 100644 --- a/src/systemd/sd-ipv4ll.h +++ b/src/systemd/sd-ipv4ll.h @@ -43,7 +43,7 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata); int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index); -int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint8_t seed[8]); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed); bool sd_ipv4ll_is_running(sd_ipv4ll *ll); int sd_ipv4ll_start(sd_ipv4ll *ll); int sd_ipv4ll_stop(sd_ipv4ll *ll); -- cgit v1.2.3-54-g00ecf From 7b713b81c27277f37420c121f2c9eeb10646ff5a Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Fri, 21 Aug 2015 23:04:14 +0200 Subject: sd-ipv4{acd,ll}: add simple test programs These programs should be run manually, typically two instances on a veth pair to check conflict detection. Both test programs take the ifname as input, the ACD also takes the IP address to check, whereas LL (optionally) takes the seed, which determines the sequence of IP addresses to try. --- Makefile.am | 20 ++++- src/libsystemd-network/test-acd.c | 117 +++++++++++++++++++++++++ src/libsystemd-network/test-ipv4ll-manual.c | 129 ++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 src/libsystemd-network/test-acd.c create mode 100644 src/libsystemd-network/test-ipv4ll-manual.c diff --git a/Makefile.am b/Makefile.am index 7b73ba24e9..3b35083d0a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1367,7 +1367,9 @@ manual_tests += \ test-watchdog \ test-log \ test-ipcrm \ - test-btrfs + test-btrfs \ + test-acd \ + test-ipv4ll-manual if HAVE_LIBIPTC manual_tests += \ @@ -3281,6 +3283,22 @@ test_ipv4ll_LDADD = \ libsystemd-network.la \ libshared.la +test_ipv4ll_manual_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/test-ipv4ll-manual.c + +test_ipv4ll_manual_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_acd_SOURCES = \ + src/systemd/sd-ipv4acd.h \ + src/libsystemd-network/test-acd.c + +test_acd_LDADD = \ + libsystemd-network.la \ + libshared.la + test_pppoe_SOURCES = \ src/systemd/sd-pppoe.h \ src/libsystemd-network/test-pppoe.c diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c new file mode 100644 index 0000000000..f1f14ee119 --- /dev/null +++ b/src/libsystemd-network/test-acd.c @@ -0,0 +1,117 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include +#include + +#include "sd-event.h" +#include "sd-netlink.h" +#include "sd-ipv4acd.h" + +#include "util.h" +#include "event-util.h" +#include "netlink-util.h" +#include "in-addr-util.h" + +static void acd_handler(sd_ipv4acd *acd, int event, void *userdata) { + assert_se(acd); + + switch (event) { + case IPV4ACD_EVENT_BIND: + log_info("bound"); + break; + case IPV4ACD_EVENT_CONFLICT: + log_info("conflict"); + break; + case IPV4ACD_EVENT_STOP: + log_error("the client was stopped"); + break; + default: + assert_not_reached("invalid ACD event"); + } +} + +static int client_run(int ifindex, const struct in_addr *pa, const struct ether_addr *ha, sd_event *e) { + sd_ipv4acd *acd; + + assert_se(sd_ipv4acd_new(&acd) >= 0); + assert_se(sd_ipv4acd_attach_event(acd, e, 0) >= 0); + + assert_se(sd_ipv4acd_set_index(acd, ifindex) >= 0); + assert_se(sd_ipv4acd_set_mac(acd, ha) >= 0); + assert_se(sd_ipv4acd_set_address(acd, pa) >= 0); + assert_se(sd_ipv4acd_set_callback(acd, acd_handler, NULL) >= 0); + + log_info("starting IPv4ACD client"); + + assert_se(sd_ipv4acd_start(acd) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4acd_unref(acd)); + + return EXIT_SUCCESS; +} + +static int test_acd(const char *ifname, const char *address) { + _cleanup_event_unref_ sd_event *e = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL, *reply = NULL; + union in_addr_union pa; + struct ether_addr ha; + int ifindex; + + assert_se(in_addr_from_string(AF_INET, address, &pa) >= 0); + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, &pa.in, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 3) + return test_acd(argv[1], argv[2]); + else { + log_error("This program takes two arguments.\n" + "\t %s ", program_invocation_short_name); + return EXIT_FAILURE; + } +} diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c new file mode 100644 index 0000000000..ad664cba51 --- /dev/null +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -0,0 +1,129 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include +#include + +#include "sd-event.h" +#include "sd-netlink.h" +#include "sd-ipv4ll.h" + +#include "util.h" +#include "event-util.h" +#include "netlink-util.h" +#include "in-addr-util.h" + +static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) { + _cleanup_free_ char *address = NULL; + struct in_addr addr = {}; + + assert_se(ll); + + if (sd_ipv4ll_get_address(ll, &addr) >= 0) + assert_se(in_addr_to_string(AF_INET, (const union in_addr_union*) &addr, &address) >= 0); + + switch (event) { + case IPV4LL_EVENT_BIND: + log_info("bound %s", strna(address)); + break; + case IPV4LL_EVENT_CONFLICT: + log_info("conflict on %s", strna(address)); + break; + case IPV4LL_EVENT_STOP: + log_error("the client was stopped with address %s", strna(address)); + break; + default: + assert_not_reached("invalid LL event"); + } +} + +static int client_run(int ifindex, const char *seed_str, const struct ether_addr *ha, sd_event *e) { + sd_ipv4ll *ll; + + assert_se(sd_ipv4ll_new(&ll) >= 0); + assert_se(sd_ipv4ll_attach_event(ll, e, 0) >= 0); + + assert_se(sd_ipv4ll_set_index(ll, ifindex) >= 0); + assert_se(sd_ipv4ll_set_mac(ll, ha) >= 0); + assert_se(sd_ipv4ll_set_callback(ll, ll_handler, NULL) >= 0); + + if (seed_str) { + unsigned seed; + + assert_se(safe_atou(seed_str, &seed) >= 0); + + assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0); + } + + log_info("starting IPv4LL client"); + + assert_se(sd_ipv4ll_start(ll) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4ll_unref(ll)); + + return EXIT_SUCCESS; +} + +static int test_ll(const char *ifname, const char *seed) { + _cleanup_event_unref_ sd_event *e = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL, *reply = NULL; + struct ether_addr ha; + int ifindex; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, seed, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 2) + return test_ll(argv[1], NULL); + else if (argc == 3) + return test_ll(argv[1], argv[2]); + else { + log_error("This program takes one or two arguments.\n" + "\t %s []", program_invocation_short_name); + return EXIT_FAILURE; + } +} -- cgit v1.2.3-54-g00ecf