From 415a43be2ae31372a33e910913d6ae0ba401e167 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Sun, 29 Sep 2019 22:16:52 +0200 Subject: Server side lease handling Extend when requested, release when being replaced. Separate address families so that one can be granted even when the pool for the other is empty. --- lease.c | 211 ++++++++++++++++++++++++++++++++++++++++++++-------- lease.h | 23 ++++-- wg-dynamic-server.c | 113 ++++++++++++++++++---------- 3 files changed, 268 insertions(+), 79 deletions(-) diff --git a/lease.c b/lease.c index 5ea4f6f..e8eb125 100644 --- a/lease.c +++ b/lease.c @@ -91,9 +91,11 @@ void leases_free() } struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, - struct in_addr *ipv4, struct in6_addr *ipv6) + const struct in_addr *ipv4, + const struct in6_addr *ipv6, + struct wg_dynamic_lease *current) { - struct wg_dynamic_lease *lease, *parent; + struct wg_dynamic_lease *lease; uint64_t index_l; uint32_t index, index_h; struct timespec tp; @@ -101,34 +103,90 @@ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, int ret; bool wants_ipv4 = !ipv4 || ipv4->s_addr; bool wants_ipv6 = !ipv6 || !IN6_IS_ADDR_UNSPECIFIED(ipv6); + bool ipv4_extended = false; + bool ipv6_extended = false; - lease = malloc(sizeof *lease); - if (!lease) - fatal("malloc()"); + char ipv4_asc[INET_ADDRSTRLEN], ipv6_asc[INET6_ADDRSTRLEN]; + wg_key_b64_string pubkey_asc; + wg_key_to_base64(pubkey_asc, pubkey); - if (wants_ipv4 && !pool.total_ipv4) - return NULL; /* no ipv4 addresses available */ + lease = calloc(1, sizeof *lease); + if (!lease) + fatal("calloc()"); + + /* Extend addresses explicitly asked for and which we already have. */ + if (lease_is_valid(current)) { + if (current->ipv4.s_addr) { + if (ipv4 && ipv4->s_addr == current->ipv4.s_addr) { + inet_ntop(AF_INET, ¤t->ipv4, ipv4_asc, + INET_ADDRSTRLEN); + debug("extending %s\n", ipv4_asc); + + memcpy(&lease->ipv4, ¤t->ipv4, + sizeof lease->ipv4); + memset(¤t->ipv4, 0, sizeof current->ipv4); + ipv4_extended = true; + } + } + if (!IN6_IS_ADDR_UNSPECIFIED(¤t->ipv6)) { + if (ipv6 && IN6_ARE_ADDR_EQUAL(ipv6, ¤t->ipv6)) { + inet_ntop(AF_INET6, ¤t->ipv6, ipv6_asc, + INET6_ADDRSTRLEN); + debug("extending %s\n", ipv6_asc); + + memcpy(&lease->ipv6, ¤t->ipv6, + sizeof lease->ipv6); + memset(¤t->ipv6, 0, sizeof current->ipv6); + ipv6_extended = true; + } + } + } - if (wants_ipv6 && !pool.totalh_ipv6 && !pool.totall_ipv6) - return NULL; /* no ipv6 addresses available */ + if (ipv4) + inet_ntop(AF_INET, ipv4, ipv4_asc, INET_ADDRSTRLEN); /* DEBUG */ - if (wants_ipv4) { - if (!ipv4) { + /* Allocate IPv4 if wanted and not already extended. */ + if (wants_ipv4 && !ipv4_extended) { + if (!pool.total_ipv4) { + debug("IPv4 pool empty\n"); + } else if (!ipv4) { index = random_bounded(pool.total_ipv4 - 1); + debug("new_lease(v4): %u of %u\n", index, pool.total_ipv4); ipp_addnth_v4(&pool, &lease->ipv4, index); } else { - if (ipp_add_v4(&pool, ipv4, 32)) - return NULL; + debug("wants %s: ", ipv4_asc); - memcpy(&lease->ipv4, ipv4, sizeof *ipv4); + if (ipp_add_v4(&pool, ipv4, 32)) { + debug("busy, possibly by us: %s\n", + lease_to_str(current)); + } else { + debug("allocated\n"); + memcpy(&lease->ipv4, ipv4, sizeof lease->ipv4); + } } } - if (wants_ipv6) { - if (!ipv6) { + /* Release IPv4 if not wanted and not extended. */ + if (!wants_ipv4 && !ipv4_extended && ipv4 && ipv4->s_addr) { + debug("releasing %s\n", ipv4_asc); + + if (ipp_del_v4(&pool, &lease->ipv4, 32)) + die("ipp_del_v4()\n"); + memset(&lease->ipv4, 0, sizeof lease->ipv4); + } + + if (ipv6) + inet_ntop(AF_INET6, ipv6, ipv6_asc, + INET6_ADDRSTRLEN); /* DEBUG */ + + /* Allocate IPv6 if wanted and not already extended. */ + if (wants_ipv6 && !ipv6_extended) { + if (!pool.totalh_ipv6 && !pool.totall_ipv6) { + debug("IPv6 pool empty\n"); + } else if (!ipv6) { if (pool.totalh_ipv6 > 0) { index_l = random_bounded(UINT64_MAX); index_h = random_bounded(pool.totalh_ipv6 - 1); @@ -139,19 +197,38 @@ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, debug("new_lease(v6): %u:%ju of %u:%ju\n", index_h, index_l, pool.totalh_ipv6, pool.totall_ipv6); + ipp_addnth_v6(&pool, &lease->ipv6, index_l, index_h); } else { - if (ipp_add_v6(&pool, ipv6, 128)) { - if (!ipv4 || ipv4->s_addr) - ipp_del_v4(&pool, ipv4, 32); + debug("wants %s: ", ipv6_asc); - return NULL; + if (ipp_add_v6(&pool, ipv6, 128)) { + debug("busy, possibly by us: %s\n", + lease_to_str(current)); + } else { + debug("allocated\n"); + memcpy(&lease->ipv6, ipv6, sizeof lease->ipv6); } - - memcpy(&lease->ipv6, ipv6, sizeof *ipv6); } } + /* Release IPv6 if not wanted and not extended. */ + if (!wants_ipv6 && !ipv6_extended && ipv6 && + !IN6_IS_ADDR_UNSPECIFIED(ipv6)) { + debug("releasing %s\n", ipv6_asc); + + if (ipp_del_v6(&pool, &lease->ipv6, 128)) + die("ipp_del_v6()\n"); + memset(&lease->ipv6, 0, sizeof lease->ipv6); + } + + /* Return NULL if we didn't get at least one address. */ + if (!lease->ipv4.s_addr && IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) { + free(lease); + return NULL; + } + + /* Set leasetime. */ if (clock_gettime(CLOCK_REALTIME, &tp)) fatal("clock_gettime(CLOCK_REALTIME)"); @@ -160,6 +237,7 @@ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, lease->leasetime = leasetime; lease->next = NULL; + /* Update hash table. */ wg_key *pubcopy = malloc(sizeof(wg_key)); if (!pubcopy) fatal("malloc()"); @@ -169,14 +247,13 @@ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, if (ret < 0) { fatal("kh_put()"); } else if (ret == 0) { - parent = kh_value(leases_ht, k); + struct wg_dynamic_lease *parent = kh_value(leases_ht, k); while (parent->next) parent = parent->next; - parent->next = lease; - } else { + } else kh_value(leases_ht, k) = lease; - } + debug("new lease: %s\n", lease_to_str(lease)); if (lease->start_mono + lease->leasetime < gexpires) gexpires = lease->start_mono + lease->leasetime; @@ -196,10 +273,50 @@ struct wg_dynamic_lease *get_leases(wg_key pubkey) return kh_val(leases_ht, k); } -bool extend_lease(struct wg_dynamic_lease *lease, uint32_t leasetime) +bool release_lease(struct wg_dynamic_lease *lease, wg_key pubkey) { - UNUSED(lease); - UNUSED(leasetime); + struct wg_dynamic_lease *first, *iter; + khiter_t k; + + if (!lease) + return true; + + k = kh_get(leaseht, leases_ht, pubkey); + if (k == kh_end(leases_ht)) + return true; + first = kh_val(leases_ht, k); + + for (iter = first; iter; iter = iter->next) + if (iter == lease) + break; + if (iter != lease) + return true; + + debug("Releasing lease: %s\n", lease_to_str(lease)); + if (lease->ipv4.s_addr && ipp_del_v4(&pool, &lease->ipv4, 32)) { + debug("Unable to delete IPv4 address from pool: %s\n", + lease_to_str(lease)); + die("ipp_del_v4()\n"); + } + if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6) && + ipp_del_v6(&pool, &lease->ipv6, 128)) { + debug("Unable to delete IPv6 address from pool: %s\n", + lease_to_str(lease)); + die("ipp_del_v6()\n"); + } + + if (lease == first) { + if (lease->next) { + kh_val(leases_ht, k) = lease->next; + } else { + kh_del(leaseht, leases_ht, k); + } + } else { + BUG_ON(first->next == NULL); + first->next = NULL; + } + free(lease); + return false; } @@ -230,11 +347,14 @@ int leases_refresh(void (*update_cb)(wg_key *, int)) if (!IN6_IS_ADDR_UNSPECIFIED(ipv6)) ipp_del_v6(&pool, ipv6, 128); - memcpy(updates[i], kh_key(leases_ht, k), sizeof(wg_key)); + memcpy(updates[i], kh_key(leases_ht, k), + sizeof(wg_key)); { wg_key_b64_string pubkey_asc; - wg_key_to_base64(pubkey_asc, updates[i]); - debug("Peer losing its lease: %s\n", pubkey_asc); + wg_key_to_base64(pubkey_asc, + updates[i]); + debug("Peer losing its lease: %s\n", + pubkey_asc); } i++; if (i == WG_DYNAMIC_LEASE_CHUNKSIZE) { @@ -365,3 +485,30 @@ void leases_update_pools(struct mnl_socket *nlsock) if (ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK) fatal("mnl_socket_recvfrom()"); } + +bool lease_is_valid(const struct wg_dynamic_lease *lease) +{ + if (!lease) + return false; + + if (get_monotonic_time() >= lease->start_mono + lease->leasetime) + return false; + + return true; +} + +#ifdef DEBUG +char *lease_to_str(const struct wg_dynamic_lease *l) +{ + static char buf[4096]; + char v4[INET_ADDRSTRLEN], v6[INET6_ADDRSTRLEN]; + + if (!l) + return "(null)"; + + inet_ntop(AF_INET, &l->ipv4, v4, sizeof v4); + inet_ntop(AF_INET6, &l->ipv6, v6, sizeof v6); + snprintf(buf, sizeof buf, "(%p) %s [%s]", l, v4, v6); + return buf; +} +#endif diff --git a/lease.h b/lease.h index 23852e6..8e83135 100644 --- a/lease.h +++ b/lease.h @@ -37,10 +37,12 @@ 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. + * of assignable IPs or if requested IP(s) are already taken. */ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, - struct in_addr *ipv4, struct in6_addr *ipv6); + const struct in_addr *ipv4, + const struct in6_addr *ipv6, + struct wg_dynamic_lease *current); /* * Returns all leases belonging to pubkey, or NULL if there are none. @@ -48,12 +50,12 @@ struct wg_dynamic_lease *new_lease(wg_key pubkey, uint32_t leasetime, struct wg_dynamic_lease *get_leases(wg_key pubkey); /* - * Extend the lease to be leasetime seconds long again. Returns true on error, - * or false otherwise. + * Release the lease and free allocated memory. */ -bool extend_lease(struct wg_dynamic_lease *lease, uint32_t leasetime); +bool release_lease(struct wg_dynamic_lease *lease, wg_key pubkey); -/* Refreshes all leases, meaning expired ones will be removed. Returns the +/* + * Refreshes all leases, meaning expired ones will be removed. Returns the * amount of seconds until the next lease will expire, or at most INT_MAX/1000. */ int leases_refresh(void (*update_cb)(wg_key *, int)); @@ -63,4 +65,13 @@ int leases_refresh(void (*update_cb)(wg_key *, int)); */ void leases_update_pools(struct mnl_socket *nlsock); +/* + * Return true if lease is !NULL and has not expired. + */ +bool lease_is_valid(const struct wg_dynamic_lease *lease); + +#ifdef DEBUG +char *lease_to_str(const struct wg_dynamic_lease *l); +#endif + #endif diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c index d1229cc..2b073f1 100644 --- a/wg-dynamic-server.c +++ b/wg-dynamic-server.c @@ -87,8 +87,7 @@ static int data_cb(const struct nlmsghdr *nlh, void *data) static bool validate_link_local_ip(uint32_t ifindex) { struct mnl_cb_data cb_data = { - .ifindex = ifindex, - .valid_ip_found = false, + .ifindex = ifindex, .valid_ip_found = false, }; iface_get_all_addrs(AF_INET6, data_cb, &cb_data); @@ -228,18 +227,22 @@ static bool send_error(struct wg_dynamic_request *req, int error) char buf[MAX_RESPONSE_SIZE]; size_t msglen = 0; - print_to_buf(buf, sizeof buf, &msglen, "errno=%d\nerrmsg=%s\n\n", error, - WG_DYNAMIC_ERR[error]); + debug("Parse error, errno=%d\n", error); + + print_to_buf(buf, sizeof buf, &msglen, "errno=%d\nerrmsg=%s\n\n", + E_INVALID_REQ, WG_DYNAMIC_ERR[E_INVALID_REQ]); return send_message(req, buf, msglen); } -static size_t serialize_lease(char *buf, size_t len, - const struct wg_dynamic_lease *lease) +static size_t serialize_request_ip(char *buf, size_t len, + const struct wg_dynamic_lease *lease) { char addrbuf[INET6_ADDRSTRLEN]; size_t off = 0; + print_to_buf(buf, len, &off, "request_ip=1\n"); + if (lease->ipv4.s_addr) { if (!inet_ntop(AF_INET, &lease->ipv4, addrbuf, sizeof addrbuf)) fatal("inet_ntop()"); @@ -254,47 +257,69 @@ static size_t serialize_lease(char *buf, size_t len, print_to_buf(buf, len, &off, "ipv6=%s/%d\n", addrbuf, 128); } - print_to_buf(buf, len, &off, "leasestart=%u\nleasetime=%u\nerrno=0\n\n", + print_to_buf(buf, len, &off, "leasestart=%u\nleasetime=%u\n", lease->start_real, lease->leasetime); return off; } -static void add_allowed_ips(wg_key pubkey, struct in_addr *ipv4, - struct in6_addr *ipv6) +static void adjust_allowed_ips(wg_key pubkey, struct in_addr *ipv4, + struct in6_addr *ipv6) { - wg_allowedip allowed_v4, allowed_v6; + wg_allowedip lladdr, allowed_v4, allowed_v6; wg_peer peer = { 0 }; - wg_device dev = { .first_peer = &peer }; + wg_device dev = {.first_peer = &peer }; strcpy(dev.name, wg_interface); memcpy(peer.public_key, pubkey, sizeof peer.public_key); wg_allowedip **cur = &peer.first_allowedip; - if (ipv4) { + wg_peer *ourpeer; + wg_for_each_peer (device, ourpeer) { + if (!memcmp(ourpeer->public_key, pubkey, sizeof(wg_key))) { + peer.flags |= WGPEER_REPLACE_ALLOWEDIPS; + memcpy(peer.public_key, pubkey, sizeof(wg_key)); + + wg_allowedip *allowedip; + wg_for_each_allowedip (ourpeer, allowedip) { + if (allowedip->family == AF_INET6 && + allowedip->cidr == 128 && + IN6_IS_ADDR_LINKLOCAL(&allowedip->ip6)) { + lladdr.family = AF_INET6; + memcpy(&lladdr.ip6, &allowedip->ip6, + sizeof(struct in6_addr)); + lladdr.cidr = 128; + *cur = &lladdr; + cur = &lladdr.next_allowedip; + break; + } + } + break; + } + } + + if (ipv4 && ipv4->s_addr) { allowed_v4 = (wg_allowedip){ - .family = AF_INET, - .cidr = 32, - .ip4 = *ipv4, + .family = AF_INET, .cidr = 32, .ip4 = *ipv4, }; *cur = &allowed_v4; cur = &allowed_v4.next_allowedip; } - if (ipv6) { + if (ipv6 && !IN6_IS_ADDR_UNSPECIFIED(ipv6)) { allowed_v6 = (wg_allowedip){ - .family = AF_INET6, - .cidr = 128, - .ip6 = *ipv6, + .family = AF_INET6, .cidr = 128, .ip6 = *ipv6, }; *cur = &allowed_v6; } - if (wg_set_device(&dev)) - fatal("wg_set_device()"); + if ((ipv4 && ipv4->s_addr) || (ipv6 && !IN6_IS_ADDR_UNSPECIFIED(ipv6))) + if (wg_set_device(&dev)) + fatal("wg_set_device()"); } -/* TODO: have UPDATES contain {wg_key, ip4 and ip6} and remove only matching addrs */ +/* TODO: have UPDATES contain {wg_key, ip4, ip6} and remove only matching addrs */ +/* FIXME: rename to remove_allowed_ips() */ static void update_allowed_ips(wg_key *updates, int nupdates) { wg_device newdev = { 0 }; @@ -348,13 +373,12 @@ static void update_allowed_ips(wg_key *updates, int nupdates) } static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey, - struct wg_dynamic_lease **lease) + struct wg_dynamic_lease **lease_out) { struct in_addr *ipv4 = NULL; struct in6_addr *ipv6 = NULL; uint32_t leasetime = WG_DYNAMIC_LEASETIME; - - *lease = get_leases(pubkey); + struct wg_dynamic_lease *current = NULL; while (cur) { switch (cur->key) { @@ -364,9 +388,6 @@ static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey, 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); @@ -374,12 +395,12 @@ static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey, cur = cur->next; } - if (ipv4 && ipv6 && !ipv4->s_addr && IN6_IS_ADDR_UNSPECIFIED(ipv6)) - return E_INVALID_REQ; + current = get_leases(pubkey); + debug("current lease: %s\n", lease_to_str(current)); + + *lease_out = new_lease(pubkey, leasetime, ipv4, ipv6, current); - *lease = new_lease(pubkey, leasetime, ipv4, ipv6); - if (!*lease) - return E_IP_UNAVAIL; + release_lease(current, pubkey); return E_NO_ERROR; } @@ -388,27 +409,37 @@ static bool send_response(struct wg_dynamic_request *req) { char buf[MAX_RESPONSE_SIZE]; struct wg_dynamic_attr *cur = req->first; - struct wg_dynamic_lease *lease; - size_t msglen; - int ret; + size_t msglen = 0; + int ret = 0; switch (req->cmd) { - case WGKEY_REQUEST_IP: + case WGKEY_REQUEST_IP: { + struct wg_dynamic_lease *lease = NULL; ret = response_request_ip(cur, req->pubkey, &lease); if (ret) break; - add_allowed_ips(req->pubkey, &lease->ipv4, &lease->ipv6); - msglen = serialize_lease(buf, sizeof buf, lease); + if (lease) { + adjust_allowed_ips(req->pubkey, &lease->ipv4, + &lease->ipv6); + msglen = serialize_request_ip(buf, sizeof buf, lease); + } break; + } default: debug("Unknown command: %d\n", req->cmd); BUG(); } - if (ret) - return send_error(req, ret); + if (ret) { + print_to_buf(buf, sizeof buf, &msglen, + "request_ip=1\nerrno=%d\nerrmsg=%s\n\n", ret, + WG_DYNAMIC_ERR[ret]); + + return send_message(req, buf, msglen); + } + print_to_buf(buf, sizeof buf, &msglen, "errno=0\n\n"); return send_message(req, buf, msglen); } -- cgit v1.2.3-59-g8ed1b