diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /shared/n-acd | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'shared/n-acd')
| -rw-r--r-- | shared/n-acd/src/n-acd-bpf-fallback.c | 30 | ||||
| -rw-r--r-- | shared/n-acd/src/n-acd-bpf.c | 317 | ||||
| -rw-r--r-- | shared/n-acd/src/n-acd-private.h | 154 | ||||
| -rw-r--r-- | shared/n-acd/src/n-acd-probe.c | 712 | ||||
| -rw-r--r-- | shared/n-acd/src/n-acd.c | 1027 | ||||
| -rw-r--r-- | shared/n-acd/src/n-acd.h | 150 | ||||
| -rw-r--r-- | shared/n-acd/src/util/timer.c | 189 | ||||
| -rw-r--r-- | shared/n-acd/src/util/timer.h | 54 |
8 files changed, 0 insertions, 2633 deletions
diff --git a/shared/n-acd/src/n-acd-bpf-fallback.c b/shared/n-acd/src/n-acd-bpf-fallback.c deleted file mode 100644 index 3cf4eb06..00000000 --- a/shared/n-acd/src/n-acd-bpf-fallback.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * A noop implementation of eBPF filter for IPv4 Address Conflict Detection - * - * These are a collection of dummy functions that have no effect, but allows - * n-acd to compile without eBPF support. - * - * See n-acd-bpf.c for documentation. - */ - -#include <c-stdaux.h> -#include <stddef.h> -#include "n-acd-private.h" - -int n_acd_bpf_map_create(int *mapfdp, size_t max_entries) { - *mapfdp = -1; - return 0; -} - -int n_acd_bpf_map_add(int mapfd, struct in_addr *addrp) { - return 0; -} - -int n_acd_bpf_map_remove(int mapfd, struct in_addr *addrp) { - return 0; -} - -int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *macp) { - *progfdp = -1; - return 0; -} diff --git a/shared/n-acd/src/n-acd-bpf.c b/shared/n-acd/src/n-acd-bpf.c deleted file mode 100644 index 57b29ddf..00000000 --- a/shared/n-acd/src/n-acd-bpf.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * eBPF filter for IPv4 Address Conflict Detection - * - * An eBPF map and an eBPF program are provided. The map contains all the - * addresses address conflict detection is performed on, and the program - * filters out all packets except exactly the packets relevant to the ACD - * protocol on the addresses currently in the map. - * - * Note that userspace still has to filter the incoming packets, as filter - * are applied when packets are queued on the socket, not when userspace - * receives them. It is therefore possible to receive packets about addresses - * that have already been removed. - */ - -#include <c-stdaux.h> -#include <errno.h> -#include <inttypes.h> -#include <linux/bpf.h> -#include <netinet/if_ether.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <string.h> -#include <sys/resource.h> -#include <sys/syscall.h> -#include <unistd.h> -#include "n-acd-private.h" - -#define BPF_LD_ABS(SIZE, IMM) \ - ((struct bpf_insn) { \ - .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \ - .dst_reg = 0, \ - .src_reg = 0, \ - .off = 0, \ - .imm = IMM, \ - }) - -#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \ - ((struct bpf_insn) { \ - .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \ - .dst_reg = DST, \ - .src_reg = SRC, \ - .off = OFF, \ - .imm = 0, \ - }) - -#define BPF_LD_MAP_FD(DST, MAP_FD) \ - ((struct bpf_insn) { \ - .code = BPF_LD | BPF_DW | BPF_IMM, \ - .dst_reg = DST, \ - .src_reg = BPF_PSEUDO_MAP_FD, \ - .off = 0, \ - .imm = (__u32) (MAP_FD), \ - }), \ - ((struct bpf_insn) { \ - .code = 0, /* zero is reserved opcode */ \ - .dst_reg = 0, \ - .src_reg = 0, \ - .off = 0, \ - .imm = ((__u64) (MAP_FD)) >> 32, \ - }) - -#define BPF_ALU_REG(OP, DST, SRC) \ - ((struct bpf_insn) { \ - .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \ - .dst_reg = DST, \ - .src_reg = SRC, \ - .off = 0, \ - .imm = 0, \ - }) - -#define BPF_ALU_IMM(OP, DST, IMM) \ - ((struct bpf_insn) { \ - .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \ - .dst_reg = DST, \ - .src_reg = 0, \ - .off = 0, \ - .imm = IMM, \ - }) - -#define BPF_MOV_REG(DST, SRC) \ - ((struct bpf_insn) { \ - .code = BPF_ALU64 | BPF_MOV | BPF_X, \ - .dst_reg = DST, \ - .src_reg = SRC, \ - .off = 0, \ - .imm = 0, \ - }) - -#define BPF_MOV_IMM(DST, IMM) \ - ((struct bpf_insn) { \ - .code = BPF_ALU64 | BPF_MOV | BPF_K, \ - .dst_reg = DST, \ - .src_reg = 0, \ - .off = 0, \ - .imm = IMM, \ - }) - -#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ - ((struct bpf_insn) { \ - .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \ - .dst_reg = DST, \ - .src_reg = SRC, \ - .off = OFF, \ - .imm = 0, \ - }) - -#define BPF_JMP_REG(OP, DST, SRC, OFF) \ - ((struct bpf_insn) { \ - .code = BPF_JMP | BPF_OP(OP) | BPF_X, \ - .dst_reg = DST, \ - .src_reg = SRC, \ - .off = OFF, \ - .imm = 0, \ - }) - -#define BPF_JMP_IMM(OP, DST, IMM, OFF) \ - ((struct bpf_insn) { \ - .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ - .dst_reg = DST, \ - .src_reg = 0, \ - .off = OFF, \ - .imm = IMM, \ - }) - -#define BPF_EMIT_CALL(FUNC) \ - ((struct bpf_insn) { \ - .code = BPF_JMP | BPF_CALL, \ - .dst_reg = 0, \ - .src_reg = 0, \ - .off = 0, \ - .imm = FUNC, \ - }) - -#define BPF_EXIT_INSN() \ - ((struct bpf_insn) { \ - .code = BPF_JMP | BPF_EXIT, \ - .dst_reg = 0, \ - .src_reg = 0, \ - .off = 0, \ - .imm = 0, \ - }) - -static int n_acd_syscall_bpf(int cmd, union bpf_attr *attr, unsigned int size) { - return (int)syscall(__NR_bpf, cmd, attr, size); -} - -int n_acd_bpf_map_create(int *mapfdp, size_t max_entries) { - union bpf_attr attr; - int mapfd; - - memset(&attr, 0, sizeof(attr)); - attr = (union bpf_attr){ - .map_type = BPF_MAP_TYPE_HASH, - .key_size = sizeof(uint32_t), - .value_size = sizeof(uint8_t), /* values are never used, but must be set */ - .max_entries = max_entries, - }; - - mapfd = n_acd_syscall_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); - if (mapfd < 0) - return -errno; - - *mapfdp = mapfd; - return 0; -} - -int n_acd_bpf_map_add(int mapfd, struct in_addr *addrp) { - union bpf_attr attr; - uint32_t addr = be32toh(addrp->s_addr); - uint8_t _dummy = 0; - int r; - - memset(&attr, 0, sizeof(attr)); - attr = (union bpf_attr){ - .map_fd = mapfd, - .key = (uint64_t)(unsigned long)&addr, - .value = (uint64_t)(unsigned long)&_dummy, - .flags = BPF_NOEXIST, - }; - - r = n_acd_syscall_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int n_acd_bpf_map_remove(int mapfd, struct in_addr *addrp) { - uint32_t addr = be32toh(addrp->s_addr); - union bpf_attr attr; - int r; - - memset(&attr, 0, sizeof(attr)); - attr = (union bpf_attr){ - .map_fd = mapfd, - .key = (uint64_t)(unsigned long)&addr, - }; - - r = n_acd_syscall_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *macp) { - const union { - uint8_t u8[6]; - uint16_t u16[3]; - uint32_t u32[1]; - } mac = { - .u8 = { - macp->ether_addr_octet[0], - macp->ether_addr_octet[1], - macp->ether_addr_octet[2], - macp->ether_addr_octet[3], - macp->ether_addr_octet[4], - macp->ether_addr_octet[5], - }, - }; - struct bpf_insn prog[] = { - /* for using BPF_LD_ABS r6 must point to the skb, currently in r1 */ - BPF_MOV_REG(6, 1), /* r6 = r1 */ - - /* drop the packet if it is too short */ - BPF_LDX_MEM(BPF_W, 0, 6, offsetof(struct __sk_buff, len)), /* r0 = skb->len */ - BPF_JMP_IMM(BPF_JGE, 0, sizeof(struct ether_arp), 2), /* if (r0 >= sizeof(ether_arp)) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - /* drop the packet if the header is not as expected */ - BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_hrd)), /* r0 = header type */ - BPF_JMP_IMM(BPF_JEQ, 0, ARPHRD_ETHER, 2), /* if (r0 == ethernet) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_pro)), /* r0 = protocol */ - BPF_JMP_IMM(BPF_JEQ, 0, ETHERTYPE_IP, 2), /* if (r0 == IP) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - BPF_LD_ABS(BPF_B, offsetof(struct ether_arp, arp_hln)), /* r0 = hw addr length */ - BPF_JMP_IMM(BPF_JEQ, 0, sizeof(struct ether_addr), 2), /* if (r0 == sizeof(ether_addr)) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - BPF_LD_ABS(BPF_B, offsetof(struct ether_arp, arp_pln)), /* r0 = protocol addr length */ - BPF_JMP_IMM(BPF_JEQ, 0, sizeof(struct in_addr), 2), /* if (r0 == sizeof(in_addr)) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - /* drop packets from our own mac address */ - BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_sha)), /* r0 = first four bytes of packet mac address */ - BPF_JMP_IMM(BPF_JNE, 0, be32toh(mac.u32[0]), 4), /* if (r0 != first four bytes of our mac address) skip 4 */ - BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_sha) + 4), /* r0 = last two bytes of packet mac address */ - BPF_JMP_IMM(BPF_JNE, 0, be16toh(mac.u16[2]), 2), /* if (r0 != last two bytes of our mac address) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - /* - * We listen for two kinds of packets: - * Conflicts) - * These are requests or replies with the sender address not set to INADDR_ANY. The - * conflicted address is the sender address, remember this in r7. - * Probes) - * These are requests with the sender address set to INADDR_ANY. The probed address - * is the target address, remember this in r7. - * Any other packets are dropped. - */ - BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_spa)), /* r0 = sender ip address */ - BPF_JMP_IMM(BPF_JEQ, 0, 0, 7), /* if (r0 == 0) skip 7 */ - BPF_MOV_REG(7, 0), /* r7 = r0 */ - BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_op)), /* r0 = operation */ - BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REQUEST, 3), /* if (r0 == request) skip 3 */ - BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REPLY, 2), /* if (r0 == reply) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - BPF_JMP_IMM(BPF_JA, 0, 0, 6), /* skip 6 */ - BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_tpa)), /* r0 = target ip address */ - BPF_MOV_REG(7, 0), /* r7 = r0 */ - BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_op)), /* r0 = operation */ - BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REQUEST, 2), /* if (r0 == request) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - /* check if the probe or conflict is for an address we are monitoring */ - BPF_STX_MEM(BPF_W, 10, 7, -4), /* *(uint32_t*)fp - 4 = r7 */ - BPF_MOV_REG(2, 10), /* r2 = fp */ - BPF_ALU_IMM(BPF_ADD, 2, -4), /* r2 -= 4 */ - BPF_LD_MAP_FD(1, mapfd), /* r1 = mapfd */ - BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), /* r0 = map_lookup_elem(r1, r2) */ - BPF_JMP_IMM(BPF_JNE, 0, 0, 2), /* if (r0 != NULL) skip 2 */ - BPF_MOV_IMM(0, 0), /* r0 = 0 */ - BPF_EXIT_INSN(), /* return */ - - /* return exactly the packet length*/ - BPF_MOV_IMM(0, sizeof(struct ether_arp)), /* r0 = sizeof(struct ether_arp) */ - BPF_EXIT_INSN(), /* return */ - }; - union bpf_attr attr; - int progfd; - - memset(&attr, 0, sizeof(attr)); - attr = (union bpf_attr){ - .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, - .insns = (uint64_t)(unsigned long)prog, - .insn_cnt = sizeof(prog) / sizeof(*prog), - .license = (uint64_t)(unsigned long)"ASL", - }; - - progfd = n_acd_syscall_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); - if (progfd < 0) - return -errno; - - *progfdp = progfd; - return 0; -} diff --git a/shared/n-acd/src/n-acd-private.h b/shared/n-acd/src/n-acd-private.h deleted file mode 100644 index 4583c018..00000000 --- a/shared/n-acd/src/n-acd-private.h +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -#include <c-list.h> -#include <c-rbtree.h> -#include <c-stdaux.h> -#include <errno.h> -#include <inttypes.h> -#include <netinet/if_ether.h> -#include <netinet/in.h> -#include <stdbool.h> -#include <stdlib.h> -#include "util/timer.h" -#include "n-acd.h" - -typedef struct NAcdEventNode NAcdEventNode; - -/* This augments the error-codes with internal ones that are never exposed. */ -enum { - _N_ACD_INTERNAL = _N_ACD_E_N, - - N_ACD_E_DROPPED, -}; - -enum { - N_ACD_PROBE_STATE_PROBING, - N_ACD_PROBE_STATE_CONFIGURING, - N_ACD_PROBE_STATE_ANNOUNCING, - N_ACD_PROBE_STATE_FAILED, -}; - -struct NAcdConfig { - int ifindex; - unsigned int transport; - uint8_t mac[ETH_ALEN]; - size_t n_mac; -}; - -#define N_ACD_CONFIG_NULL(_x) { \ - .transport = _N_ACD_TRANSPORT_N, \ - } - -struct NAcdProbeConfig { - struct in_addr ip; - uint64_t timeout_msecs; -}; - -#define N_ACD_PROBE_CONFIG_NULL(_x) { \ - .timeout_msecs = N_ACD_TIMEOUT_RFC5227, \ - } - -struct NAcdEventNode { - CList acd_link; - CList probe_link; - NAcdEvent event; - uint8_t sender[ETH_ALEN]; - bool is_public : 1; -}; - -#define N_ACD_EVENT_NODE_NULL(_x) { \ - .acd_link = C_LIST_INIT((_x).acd_link), \ - .probe_link = C_LIST_INIT((_x).probe_link), \ - } - -struct NAcd { - unsigned long n_refs; - unsigned int seed; - int fd_epoll; - int fd_socket; - CRBTree ip_tree; - CList event_list; - Timer timer; - - /* BPF map */ - int fd_bpf_map; - size_t n_bpf_map; - size_t max_bpf_map; - - /* configuration */ - int ifindex; - uint8_t mac[ETH_ALEN]; - - /* flags */ - bool preempted : 1; -}; - -#define N_ACD_NULL(_x) { \ - .n_refs = 1, \ - .fd_epoll = -1, \ - .fd_socket = -1, \ - .ip_tree = C_RBTREE_INIT, \ - .event_list = C_LIST_INIT((_x).event_list), \ - .timer = TIMER_NULL((_x).timer), \ - .fd_bpf_map = -1, \ - } - -struct NAcdProbe { - NAcd *acd; - CRBNode ip_node; - CList event_list; - Timeout timeout; - - /* configuration */ - struct in_addr ip; - uint64_t timeout_multiplier; - void *userdata; - - /* state */ - unsigned int state; - unsigned int n_iteration; - unsigned int defend; - uint64_t last_defend; -}; - -#define N_ACD_PROBE_NULL(_x) { \ - .ip_node = C_RBNODE_INIT((_x).ip_node), \ - .event_list = C_LIST_INIT((_x).event_list), \ - .timeout = TIMEOUT_INIT((_x).timeout), \ - .state = N_ACD_PROBE_STATE_PROBING, \ - .defend = N_ACD_DEFEND_NEVER, \ - } - -/* events */ - -int n_acd_event_node_new(NAcdEventNode **nodep); -NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node); - -/* contexts */ - -void n_acd_remember(NAcd *acd, uint64_t now, bool success); -int n_acd_raise(NAcd *acd, NAcdEventNode **nodep, unsigned int event); -int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa); -int n_acd_ensure_bpf_map_space(NAcd *acd); - -/* probes */ - -int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config); -int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int event); -int n_acd_probe_handle_timeout(NAcdProbe *probe); -int n_acd_probe_handle_packet(NAcdProbe *probe, struct ether_arp *packet, bool hard_conflict); - -/* eBPF */ - -int n_acd_bpf_map_create(int *mapfdp, size_t max_elements); -int n_acd_bpf_map_add(int mapfd, struct in_addr *addr); -int n_acd_bpf_map_remove(int mapfd, struct in_addr *addr); - -int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *mac); - -/* inline helpers */ - -static inline void n_acd_event_node_freep(NAcdEventNode **node) { - if (*node) - n_acd_event_node_free(*node); -} diff --git a/shared/n-acd/src/n-acd-probe.c b/shared/n-acd/src/n-acd-probe.c deleted file mode 100644 index 2a5c6451..00000000 --- a/shared/n-acd/src/n-acd-probe.c +++ /dev/null @@ -1,712 +0,0 @@ -/* - * IPv4 Address Conflict Detection - * - * This file implements the probe object. A probe is basically the - * state-machine of a single ACD run. It takes an address to probe for, checks - * for conflicts and then defends it once configured. - */ - -#include <assert.h> -#include <c-rbtree.h> -#include <c-stdaux.h> -#include <endian.h> -#include <errno.h> -#include <inttypes.h> -#include <limits.h> -#include <netinet/if_ether.h> -#include <netinet/in.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <unistd.h> -#include "n-acd.h" -#include "n-acd-private.h" - -/* - * These parameters and timing intervals are specified in RFC-5227. The - * original values are: - * - * PROBE_NUM 3 - * PROBE_WAIT 1s - * PROBE_MIN 1s - * PROBE_MAX 3s - * ANNOUNCE_NUM 3 - * ANNOUNCE_WAIT 2s - * ANNOUNCE_INTERVAL 2s - * MAX_CONFLICTS 10 - * RATE_LIMIT_INTERVAL 60s - * DEFEND_INTERVAL 10s - * - * If we assume a best-case and worst-case scenario for non-conflicted runs, we - * end up with a runtime between 4s and 9s to finish the probe. Then it still - * takes a fixed 4s to finish the announcements. - * - * RFC 5227 section 1.1: - * [...] (Note that the values listed here are fixed constants; they are - * not intended to be modifiable by implementers, operators, or end users. - * These constants are given symbolic names here to facilitate the writing - * of future standards that may want to reference this document with - * different values for these named constants; however, at the present time - * no such future standards exist.) [...] - * - * Unfortunately, no-one ever stepped up to write a "future standard" to revise - * the timings. A 9s timeout for successful link setups is not acceptable today. - * Hence, we will just go forward and ignore the proposed values. On both - * wired and wireless local links round-trip latencies of below 3ms are common. - * We require the caller to set a timeout multiplier, where 1 corresponds to a - * total probe time between 0.5 ms and 1.0 ms. On modern networks a multiplier - * of about 100 should be a reasonable default. To comply with the RFC select a - * multiplier of 9000. - */ -#define N_ACD_RFC_PROBE_NUM (3) -#define N_ACD_RFC_PROBE_WAIT_NSEC (UINT64_C(111111)) /* 1/9 ms */ -#define N_ACD_RFC_PROBE_MIN_NSEC (UINT64_C(111111)) /* 1/9 ms */ -#define N_ACD_RFC_PROBE_MAX_NSEC (UINT64_C(333333)) /* 3/9 ms */ -#define N_ACD_RFC_ANNOUNCE_NUM (3) -#define N_ACD_RFC_ANNOUNCE_WAIT_NSEC (UINT64_C(222222)) /* 2/9 ms */ -#define N_ACD_RFC_ANNOUNCE_INTERVAL_NSEC (UINT64_C(222222)) /* 2/9 ms */ -#define N_ACD_RFC_MAX_CONFLICTS (10) -#define N_ACD_RFC_RATE_LIMIT_INTERVAL_NSEC (UINT64_C(60000000000)) /* 60s */ -#define N_ACD_RFC_DEFEND_INTERVAL_NSEC (UINT64_C(10000000000)) /* 10s */ - -/** - * n_acd_probe_config_new() - create probe configuration - * @configp: output argument for new probe configuration - * - * This creates a new probe configuration. It will be returned in @configp to - * the caller, which upon return fully owns the object. - * - * A probe configuration collects parameters for probes. It never validates the - * input, but this is left to the consumer of the configuration to do. - * - * Return: 0 on success, negative error code on failure. - */ -_c_public_ int n_acd_probe_config_new(NAcdProbeConfig **configp) { - _c_cleanup_(n_acd_probe_config_freep) NAcdProbeConfig *config = NULL; - - config = malloc(sizeof(*config)); - if (!config) - return -ENOMEM; - - *config = (NAcdProbeConfig)N_ACD_PROBE_CONFIG_NULL(*config); - - *configp = config; - config = NULL; - return 0; -} - -/** - * n_acd_probe_config_free() - destroy probe configuration - * @config: configuration to operate on, or NULL - * - * This destroys the probe configuration and all associated objects. If @config - * is NULL, this is a no-op. - * - * Return: NULL is returned. - */ -_c_public_ NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config) { - if (!config) - return NULL; - - free(config); - - return NULL; -} - -/** - * n_acd_probe_config_set_ip() - set ip property - * @config: configuration to operate on - * @ip: ip to set - * - * This sets the IP property to the value `ip`. The address is copied into the - * configuration object. No validation is performed. - * - * The IP property selects the IP address that a probe checks for. It is the - * caller's responsibility to guarantee the address is valid and can be used. - */ -_c_public_ void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip) { - config->ip = ip; -} - -/** - * n_acd_probe_config_set_timeout() - set timeout property - * @config: configuration to operate on - * @msecs: timeout to set, in milliseconds - * - * This sets the timeout to use for a conflict detection probe. The - * specification default is provided as `N_ACD_TIMEOUT_RFC5227` and corresponds - * to 9 seconds. - * - * If set to 0, conflict detection is skipped and the address is immediately - * advertised and defended. - * - * Depending on the transport used, the API user should select a suitable - * timeout. Since `ACD` only operates on the link layer, timeouts in the - * hundreds of milliseconds range should be more than enough for any modern - * network. Note that increasing this value directly affects the time it takes - * to connect to a network, since an address should not be used unless conflict - * detection finishes. - * - * Using the specification default is **discouraged**. It is way too slow and - * not appropriate for modern networks. - * - * Default value is `N_ACD_TIMEOUT_RFC5227`. - */ -_c_public_ void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs) { - config->timeout_msecs = msecs; -} - -static void n_acd_probe_schedule(NAcdProbe *probe, uint64_t n_timeout, unsigned int n_jitter) { - uint64_t n_time; - - timer_now(&probe->acd->timer, &n_time); - n_time += n_timeout; - - /* - * ACD specifies jitter values to reduce packet storms on the local - * link. This call accepts the maximum relative jitter value in - * nanoseconds as @n_jitter. We then use rand_r(3p) to get a - * pseudo-random jitter on top of the real timeout given as @n_timeout. - */ - if (n_jitter) { - uint64_t random; - - random = ((uint64_t)rand_r(&probe->acd->seed) << 32) | (uint64_t)rand_r(&probe->acd->seed); - n_time += random % n_jitter; - } - - timeout_schedule(&probe->timeout, &probe->acd->timer, n_time); -} - -static void n_acd_probe_unschedule(NAcdProbe *probe) { - timeout_unschedule(&probe->timeout); -} - -static bool n_acd_probe_is_unique(NAcdProbe *probe) { - NAcdProbe *sibling; - - if (!c_rbnode_is_linked(&probe->ip_node)) - return false; - - sibling = c_rbnode_entry(c_rbnode_next(&probe->ip_node), NAcdProbe, ip_node); - if (sibling && sibling->ip.s_addr == probe->ip.s_addr) - return false; - - sibling = c_rbnode_entry(c_rbnode_prev(&probe->ip_node), NAcdProbe, ip_node); - if (sibling && sibling->ip.s_addr == probe->ip.s_addr) - return false; - - return true; -} - -static int n_acd_probe_link(NAcdProbe *probe) { - int r; - - /* - * Make sure the kernel bpf map has space for at least one more - * entry. - */ - r = n_acd_ensure_bpf_map_space(probe->acd); - if (r) - return r; - - /* - * Link entry into context, indexed by its IP. Note that we allow - * duplicates just fine. It is up to you to decide whether to avoid - * duplicates, if you don't want them. Duplicates on the same context - * do not conflict with each other, though. - */ - { - CRBNode **slot, *parent; - NAcdProbe *other; - - slot = &probe->acd->ip_tree.root; - parent = NULL; - while (*slot) { - other = c_rbnode_entry(*slot, NAcdProbe, ip_node); - parent = *slot; - if (probe->ip.s_addr < other->ip.s_addr) - slot = &(*slot)->left; - else - slot = &(*slot)->right; - } - - c_rbtree_add(&probe->acd->ip_tree, parent, slot, &probe->ip_node); - } - - /* - * Add the ip address to the map, if it is not already there. - */ - if (n_acd_probe_is_unique(probe)) { - r = n_acd_bpf_map_add(probe->acd->fd_bpf_map, &probe->ip); - if (r) { - /* - * Make sure the IP address is linked in userspace iff - * it is linked in the kernel. - */ - c_rbnode_unlink(&probe->ip_node); - return r; - } - ++probe->acd->n_bpf_map; - } - - return 0; -} - -static void n_acd_probe_unlink(NAcdProbe *probe) { - int r; - - /* - * If this is the only probe for a given IP, remove the IP from the - * kernel BPF map. - */ - if (n_acd_probe_is_unique(probe)) { - r = n_acd_bpf_map_remove(probe->acd->fd_bpf_map, &probe->ip); - c_assert(r >= 0); - --probe->acd->n_bpf_map; - } - c_rbnode_unlink(&probe->ip_node); -} - -int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config) { - _c_cleanup_(n_acd_probe_freep) NAcdProbe *probe = NULL; - int r; - - if (!config->ip.s_addr) - return N_ACD_E_INVALID_ARGUMENT; - - probe = malloc(sizeof(*probe)); - if (!probe) - return -ENOMEM; - - *probe = (NAcdProbe)N_ACD_PROBE_NULL(*probe); - probe->acd = n_acd_ref(acd); - probe->ip = config->ip; - - /* - * We use the provided timeout-length as multiplier for all our - * timeouts. The provided timeout defines the maximum length of an - * entire probe-interval until the first announcement. Given the - * spec-provided parameters, this ends up as: - * - * PROBE_WAIT + PROBE_MAX + PROBE_MAX + ANNOUNCE_WAIT - * = 1s + 3s + 3s + 2s - * = 9s - * - * Hence, the default value for this timeout is 9000ms, which just - * ends up matching the spec-provided values. - * - * What we now semantically do is divide this timeout by 1ns/1000000. - * This first turns it into nanoseconds, then strips the unit by - * turning it into a multiplier. However, rather than performing the - * division here, we multiplier all our timeouts by 1000000 statically - * at compile time. Therefore, we can use the user-provided timeout as - * unmodified multiplier. No conversion necessary. - */ - probe->timeout_multiplier = config->timeout_msecs; - - r = n_acd_probe_link(probe); - if (r) - return r; - - /* - * Now that everything is set up, we have to send the first probe. This - * is done after ~PROBE_WAIT seconds, hence we schedule our timer. - * In case no timeout-multiplier is set, we pretend we already sent all - * probes successfully and schedule the timer so we proceed with the - * announcements. We must schedule a fake timer there, since we are not - * allowed to advance the state machine outside of n_acd_dispatch(). - */ - if (probe->timeout_multiplier) { - probe->n_iteration = 0; - n_acd_probe_schedule(probe, - 0, - probe->timeout_multiplier * N_ACD_RFC_PROBE_WAIT_NSEC); - } else { - probe->n_iteration = N_ACD_RFC_PROBE_NUM; - n_acd_probe_schedule(probe, 0, 0); - } - - *probep = probe; - probe = NULL; - return 0; -} - -/** - * n_acd_probe_free() - destroy a probe - * @probe: probe to operate on, or NULL - * - * This destroys the probe specified by @probe. All operations are immediately - * ceded and all associated objects are released. - * - * If @probe is NULL, this is a no-op. - * - * This function will flush all events associated with @probe from the event - * queue. That is, no events will be returned for this @probe anymore. - * - * Return: NULL is returned. - */ -_c_public_ NAcdProbe *n_acd_probe_free(NAcdProbe *probe) { - NAcdEventNode *node, *t_node; - - if (!probe) - return NULL; - - c_list_for_each_entry_safe(node, t_node, &probe->event_list, probe_link) - n_acd_event_node_free(node); - - n_acd_probe_unschedule(probe); - n_acd_probe_unlink(probe); - probe->acd = n_acd_unref(probe->acd); - free(probe); - - return NULL; -} - -int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int event) { - _c_cleanup_(n_acd_event_node_freep) NAcdEventNode *node = NULL; - int r; - - r = n_acd_raise(probe->acd, &node, event); - if (r) - return r; - - switch (event) { - case N_ACD_EVENT_READY: - node->event.ready.probe = probe; - break; - case N_ACD_EVENT_USED: - node->event.used.probe = probe; - break; - case N_ACD_EVENT_DEFENDED: - node->event.defended.probe = probe; - break; - case N_ACD_EVENT_CONFLICT: - node->event.conflict.probe = probe; - break; - default: - c_assert(0); - return -ENOTRECOVERABLE; - } - - c_list_link_tail(&probe->event_list, &node->probe_link); - - if (nodep) - *nodep = node; - node = NULL; - return 0; -} - -int n_acd_probe_handle_timeout(NAcdProbe *probe) { - int r; - - switch (probe->state) { - case N_ACD_PROBE_STATE_PROBING: - /* - * We are still PROBING. We send 3 probes with a random timeout - * scheduled between each. If, after a fixed timeout, we did - * not receive any conflict we consider the probing successful. - */ - if (probe->n_iteration < N_ACD_RFC_PROBE_NUM) { - /* - * We have not sent all 3 probes, yet. A timer fired, - * so we are ready to send the next probe. If this is - * the third probe, schedule a timer for ANNOUNCE_WAIT - * to give other peers a chance to answer. If this is - * not the third probe, wait between PROBE_MIN and - * PROBE_MAX for the next probe. - */ - - r = n_acd_send(probe->acd, &probe->ip, NULL); - if (r) { - if (r != -N_ACD_E_DROPPED) - return r; - - /* - * Packet was dropped, and we know about it. It - * never reached the network. Reasons are - * manifold, and n_acd_send() raises events if - * necessary. - * From a probe-perspective, we simply pretend - * we never sent the probe and schedule a - * timeout for the next probe, effectively - * doubling a single probe-interval. - */ - } else { - /* Successfully sent, so advance counter. */ - ++probe->n_iteration; - } - - if (probe->n_iteration < N_ACD_RFC_PROBE_NUM) - n_acd_probe_schedule(probe, - probe->timeout_multiplier * N_ACD_RFC_PROBE_MIN_NSEC, - probe->timeout_multiplier * (N_ACD_RFC_PROBE_MAX_NSEC - N_ACD_RFC_PROBE_MIN_NSEC)); - else - n_acd_probe_schedule(probe, - probe->timeout_multiplier * N_ACD_RFC_ANNOUNCE_WAIT_NSEC, - 0); - } else { - /* - * All 3 probes succeeded and we waited enough to - * consider this address usable by now. Do not announce - * the address, yet. We must first give the caller a - * chance to configure the address (so they can answer - * ARP requests), before announcing it. - */ - r = n_acd_probe_raise(probe, NULL, N_ACD_EVENT_READY); - if (r) - return r; - - probe->state = N_ACD_PROBE_STATE_CONFIGURING; - } - - break; - - case N_ACD_PROBE_STATE_ANNOUNCING: - /* - * We are ANNOUNCING, meaning the caller configured the address - * on the interface and is actively using it. We send 3 - * announcements out, in a short interval, and then just - * perform passive conflict detection. - * Note that once all 3 announcements are sent, we no longer - * schedule a timer, so this part should not trigger, anymore. - */ - - r = n_acd_send(probe->acd, &probe->ip, &probe->ip); - if (r) { - if (r != -N_ACD_E_DROPPED) - return r; - - /* - * See above in STATE_PROBING for details. We know the - * packet was never sent, so we simply try again after - * extending the timer. - */ - } else { - /* Successfully sent, so advance counter. */ - ++probe->n_iteration; - } - - if (probe->n_iteration < N_ACD_RFC_ANNOUNCE_NUM) { - /* - * Announcements are always scheduled according to the - * time-intervals specified in the spec. We always use - * the RFC5227-mandated multiplier. - * If you reconsider this, note that timeout_multiplier - * might be 0 here. - */ - n_acd_probe_schedule(probe, - N_ACD_TIMEOUT_RFC5227 * N_ACD_RFC_ANNOUNCE_INTERVAL_NSEC, - 0); - } - - break; - - case N_ACD_PROBE_STATE_CONFIGURING: - case N_ACD_PROBE_STATE_FAILED: - default: - /* - * There are no timeouts in these states. If we trigger one, - * something is fishy. - */ - c_assert(0); - return -ENOTRECOVERABLE; - } - - return 0; -} - -int n_acd_probe_handle_packet(NAcdProbe *probe, struct ether_arp *packet, bool hard_conflict) { - NAcdEventNode *node; - uint64_t now; - int r; - - timer_now(&probe->acd->timer, &now); - - switch (probe->state) { - case N_ACD_PROBE_STATE_PROBING: - /* - * Regardless whether this is a hard or soft conflict, we must - * treat this as a probe failure. That is, notify the caller of - * the conflict and wait for further instructions. We do not - * react to this, until the caller tells us what to do, but we - * do stop sending further probes. - */ - r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_USED); - if (r) - return r; - - node->event.used.sender = node->sender; - node->event.used.n_sender = ETH_ALEN; - memcpy(node->sender, packet->arp_sha, ETH_ALEN); - - n_acd_probe_unschedule(probe); - n_acd_probe_unlink(probe); - probe->state = N_ACD_PROBE_STATE_FAILED; - - break; - - case N_ACD_PROBE_STATE_CONFIGURING: - /* - * We are waiting for the caller to configure the interface and - * start ANNOUNCING. In this state, we cannot defend the - * address as that would indicate that it is ready to be used, - * and we cannot signal CONFLICT or USED as the caller may - * already have started to use the address (and may have - * configured the engine to always defend it, which means they - * should be able to rely on never losing it after READY). - * Simply drop the event, and rely on the anticipated ANNOUNCE - * to trigger it again. - */ - - break; - - case N_ACD_PROBE_STATE_ANNOUNCING: { - /* - * We were already instructed to announce the address, which - * means the address is configured and in use. Hence, the - * caller is responsible to serve regular ARP queries. Meaning, - * we can ignore any soft conflicts (other peers doing ACD). - * - * But if we see a hard-conflict, we either defend the address - * according to the caller's instructions, or we report the - * conflict and bail out. - */ - bool conflict = false, rate_limited = false; - - if (!hard_conflict) - break; - - rate_limited = now < probe->last_defend + N_ACD_RFC_DEFEND_INTERVAL_NSEC; - - switch (probe->defend) { - case N_ACD_DEFEND_NEVER: - conflict = true; - break; - case N_ACD_DEFEND_ONCE: - if (rate_limited) { - conflict = true; - break; - } - - /* fallthrough */ - case N_ACD_DEFEND_ALWAYS: - if (!rate_limited) { - r = n_acd_send(probe->acd, &probe->ip, &probe->ip); - if (r) { - if (r != -N_ACD_E_DROPPED) - return r; - - if (probe->defend == N_ACD_DEFEND_ONCE) { - conflict = true; - break; - } - } - - if (r != -N_ACD_E_DROPPED) - probe->last_defend = now; - } - - r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_DEFENDED); - if (r) - return r; - - node->event.defended.sender = node->sender; - node->event.defended.n_sender = ETH_ALEN; - memcpy(node->sender, packet->arp_sha, ETH_ALEN); - - break; - } - - if (conflict) { - r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_CONFLICT); - if (r) - return r; - - node->event.conflict.sender = node->sender; - node->event.conflict.n_sender = ETH_ALEN; - memcpy(node->sender, packet->arp_sha, ETH_ALEN); - - n_acd_probe_unschedule(probe); - n_acd_probe_unlink(probe); - probe->state = N_ACD_PROBE_STATE_FAILED; - } - - break; - } - - case N_ACD_PROBE_STATE_FAILED: - default: - /* - * We are not listening for packets in these states. If we receive one, - * something is fishy. - */ - c_assert(0); - return -ENOTRECOVERABLE; - } - - return 0; -} - -/** - * n_acd_probe_set_userdata - set userdata - * @probe: probe to operate on - * @userdata: userdata pointer - * - * This can be used to set a caller-controlled user-data pointer on @probe. The - * value of the pointer is never inspected or used by `n-acd` and is fully - * under control of the caller. - * - * The default value is NULL. - */ -_c_public_ void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata) { - probe->userdata = userdata; -} - -/** - * n_acd_probe_get_userdata - get userdata - * @probe: probe to operate on - * - * This queries the userdata pointer that was previously set through - * n_acd_probe_set_userdata(). - * - * The default value is NULL. - * - * Return: The stored userdata pointer is returned. - */ -_c_public_ void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap) { - *userdatap = probe->userdata; -} - -/** - * n_acd_probe_announce() - announce the configured IP address - * @probe: probe to operate on - * @defend: defense policy - * - * Announce the IP address on the local link, and start defending it according - * to the given policy, which mut be one of N_ACD_DEFEND_ONCE, - * N_ACD_DEFEND_NEVER, or N_ACD_DEFEND_ALWAYS. - * - * This must be called in response to an N_ACD_EVENT_READY event, and only - * after the given address has been configured on the given network interface. - * - * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defense policy - * is invalid, negative error code on failure. - */ -_c_public_ int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend) { - if (defend >= _N_ACD_DEFEND_N) - return N_ACD_E_INVALID_ARGUMENT; - - probe->state = N_ACD_PROBE_STATE_ANNOUNCING; - probe->defend = defend; - probe->n_iteration = 0; - - /* - * We must schedule a fake-timeout, since we are not allowed to - * advance the state-machine outside of n_acd_dispatch(). - */ - n_acd_probe_schedule(probe, 0, 0); - - return 0; -} diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c deleted file mode 100644 index c1d92865..00000000 --- a/shared/n-acd/src/n-acd.c +++ /dev/null @@ -1,1027 +0,0 @@ -/* - * IPv4 Address Conflict Detection - * - * This file contains the main context initialization and management functions, - * as well as a bunch of utilities used through the n-acd modules. - */ - -/** - * DOC: IPv4 Address Conflict Detection - * - * The `n-acd` project implements the IPv4 Address Conflict Detection protocol - * as defined in RFC-5227. The protocol originates in the IPv4 Link Local - * Address selection but was later on generalized and resulted in `ACD`. The - * idea is to use `ARP` to query a link for an address to see whether it - * already exists on the network, as well as defending an address that is in - * use on a network interface. Furthermore, `ACD` provides passive diagnostics - * for administrators, as it will detect address conflicts automatically, which - * then can be logged or shown to a user. - * - * The main context object of `n-acd` is the `NAcd` structure. It is a passive - * ref-counted context object which drives `ACD` probes running on it. A - * context is specific to a linux network device and transport. If multiple - * network devices are used, then separate `NAcd` contexts must be deployed. - * - * The `NAcdProbe` object drives a single `ACD` state-machine. A probe is - * created on an `NAcd` context by providing an address to probe for. The probe - * will then raise notifications whether the address conflict detection found - * something, or whether the address is ready to be used. Optionally, the probe - * will then enter into passive mode and defend the address as long as it is - * kept active. - * - * Note that the `n-acd` project only implements the networking protocol. It - * never queries or modifies network interfaces. It completely relies on the - * API user to react to notifications and update network interfaces - * respectively. `n-acd` uses an event-mechanism on every context object. All - * events raise by any probe or operation on a given context will queue all - * events on that context object. The event-queue can then be drained by the - * API user. All events are properly asynchronous and designed in a way that no - * synchronous reaction to any event is required. That is, the events are - * carefully designed to allow forwarding via IPC (or even networks) to a - * controller that handles them and specifies how to react. Furthermore, none - * of the function calls of `n-acd` require synchronous error handling. - * Instead, functions only ever return values on fatal errors. Everything else - * is queued as events, thus guaranteeing that synchronous handling of return - * values is not required. Exceptions are functions that do not affect internal - * state or do not have an associated context object. - */ - -#include <assert.h> -#include <c-list.h> -#include <c-rbtree.h> -#include <c-siphash.h> -#include <c-stdaux.h> -#include <endian.h> -#include <errno.h> -#include <inttypes.h> -#include <limits.h> -#include <linux/if_packet.h> -#include <netinet/if_ether.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <string.h> -#include <sys/auxv.h> -#include <sys/epoll.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> -#include "n-acd.h" -#include "n-acd-private.h" - -enum { - N_ACD_EPOLL_TIMER, - N_ACD_EPOLL_SOCKET, -}; - -static int n_acd_get_random(unsigned int *random) { - uint8_t hash_seed[] = { - 0x3a, 0x0c, 0xa6, 0xdd, 0x44, 0xef, 0x5f, 0x7a, - 0x5e, 0xd7, 0x25, 0x37, 0xbf, 0x4e, 0x80, 0xa1, - }; - CSipHash hash = C_SIPHASH_NULL; - struct timespec ts; - const uint8_t *p; - int r; - - /* - * We need random jitter for all timeouts when handling ARP probes. Use - * AT_RANDOM to get a seed for rand_r(3p), if available (should always - * be available on linux). See the time-out scheduler for details. - * Additionally, we include the current time in the seed. This avoids - * using the same jitter in case you run multiple ACD engines in the - * same process. Lastly, the seed is hashed with SipHash24 to avoid - * exposing the value of AT_RANDOM on the network. - */ - c_siphash_init(&hash, hash_seed); - - p = (const uint8_t *)getauxval(AT_RANDOM); - if (p) - c_siphash_append(&hash, p, 16); - - r = clock_gettime(CLOCK_MONOTONIC, &ts); - if (r < 0) - return -c_errno(); - - c_siphash_append(&hash, (const uint8_t *)&ts.tv_sec, sizeof(ts.tv_sec)); - c_siphash_append(&hash, (const uint8_t *)&ts.tv_nsec, sizeof(ts.tv_nsec)); - - *random = c_siphash_finalize(&hash); - return 0; -} - -static int n_acd_socket_new(int *fdp, int fd_bpf_prog, NAcdConfig *config) { - const struct sockaddr_ll address = { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_ARP), - .sll_ifindex = config->ifindex, - .sll_halen = ETH_ALEN, - .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - }; - int r, s = -1; - - s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) { - r = -c_errno(); - goto error; - } - - if (fd_bpf_prog >= 0) { - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_BPF, &fd_bpf_prog, sizeof(fd_bpf_prog)); - if (r < 0) { - r = -c_errno(); - goto error; - } - } - - r = bind(s, (struct sockaddr *)&address, sizeof(address)); - if (r < 0) { - r = -c_errno(); - goto error; - } - - *fdp = s; - s = -1; - return 0; - -error: - if (s >= 0) - close(s); - return r; -} - -/** - * n_acd_config_new() - create configuration object - * @configp: output argument for new configuration - * - * This creates a new configuration object and provides it to the caller. The - * object is fully owned by the caller upon function return. - * - * A configuration object is a passive structure that is used to collect - * information that is then passed to a constructor or other function. A - * configuration never validates the data, but it is up to the consumer of a - * configuration to do that. - * - * Return: 0 on success, negative error code on failure. - */ -_c_public_ int n_acd_config_new(NAcdConfig **configp) { - _c_cleanup_(n_acd_config_freep) NAcdConfig *config = NULL; - - config = malloc(sizeof(*config)); - if (!config) - return -ENOMEM; - - *config = (NAcdConfig)N_ACD_CONFIG_NULL(*config); - - *configp = config; - config = NULL; - return 0; -} - -/** - * n_acd_config_free() - destroy configuration object - * @config: configuration to operate on, or NULL - * - * This destroys the configuration object @config. If @config is NULL, this is - * a no-op. - * - * Return: NULL is returned. - */ -_c_public_ NAcdConfig *n_acd_config_free(NAcdConfig *config) { - if (!config) - return NULL; - - free(config); - - return NULL; -} - -/** - * n_acd_config_set_ifindex() - set ifindex property - * @config: configuration to operate on - * @ifindex: ifindex to set - * - * This sets the @ifindex property of the configuration object. Any previous - * value is overwritten. - * - * A valid ifindex is a 32bit integer greater than 0. Any other value is - * treated as unspecified. - * - * The ifindex corresponds to the interface index provided by the linux kernel. - * It specifies the network device to be used. - */ -_c_public_ void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex) { - config->ifindex = ifindex; -} - -/** - * n_acd_config_set_transport() - set transport property - * @config: configuration to operate on - * @transport: transport to set - * - * This specifies the transport to use. A transport must be one of the - * `N_ACD_TRANSPORT_*` identifiers. It selects which transport protocol `n-acd` - * will run on. - */ -_c_public_ void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport) { - config->transport = transport; -} - -/** - * n_acd_config_set_mac() - set mac property - * @config: configuration to operate on - * @mac: mac to set - * - * This specifies the hardware address (also referred to as `MAC Address`) to - * use. Any hardware address can be specified. It is the caller's - * responsibility to make sure the address can actually be used. - * - * The address in @mac is copied into @config. It does not have to be retained - * by the caller. - */ -_c_public_ void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac) { - /* - * We truncate the address at the maximum we support. We still remember - * the original length, so any consumer of this configuration can then - * complain about an unsupported address length. This allows us to - * avoid a memory allocation here and having to return `int`. - */ - config->n_mac = n_mac; - memcpy(config->mac, mac, n_mac > ETH_ALEN ? ETH_ALEN : n_mac); -} - -int n_acd_event_node_new(NAcdEventNode **nodep) { - NAcdEventNode *node; - - node = malloc(sizeof(*node)); - if (!node) - return -ENOMEM; - - *node = (NAcdEventNode)N_ACD_EVENT_NODE_NULL(*node); - - *nodep = node; - return 0; -} - -NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) { - if (!node) - return NULL; - - c_list_unlink(&node->probe_link); - c_list_unlink(&node->acd_link); - free(node); - - return NULL; -} - -int n_acd_ensure_bpf_map_space(NAcd *acd) { - NAcdProbe *probe; - _c_cleanup_(c_closep) int fd_map = -1, fd_prog = -1; - size_t max_map; - int r; - - if (acd->n_bpf_map < acd->max_bpf_map) - return 0; - - max_map = 2 * acd->max_bpf_map; - - r = n_acd_bpf_map_create(&fd_map, max_map); - if (r) - return r; - - c_rbtree_for_each_entry(probe, &acd->ip_tree, ip_node) { - r = n_acd_bpf_map_add(fd_map, &probe->ip); - if (r) - return r; - } - - r = n_acd_bpf_compile(&fd_prog, fd_map, (struct ether_addr*) acd->mac); - if (r) - return r; - - if (fd_prog >= 0) { - r = setsockopt(acd->fd_socket, SOL_SOCKET, SO_ATTACH_BPF, &fd_prog, sizeof(fd_prog)); - if (r) - return -c_errno(); - } - - if (acd->fd_bpf_map >= 0) - close(acd->fd_bpf_map); - acd->fd_bpf_map = fd_map; - fd_map = -1; - acd->max_bpf_map = max_map; - return 0; -} - -/** - * n_acd_new() - create a new ACD context - * @acdp: output argument for new context object - * @config: configuration parameters - * - * Create a new ACD context and return it in @acdp. The configuration @config - * must be initialized by the caller and must specify a valid network - * interface, transport mechanism, as well as hardware address compatible with - * the selected transport. The configuration is copied into the context. The - * @config object thus does not have to be retained by the caller. - * - * Return: 0 on success, negative error code on failure. - */ -_c_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { - _c_cleanup_(n_acd_unrefp) NAcd *acd = NULL; - _c_cleanup_(c_closep) int fd_bpf_prog = -1; - struct epoll_event eevent; - int r; - - if (config->ifindex <= 0 || - config->transport != N_ACD_TRANSPORT_ETHERNET || - config->n_mac != ETH_ALEN || - !memcmp(config->mac, (uint8_t[ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ETH_ALEN)) - return N_ACD_E_INVALID_ARGUMENT; - - acd = malloc(sizeof(*acd)); - if (!acd) - return -ENOMEM; - - *acd = (NAcd)N_ACD_NULL(*acd); - acd->ifindex = config->ifindex; - memcpy(acd->mac, config->mac, ETH_ALEN); - - r = n_acd_get_random(&acd->seed); - if (r) - return r; - - acd->fd_epoll = epoll_create1(EPOLL_CLOEXEC); - if (acd->fd_epoll < 0) - return -c_errno(); - - r = timer_init(&acd->timer); - if (r < 0) - return r; - - acd->max_bpf_map = 8; - - r = n_acd_bpf_map_create(&acd->fd_bpf_map, acd->max_bpf_map); - if (r) - return r; - - r = n_acd_bpf_compile(&fd_bpf_prog, acd->fd_bpf_map, (struct ether_addr*) acd->mac); - if (r) - return r; - - r = n_acd_socket_new(&acd->fd_socket, fd_bpf_prog, config); - if (r) - return r; - - eevent = (struct epoll_event){ - .events = EPOLLIN, - .data.u32 = N_ACD_EPOLL_TIMER, - }; - r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->timer.fd, &eevent); - if (r < 0) - return -c_errno(); - - eevent = (struct epoll_event){ - .events = EPOLLIN, - .data.u32 = N_ACD_EPOLL_SOCKET, - }; - r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_socket, &eevent); - if (r < 0) - return -c_errno(); - - *acdp = acd; - acd = NULL; - return 0; -} - -static void n_acd_free_internal(NAcd *acd) { - NAcdEventNode *node, *t_node; - - if (!acd) - return; - - c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link) - n_acd_event_node_free(node); - - c_assert(c_rbtree_is_empty(&acd->ip_tree)); - - if (acd->fd_socket >= 0) { - c_assert(acd->fd_epoll >= 0); - epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_socket, NULL); - close(acd->fd_socket); - acd->fd_socket = -1; - } - - if (acd->fd_bpf_map >= 0) { - close(acd->fd_bpf_map); - acd->fd_bpf_map = -1; - } - - if (acd->timer.fd >= 0) { - c_assert(acd->fd_epoll >= 0); - epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->timer.fd, NULL); - timer_deinit(&acd->timer); - } - - if (acd->fd_epoll >= 0) { - close(acd->fd_epoll); - acd->fd_epoll = -1; - } - - free(acd); -} - -/** - * n_acd_ref() - acquire reference - * @acd: context to operate on, or NULL - * - * This acquires a single reference to the context specified as @acd. If @acd - * is NULL, this is a no-op. - * - * Return: @acd is returned. - */ -_c_public_ NAcd *n_acd_ref(NAcd *acd) { - if (acd) - ++acd->n_refs; - return acd; -} - -/** - * n_acd_unref() - release reference - * @acd: context to operate on, or NULL - * - * This releases a single reference to the context @acd. If this is the last - * reference, the context is torn down and deallocated. - * - * Return: NULL is returned. - */ -_c_public_ NAcd *n_acd_unref(NAcd *acd) { - if (acd && !--acd->n_refs) - n_acd_free_internal(acd); - return NULL; -} - -int n_acd_raise(NAcd *acd, NAcdEventNode **nodep, unsigned int event) { - NAcdEventNode *node; - int r; - - r = n_acd_event_node_new(&node); - if (r) - return r; - - node->event.event = event; - c_list_link_tail(&acd->event_list, &node->acd_link); - - if (nodep) - *nodep = node; - return 0; -} - -int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa) { - struct sockaddr_ll address = { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_ARP), - .sll_ifindex = acd->ifindex, - .sll_halen = ETH_ALEN, - .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - }; - struct ether_arp arp = { - .ea_hdr = { - .ar_hrd = htobe16(ARPHRD_ETHER), - .ar_pro = htobe16(ETHERTYPE_IP), - .ar_hln = sizeof(acd->mac), - .ar_pln = sizeof(uint32_t), - .ar_op = htobe16(ARPOP_REQUEST), - }, - }; - ssize_t l; - int r; - - memcpy(arp.arp_sha, acd->mac, sizeof(acd->mac)); - memcpy(arp.arp_tpa, &tpa->s_addr, sizeof(uint32_t)); - - if (spa) - memcpy(arp.arp_spa, &spa->s_addr, sizeof(spa->s_addr)); - - l = sendto(acd->fd_socket, - &arp, - sizeof(arp), - MSG_NOSIGNAL, - (struct sockaddr *)&address, - sizeof(address)); - if (l < 0) { - if (errno == EAGAIN || errno == ENOBUFS) { - /* - * We never maintain outgoing queues. We rely on the - * network device to do that for us. In case the queues - * are full, or the kernel refuses to queue the packet - * for other reasons, we must tell our caller that the - * packet was dropped. - */ - return N_ACD_E_DROPPED; - } else if (errno == ENETDOWN || errno == ENXIO) { - /* - * These errors happen if the network device went down - * or was actually removed. We always propagate this as - * event, so the user can react accordingly (similarly - * to the recvmmsg(2) handler). In case the user does - * not immediately react, we also tell our caller that - * the packet was dropped, so we don't erroneously - * treat this as success. - */ - - r = n_acd_raise(acd, NULL, N_ACD_EVENT_DOWN); - if (r) - return r; - - return N_ACD_E_DROPPED; - } - - /* - * Random network error. We treat this as fatal and propagate - * the error, so it is noticed and can be investigated. - */ - return -c_errno(); - } else if (l != (ssize_t)sizeof(arp)) { - /* - * Ugh, the kernel modified the packet. This is unexpected. We - * consider the packet lost. - */ - return N_ACD_E_DROPPED; - } - - return 0; -} - -/** - * n_acd_get_fd() - get pollable file descriptor - * @acd: context object to operate on - * @fdp: output argument for file descriptor - * - * This returns the backing file-descriptor of the context object @acd. The - * file-descriptor is owned by @acd and valid as long as @acd is. The - * file-descriptor never changes, so it can be cached by the caller as long as - * they hold a reference to @acd. - * - * The file-descriptor is internal to the @acd context and should not be - * modified by the caller. It is only exposed to allow the caller to poll on - * it. Whenever the file-descriptor polls readable, n_acd_dispatch() should be - * called. - * - * Currently, the file-descriptor is an epoll-fd. - */ -_c_public_ void n_acd_get_fd(NAcd *acd, int *fdp) { - *fdp = acd->fd_epoll; -} - -static int n_acd_handle_timeout(NAcd *acd) { - NAcdProbe *probe; - uint64_t now; - int r; - - /* - * Read the current time once, and handle all timeouts that triggered - * before the current time. Rereading the current time in each loop - * might risk creating a live-lock, and the fact that we read the - * time after reading the timer guarantees that the timeout which - * woke us up is handled. - * - * When there are no more timeouts to handle at the given time, we - * rearm the timer to potentially wake us up again in the future. - */ - timer_now(&acd->timer, &now); - - for (;;) { - Timeout *timeout; - - r = timer_pop_timeout(&acd->timer, now, &timeout); - if (r < 0) { - return r; - } else if (!timeout) { - /* - * There are no more timeouts pending before @now. Rearm - * the timer to fire again at the next timeout. - */ - timer_rearm(&acd->timer); - break; - } - - probe = (void *)timeout - offsetof(NAcdProbe, timeout); - r = n_acd_probe_handle_timeout(probe); - if (r) - return r; - } - - return 0; -} - -static int n_acd_handle_packet(NAcd *acd, struct ether_arp *packet) { - bool hard_conflict; - NAcdProbe *probe; - uint32_t addr; - CRBNode *node; - int r; - - /* - * We are interested in 2 kinds of ARP messages: - * - * 1) Someone who is *NOT* us sends *ANY* ARP message with our IP - * address as sender. This is never good, because it implies an - * address conflict. - * We call this a hard-conflict. - * - * 2) Someone who is *NOT* us sends an ARP REQUEST without any sender - * IP, but our IP as target. This implies someone else performs an - * ARP Probe with our address. This also implies a conflict, but - * one that can be resolved by responding to the probe. - * We call this a soft-conflict. - * - * We are never interested in any other ARP message. The kernel already - * deals with everything else, hence, we can silently ignore those. - * - * Now, we simply check whether a sender-address is set. This allows us - * to distinguish both cases. We then check further conditions, so we - * can bail out early if neither is the case. - * - * Lastly, we perform a lookup in our probe-set to check whether the - * address actually matches, so we can let these probes dispatch the - * message. Note that we allow duplicate probes, so we need to dispatch - * each matching probe, not just one. - */ - - if (memcmp(packet->arp_spa, (uint8_t[4]){ }, sizeof(packet->arp_spa))) { - memcpy(&addr, packet->arp_spa, sizeof(addr)); - hard_conflict = true; - } else if (packet->ea_hdr.ar_op == htobe16(ARPOP_REQUEST)) { - memcpy(&addr, packet->arp_tpa, sizeof(addr)); - hard_conflict = false; - } else { - /* - * The BPF filter will not let through any other packet. - */ - return -EIO; - } - - /* Find top-most node that matches @addr. */ - node = acd->ip_tree.root; - while (node) { - probe = c_rbnode_entry(node, NAcdProbe, ip_node); - if (addr < probe->ip.s_addr) - node = node->left; - else if (addr > probe->ip.s_addr) - node = node->right; - else - break; - } - - /* - * If the address is unknown, we drop the package. This might happen if - * the kernel queued the packet and passed the BPF filter, but we - * modified the set before dequeuing the message. - */ - if (!node) - return 0; - - /* Forward to left-most child that still matches @addr. */ - while (node->left && addr == c_rbnode_entry(node->left, - NAcdProbe, - ip_node)->ip.s_addr) - node = node->left; - - /* Iterate all matching entries in-order. */ - do { - probe = c_rbnode_entry(node, NAcdProbe, ip_node); - - r = n_acd_probe_handle_packet(probe, packet, hard_conflict); - if (r) - return r; - - node = c_rbnode_next(node); - } while (node && addr == c_rbnode_entry(node, - NAcdProbe, - ip_node)->ip.s_addr); - - return 0; -} - -static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) { - int r; - - if (event->events & (EPOLLHUP | EPOLLERR)) { - /* - * There is no way to handle either gracefully. If we ignored - * them, we would busy-loop, so lets rather forward the error - * to the caller. - */ - return -EIO; - } - - if (event->events & EPOLLIN) { - r = timer_read(&acd->timer); - if (r <= 0) - return r; - - c_assert(r == TIMER_E_TRIGGERED); - - /* - * A timer triggered, handle all pending timeouts at a given - * point in time. There can only be a finite number of pending - * timeouts, any new ones will be in the future, so not handled - * now, but guaranteed to wake us up again when they do trigger. - */ - r = n_acd_handle_timeout(acd); - if (r) - return r; - } - - return 0; -} - -static bool n_acd_packet_is_valid(NAcd *acd, void *packet, size_t n_packet) { - struct ether_arp *arp; - - /* - * The eBPF filter will ensure that this function always returns true, however, - * this allows the eBPF filter to be an optional optimization which is necessary - * on older kernels. - * - * See comments in n-acd-bpf.c for details. - */ - - if (n_packet != sizeof(*arp)) - return false; - - arp = packet; - - if (arp->arp_hrd != htobe16(ARPHRD_ETHER)) - return false; - - if (arp->arp_pro != htobe16(ETHERTYPE_IP)) - return false; - - if (arp->arp_hln != sizeof(struct ether_addr)) - return false; - - if (arp->arp_pln != sizeof(struct in_addr)) - return false; - - if (!memcmp(arp->arp_sha, acd->mac, sizeof(struct ether_addr))) - return false; - - if (memcmp(arp->arp_spa, &((struct in_addr) { INADDR_ANY }), sizeof(struct in_addr))) { - if (arp->arp_op != htobe16(ARPOP_REQUEST) && arp->arp_op != htobe16(ARPOP_REPLY)) - return false; - } else if (arp->arp_op != htobe16(ARPOP_REQUEST)) { - return false; - } - - return true; -} - -static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) { - const size_t n_batch = 8; - struct mmsghdr msgs[n_batch]; - struct iovec iovecs[n_batch]; - struct ether_arp data[n_batch]; - size_t i; - int r, n; - - for (i = 0; i < n_batch; ++i) { - iovecs[i].iov_base = data + i; - iovecs[i].iov_len = sizeof(data[i]); - msgs[i].msg_hdr = (struct msghdr){ - .msg_iov = iovecs + i, - .msg_iovlen = 1, - }; - } - - /* - * We always directly call into recvmmsg(2), regardless which EPOLL* - * event is signalled. On sockets, the recv(2)-family of syscalls does - * a suitable job of handling all possible scenarios and telling us - * about it. Hence, lets take the easy route and always ask the kernel - * about the current state. - */ - n = recvmmsg(acd->fd_socket, msgs, n_batch, 0, NULL); - if (n < 0) { - if (errno == ENETDOWN) { - /* - * We get ENETDOWN if the network-device goes down or - * is removed. This error is temporary and only queued - * once. Subsequent reads will simply return EAGAIN - * until the device is up again and has data queued. - * Usually, the caller should tear down all probes when - * an interface goes down, but we leave it up to the - * caller to decide what to do. We propagate the code - * and continue. - */ - return n_acd_raise(acd, NULL, N_ACD_EVENT_DOWN); - } else if (errno == EAGAIN) { - /* - * There is no more data queued and we did not get - * preempted. Everything is good to go. - * As a safety-net against busy-looping, we do check - * for HUP/ERR. Neither should be set, since they imply - * error-dequeue behavior on all socket calls. Lets - * fail hard if we trigger it, so we can investigate. - */ - if (event->events & (EPOLLHUP | EPOLLERR)) - return -EIO; - - return 0; - } else { - /* - * Something went wrong. Propagate the error-code, so - * this can be investigated. - */ - return -c_errno(); - } - } else if (n >= (ssize_t)n_batch) { - /* - * If all buffers were filled with data, we cannot be sure that - * there is nothing left to read. But to avoid starvation, we - * cannot loop on this condition. Instead, we mark the context - * as preempted so the caller can call us again. - * Note that in level-triggered event-loops this condition can - * be neglected, but in edge-triggered event-loops it is - * crucial to forward this information. - * - * On the other hand, there are several conditions where the - * kernel might return less batches than requested, but was - * still preempted. However, all of those cases require the - * preemption to have triggered a wakeup *after* we entered - * recvmmsg(). Hence, even if we did not recognize the - * preemption, an edge must have triggered and as such we will - * handle the event on the next turn. - */ - acd->preempted = true; - } - - for (i = 0; (ssize_t)i < n; ++i) { - if (!n_acd_packet_is_valid(acd, data + i, msgs[i].msg_len)) - continue; - /* - * Handle the packet. Bail out if something went wrong. Note - * that this must be fatal errors, since we discard all other - * packets that follow. - */ - r = n_acd_handle_packet(acd, data + i); - if (r) - return r; - } - - return 0; -} - -/** - * n_acd_dispatch() - dispatch context - * @acd: context object to operate on - * - * This dispatches the internal state-machine of all probes and operations - * running on the context @acd. - * - * Any outside effect or event triggered by this dispatcher will be queued on - * the event-queue of @acd. Whenever the dispatcher returns, the caller is - * required to drain the event-queue via n_acd_pop_event() until it is empty. - * - * This function dispatches as many events as possible up to a static limit to - * prevent stalling execution. If the static limit is reached, this function - * will return with N_ACD_E_PREEMPTED, otherwise 0 is returned. In most cases - * preemption can be ignored, because level-triggered event notification - * handles it automatically. However, in case of edge-triggered event - * mechanisms, the caller must make sure to call the dispatcher again. - * - * Return: 0 on success, N_ACD_E_PREEMPTED on preemption, negative error code - * on failure. - */ -_c_public_ int n_acd_dispatch(NAcd *acd) { - struct epoll_event events[2]; - int n, i, r = 0; - - n = epoll_wait(acd->fd_epoll, events, sizeof(events) / sizeof(*events), 0); - if (n < 0) { - /* Linux never returns EINTR if `timeout == 0'. */ - return -c_errno(); - } - - acd->preempted = false; - - for (i = 0; i < n; ++i) { - switch (events[i].data.u32) { - case N_ACD_EPOLL_TIMER: - r = n_acd_dispatch_timer(acd, events + i); - break; - case N_ACD_EPOLL_SOCKET: - r = n_acd_dispatch_socket(acd, events + i); - break; - default: - c_assert(0); - r = 0; - break; - } - - if (r) - return r; - } - - return acd->preempted ? N_ACD_E_PREEMPTED : 0; -} - -/** - * n_acd_pop_event() - get the next pending event - * @acd: context object to operate on - * @eventp: output argument for the event - * - * Returns a pointer to the next pending event. The event is still owend by - * the context, and is only valid until the next call to n_acd_pop_event() - * or until the owning object is freed (either the ACD context or the indicated - * probe object). - * - * An event either originates on the ACD context, or one of the configured - * probes. If the event-type has a 'probe' pointer, it originated on the - * indicated probe (which is *never* NULL), otherwise it originated on the - * context. - * - * Users must call this function repeatedly until either an error is returned, - * or the event-pointer is NULL. Wakeups on the epoll-fd are only guaranteed - * for each batch of events. Hence, it is the callers responsibility to drain - * the event-queue somehow after each call to n_acd_dispatch(). Note that - * events can only be added by n_acd_dispatch(), hence, you cannot live-lock - * when draining the event queue. - * - * The possible events are: - * * N_ACD_EVENT_READY: A configured IP address was probed successfully - * and is ready to be used. Once configured on the - * interface, the caller must call n_acd_announce() - * to announce and start defending the address. - * * N_ACD_EVENT_USED: Someone is already using the IP address being - * probed. The probe is put into stopped state and - * should be freed by the caller. - * * N_ACD_EVENT_DEFENDED: A conflict was detected for an announced IP - * address, and the engine attempted to defend it. - * This is purely informational, and no action is - * required by the caller. - * * N_ACD_EVENT_CONFLICT: A conflict was detected for an announced IP - * address, and the probe was not able to defend - * it (according to the configured policy). The - * probe halted, the caller must stop using - * the address immediately, and should free the probe. - * * N_ACD_EVENT_DOWN: The specified network interface was put down. The - * user is recommended to free *ALL* probes and - * recreate them as soon as the interface is up again. - * Note that this event is purely informational. The - * probes will continue running, but all packets will - * be blackholed, and no network packets are received, - * until the network is back up again. Hence, from an - * operational perspective, the legitimacy of the ACD - * probes is lost and the user better re-probes all - * addresses. - * - * Returns: 0 on success, negative error code on failure. The popped event is - * returned in @eventp. If no event is pending, NULL is placed in - * @eventp and 0 is returned. If an error is returned, @eventp is left - * untouched. - */ -_c_public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) { - NAcdEventNode *node, *t_node; - - c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link) { - if (node->is_public) { - n_acd_event_node_free(node); - continue; - } - - node->is_public = true; - *eventp = &node->event; - return 0; - } - - *eventp = NULL; - return 0; -} - -/** - * n_acd_probe() - start new probe - * @acd: context object to operate on - * @probep: output argument for new probe - * @config: probe configuration - * - * This creates a new probe on the context @acd and returns the probe in - * @probep. The configuration @config must provide valid probe parameters. At - * least a valid IP address must be provided through the configuration. - * - * This function does not reject duplicate probes for the same address. It is - * the caller's decision whether duplicates are allowed or not. But note that - * duplicate probes on the same context will not conflict each other. That is, - * running a probe for the same address twice on the same context will not - * cause them to consider each other a duplicate. - * - * Probes are rather lightweight objects. They do not create any - * file-descriptors or other kernel objects. Probes always re-use the - * infrastructure provided by the context object @acd. This allows running many - * probes simultaneously without exhausting resources. - * - * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT on invalid configuration - * parameters, negative error code on failure. - */ -_c_public_ int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config) { - return n_acd_probe_new(probep, acd, config); -} diff --git a/shared/n-acd/src/n-acd.h b/shared/n-acd/src/n-acd.h deleted file mode 100644 index e2b01270..00000000 --- a/shared/n-acd/src/n-acd.h +++ /dev/null @@ -1,150 +0,0 @@ -#pragma once - -/* - * IPv4 Address Conflict Detection - * - * This is the public header of the n-acd library, implementing IPv4 Address - * Conflict Detection as described in RFC-5227. This header defines the public - * API and all entry points of n-acd. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <netinet/in.h> -#include <inttypes.h> -#include <stdbool.h> -#include <stdlib.h> - -typedef struct NAcd NAcd; -typedef struct NAcdConfig NAcdConfig; -typedef struct NAcdEvent NAcdEvent; -typedef struct NAcdProbe NAcdProbe; -typedef struct NAcdProbeConfig NAcdProbeConfig; - -#define N_ACD_TIMEOUT_RFC5227 (UINT64_C(9000)) - -enum { - _N_ACD_E_SUCCESS, - - N_ACD_E_PREEMPTED, - N_ACD_E_INVALID_ARGUMENT, - - _N_ACD_E_N, -}; - -enum { - N_ACD_TRANSPORT_ETHERNET, - _N_ACD_TRANSPORT_N, -}; - -enum { - N_ACD_EVENT_READY, - N_ACD_EVENT_USED, - N_ACD_EVENT_DEFENDED, - N_ACD_EVENT_CONFLICT, - N_ACD_EVENT_DOWN, - _N_ACD_EVENT_N, -}; - -enum { - N_ACD_DEFEND_NEVER, - N_ACD_DEFEND_ONCE, - N_ACD_DEFEND_ALWAYS, - _N_ACD_DEFEND_N, -}; - -struct NAcdEvent { - unsigned int event; - union { - struct { - NAcdProbe *probe; - } ready; - struct { - } down; - struct { - NAcdProbe *probe; - uint8_t *sender; - size_t n_sender; - } used, defended, conflict; - }; -}; - -/* configs */ - -int n_acd_config_new(NAcdConfig **configp); -NAcdConfig *n_acd_config_free(NAcdConfig *config); - -void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex); -void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport); -void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac); - -int n_acd_probe_config_new(NAcdProbeConfig **configp); -NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config); - -void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip); -void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs); - -/* contexts */ - -int n_acd_new(NAcd **acdp, NAcdConfig *config); -NAcd *n_acd_ref(NAcd *acd); -NAcd *n_acd_unref(NAcd *acd); - -void n_acd_get_fd(NAcd *acd, int *fdp); -int n_acd_dispatch(NAcd *acd); -int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp); - -int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config); - -/* probes */ - -NAcdProbe *n_acd_probe_free(NAcdProbe *probe); - -void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata); -void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap); - -int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend); - -/* inline helpers */ - -static inline void n_acd_config_freep(NAcdConfig **config) { - if (*config) - n_acd_config_free(*config); -} - -static inline void n_acd_config_freev(NAcdConfig *config) { - n_acd_config_free(config); -} - -static inline void n_acd_probe_config_freep(NAcdProbeConfig **config) { - if (*config) - n_acd_probe_config_free(*config); -} - -static inline void n_acd_probe_config_freev(NAcdProbeConfig *config) { - n_acd_probe_config_free(config); -} - -static inline void n_acd_unrefp(NAcd **acd) { - if (*acd) - n_acd_unref(*acd); -} - -static inline void n_acd_unrefv(NAcd *acd) { - n_acd_unref(acd); -} - -static inline void n_acd_probe_freep(NAcdProbe **probe) { - if (*probe) - n_acd_probe_free(*probe); -} - -static inline void n_acd_probe_freev(NAcdProbe *probe) { - n_acd_probe_free(probe); -} - -#ifdef __cplusplus -} -#endif diff --git a/shared/n-acd/src/util/timer.c b/shared/n-acd/src/util/timer.c deleted file mode 100644 index af2a887c..00000000 --- a/shared/n-acd/src/util/timer.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Timer Utility Library - */ - -#include <assert.h> -#include <c-rbtree.h> -#include <c-stdaux.h> -#include <errno.h> -#include <stdlib.h> -#include <sys/timerfd.h> -#include <time.h> -#include "timer.h" - -int timer_init(Timer *timer) { - clockid_t clock = CLOCK_BOOTTIME; - int r; - - r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); - if (r < 0 && errno == EINVAL) { - clock = CLOCK_MONOTONIC; - r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); - } - if (r < 0) - return -errno; - - *timer = (Timer)TIMER_NULL(*timer); - timer->fd = r; - timer->clock = clock; - - return 0; -} - -void timer_deinit(Timer *timer) { - c_assert(c_rbtree_is_empty(&timer->tree)); - - if (timer->fd >= 0) { - close(timer->fd); - timer->fd = -1; - } -} - -void timer_now(Timer *timer, uint64_t *nowp) { - struct timespec ts; - int r; - - r = clock_gettime(timer->clock, &ts); - c_assert(r >= 0); - - *nowp = ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec; -} - -void timer_rearm(Timer *timer) { - uint64_t time; - Timeout *timeout; - int r; - - /* - * A timeout value of 0 clears the timer, we should only set that if - * no timeout exists in the tree. - */ - - timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node); - c_assert(!timeout || timeout->timeout); - - time = timeout ? timeout->timeout : 0; - - if (time != timer->scheduled_timeout) { - r = timerfd_settime(timer->fd, - TFD_TIMER_ABSTIME, - &(struct itimerspec){ - .it_value = { - .tv_sec = time / UINT64_C(1000000000), - .tv_nsec = time % UINT64_C(1000000000), - }, - }, - NULL); - c_assert(r >= 0); - - timer->scheduled_timeout = time; - } -} - -int timer_read(Timer *timer) { - uint64_t v; - int r; - - r = read(timer->fd, &v, sizeof(v)); - if (r < 0) { - if (errno == EAGAIN) { - /* - * No more pending events. - */ - return 0; - } else { - /* - * Something failed. We use CLOCK_BOOTTIME/MONOTONIC, - * so ECANCELED cannot happen. Hence, there is no - * error that we could gracefully handle. Fail hard - * and let the caller deal with it. - */ - return -errno; - } - } else if (r != sizeof(v) || v == 0) { - /* - * Kernel guarantees 8-byte reads, and only to return - * data if at least one timer triggered; fail hard if - * it suddenly starts doing weird shit. - */ - return -EIO; - } - - return TIMER_E_TRIGGERED; -} - - -int timer_pop_timeout(Timer *timer, uint64_t until, Timeout **timeoutp) { - Timeout *timeout; - - /* - * If the first timeout is scheduled before @until, then unlink - * it and return it. Otherwise, return NULL. - */ - timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node); - if (timeout && timeout->timeout <= until) { - c_rbnode_unlink(&timeout->node); - timeout->timeout = 0; - *timeoutp = timeout; - } else { - *timeoutp = NULL; - } - - return 0; -} - -void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time) { - c_assert(time); - - /* - * In case @timeout was already scheduled, remove it from the - * tree. If we are moving it to a new timer, rearm the old one. - */ - if (timeout->timer) { - c_rbnode_unlink(&timeout->node); - if (timeout->timer != timer) - timer_rearm(timeout->timer); - } - timeout->timer = timer; - timeout->timeout = time; - - /* - * Now insert it back into the tree in the correct new position. - * We allow duplicates in the tree, so this insertion is open-coded. - */ - { - Timeout *other; - CRBNode **slot, *parent; - - slot = &timer->tree.root; - parent = NULL; - while (*slot) { - other = c_rbnode_entry(*slot, Timeout, node); - parent = *slot; - if (timeout->timeout < other->timeout) - slot = &(*slot)->left; - else - slot = &(*slot)->right; - } - - c_rbtree_add(&timer->tree, parent, slot, &timeout->node); - } - - /* - * Rearm the timer as we updated the timeout tree. - */ - timer_rearm(timer); -} - -void timeout_unschedule(Timeout *timeout) { - Timer *timer = timeout->timer; - - if (!timer) - return; - - c_rbnode_unlink(&timeout->node); - timeout->timeout = 0; - timeout->timer = NULL; - - timer_rearm(timer); -} diff --git a/shared/n-acd/src/util/timer.h b/shared/n-acd/src/util/timer.h deleted file mode 100644 index d01b2741..00000000 --- a/shared/n-acd/src/util/timer.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <c-rbtree.h> -#include <c-stdaux.h> -#include <inttypes.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> - -typedef struct Timer Timer; -typedef struct Timeout Timeout; - -enum { - _TIMER_E_SUCCESS, - - TIMER_E_TRIGGERED, - - _TIMER_E_N, -}; - -struct Timer { - int fd; - clockid_t clock; - CRBTree tree; - uint64_t scheduled_timeout; -}; - -#define TIMER_NULL(_x) { \ - .fd = -1, \ - .tree = C_RBTREE_INIT, \ - } - -struct Timeout { - Timer *timer; - CRBNode node; - uint64_t timeout; -}; - -#define TIMEOUT_INIT(_x) { \ - .node = C_RBNODE_INIT((_x).node), \ - } - -int timer_init(Timer *timer); -void timer_deinit(Timer *timer); - -void timer_now(Timer *timer, uint64_t *nowp); - -int timer_pop_timeout(Timer *timer, uint64_t now, Timeout **timerp); -void timer_rearm(Timer *timer); -int timer_read(Timer *timer); - -void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time); -void timeout_unschedule(Timeout *timeout); - |