aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lease.c211
-rw-r--r--lease.h23
-rw-r--r--wg-dynamic-server.c113
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, &current->ipv4, ipv4_asc,
+ INET_ADDRSTRLEN);
+ debug("extending %s\n", ipv4_asc);
+
+ memcpy(&lease->ipv4, &current->ipv4,
+ sizeof lease->ipv4);
+ memset(&current->ipv4, 0, sizeof current->ipv4);
+ ipv4_extended = true;
+ }
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&current->ipv6)) {
+ if (ipv6 && IN6_ARE_ADDR_EQUAL(ipv6, &current->ipv6)) {
+ inet_ntop(AF_INET6, &current->ipv6, ipv6_asc,
+ INET6_ADDRSTRLEN);
+ debug("extending %s\n", ipv6_asc);
+
+ memcpy(&lease->ipv6, &current->ipv6,
+ sizeof lease->ipv6);
+ memset(&current->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);
}