From 0f06351cb0085c79b617a4acc0bd6749b53c638e Mon Sep 17 00:00:00 2001 From: Thomas Gschwantner Date: Tue, 23 Apr 2019 22:06:22 +0200 Subject: Implement basic lease management --- Makefile | 2 +- common.c | 12 ++- common.h | 15 +--- khash.h | 21 +++++ lease.c | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ lease.h | 68 +++++++++++++++++ random.c | 92 ++++++++++++++++++++++ random.h | 13 ++++ wg-dynamic-client.c | 50 ++++++------ wg-dynamic-server.c | 215 ++++++++++++++++++++++------------------------------ 10 files changed, 536 insertions(+), 162 deletions(-) create mode 100644 lease.c create mode 100644 lease.h create mode 100644 random.c create mode 100644 random.h diff --git a/Makefile b/Makefile index 575fc38..11deced 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ endif all: wg-dynamic-server wg-dynamic-client wg-dynamic-client: wg-dynamic-client.o netlink.o common.o -wg-dynamic-server: wg-dynamic-server.o netlink.o radix-trie.o common.o +wg-dynamic-server: wg-dynamic-server.o netlink.o radix-trie.o common.o random.o lease.o ifneq ($(V),1) clean: diff --git a/common.c b/common.c index a3ce7ae..5be1226 100644 --- a/common.c +++ b/common.c @@ -25,12 +25,20 @@ static bool parse_ip_cidr(struct wg_combined_ip *ip, char *value) { uintmax_t res; char *endptr; - char *sep = strchr(value, '/'); + char *sep; + + if (value[0] == '\0') { + memset(ip, 0, ip->family == AF_INET ? 4 : 16); + ip->cidr = 0; + return true; + } + + sep = strchr(value, '/'); if (!sep) return false; *sep = '\0'; - if (inet_pton(ip->family, value, &ip->ip) != 1) + if (inet_pton(ip->family, value, ip) != 1) return false; res = strtoumax(sep + 1, &endptr, 10); diff --git a/common.h b/common.h index 7c0c0e9..d0f8ffd 100644 --- a/common.h +++ b/common.h @@ -6,10 +6,11 @@ #ifndef __COMMON_H__ #define __COMMON_H__ +#include +#include #include #include #include -#include #include "netlink.h" @@ -65,22 +66,14 @@ struct wg_dynamic_request { }; struct wg_combined_ip { - uint16_t family; union { struct in_addr ip4; struct in6_addr ip6; - } ip; + }; + uint16_t family; uint8_t cidr; }; -struct wg_dynamic_lease { - struct wg_combined_ip ip4; - struct wg_combined_ip ip6; - uint32_t start; - uint32_t leasetime; - struct wg_dynamic_lease *next; -}; - #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) void free_wg_dynamic_request(struct wg_dynamic_request *req); diff --git a/khash.h b/khash.h index f75f347..445ee3d 100644 --- a/khash.h +++ b/khash.h @@ -624,4 +624,25 @@ typedef const char *kh_cstr_t; #define KHASH_MAP_INIT_STR(name, khval_t) \ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) +/* Custom functions for wg_key (32 bytes) */ +typedef const unsigned char *khwgkey_t; + +#define kh_wgkey_hash_equal(a, b) (memcmp(a, b, 32) == 0) + +static kh_inline khint_t __fnv_1a_32_hash(const unsigned char *wgkey) +{ + khint_t hash = 0x811c9dc5UL; + for (int i = 0; i < 32; ++i) { + hash ^= wgkey[i]; + hash *= 16777619UL; + } + return hash; +} + +#define KHASH_SET_INIT_WGKEY(name) \ + KHASH_INIT(name, khwgkey_t, char, 0, __fnv_1a_32_hash, kh_wgkey_hash_equal) + +#define KHASH_MAP_INIT_WGKEY(name, khval_t) \ + KHASH_INIT(name, khwgkey_t, khval_t, 1, __fnv_1a_32_hash, kh_wgkey_hash_equal) + #endif /* __AC_KHASH_H */ diff --git a/lease.c b/lease.c new file mode 100644 index 0000000..936a4a8 --- /dev/null +++ b/lease.c @@ -0,0 +1,210 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include + +#include "dbg.h" +#include "khash.h" +#include "lease.h" +#include "netlink.h" +#include "radix-trie.h" +#include "random.h" + +static struct ip_pool pools; +static time_t gexpires; + +KHASH_MAP_INIT_WGKEY(leaseht, struct wg_dynamic_lease *) +khash_t(leaseht) * leases_ht; + +static uint64_t totall_ipv6; +static uint32_t totalh_ipv6, total_ipv4; + +static time_t get_monotonic_time() +{ + struct timespec monotime; +#ifdef __linux__ + /* in linux 4.17, CLOCK_MONOTONIC was changed to be like CLOCK_BOOTTIME, + * see https://git.kernel.org/torvalds/c/d6ed449, but glibc's wrapper + * seems to still have the old behavior + */ + if (clock_gettime(CLOCK_BOOTTIME, &monotime)) + fatal("clock_gettime(CLOCK_BOOTTIME)"); +#else + /* CLOCK_MONOTONIC works on openbsd, but apparently not (yet) on + * freebsd: https://lists.freebsd.org/pipermail/freebsd-hackers/2018-June/052899.html + */ + if (clock_gettime(CLOCK_MONOTONIC, &monotime)) + fatal("clock_gettime(CLOCK_MONOTONIC)"); + +#endif + /* TODO: what about darwin? */ + + return monotime.tv_sec; +} + +void leases_init(char *fname) +{ + UNUSED(fname); /* TODO: open file and initialize from it */ + + leases_ht = kh_init(leaseht); + + ipp_init(&pools); + + /* TODO: initialize pools properly from routes */ + struct in_addr pool1_v4, pool2_v4; + struct in6_addr pool1_v6, pool2_v6; + inet_pton(AF_INET, "192.168.4.0", &pool1_v4); + inet_pton(AF_INET, "192.168.73.0", &pool2_v4); + inet_pton(AF_INET6, "2001:db8:1234::", &pool1_v6); + inet_pton(AF_INET6, "2001:db8:7777::", &pool2_v6); + + ipp_addpool_v4(&pools, &pool1_v4, 28); + ipp_addpool_v4(&pools, &pool2_v4, 27); + ipp_addpool_v6(&pools, &pool1_v6, 124); + ipp_addpool_v6(&pools, &pool2_v6, 124); + + total_ipv4 = ipp_gettotal_v4(&pools); + totall_ipv6 = ipp_gettotal_v6(&pools, &totalh_ipv6); +} + +void leases_free() +{ + kh_destroy(leaseht, leases_ht); + ipp_free(&pools); +} + +struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, + struct in_addr *ipv4, struct in6_addr *ipv6, + time_t *expires) +{ + struct wg_dynamic_lease *lease, *parent; + uint64_t index_low; + uint32_t index, index_high; + struct timespec tp; + khiter_t k; + int ret; + + lease = malloc(sizeof *lease); + if (!lease) + fatal("malloc()"); + + if (!ipv4 || ipv4->s_addr) { + if (total_ipv4 == 0) + return NULL; + + --total_ipv4; + } + if (!ipv6 || !IN6_IS_ADDR_UNSPECIFIED(ipv6)) { + if (totalh_ipv6 == 0 && totall_ipv6 == 0) { + if (!ipv4 || ipv4->s_addr) + ++total_ipv4; + + return NULL; + } + + if (totall_ipv6 == 0) + --totalh_ipv6; + + --totall_ipv6; + } + + if (!ipv4 || ipv4->s_addr) { + if (!ipv4) { + index = random_bounded(total_ipv4); + debug("new_lease(v4): %u of %u\n", index, total_ipv4); + ipp_addnth_v4(&pools, &lease->ipv4, index); + } else { + if (ipp_add_v4(&pools, ipv4, 32)) + return NULL; + memcpy(&lease->ipv4, ipv4, sizeof *ipv4); + } + } + if (!ipv6 || !IN6_IS_ADDR_UNSPECIFIED(ipv6)) { + if (!ipv6) { + if (totalh_ipv6 > 0) { + index_low = random_bounded(UINT64_MAX); + if (totall_ipv6 - index_low > totall_ipv6) + --totalh_ipv6; + + index_high = random_bounded(totalh_ipv6); + } else { + index_low = random_bounded(totall_ipv6); + index_high = 0; + } + debug("new_lease(v6): %u:%ju of %u:%ju\n", index_high, + index_low, totalh_ipv6, totall_ipv6); + + ipp_addnth_v6(&pools, &lease->ipv6, index_low, + index_high); + } else { + if (ipp_add_v6(&pools, ipv6, 128)) + return NULL; /* TODO: free ipv4 addr */ + memcpy(&lease->ipv6, ipv6, sizeof *ipv6); + } + } + + if (clock_gettime(CLOCK_REALTIME, &tp)) + fatal("clock_gettime(CLOCK_REALTIME)"); + + lease->start_real = tp.tv_sec; + lease->start_mono = get_monotonic_time(); + lease->leasetime = leasetime; + lease->next = NULL; + + k = kh_put(leaseht, leases_ht, pubkey, &ret); + if (ret < 0) { + die("kh_put()"); + } else if (ret == 0) { + parent = kh_value(leases_ht, k); + while (parent->next) + parent = parent->next; + + parent->next = lease; + } else { + kh_value(leases_ht, k) = lease; + } + + if (lease->start_mono < gexpires) + gexpires = lease->start_mono; + + *expires = gexpires; + + /* TODO: add record to file */ + + return lease; +} + +struct wg_dynamic_lease *get_leases(wg_key pubkey) +{ + khiter_t k = kh_get(leaseht, leases_ht, pubkey); + + if (k == kh_end(leases_ht)) + return NULL; + else + return kh_val(leases_ht, k); +} + +bool extend_lease(struct wg_dynamic_lease *lease, uint32_t leasetime, + time_t *expires) +{ + UNUSED(lease); + UNUSED(leasetime); + UNUSED(expires); + return false; +} + +time_t leases_refresh() +{ + /* TODO: remove expired leases */ + return gexpires; +} + +void leases_update_pools(int fd) +{ + UNUSED(fd); +} diff --git a/lease.h b/lease.h new file mode 100644 index 0000000..4052bf9 --- /dev/null +++ b/lease.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +#ifndef __LEASE_H__ +#define __LEASE_H__ + +#include +#include +#include + +#include "common.h" +#include "netlink.h" + +struct wg_dynamic_lease { + time_t start_real; + time_t start_mono; + uint32_t leasetime; /* in seconds */ + struct in_addr ipv4; + struct in6_addr ipv6; + struct wg_dynamic_lease *next; +}; + +/* + * Initializes internal state, reads leases from fname. + */ +void leases_init(char *fname); + +/* + * Frees everything, closes file. + */ +void leases_free(); + +/* + * Creates a new lease and returns a pointer to it, or NULL if either we ran out + * of assignable IPs or the requested IP is already taken. + * expires contains the (monotonic) timestamp after which the next lease, + * possibly the newly created one, will expire. + */ +struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, + struct in_addr *ipv4, struct in6_addr *ipv6, + time_t *expires); + +/* + * Returns all leases belonging to pubkey, or NULL if there are none. + */ +struct wg_dynamic_lease *get_leases(wg_key pubkey); + +/* + * Extend the lease to be leasetime long again. Returns true on error, or false + * otherwise. expires behaves exactly as in new_lease(). + */ +bool extend_lease(struct wg_dynamic_lease *lease, uint32_t leasetime, + time_t *expires); + +/* + * Refreshes all leases, meaning expired ones will be removed. Returns the + * expiration timestamp of the lease that will expire next. + */ +time_t leases_refresh(); + +/* + * Updates all pools with information from the netlink file descriptor fd. + */ +void leases_update_pools(int fd); + +#endif diff --git a/random.c b/random.c new file mode 100644 index 0000000..eedb52f --- /dev/null +++ b/random.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dbg.h" + +#ifdef __linux__ +#include +#endif +#ifdef __APPLE__ +#include +#ifndef MAC_OS_X_VERSION_10_12 +#define MAC_OS_X_VERSION_10_12 101200 +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 +#include +#endif +#endif + +static inline bool __attribute__((__warn_unused_result__)) +get_random_bytes(uint8_t *out, size_t len) +{ + ssize_t ret = 0; + size_t i; + int fd; + + if (len > 256) { + errno = EOVERFLOW; + return false; + } + +#if defined(__OpenBSD__) || \ + (defined(__APPLE__) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || \ + (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) + if (!getentropy(out, len)) + return true; +#endif + +#if defined(__NR_getrandom) && defined(__linux__) + if (syscall(__NR_getrandom, out, len, 0) == (ssize_t)len) + return true; +#endif + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return false; + for (errno = 0, i = 0; i < len; i += ret, ret = 0) { + ret = read(fd, out + i, len - i); + if (ret <= 0) { + ret = errno ? -errno : -EIO; + break; + } + } + close(fd); + errno = -ret; + return i == len; +} + +uint64_t random_bounded(uint64_t bound) +{ + uint64_t ret; + + if (bound == 0) + return 0; + + if (bound == 1) { + if (!get_random_bytes((uint8_t *)&ret, sizeof(ret))) + fatal("get_random_bytes()"); + return (ret > 0x7FFFFFFFFFFFFFFF) ? 1 : 0; + } + + const uint64_t max_mod_bound = (1 + ~bound) % bound; + + do { + if (!get_random_bytes((uint8_t *)&ret, sizeof(ret))) + fatal("get_random_bytes()"); + } while (ret < max_mod_bound); + + return ret % bound; +} diff --git a/random.h b/random.h new file mode 100644 index 0000000..04271a4 --- /dev/null +++ b/random.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +#ifndef __RANDOM_H__ +#define __RANDOM_H__ + +#include + +uint64_t random_bounded(uint64_t bound); + +#endif diff --git a/wg-dynamic-client.c b/wg-dynamic-client.c index fe0b6b5..2224a22 100644 --- a/wg-dynamic-client.c +++ b/wg-dynamic-client.c @@ -20,6 +20,14 @@ #include "dbg.h" #include "netlink.h" +struct wg_dynamic_lease { + struct wg_combined_ip ip4; + struct wg_combined_ip ip6; + uint32_t start; + uint32_t leasetime; + struct wg_dynamic_lease *next; +}; + static const char *progname; static const char *wg_interface; static wg_device *device = NULL; @@ -70,12 +78,12 @@ int data_cb(const struct nlmsghdr *nlh, void *data) switch (ifa->ifa_family) { case AF_INET: cb_data->gaddr4->family = ifa->ifa_family; - memcpy(&cb_data->gaddr4->ip, addr, 4); + memcpy(&cb_data->gaddr4->ip4, addr, 4); cb_data->gaddr4->cidr = ifa->ifa_prefixlen; break; case AF_INET6: cb_data->gaddr6->family = ifa->ifa_family; - memcpy(&cb_data->gaddr6->ip, addr, 16); + memcpy(&cb_data->gaddr6->ip6, addr, 16); cb_data->gaddr6->cidr = ifa->ifa_prefixlen; break; default: @@ -114,8 +122,7 @@ static void iface_update(uint16_t cmd, uint16_t flags, uint32_t ifindex, ifaddr->ifa_prefixlen = addr->cidr; ifaddr->ifa_scope = RT_SCOPE_UNIVERSE; /* linux/rtnetlink.h */ ifaddr->ifa_index = ifindex; - mnl_attr_put(nlh, IFA_LOCAL, addr->family == AF_INET ? 4 : 16, - &addr->ip); + mnl_attr_put(nlh, IFA_LOCAL, addr->family == AF_INET ? 4 : 16, &addr); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) fatal("mnl_socket_sendto"); @@ -138,8 +145,8 @@ static void iface_remove_addr(uint32_t ifindex, { char ipstr[INET6_ADDRSTRLEN]; debug("removing %s/%u from interface %u\n", - inet_ntop(addr->family, &addr->ip, ipstr, sizeof ipstr), - addr->cidr, ifindex); + inet_ntop(addr->family, &addr, ipstr, sizeof ipstr), addr->cidr, + ifindex); iface_update(RTM_DELADDR, 0, ifindex, addr); } @@ -147,8 +154,8 @@ static void iface_add_addr(uint32_t ifindex, const struct wg_combined_ip *addr) { char ipstr[INET6_ADDRSTRLEN]; debug("adding %s/%u to interface %u\n", - inet_ntop(addr->family, &addr->ip, ipstr, sizeof ipstr), - addr->cidr, ifindex); + inet_ntop(addr->family, &addr, ipstr, sizeof ipstr), addr->cidr, + ifindex); iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex, addr); } @@ -172,7 +179,7 @@ static bool get_and_validate_local_addrs(uint32_t ifindex, static int try_connect(int *fd) { - struct timeval tval = {.tv_sec = 1, .tv_usec = 0 }; + struct timeval tval = { .tv_sec = 1, .tv_usec = 0 }; struct sockaddr_in6 our_addr = { .sin6_family = AF_INET6, .sin6_addr = our_lladdr, @@ -227,15 +234,15 @@ static void request_ip(int fd, const struct wg_dynamic_lease *lease) msglen += print_to_buf((char *)buf, sizeof buf, msglen, "%s=%d\n", WG_DYNAMIC_KEY[WGKEY_REQUEST_IP], 1); - if (lease && lease->ip4.ip.ip4.s_addr) { - if (!inet_ntop(AF_INET, &lease->ip4.ip.ip4, addrstr, + if (lease && lease->ip4.ip4.s_addr) { + if (!inet_ntop(AF_INET, &lease->ip4.ip4, addrstr, sizeof addrstr)) fatal("inet_ntop()"); msglen += print_to_buf((char *)buf, sizeof buf, msglen, "ipv4=%s/32\n", addrstr); } - if (lease && !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)) { - if (!inet_ntop(AF_INET6, &lease->ip6.ip.ip6, addrstr, + if (lease && !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip6)) { + if (!inet_ntop(AF_INET6, &lease->ip6.ip6, addrstr, sizeof addrstr)) fatal("inet_ntop()"); msglen += print_to_buf((char *)buf, sizeof buf, msglen, @@ -307,9 +314,8 @@ static int handle_received_lease(const struct wg_dynamic_request *req) attr = attr->next; } - if (lease->leasetime == 0 || - (lease->ip4.ip.ip4.s_addr == 0 && - IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6))) + if (lease->leasetime == 0 || (lease->ip4.ip4.s_addr == 0 && + IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip6))) return -EINVAL; if (abs(now - lease_start) < 15) @@ -342,16 +348,16 @@ static bool handle_error(int fd, int ret) static void maybe_update_iface() { - if (memcmp(&our_gaddr4.ip, &our_lease.ip4.ip, sizeof our_gaddr4.ip) || + if (memcmp(&our_gaddr4, &our_lease.ip4, sizeof our_gaddr4) || our_gaddr4.cidr != our_lease.ip4.cidr) { - if (our_gaddr4.ip.ip4.s_addr) + if (our_gaddr4.ip4.s_addr) iface_remove_addr(device->ifindex, &our_gaddr4); iface_add_addr(device->ifindex, &our_lease.ip4); memcpy(&our_gaddr4, &our_lease.ip4, sizeof our_gaddr4); } - if (memcmp(&our_gaddr6.ip, &our_lease.ip6.ip, sizeof our_gaddr6.ip) || + if (memcmp(&our_gaddr6, &our_lease.ip6, sizeof our_gaddr6) || our_gaddr6.cidr != our_lease.ip6.cidr) { - if (!IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip.ip6)) + if (!IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip6)) iface_remove_addr(device->ifindex, &our_gaddr6); iface_add_addr(device->ifindex, &our_lease.ip6); memcpy(&our_gaddr6, &our_lease.ip6, sizeof our_gaddr6); @@ -420,8 +426,8 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) /* If we have an address configured, let's assume it's from a * lease in order to get renewal done. */ - if (our_gaddr4.ip.ip4.s_addr || - !IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip.ip6)) { + if (our_gaddr4.ip4.s_addr || + !IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip6)) { our_lease.start = current_time(); our_lease.leasetime = 15; memcpy(&our_lease.ip4, &our_gaddr4, diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c index a408a75..168a902 100644 --- a/wg-dynamic-server.c +++ b/wg-dynamic-server.c @@ -25,6 +25,7 @@ #include "common.h" #include "dbg.h" #include "khash.h" +#include "lease.h" #include "netlink.h" static const char *progname; @@ -246,57 +247,6 @@ static void accept_incoming(int sockfd, struct wg_dynamic_request *reqs) } } -static int allocate_from_pool(struct wg_dynamic_request *const req, - struct wg_dynamic_lease *lease) -{ - struct wg_dynamic_attr *attr; - struct wg_combined_ip default_v4 = { .family = AF_INET, .cidr = 32 }, - default_v6 = { .family = AF_INET6, .cidr = 128 }; - - /* NOTE: handing out whatever the client asks for */ - /* TODO: choose an ip address from pool of available - * addresses, together with an appropriate lease time */ - /* NOTE: the pool is to be drawn from what routes are pointing - * to the wg interface, and kept up to date as the routing - * table changes */ - - if (inet_pton(AF_INET, "192.168.47.11", &default_v4.ip.ip4) != 1) - fatal("inet_pton()"); - memcpy(&lease->ip4, &default_v4, sizeof(struct wg_combined_ip)); - - if (inet_pton(AF_INET6, "fd00::4711", &default_v6.ip.ip6) != 1) - fatal("inet_pton()"); - memcpy(&lease->ip6, &default_v6, sizeof(struct wg_combined_ip)); - - lease->start = current_time(); - lease->leasetime = WG_DYNAMIC_LEASETIME; - - attr = req->first; - while (attr) { - switch (attr->key) { - case WGKEY_IPV4: - memcpy(&lease->ip4, attr->value, - sizeof(struct wg_combined_ip)); - break; - case WGKEY_IPV6: - memcpy(&lease->ip6, attr->value, - sizeof(struct wg_combined_ip)); - break; - case WGKEY_LEASETIME: - memcpy(&lease->leasetime, attr->value, - sizeof(uint32_t)); - break; - default: - debug("Ignoring invalid attribute for request_ip: %d\n", - attr->key); - } - - attr = attr->next; - } - - return 0; -} - static bool send_error(int fd, int ret) { UNUSED(fd); @@ -304,39 +254,32 @@ static bool send_error(int fd, int ret) return true; } -static bool serialise_lease(char *buf, size_t bufsize, size_t *offset, +static void serialise_lease(char *buf, size_t bufsize, size_t *offset, const struct wg_dynamic_lease *lease) { char addrbuf[INET6_ADDRSTRLEN]; - bool ret = false; - if (lease->ip4.ip.ip4.s_addr) { - if (!inet_ntop(AF_INET, &lease->ip4.ip.ip4, addrbuf, - sizeof addrbuf)) + if (lease->ipv4.s_addr) { + if (!inet_ntop(AF_INET, &lease->ipv4, addrbuf, sizeof addrbuf)) fatal("inet_ntop()"); *offset += print_to_buf(buf, bufsize, *offset, "ipv4=%s/%d\n", - addrbuf, lease->ip4.cidr); - ret = true; + addrbuf, 32); } - if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)) { - if (!inet_ntop(AF_INET6, &lease->ip6.ip.ip6, addrbuf, - sizeof addrbuf)) + + if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) { + if (!inet_ntop(AF_INET6, &lease->ipv6, addrbuf, sizeof addrbuf)) fatal("inet_ntop()"); *offset += print_to_buf(buf, bufsize, *offset, "ipv6=%s/%d\n", - addrbuf, lease->ip6.cidr); - ret = true; + addrbuf, 128); } - if (ret) { - *offset += print_to_buf(buf, bufsize, *offset, - "leasestart=%u\n", lease->start); - *offset += print_to_buf(buf, bufsize, *offset, "leasetime=%u\n", - lease->leasetime); - } - - return ret; + *offset += print_to_buf(buf, bufsize, *offset, "leasestart=%u\n", + lease->start_real); + *offset += print_to_buf(buf, bufsize, *offset, "leasetime=%u\n", + lease->leasetime); } +/* TODO: put this in a hashtable instead? */ static struct wg_peer *current_peer(struct wg_dynamic_request *req) { struct wg_peer *peer; @@ -346,29 +289,12 @@ static struct wg_peer *current_peer(struct wg_dynamic_request *req) return peer; } - return NULL; + die("Unable to find peer\n"); } -static void insert_allowed_ip(struct wg_peer *peer, - const struct wg_combined_ip *ip) +/* TODO: this will overwrite changes done to the interface by others */ +static void insert_allowed_ip(struct wg_peer *peer, struct wg_allowedip *newip) { - struct wg_allowedip *newip; - - newip = calloc(1, sizeof(struct wg_allowedip)); - if (!newip) - fatal("calloc()"); - - newip->family = ip->family; - switch (newip->family) { - case AF_INET: - memcpy(&newip->ip4, &ip->ip.ip4, sizeof(struct in_addr)); - break; - case AF_INET6: - memcpy(&newip->ip6, &ip->ip.ip6, sizeof(struct in6_addr)); - break; - } - newip->cidr = ip->cidr; - if (!peer->first_allowedip) peer->first_allowedip = newip; else @@ -376,62 +302,96 @@ static void insert_allowed_ip(struct wg_peer *peer, peer->last_allowedip = newip; } -static int add_allowed_ips(struct wg_peer *peer, - const struct wg_dynamic_lease *lease) +static int add_allowed_ips(struct wg_peer *peer, struct in_addr *ipv4, + struct in6_addr *ipv6) { - if (lease->ip4.ip.ip4.s_addr) - insert_allowed_ip(peer, &lease->ip4); - if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)) - insert_allowed_ip(peer, &lease->ip6); + struct wg_allowedip *newip; + + if (ipv4 && ipv4->s_addr) { + newip = calloc(1, sizeof *newip); + if (!newip) + fatal("calloc()"); + + newip->family = AF_INET; + newip->cidr = 32; + memcpy(&newip->ip4, &ipv4->s_addr, sizeof(struct in_addr)); + insert_allowed_ip(peer, newip); + } + if (ipv6 && !IN6_IS_ADDR_UNSPECIFIED(ipv6)) { + newip = calloc(1, sizeof *newip); + if (!newip) + fatal("calloc()"); + + newip->family = AF_INET6; + newip->cidr = 128; + memcpy(&newip->ip6, &ipv6->s6_addr, sizeof(struct in6_addr)); + insert_allowed_ip(peer, newip); + } return wg_set_device(device); } +static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey, + struct wg_dynamic_lease **lease) +{ + time_t expires; + struct in_addr *ipv4 = NULL; + struct in6_addr *ipv6 = NULL; + uint32_t leasetime = WG_DYNAMIC_LEASETIME; + + *lease = get_leases(pubkey); + + while (cur) { + switch (cur->key) { + case WGKEY_IPV4: + ipv4 = &((struct wg_combined_ip *)cur->value)->ip4; + break; + case WGKEY_IPV6: + ipv6 = &((struct wg_combined_ip *)cur->value)->ip6; + break; + case WGKEY_LEASETIME: + leasetime = *(uint32_t *)cur->value; + break; + default: + debug("Ignoring invalid attribute for request_ip: %d\n", + cur->key); + } + cur = cur->next; + } + + if (ipv4 && ipv6 && !ipv4->s_addr && IN6_IS_ADDR_UNSPECIFIED(ipv6)) + return 2; /* TODO: invalid request */ + + *lease = new_lease(pubkey, leasetime, ipv4, ipv6, &expires); + if (!*lease) + return 1; /* TODO: either out of IPs or IP unavailable */ + + return 0; +} + static bool send_response(int fd, struct wg_dynamic_request *req) { - int ret; char *errmsg = "OK"; + struct wg_dynamic_attr *cur = req->first; + struct wg_dynamic_lease *lease; unsigned char buf[MAX_RESPONSE_SIZE + 1]; size_t msglen; size_t written; - struct wg_dynamic_lease lease = { 0 }; - struct wg_peer *peer; + int ret = 0; - peer = current_peer(req); - if (!peer) - die("Unable to find peer\n"); - - ret = 0; - msglen = 0; switch (req->cmd) { case WGKEY_REQUEST_IP: msglen = print_to_buf((char *)buf, sizeof buf, 0, "%s=%d\n", WG_DYNAMIC_KEY[req->cmd], 1); - ret = allocate_from_pool(req, &lease); + ret = response_request_ip(cur, req->pubkey, &lease); if (ret) { - debug("IP address allocation failing with %d\n", ret); - ret = 0x02; - errmsg = "Out of IP addresses"; - break; - } - - ret = add_allowed_ips(peer, &lease); - if (ret) { - debug("Unable to add address(es) to peer: %s\n", - strerror(-ret)); - ret = 0x1; - errmsg = "Internal error"; - break; - } - - if (!serialise_lease((char *)buf, sizeof buf, &msglen, - &lease)) { - die("Nothing to hand out, despite succeeding allocate_from_pool()\n"); + errmsg = "Out of IP addresses"; /* TODO: distinguish */ break; } + add_allowed_ips(current_peer(req), &lease->ipv4, &lease->ipv6); + serialise_lease((char *)buf, sizeof buf, &msglen, lease); break; - default: debug("Unknown command: %d\n", req->cmd); return true; @@ -486,6 +446,7 @@ static void setup_socket(int *fd) static void cleanup() { + leases_free(); kh_destroy(allowedht, allowedips_ht); wg_free_device(device); @@ -519,6 +480,8 @@ int main(int argc, char *argv[]) allowedips_ht = kh_init(allowedht); + leases_init("leases_file"); + wg_interface = argv[1]; if (atexit(cleanup)) die("Failed to set exit function\n"); -- cgit v1.2.3-59-g8ed1b