aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--common.c503
-rw-r--r--common.h49
-rw-r--r--docs/ip-request.md255
-rw-r--r--docs/protocol.md18
-rw-r--r--ipm.c216
-rw-r--r--ipm.h21
-rw-r--r--khash.h28
-rw-r--r--lease.c479
-rw-r--r--lease.h22
-rw-r--r--radix-trie.c476
-rw-r--r--radix-trie.h41
-rw-r--r--random.c27
-rw-r--r--random.h4
-rw-r--r--siphash.c204
-rw-r--r--siphash.h85
-rwxr-xr-xtests/clientsh.bash302
-rwxr-xr-xtests/netsh.sh373
-rw-r--r--wg-dynamic-client.c573
-rw-r--r--wg-dynamic-server.c346
20 files changed, 2055 insertions, 1975 deletions
diff --git a/Makefile b/Makefile
index 7d7a327..cc55d5b 100644
--- a/Makefile
+++ b/Makefile
@@ -43,10 +43,10 @@ COMPILE.c = @echo " CC $@";
COMPILE.c += $(BUILT_IN_COMPILE.c)
endif
-all: wg-dynamic-server
+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 random.o lease.o
+wg-dynamic-client: wg-dynamic-client.o netlink.o common.o ipm.o
+wg-dynamic-server: wg-dynamic-server.o netlink.o radix-trie.o common.o random.o lease.o ipm.o siphash.o
ifneq ($(V),1)
clean:
@@ -56,7 +56,7 @@ clean:
$(RM) wg-dynamic-client wg-dynamic-server *.o *.d
endif
-install: wg
+install: all
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 wg-dynamic-server "$(DESTDIR)$(BINDIR)/wg-dynamic-server"
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 wg-dynamic-server "$(DESTDIR)$(BINDIR)/wg-dynamic-client"
@install -v -d "$(DESTDIR)$(MANDIR)/man8" && install -v -m 0644 man/wg-dynamic-server.8 "$(DESTDIR)$(MANDIR)/man8/wg-dynamic-server.8"
diff --git a/common.c b/common.c
index 3edf872..2ad5717 100644
--- a/common.c
+++ b/common.c
@@ -20,311 +20,336 @@
#include "common.h"
#include "dbg.h"
-static bool parse_ip_cidr(struct wg_combined_ip *ip, char *value)
+union kvalues {
+ uint32_t u32;
+ struct wg_combined_ip ip;
+ char errmsg[256];
+};
+
+static void request_ip(enum wg_dynamic_key key, union kvalues kv, void **dest)
{
- uintmax_t res;
- char *endptr;
- char *sep;
+ struct wg_combined_ip *ip = &kv.ip;
+ struct wg_dynamic_request_ip *r = (struct wg_dynamic_request_ip *)*dest;
- if (value[0] == '\0') {
- memset(ip, 0, ip->family == AF_INET ? 4 : 16);
- ip->cidr = 0;
- return true;
+ switch (key) {
+ case WGKEY_REQUEST_IP:
+ BUG_ON(*dest);
+ *dest = calloc(1, sizeof(struct wg_dynamic_request_ip));
+ if (!*dest)
+ fatal("calloc()");
+
+ break;
+ case WGKEY_IP:
+ if (ip->family == AF_INET) {
+ memcpy(&r->ipv4, &ip->ip4, sizeof r->ipv4);
+ r->cidrv4 = ip->cidr;
+ r->has_ipv4 = true;
+ } else {
+ memcpy(&r->ipv6, &ip->ip6, sizeof r->ipv6);
+ r->cidrv6 = ip->cidr;
+ r->has_ipv6 = true;
+ }
+ break;
+ case WGKEY_LEASESTART:
+ r->start = kv.u32;
+ break;
+ case WGKEY_LEASETIME:
+ r->leasetime = kv.u32;
+ break;
+ case WGKEY_ERRNO:
+ r->wg_errno = kv.u32;
+ break;
+ case WGKEY_ERRMSG:
+ r->errmsg = strdup(kv.errmsg);
+ break;
+ default:
+ debug("Invalid key %d, aborting\n", key);
+ BUG();
}
+}
+
+static void (*const deserialize_fptr[])(enum wg_dynamic_key key,
+ union kvalues kv, void **dest) = {
+ NULL,
+ NULL,
+ request_ip,
+};
+
+static bool parse_ip_cidr(struct wg_combined_ip *ip, char *value)
+{
+ char *sep;
sep = strchr(value, '/');
- if (!sep)
- return false;
+ if (sep) {
+ char *endptr;
+ uintmax_t res = strtoumax(sep + 1, &endptr, 10);
+ if (res > UINT8_MAX || *endptr != '\0' || sep + 1 == endptr)
+ return false;
+
+ if ((ip->family == AF_INET && res > 32) ||
+ (ip->family == AF_INET6 && res > 128))
+ return false;
+
+ *sep = '\0';
+ ip->cidr = (uint8_t)res;
+ } else {
+ ip->cidr = ip->family == AF_INET ? 32 : 128;
+ }
- *sep = '\0';
if (inet_pton(ip->family, value, ip) != 1)
return false;
- res = strtoumax(sep + 1, &endptr, 10);
- if (res > UINT8_MAX || *endptr != '\0' || sep + 1 == endptr)
- return false;
-
- // TODO: validate cidr range depending on ip->family
- ip->cidr = (uint8_t)res;
-
return true;
}
-static struct wg_dynamic_attr *parse_value(enum wg_dynamic_key key, char *value)
+static bool parse_value(enum wg_dynamic_key key, char *str, union kvalues *kv)
{
- struct wg_dynamic_attr *attr;
- size_t len;
char *endptr;
uintmax_t uresult;
- union {
- uint32_t uint32;
- char errmsg[72];
- struct wg_combined_ip ip;
- } data = { 0 };
+ struct wg_combined_ip *ip;
switch (key) {
- case WGKEY_IPV4:
- len = sizeof data.ip;
- data.ip.family = AF_INET;
- if (!parse_ip_cidr(&data.ip, value))
- return NULL;
-
- break;
- case WGKEY_IPV6:
- len = sizeof data.ip;
- data.ip.family = AF_INET6;
- if (!parse_ip_cidr(&data.ip, value))
- return NULL;
+ case WGKEY_IP:
+ ip = &kv->ip;
+ ip->family = strchr(str, ':') ? AF_INET6 : AF_INET;
+ if (!parse_ip_cidr(ip, str))
+ return false;
break;
+ case WGKEY_REQUEST_IP:
case WGKEY_LEASESTART:
case WGKEY_LEASETIME:
case WGKEY_ERRNO:
- len = sizeof data.uint32;
- uresult = strtoumax(value, &endptr, 10);
+ uresult = strtoumax(str, &endptr, 10);
if (uresult > UINT32_MAX || *endptr != '\0')
- return NULL;
- data.uint32 = (uint32_t)uresult;
+ return false;
+ kv->u32 = (uint32_t)uresult;
break;
case WGKEY_ERRMSG:
- strncpy(data.errmsg, value, sizeof data.errmsg - 1);
- data.errmsg[sizeof data.errmsg - 1] = '\0';
- len = MIN(sizeof data.errmsg,
- strlen(value) + 1); /* Copying the NUL byte too. */
-
+ strncpy(kv->errmsg, str, sizeof kv->errmsg);
+ kv->errmsg[sizeof kv->errmsg - 1] = '\0';
break;
default:
debug("Invalid key %d, aborting\n", key);
BUG();
}
- attr = malloc(sizeof(struct wg_dynamic_attr) + len);
- if (!attr)
- fatal("malloc()");
-
- attr->len = len;
- attr->key = key;
- attr->next = NULL;
- memcpy(&attr->value, &data, len);
-
- return attr;
+ return true;
}
static enum wg_dynamic_key parse_key(char *key)
{
- for (enum wg_dynamic_key e = 1; e < ARRAY_SIZE(WG_DYNAMIC_KEY); ++e)
+ for (enum wg_dynamic_key e = 2; e < ARRAY_SIZE(WG_DYNAMIC_KEY); ++e)
if (!strcmp(key, WG_DYNAMIC_KEY[e]))
return e;
return WGKEY_UNKNOWN;
}
-/* Consumes one full line from buf, or up to MAX_LINESIZE-1 bytes if no newline
- * character was found.
- * If req != NULL then we expect to parse a command and will set cmd and version
- * of req accordingly, while *attr will be set to NULL.
- * Otherwise we expect to parse a normal key=value pair, that will be stored
- * in a newly allocated wg_dynamic_attr, pointed to by *attr.
+/* Consumes one full line from buf, or up to MAX_LINESIZE bytes if no newline
+ * character was found. If less then MAX_LINESIZE bytes are available, a new
+ * buffer will be allocated and req->buf and req->len set accordingly.
*
* Return values:
* > 0 : Amount of bytes consumed (<= MAX_LINESIZE)
+ * = 0 : Consumed len bytes; need more for a full line
* < 0 : Error
- * = 0 : End of message
*/
static ssize_t parse_line(unsigned char *buf, size_t len,
- struct wg_dynamic_attr **attr,
- struct wg_dynamic_request *req)
+ struct wg_dynamic_request *req,
+ enum wg_dynamic_key *key, union kvalues *kv)
{
unsigned char *line_end, *key_end;
- enum wg_dynamic_key key;
ssize_t line_len;
- char *endptr;
- uintmax_t res;
- line_end = memchr(buf, '\n', len > MAX_LINESIZE ? MAX_LINESIZE : len);
+ line_end = memchr(buf, '\n', MIN(len, MAX_LINESIZE));
if (!line_end) {
if (len >= MAX_LINESIZE)
return -E2BIG;
- *attr = malloc(sizeof(struct wg_dynamic_attr) + len);
- if (!*attr)
+ req->len = len;
+ req->buf = malloc(len);
+ if (!req->buf)
fatal("malloc()");
- (*attr)->key = WGKEY_INCOMPLETE;
- (*attr)->len = len;
- (*attr)->next = NULL;
- memcpy((*attr)->value, buf, len);
-
- return len;
+ memcpy(req->buf, buf, len);
+ return 0;
}
- if (line_end == buf)
- return 0; /* \n\n - end of message */
+ if (line_end == buf) {
+ *key = WGKEY_EOMSG;
+ return 1;
+ }
*line_end = '\0';
line_len = line_end - buf + 1;
key_end = memchr(buf, '=', line_len - 1);
- if (!key_end)
+ if (!key_end || key_end == buf)
return -EINVAL;
*key_end = '\0';
- key = parse_key((char *)buf);
- if (key == WGKEY_UNKNOWN)
- return -ENOENT;
-
- if (req) {
- if (key >= WGKEY_ENDCMD)
- return -ENOENT;
+ *key = parse_key((char *)buf);
+ if (*key == WGKEY_UNKNOWN)
+ return line_len;
- *attr = NULL;
- res = strtoumax((char *)key_end + 1, &endptr, 10);
-
- if (res > UINT32_MAX || *endptr != '\0')
- return -EINVAL;
-
- req->cmd = key;
- req->version = (uint32_t)res;
-
- if (req->version != 1)
- return -EPROTONOSUPPORT;
- } else {
- if (key <= WGKEY_ENDCMD)
- return -ENOENT;
-
- *attr = parse_value(key, (char *)key_end + 1);
- if (!*attr)
- return -EINVAL;
- }
+ if (!parse_value(*key, (char *)key_end + 1, kv))
+ return -EINVAL;
return line_len;
}
-static int parse_request(struct wg_dynamic_request *req, unsigned char *buf,
- size_t len)
+static ssize_t parse_request(struct wg_dynamic_request *req, unsigned char *buf,
+ size_t len)
{
- struct wg_dynamic_attr *attr;
- size_t offset = 0;
- ssize_t ret;
+ ssize_t ret, offset = 0;
+ enum wg_dynamic_key key;
+ union kvalues kv;
+ void (*deserialize)(enum wg_dynamic_key key, union kvalues kv,
+ void **dest);
if (memchr(buf, '\0', len))
return -EINVAL; /* don't allow null bytes */
- if (req->last && req->last->key == WGKEY_INCOMPLETE) {
- len += req->last->len;
+ if (req->cmd == WGKEY_UNKNOWN) {
+ ret = parse_line(buf, len, req, &req->cmd, &kv);
+ if (ret <= 0)
+ return ret;
- memmove(buf + req->last->len, buf, len);
- memcpy(buf, req->last->value, req->last->len);
- free(req->last);
+ req->version = kv.u32;
+ if (req->cmd >= WGKEY_ENDCMD || req->cmd <= WGKEY_EOMSG ||
+ req->version != 1)
+ return -EPROTONOSUPPORT;
- if (req->first == req->last) {
- req->first = NULL;
- req->last = NULL;
- } else {
- attr = req->first;
- while (attr->next != req->last)
- attr = attr->next;
+ len -= ret;
+ offset += ret;
- attr->next = NULL;
- req->last = attr;
- }
+ deserialize = deserialize_fptr[req->cmd];
+ deserialize(req->cmd, kv, &req->result);
+ } else {
+ deserialize = deserialize_fptr[req->cmd];
}
while (len > 0) {
- ret = parse_line(buf + offset, len, &attr,
- req->cmd == WGKEY_UNKNOWN ? req : NULL);
+ ret = parse_line(buf + offset, len, req, &key, &kv);
if (ret <= 0)
- return ret; /* either error or message complete */
+ return ret;
len -= ret;
offset += ret;
- if (!attr)
- continue;
- if (!req->first)
- req->first = attr;
- else
- req->last->next = attr;
+ if (key == WGKEY_EOMSG)
+ return offset;
+ else if (key == WGKEY_UNKNOWN)
+ continue;
+ else if (key <= WGKEY_ENDCMD)
+ return -EINVAL;
- req->last = attr;
+ deserialize(key, kv, &req->result);
}
- return 1;
+ return 0;
}
-bool handle_request(struct wg_dynamic_request *req,
- bool (*success)(struct wg_dynamic_request *),
- bool (*error)(struct wg_dynamic_request *, int))
+int handle_request(int fd, struct wg_dynamic_request *req,
+ unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE],
+ size_t *remaining)
{
- ssize_t bytes;
- int ret;
- unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE];
+ ssize_t bytes, processed;
+ size_t leftover;
- while (1) {
- bytes = read(req->fd, buf, RECV_BUFSIZE);
- if (bytes < 0) {
- if (errno == EWOULDBLOCK || errno == EAGAIN)
- break;
+ BUG_ON((*remaining > 0 && req->buf) || (req->buf && !req->len));
- // TODO: handle EINTR
+ do {
+ leftover = req->len;
+ if (*remaining > 0)
+ bytes = *remaining;
+ else
+ bytes = read(fd, buf + leftover, RECV_BUFSIZE);
- debug("Reading from socket %d failed: %s\n", req->fd,
+ if (bytes < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN ||
+ errno == EINTR)
+ return 0;
+
+ debug("Reading from socket %d failed: %s\n", fd,
strerror(errno));
- return true;
+ return -1;
} else if (bytes == 0) {
- debug("Peer disconnected unexpectedly\n");
- return true;
+ return -1;
}
- ret = parse_request(req, buf, bytes);
- if (ret < 0)
- return error(req, -ret);
- else if (ret == 0)
- return success(req);
- }
+ if (req->buf) {
+ memcpy(buf, req->buf, leftover);
+ free(req->buf);
+ req->buf = NULL;
+ req->len = 0;
+ }
- return false;
+ processed = parse_request(req, buf, bytes + leftover);
+ if (processed < 0)
+ return processed; /* Parsing error */
+ if (!processed)
+ *remaining = 0;
+ } while (processed == 0);
+
+ *remaining = (bytes + leftover) - processed;
+ memmove(buf, buf + processed, *remaining);
+
+ return 1;
}
-bool send_message(struct wg_dynamic_request *req, const void *buf, size_t len)
+void free_wg_dynamic_request(struct wg_dynamic_request *req)
{
- size_t offset = 0;
+ BUG_ON(req->buf || req->len);
- while (1) {
- ssize_t written = write(req->fd, buf + offset, len - offset);
- if (written < 0) {
- if (errno == EWOULDBLOCK || errno == EAGAIN)
- break;
+ req->cmd = WGKEY_UNKNOWN;
+ req->version = 0;
+ if (req->result) {
+ free(((struct wg_dynamic_request_ip *)req->result)->errmsg);
+ free(req->result);
+ req->result = NULL;
+ }
+}
- // TODO: handle EINTR
+size_t serialize_request_ip(bool send, char *buf, size_t len,
+ struct wg_dynamic_request_ip *rip)
+{
+ size_t off = 0;
+ char addrbuf[INET6_ADDRSTRLEN];
- debug("Writing to socket %d failed: %s\n", req->fd,
- strerror(errno));
- return true;
- }
+ if (send)
+ print_to_buf(buf, len, &off, "request_ip=1\n");
- offset += written;
- if (offset == len)
- return true;
+ if (rip->has_ipv4) {
+ if (!inet_ntop(AF_INET, &rip->ipv4, addrbuf, sizeof addrbuf))
+ fatal("inet_ntop()");
+
+ print_to_buf(buf, len, &off, "ip=%s/32\n", addrbuf);
}
- debug("Socket %d blocking on write with %lu bytes left, postponing\n",
- req->fd, len - offset);
+ if (rip->has_ipv6) {
+ if (!inet_ntop(AF_INET6, &rip->ipv6, addrbuf, sizeof addrbuf))
+ fatal("inet_ntop()");
- if (!req->buf) {
- req->buflen = len - offset;
- req->buf = malloc(req->buflen);
- if (!req->buf)
- fatal("malloc()");
-
- memcpy(req->buf, buf + offset, req->buflen);
- } else {
- req->buflen = len - offset;
- memmove(req->buf, buf + offset, req->buflen);
+ print_to_buf(buf, len, &off, "ip=%s/128\n", addrbuf);
}
- return false;
+ if (rip->has_ipv4 || rip->has_ipv6)
+ print_to_buf(buf, len, &off, "leasestart=%u\nleasetime=%u\n",
+ rip->start, rip->leasetime);
+
+ if (!send)
+ print_to_buf(buf, len, &off, "errno=%u\n", rip->wg_errno);
+
+ if (rip->wg_errno)
+ print_to_buf(buf, len, &off, "errmsg=%s\n",
+ WG_DYNAMIC_ERR[rip->wg_errno]);
+
+ print_to_buf(buf, len, &off, "\n");
+
+ return off;
}
void print_to_buf(char *buf, size_t bufsize, size_t *offset, char *fmt, ...)
@@ -345,106 +370,8 @@ void print_to_buf(char *buf, size_t bufsize, size_t *offset, char *fmt, ...)
*offset += n;
}
-uint32_t current_time()
-{
- struct timespec tp;
- if (clock_gettime(CLOCK_REALTIME, &tp))
- fatal("clock_gettime(CLOCK_REALTIME)");
- return tp.tv_sec;
-}
-
-void close_connection(struct wg_dynamic_request *req)
-{
- struct wg_dynamic_attr *prev, *cur = req->first;
-
- if (close(req->fd))
- debug("Failed to close socket\n");
-
- while (cur) {
- prev = cur;
- cur = cur->next;
- free(prev);
- }
-
- req->cmd = WGKEY_UNKNOWN;
- req->version = 0;
- req->fd = -1;
- free(req->buf);
- req->buf = NULL;
- req->buflen = 0;
- req->first = NULL;
- req->last = NULL;
-}
-
bool is_link_local(unsigned char *addr)
{
/* TODO: check if the remaining 54 bits are 0 */
return IN6_IS_ADDR_LINKLOCAL(addr);
}
-
-void iface_get_all_addrs(uint8_t family, mnl_cb_t data_cb, void *cb_data)
-{
- struct mnl_socket *nl;
- char buf[MNL_SOCKET_BUFFER_SIZE];
- struct nlmsghdr *nlh;
- /* TODO: rtln-addr-dump from libmnl uses rtgenmsg here? */
- struct ifaddrmsg *ifaddr;
- int ret;
- unsigned int seq, portid;
-
- nl = mnl_socket_open(NETLINK_ROUTE);
- if (nl == NULL)
- fatal("mnl_socket_open");
-
- if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
- fatal("mnl_socket_bind");
-
- /* You'd think that we could just request addresses from a specific
- * interface, via NLM_F_MATCH or something, but we can't. See also:
- * https://marc.info/?l=linux-netdev&m=132508164508217
- */
- seq = time(NULL);
- portid = mnl_socket_get_portid(nl);
- nlh = mnl_nlmsg_put_header(buf);
- nlh->nlmsg_type = RTM_GETADDR;
- nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- nlh->nlmsg_seq = seq;
- ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
- ifaddr->ifa_family = family;
-
- if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
- fatal("mnl_socket_sendto");
-
- do {
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- if (ret <= MNL_CB_STOP)
- break;
- ret = mnl_cb_run(buf, ret, seq, portid, data_cb, cb_data);
- } while (ret > 0);
-
- if (ret == -1)
- fatal("mnl_cb_run/mnl_socket_recvfrom");
-
- mnl_socket_close(nl);
-}
-
-int data_attr_cb(const struct nlattr *attr, void *data)
-{
- const struct nlattr **tb = data;
- int type = mnl_attr_get_type(attr);
-
- /* skip unsupported attribute in user-space */
- if (mnl_attr_type_valid(attr, IFA_MAX) < 0)
- return MNL_CB_OK;
-
- switch (type) {
- case IFA_ADDRESS:
- if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) {
- perror("mnl_attr_validate");
- return MNL_CB_ERROR;
- }
- break;
- }
- tb[type] = attr;
- return MNL_CB_OK;
-}
diff --git a/common.h b/common.h
index ea550d9..bdd5bc1 100644
--- a/common.h
+++ b/common.h
@@ -15,27 +15,21 @@
#include "netlink.h"
#define MAX_CONNECTIONS 16
-
#define MAX_LINESIZE 4096
-
#define RECV_BUFSIZE 8192
-
#define MAX_RESPONSE_SIZE 8192
static const char WG_DYNAMIC_ADDR[] = "fe80::";
static const uint16_t WG_DYNAMIC_PORT = 970; /* ASCII sum of "wireguard" */
-#define WG_DYNAMIC_DEFAULT_LEASETIME 3600
-
#define ITEMS \
E(WGKEY_UNKNOWN, "") /* must be the first entry */ \
+ E(WGKEY_EOMSG, "") \
/* CMD START */ \
E(WGKEY_REQUEST_IP, "request_ip") \
E(WGKEY_ENDCMD, "") \
/* CMD END */ \
- E(WGKEY_INCOMPLETE, "") \
- E(WGKEY_IPV4, "ipv4") \
- E(WGKEY_IPV6, "ipv6") \
+ E(WGKEY_IP, "ip") \
E(WGKEY_LEASESTART, "leasestart") \
E(WGKEY_LEASETIME, "leasetime") \
E(WGKEY_ERRNO, "errno") \
@@ -52,7 +46,8 @@ static const char *const WG_DYNAMIC_KEY[] = { ITEMS };
#define ITEMS \
E(E_NO_ERROR, "Success") /* must be the first entry */ \
E(E_INVALID_REQ, "Invalid request") \
- E(E_IP_UNAVAIL, "Chosen IP unavailable")
+ E(E_UNSUPP_PROTO, "Unsupported protocol") \
+ E(E_IP_UNAVAIL, "Chosen IP(s) unavailable")
#define E(x, y) x,
enum wg_dynamic_err { ITEMS };
@@ -62,22 +57,21 @@ static const char *const WG_DYNAMIC_ERR[] = { ITEMS };
#undef E
#undef ITEMS
-struct wg_dynamic_attr {
- enum wg_dynamic_key key;
- size_t len;
- struct wg_dynamic_attr *next;
- unsigned char value[];
-};
-
struct wg_dynamic_request {
enum wg_dynamic_key cmd;
uint32_t version;
- int fd;
- wg_key pubkey;
- struct in6_addr lladdr;
unsigned char *buf;
- size_t buflen;
- struct wg_dynamic_attr *first, *last;
+ size_t len; /* <= MAX_LINESIZE */
+ void *result;
+};
+
+struct wg_dynamic_request_ip {
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+ uint8_t cidrv4, cidrv6;
+ uint32_t leasetime, start, wg_errno;
+ bool has_ipv4, has_ipv6;
+ char *errmsg;
};
struct wg_combined_ip {
@@ -91,15 +85,12 @@ struct wg_combined_ip {
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+int handle_request(int fd, struct wg_dynamic_request *req,
+ unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE],
+ size_t *remaining);
void free_wg_dynamic_request(struct wg_dynamic_request *req);
-bool handle_request(struct wg_dynamic_request *req,
- bool (*success)(struct wg_dynamic_request *),
- bool (*error)(struct wg_dynamic_request *, int));
-bool send_message(struct wg_dynamic_request *req, const void *buf, size_t len);
+size_t serialize_request_ip(bool include_header, char *buf, size_t len,
+ struct wg_dynamic_request_ip *rip);
void print_to_buf(char *buf, size_t bufsize, size_t *offset, char *fmt, ...);
-uint32_t current_time();
-void close_connection(struct wg_dynamic_request *req);
bool is_link_local(unsigned char *addr);
-void iface_get_all_addrs(uint8_t family, mnl_cb_t data_cb, void *cb_data);
-int data_attr_cb(const struct nlattr *attr, void *data);
#endif
diff --git a/docs/ip-request.md b/docs/ip-request.md
deleted file mode 100644
index 1852bae..0000000
--- a/docs/ip-request.md
+++ /dev/null
@@ -1,255 +0,0 @@
-
-DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT
-DRAFT DRAFT
-DRAFT DRAFT
-DRAFT 2019-09-27 DRAFT
-DRAFT DRAFT
-DRAFT DRAFT
-DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT
-
-# Dynamic IP address allocation
-
-This document describes version 1 of the wg-dynamic `request_ip`
-command.
-
-See protocol.md for a general description of the protocol.
-
-
-## Server
-
-The wg-dynamic server is a daemon responsible for handing out IP
-addresses in form of leases to wg-dynamic clients requesting them.
-
-Unless the client asks for a specific address, one is picked uniformly
-random from a pool of free addresses and is handed out to wg-dynamic
-clients upon request.
-
-A pool of available addresses, both IPv4 and IPv6, is being maintained
-by the server. The pool is populated with the list of addresses being
-routed over the wg interface and updated as leases are being granted
-and expire. The first and last IPv4 addresses of an address block with
-a prefix length less than 31 bits are not handed out.
-
-A request for a specific IP address is granted if the address is
-present in the pool and the server plans on keeping it in the pool for
-the lifetime of the lease.
-
-A request not including an IP address should result in a lease for one
-IPv4 address and one IPv6 address. The server is allowed to impose
-rate limiting on the churn of addresses granted to a given client.
-
-A given client is at any given time granted at most one IPv4 address
-and at most one IPv6 address.
-
-A lease is valid for a configured amount of time, by default 3600
-seconds.
-
-
-## Client
-
-The wg-dynamic client is a daemon responsible for requesting IP
-address leases from a wg-dynamic server, for a given wg
-interface. Requests for leases are sent over the clients wg interface
-to a server on a well known IPv6 link local address and well known low
-TCP port. The source port used MUST be the same as the well known
-server port.
-
-At any given time, a client has one or zero leases. A lease has one or
-zero IPv4 addresses and one or zero IPv6 addresses.
-
-A client starts refreshing its lease 300 seconds (plus/minus a random
-jitter, 0s-30s) before the lease runs out. It keeps refreshing its
-lease until it has one with a lifetime longer than 300 seconds.
-
-### Security
-
-A client MUST send requests from an IPv6 link local address with
-netmask 128 and with the scope of the wg interface. It MUST use the
-same low TCP port as being used by the server. This way wg-dynamic
-requests are guaranteed to be sent only to and received only from the
-configured wg-dynamic server endpoint and only sent by a process with
-permissions to bind to a low port.
-
-TODO: no routing information is being accepted by clients, eg only
-configuring /32 and /128 addresses
-
-## Protocol
-
-A client requests IP address leasees by sending a request\_ip
-command. The request\_ip command has the following optional attribute:
-
-- ipv4 (optional)
-
- Omitting this attribute indicates that the client is fine with any
- IPv4 address and gets an address by random, given that the IPv4 pool
- is not exhausted.
-
- Sending this attribute with an IPv4 address in CIDR notation as its
- value means that the client would like to have this particular
- address granted.
-
- Sending this attribute without a value ("ipv4=") means that the
- client does not want an IPv4 address and that any IPv4 address being
- allocated for it should be released.
-
-- ipv6 (optional)
-
- Omitting this attribute indicates that the client is fine with any
- IPv6 address and gets an address by random, given that the IPv6 pool
- is not exhausted.
-
- Sending this attribute with an IPv6 address in CIDR notation as its
- value means that the client would like to have this particular
- address granted.
-
- Sending this attribute without a value ("ipv6=") means that the
- client does not want an IPv6 address and that any IPv6 address being
- allocated for it should be released.
-
-
-The server response to a request\_ip command contains an errno
-attribute and, if the request was succesful, up to three attributes
-containing the result:
-
-- ipv4
-
- IPv4 address for the client to use. A server MUST offer IPv4
- addresses with a prefix length of 32 bits and a client MUST refuse an
- address with a shorter prefix.
-
-- ipv6
-
- IPv6 address for the client to use. A server MUST offer IPv6
- addresses with a prefix length of 128 bits and a client MUST refuse
- an address with a shorter prefix.
-
-- leasestart
-
- The start time of the lease, in seconds since the epoch.
-
- A client receiving a response with a leasestart that deviates from
- current time of the client by more than 15 seconds MUST use current
- time instead of leasestart.
-
-- leasetime
-
- The number of seconds that this lease is valid, starting from
- leasestart.
-
- MUST be the current time of the server.
-
-- errno
-
- Errno is 0 for a successful operation.
-
-A point in time is expressed as seconds since the epoch (1970-01-01
-00:00 UTC). When this document talks about current time, the current
-time of the computers clock in UTC is what's being refered to.
-
-If the request fails, errno is != 0 and an errmsg attribute is the
-only other attribute in the response:
-
-- errno
-
- A positive integer indicating what made the request fail. The
- following error codes are defined:
-
- - 1: Undefined internal error
-
-- errmsg
-
- A text suitable for human consumption, describing what made the
- request fail.
-
-### Examples
-
-#### Example 1
-
-Client asking for any IPv4 address and any IPv6 address, receiving
-both. This results in two leases being recorded by the client, one per
-address.
-
-client -> server request
-
- request_ip=1
-
-server -> client response
-
- request_ip=1
- ipv4=192.168.47.11/32
- ipv6=fd00::4711/128
- leasestart=1569234893
- leasetime=1800
- errno=0
-
-#### Example 2
-
-Client asking for a specific IPv4 address and a specific IPv6 address,
-receiving both.
-
-client -> server request
-
- request_ip=1
- ipv4=192.168.47.11/32
- ipv6=fd00::4711/128
-
-server -> client response
-
- request_ip=1
- ipv4=192.168.47.11/32
- ipv6=fd00::4711/128
- leasestart=1569236593
- leasetime=1800
- errno=0
-
-#### Example 3
-
-Client asking for a specific IPv4 address and a specific IPv6 address,
-receiving none because both the IPv4 and IPv6 pools are empty.
-
-client -> server request
-
- request_ip=1
- ipv4=192.168.47.11/32
- ipv6=fd00::4711/128
-
-server -> client response
-
- request_ip=1
- errno=0
-
-#### Example 4
-
-Client asking for any IPv4 address and a specific IPv6 address,
-receiving no IPv4 address because that pool is empty and another IPv6
-because the requested address is not free.
-
-client -> server request
-
- request_ip=1
- ipv6=fd00::4711/128
-
-server -> client response
-
- request_ip=1
- ipv6=fd00::42/128
- leasestart=1569273827
- leasetime=1800
- errno=0
-
-#### Example 5
-
-Client asking for any IPv4 address and no IPv6 address, receiving an
-IPv4 address and no IPv6 address. If the client did have an IPv6
-address allocated at the time of the request it's now been released.
-
-client -> server request
-
- request_ip=1
- ipv6=
-
-server -> client response
-
- request_ip=1
- ipv4=192.168.47.22/32
- errno=0
diff --git a/docs/protocol.md b/docs/protocol.md
deleted file mode 100644
index 4ff9bd9..0000000
--- a/docs/protocol.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# wg-dynamic protocol
-
-This document describes version 1 of the wg-dynamic protocol.
-
-The wg-dynamic protocol runs over a reliable and ordered data stream
-between a client and a server. Addressing is done on an upper layer,
-typically IP.
-
-Clients send a request message to a server and the server responds
-with a response message.
-
-Messages are ASCII text with key=value pairs separated by newline
-characters. A message ends with two consecutive newline characters.
-
-The first key=value pair is treated as a command with the key being
-the command and the value being the protocol version. The key=value
-pairs following the first pair are command attributes. The command in
-a response matches the command of the request it's a response to.
diff --git a/ipm.c b/ipm.c
new file mode 100644
index 0000000..81135bb
--- /dev/null
+++ b/ipm.c
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <linux/rtnetlink.h>
+#include <stdint.h>
+#include <time.h>
+
+#include "dbg.h"
+#include "common.h"
+
+struct mnl_cb_data {
+ uint32_t ifindex;
+ struct wg_combined_ip *ip;
+ bool ip_found;
+ bool duplicate;
+};
+
+static struct mnl_socket *nl = NULL;
+
+static int iface_update(uint16_t cmd, uint16_t flags, uint32_t ifindex,
+ const uint8_t *addr, uint8_t cidr, sa_family_t family)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ unsigned int seq, portid;
+ struct ifaddrmsg *ifaddr;
+ int ret;
+
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = cmd;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
+ nlh->nlmsg_seq = seq = time(NULL);
+ ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
+ ifaddr->ifa_family = family;
+ ifaddr->ifa_prefixlen = cidr;
+ ifaddr->ifa_scope = RT_SCOPE_UNIVERSE;
+ ifaddr->ifa_index = ifindex;
+ mnl_attr_put(nlh, IFA_LOCAL, family == AF_INET ? 4 : 16, addr);
+
+ if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+ return -1;
+
+ do {
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret <= MNL_CB_STOP)
+ break;
+ ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+ } while (ret > 0);
+
+ if (ret == -1)
+ return -1;
+
+ return 0;
+}
+
+static int data_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ /* skip unsupported attribute in user-space */
+ if (mnl_attr_type_valid(attr, IFA_MAX) < 0)
+ return MNL_CB_OK;
+
+ switch (type) {
+ case IFA_ADDRESS:
+ if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+ break;
+ }
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int data_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[IFA_MAX + 1] = {};
+ struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh);
+ struct mnl_cb_data *cb_data = (struct mnl_cb_data *)data;
+
+ if (ifa->ifa_index != cb_data->ifindex)
+ return MNL_CB_OK;
+
+ if (ifa->ifa_scope != RT_SCOPE_LINK)
+ return MNL_CB_OK;
+
+ mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb);
+
+ if (!tb[IFA_ADDRESS])
+ return MNL_CB_OK;
+
+ if (cb_data->ip_found) {
+ cb_data->duplicate = true;
+ return MNL_CB_OK;
+ }
+
+ memcpy(cb_data->ip, mnl_attr_get_payload(tb[IFA_ADDRESS]),
+ ifa->ifa_family == AF_INET ? 4 : 16);
+ cb_data->ip->cidr = ifa->ifa_prefixlen;
+ cb_data->ip->family = ifa->ifa_family;
+
+ char out[INET6_ADDRSTRLEN];
+ inet_ntop(ifa->ifa_family, cb_data->ip, out, sizeof(out));
+ debug("index=%d, family=%d, addr=%s\n", ifa->ifa_index, ifa->ifa_family,
+ out);
+
+ cb_data->ip_found = true;
+
+ return MNL_CB_OK;
+}
+
+static int iface_get_all_addrs(uint8_t family, void *cb_data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ /* TODO: rtln-addr-dump from libmnl uses rtgenmsg here? */
+ struct ifaddrmsg *ifaddr;
+ int ret;
+ unsigned int seq, portid;
+
+ /* You'd think that we could just request addresses from a specific
+ * interface, via NLM_F_MATCH or something, but we can't. See also:
+ * https://marc.info/?l=linux-netdev&m=132508164508217
+ */
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = RTM_GETADDR;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
+ ifaddr->ifa_family = family;
+
+ if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+ return -1;
+
+ do {
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret <= MNL_CB_STOP)
+ break;
+ ret = mnl_cb_run(buf, ret, seq, portid, data_cb, cb_data);
+ } while (ret > 0);
+
+ if (ret == -1)
+ return -1;
+
+ return 0;
+}
+
+void ipm_init()
+{
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (nl == NULL)
+ fatal("mnl_socket_open()");
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+ fatal("mnl_socket_bind()");
+}
+
+void ipm_free()
+{
+ if (nl)
+ mnl_socket_close(nl);
+}
+
+int ipm_newaddr_v4(uint32_t ifindex, const struct in_addr *ip)
+{
+ return iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex,
+ (uint8_t *)ip, 32, AF_INET);
+}
+
+int ipm_newaddr_v6(uint32_t ifindex, const struct in6_addr *ip)
+{
+ return iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex,
+ (uint8_t *)ip, 128, AF_INET6);
+}
+
+int ipm_deladdr_v4(uint32_t ifindex, const struct in_addr *ip)
+{
+ return iface_update(RTM_DELADDR, 0, ifindex, (uint8_t *)ip, 32,
+ AF_INET);
+}
+
+int ipm_deladdr_v6(uint32_t ifindex, const struct in6_addr *ip)
+{
+ return iface_update(RTM_DELADDR, 0, ifindex, (uint8_t *)ip, 128,
+ AF_INET6);
+}
+
+int ipm_getlladdr(uint32_t ifindex, struct wg_combined_ip *addr)
+{
+ struct mnl_cb_data cb_data = {
+ .ifindex = ifindex,
+ .ip = addr,
+ .ip_found = false,
+ .duplicate = false,
+ };
+
+ if (iface_get_all_addrs(AF_INET6, &cb_data))
+ return -1;
+
+ if (!cb_data.ip_found)
+ return -2;
+
+ if (cb_data.duplicate)
+ return -3;
+
+ return 0;
+}
diff --git a/ipm.h b/ipm.h
new file mode 100644
index 0000000..b10feac
--- /dev/null
+++ b/ipm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+#ifndef __IPM_H__
+#define __IPM_H__
+
+#include <stdint.h>
+
+#include "common.h"
+
+void ipm_init();
+void ipm_free();
+int ipm_newaddr_v4(uint32_t ifindex, const struct in_addr *ip);
+int ipm_newaddr_v6(uint32_t ifindex, const struct in6_addr *ip);
+int ipm_deladdr_v4(uint32_t ifindex, const struct in_addr *ip);
+int ipm_deladdr_v6(uint32_t ifindex, const struct in6_addr *ip);
+int ipm_getlladdr(uint32_t ifindex, struct wg_combined_ip *addr);
+
+#endif
diff --git a/khash.h b/khash.h
index 445ee3d..0769711 100644
--- a/khash.h
+++ b/khash.h
@@ -129,6 +129,9 @@ int main() {
#include <string.h>
#include <limits.h>
+#include "random.h"
+#include "siphash.h"
+
/* compiler specific configuration */
#if UINT_MAX == 0xffffffffu
@@ -197,6 +200,7 @@ static const double __ac_HASH_UPPER = 0.77;
khint32_t *flags; \
khkey_t *keys; \
khval_t *vals; \
+ siphash_key_t siphash_key; \
} kh_##name##_t;
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
@@ -210,7 +214,10 @@ static const double __ac_HASH_UPPER = 0.77;
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
SCOPE kh_##name##_t *kh_init_##name(void) { \
- return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ kh_##name##_t *s = kcalloc(1, sizeof(kh_##name##_t)); \
+ if (!get_random_bytes((uint8_t *)&s->siphash_key, 16)) \
+ return NULL; \
+ return s; \
} \
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
{ \
@@ -629,20 +636,13 @@ 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 __SIPHASH(key) ((khint32_t) siphash(key, 32, &h->siphash_key))
+#define __SIPHASH_u64(key) ((khint32_t) siphash_1u64(key, &h->siphash_key))
-#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_SECURE_INT64(name, khval_t) \
+ KHASH_INIT(name, khint64_t, khval_t, 1, __SIPHASH_u64, kh_int64_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)
+#define KHASH_MAP_INIT_SECURE_WGKEY(name, khval_t) \
+ KHASH_INIT(name, khwgkey_t, khval_t, 1, __SIPHASH, kh_wgkey_hash_equal)
#endif /* __AC_KHASH_H */
diff --git a/lease.c b/lease.c
index d3efe19..de16cab 100644
--- a/lease.c
+++ b/lease.c
@@ -12,9 +12,9 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/socket.h>
#include <time.h>
-#include <string.h>
#include "common.h"
#include "dbg.h"
@@ -26,11 +26,13 @@
#define TIME_T_MAX (((time_t)1 << (sizeof(time_t) * CHAR_BIT - 2)) - 1) * 2 + 1
-static struct ip_pool pool;
+static const char *devname = NULL;
+static int ifindex = 0;
+static struct ipns ipns;
static time_t gexpires = TIME_T_MAX;
static bool synchronized;
-KHASH_MAP_INIT_WGKEY(leaseht, struct wg_dynamic_lease *)
+KHASH_MAP_INIT_SECURE_WGKEY(leaseht, struct wg_dynamic_lease *)
khash_t(leaseht) *leases_ht = NULL;
static time_t get_monotonic_time()
@@ -52,16 +54,23 @@ static time_t get_monotonic_time()
return monotime.tv_sec;
}
-void leases_init(char *fname, struct mnl_socket *nlsock)
+void leases_init(const char *device_name, int interface_index, char *fname,
+ struct mnl_socket *nlsock)
{
struct nlmsghdr *nlh;
struct rtmsg *rtm;
char buf[MNL_NLMSG_HDRLEN + MNL_ALIGN(sizeof *rtm)];
unsigned int seq;
+ devname = device_name;
+ ifindex = interface_index;
+
synchronized = false;
leases_ht = kh_init(leaseht);
- ipp_init(&pool);
+ if (!leases_ht)
+ fatal("kh_init()");
+
+ ipp_init(&ipns);
nlh = mnl_nlmsg_put_header(buf);
nlh->nlmsg_type = RTM_GETROUTE;
@@ -88,187 +97,210 @@ void leases_free()
}
kh_destroy(leaseht, leases_ht);
- ipp_free(&pool);
+ ipp_free(&ipns);
}
-static struct wg_dynamic_lease *new_lease(const struct wg_dynamic_lease *lease,
- const struct in_addr *ipv4,
- const struct in6_addr *ipv6)
+struct allowedips_update {
+ wg_key peer_pubkey;
+ struct wg_dynamic_lease *lease;
+};
+
+static char *updates_to_str(const struct allowedips_update *u)
{
- struct wg_dynamic_lease *newlease;
+ static char buf[4096];
+ wg_key_b64_string pubkey_asc;
+ char ll[INET6_ADDRSTRLEN] = { 0 }, v4[INET_ADDRSTRLEN] = { 0 },
+ v6[INET6_ADDRSTRLEN] = { 0 };
- newlease = calloc(1, sizeof(*newlease));
- if (!newlease)
- fatal("calloc()");
+ if (!u)
+ return "(null)";
- if (!lease_is_valid(lease))
- return newlease;
+ wg_key_to_base64(pubkey_asc, u->peer_pubkey);
+ inet_ntop(AF_INET, &u->lease->ipv4, v4, sizeof v4);
+ inet_ntop(AF_INET6, &u->lease->ipv6, v6, sizeof v6);
+ inet_ntop(AF_INET6, &u->lease->lladdr, ll, sizeof ll);
+ snprintf(buf, sizeof buf, "(%p) [%s] %s [%s]", u->lease, ll, v4, v6);
- if (ipv4 && ipv4->s_addr && ipv4->s_addr == lease->ipv4.s_addr) {
- char ip_asc[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, ipv4, ip_asc, sizeof(ip_asc));
- debug("extending %s\n", ip_asc);
+ return buf;
+}
- newlease->ipv4 = lease->ipv4;
- }
+static void update_allowed_ips_bulk(const struct allowedips_update *updates,
+ int nupdates)
+{
+ wg_peer peers[WG_DYNAMIC_LEASE_CHUNKSIZE] = { 0 };
+ wg_allowedip allowedips[3 * WG_DYNAMIC_LEASE_CHUNKSIZE] = { 0 };
+ wg_device dev = { 0 };
+ wg_peer **pp = &dev.first_peer;
+
+ BUG_ON(nupdates > WG_DYNAMIC_LEASE_CHUNKSIZE);
+ for (int i = 0; i < nupdates; i++) {
+ debug("setting allowedips for %s\n",
+ updates_to_str(&updates[i]));
+
+ peers[i].flags |= WGPEER_REPLACE_ALLOWEDIPS;
+ memcpy(peers[i].public_key, updates[i].peer_pubkey,
+ sizeof(wg_key));
+ wg_allowedip **aipp = &peers[i].first_allowedip;
- if (ipv6 && !IN6_IS_ADDR_UNSPECIFIED(ipv6) &&
- IN6_ARE_ADDR_EQUAL(ipv6, &lease->ipv6)) {
- char ip_asc[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, ipv6, ip_asc, sizeof(ip_asc));
- debug("extending %s\n", ip_asc);
+ if (!IN6_IS_ADDR_UNSPECIFIED(&updates[i].lease->lladdr)) {
+ allowedips[i * 3 + 0] = (wg_allowedip){
+ .family = AF_INET6,
+ .cidr = 128,
+ .ip6 = updates[i].lease->lladdr,
+ };
+ *aipp = &allowedips[i * 3 + 0];
+ aipp = &allowedips[i * 3 + 0].next_allowedip;
+ }
+ if (updates[i].lease->ipv4.s_addr) {
+ allowedips[i * 3 + 1] = (wg_allowedip){
+ .family = AF_INET,
+ .cidr = 32,
+ .ip4 = updates[i].lease->ipv4,
+ };
+ *aipp = &allowedips[i * 3 + 1];
+ aipp = &allowedips[i * 3 + 1].next_allowedip;
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&updates[i].lease->ipv6)) {
+ allowedips[i * 3 + 2] = (wg_allowedip){
+ .family = AF_INET6,
+ .cidr = 128,
+ .ip6 = updates[i].lease->ipv6,
+ };
+ *aipp = &allowedips[i * 3 + 2];
+ }
- newlease->ipv6 = lease->ipv6;
+ *pp = &peers[i];
+ pp = &peers[i].next_peer;
}
- return newlease;
+ strncpy(dev.name, devname, sizeof(dev.name) - 1);
+ if (wg_set_device(&dev))
+ fatal("wg_set_device()");
+}
+
+/* Updates allowedips for peer_pubkey, adding what's in lease
+ * (including lladdr), removing all others.
+ */
+static void update_allowed_ips(wg_key peer_pubkey,
+ struct wg_dynamic_lease *lease)
+{
+ struct allowedips_update update;
+
+ memcpy(update.peer_pubkey, peer_pubkey, sizeof(wg_key));
+ update.lease = lease;
+
+ update_allowed_ips_bulk(&update, 1);
}
-struct wg_dynamic_lease *set_lease(const char *devname, wg_key pubkey,
- uint32_t leasetime,
+struct wg_dynamic_lease *set_lease(wg_key pubkey, uint32_t leasetime,
const struct in6_addr *lladdr,
const struct in_addr *ipv4,
const struct in6_addr *ipv6)
{
- struct wg_dynamic_lease *current, *new;
- uint64_t index_l;
- uint32_t index, index_h;
+ bool delete_ipv4 = !ipv4 || (ipv4 && !ipv4->s_addr);
+ bool delete_ipv6 = !ipv6 || (ipv6 && IN6_IS_ADDR_UNSPECIFIED(ipv6));
+ struct wg_dynamic_lease *lease;
struct timespec tp;
khiter_t k;
- int ret;
- bool wants_ipv4 = !ipv4 || ipv4->s_addr;
- bool wants_ipv6 = !ipv6 || !IN6_IS_ADDR_UNSPECIFIED(ipv6);
- wg_key_b64_string pubkey_asc;
- wg_key_to_base64(pubkey_asc, pubkey);
-
- current = get_leases(pubkey);
- new = new_lease(current, ipv4, ipv6);
-
- if (current) {
- char ip_asc[INET6_ADDRSTRLEN];
+ int kh_ret;
- if (current->ipv4.s_addr && !new->ipv4.s_addr) {
- inet_ntop(AF_INET, &current->ipv4, ip_asc,
- sizeof(ip_asc));
- debug("deleting from pool: %s\n", ip_asc);
-
- if (ipp_del_v4(&pool, &current->ipv4, 32))
- die("ipp_del_v4()\n");
- }
+ lease = get_leases(pubkey);
+ if (!lease) {
+ lease = calloc(1, sizeof(*lease));
+ lease->lladdr = *lladdr;
+ }
- if (!IN6_IS_ADDR_UNSPECIFIED(&current->ipv6) &&
- IN6_IS_ADDR_UNSPECIFIED(&new->ipv6)) {
- inet_ntop(AF_INET6, &current->ipv6, ip_asc,
- sizeof(ip_asc));
- debug("deleting from pool: %s\n", ip_asc);
+ if (delete_ipv4 && lease->ipv4.s_addr) {
+ if (ipp_del_v4(&ipns, &lease->ipv4, 32))
+ die("ipp_del_v4()\n");
+ memset(&lease->ipv4, 0, sizeof(lease->ipv4));
+ }
- if (ipp_del_v6(&pool, &current->ipv6, 128))
- die("ipp_del_v6()\n");
- }
+ if (delete_ipv6 && !IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) {
+ if (ipp_del_v6(&ipns, &lease->ipv6, 128))
+ die("ipp_del_v6()\n");
+ memset(&lease->ipv6, 0, sizeof(lease->ipv6));
}
- if (wants_ipv4 && !new->ipv4.s_addr) {
- if (!pool.total_ipv4) {
+ if (ipv4 && !ipv4->s_addr) {
+ if (!ipns.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, &new->ipv4, index);
+ memset(&lease->ipv4, 0, sizeof(lease->ipv4));
} else {
- char ip_asc[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, ipv4, ip_asc, sizeof(ip_asc));
- debug("wants %s: ", ip_asc);
-
- if (!ipp_add_v4(&pool, ipv4, 32)) {
- debug("allocated\n");
-
- new->ipv4 = *ipv4;
+ uint32_t index = random_bounded(ipns.total_ipv4);
+ debug("new_lease(v4): %u of %ju\n", index,
+ ipns.total_ipv4);
+ ipp_addnth_v4(&ipns, &lease->ipv4, index);
+ }
+ } else if (ipv4) {
+ if (!memcmp(&lease->ipv4, ipv4, sizeof(*ipv4))) {
+ debug("extending(v4)\n");
+ } else {
+ if (!ipp_add_v4(&ipns, ipv4, 32)) {
+ lease->ipv4 = *ipv4;
} else {
- debug("not free\n");
+ memset(&lease->ipv4, 0, sizeof(lease->ipv4));
}
}
}
- if (wants_ipv6 && IN6_IS_ADDR_UNSPECIFIED(&new->ipv6)) {
- if (!pool.totalh_ipv6 && !pool.totall_ipv6) {
+ if (ipv6 && IN6_IS_ADDR_UNSPECIFIED(ipv6)) {
+ if (!ipns.totalh_ipv6 && !ipns.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);
+ memset(&lease->ipv6, 0, sizeof(lease->ipv6));
+ } else {
+ uint64_t index_l;
+ uint32_t index_h;
+ if (ipns.totalh_ipv6 > 0) {
+ index_l = random_u64();
+ index_h = random_bounded(ipns.totalh_ipv6);
} else {
- index_l = random_bounded(pool.totall_ipv6 - 1);
+ index_l = random_bounded(ipns.totall_ipv6);
index_h = 0;
}
debug("new_lease(v6): %u:%ju of %u:%ju\n", index_h,
- index_l, pool.totalh_ipv6, pool.totall_ipv6);
- ipp_addnth_v6(&pool, &new->ipv6, index_l, index_h);
+ index_l, ipns.totalh_ipv6, ipns.totall_ipv6);
+ ipp_addnth_v6(&ipns, &lease->ipv6, index_l, index_h);
+ }
+ } else if (ipv6) {
+ if (!memcmp(&lease->ipv6, ipv6, sizeof(*ipv6))) {
+ debug("extending(v6)\n");
} else {
- char ip_asc[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, ipv6, ip_asc, sizeof(ip_asc));
- debug("wants %s: ", ip_asc);
-
- if (!ipp_add_v6(&pool, ipv6, 128)) {
- debug("allocated\n");
-
- new->ipv6 = *ipv6;
+ if (!ipp_add_v6(&ipns, ipv6, 128)) {
+ lease->ipv6 = *ipv6;
} else {
- debug("not free\n");
+ memset(&lease->ipv6, 0, sizeof(lease->ipv6));
}
}
}
- new->lladdr = *lladdr;
- update_allowed_ips(devname, pubkey, new);
-
- if (!new->ipv4.s_addr && IN6_IS_ADDR_UNSPECIFIED(&new->ipv6)) {
- khiter_t k = kh_get(leaseht, leases_ht, pubkey);
- if (k != kh_end(leases_ht)) {
- BUG_ON(!current);
- BUG_ON(kh_value(leases_ht, k) != current);
- debug("freeing lease: %s\n", lease_to_str(current));
- free(current);
- free((char *)kh_key(leases_ht, k));
- kh_del(leaseht, leases_ht, k);
- }
-
- free(new);
- return NULL;
- }
+ update_allowed_ips(pubkey, lease);
if (clock_gettime(CLOCK_REALTIME, &tp))
fatal("clock_gettime(CLOCK_REALTIME)");
- new->start_real = tp.tv_sec;
- new->start_mono = get_monotonic_time();
- new->leasetime = leasetime;
+ lease->start_real = tp.tv_sec;
+ lease->start_mono = get_monotonic_time();
+ lease->leasetime = leasetime;
wg_key *pubcopy = malloc(sizeof(wg_key));
if (!pubcopy)
fatal("malloc()");
memcpy(pubcopy, pubkey, sizeof(wg_key));
- k = kh_put(leaseht, leases_ht, *pubcopy, &ret);
- if (ret < 0) {
- fatal("kh_put()");
- } else if (ret == 0) {
- BUG_ON(!current);
- BUG_ON(kh_value(leases_ht, k) != current);
- debug("freeing lease (replace): %s\n", lease_to_str(current));
- free(current);
- }
- kh_value(leases_ht, k) = new;
+ k = kh_put(leaseht, leases_ht, *pubcopy, &kh_ret);
+
+ if (kh_ret < 0)
+ die("kh_put(): %d\n", kh_ret);
- debug("new lease: %s\n", lease_to_str(new));
+ kh_value(leases_ht, k) = lease;
- if (new->start_mono + new->leasetime < gexpires)
- gexpires = new->start_mono + new->leasetime;
+ if (lease->start_mono + lease->leasetime < gexpires)
+ gexpires = lease->start_mono + lease->leasetime;
/* TODO: add record to file */
- return new;
+ return lease;
}
struct wg_dynamic_lease *get_leases(wg_key pubkey)
@@ -281,105 +313,7 @@ struct wg_dynamic_lease *get_leases(wg_key pubkey)
return kh_val(leases_ht, k);
}
-struct allowedips_update {
- wg_key peer_pubkey;
- struct in6_addr lladdr;
- struct in_addr ipv4;
- struct in6_addr ipv6;
-};
-
-static char *updates_to_str(const struct allowedips_update *u)
-{
- static char buf[4096];
- wg_key_b64_string pubkey_asc;
- char ll[INET6_ADDRSTRLEN], v4[INET_ADDRSTRLEN], v6[INET6_ADDRSTRLEN];
-
- if (!u)
- return "(null)";
-
- wg_key_to_base64(pubkey_asc, u->peer_pubkey);
- inet_ntop(AF_INET, &u->ipv4, v4, sizeof v4);
- inet_ntop(AF_INET6, &u->ipv6, v6, sizeof v6);
- inet_ntop(AF_INET6, &u->lladdr, ll, sizeof ll);
- snprintf(buf, sizeof buf, "(%p) [%s] %s [%s]", u, ll, v4, v6);
-
- return buf;
-}
-
-static void update_allowed_ips_bulk(const char *devname,
- const struct allowedips_update *updates,
- int nupdates)
-{
- wg_peer peers[WG_DYNAMIC_LEASE_CHUNKSIZE] = { 0 };
- wg_allowedip allowedips[3 * WG_DYNAMIC_LEASE_CHUNKSIZE] = { 0 };
- wg_device dev = { 0 };
- wg_peer **pp = &dev.first_peer;
-
- int peer_idx = 0;
- int allowedips_idx = 0;
- for (int i = 0; i < nupdates; i++) {
- debug("setting allowedips for %s\n",
- updates_to_str(&updates[i]));
-
- peers[peer_idx].flags |= WGPEER_REPLACE_ALLOWEDIPS;
- memcpy(peers[peer_idx].public_key, updates[i].peer_pubkey,
- sizeof(wg_key));
- wg_allowedip **aipp = &peers[peer_idx].first_allowedip;
-
- if (!IN6_IS_ADDR_UNSPECIFIED(&updates[i].lladdr)) {
- allowedips[allowedips_idx] = (wg_allowedip){
- .family = AF_INET6,
- .cidr = 128,
- .ip6 = updates[i].lladdr,
- };
- *aipp = &allowedips[allowedips_idx];
- aipp = &allowedips[allowedips_idx].next_allowedip;
- ++allowedips_idx;
- }
- if (updates[i].ipv4.s_addr) {
- allowedips[allowedips_idx] = (wg_allowedip){
- .family = AF_INET,
- .cidr = 32,
- .ip4 = updates[i].ipv4,
- };
- *aipp = &allowedips[allowedips_idx];
- aipp = &allowedips[allowedips_idx].next_allowedip;
- ++allowedips_idx;
- }
- if (!IN6_IS_ADDR_UNSPECIFIED(&updates[i].ipv6)) {
- allowedips[allowedips_idx] = (wg_allowedip){
- .family = AF_INET6,
- .cidr = 128,
- .ip6 = updates[i].ipv6,
- };
- *aipp = &allowedips[allowedips_idx];
- ++allowedips_idx;
- }
-
- *pp = &peers[peer_idx];
- pp = &peers[peer_idx].next_peer;
- ++peer_idx;
- }
-
- strncpy(dev.name, devname, sizeof(dev.name) - 1);
- if (wg_set_device(&dev))
- fatal("wg_set_device()");
-}
-
-void update_allowed_ips(const char *devname, wg_key peer_pubkey,
- const struct wg_dynamic_lease *lease)
-{
- struct allowedips_update update;
-
- memcpy(update.peer_pubkey, peer_pubkey, sizeof(wg_key));
- update.lladdr = lease->lladdr;
- update.ipv4 = lease->ipv4;
- update.ipv6 = lease->ipv6;
-
- update_allowed_ips_bulk(devname, &update, 1);
-}
-
-int leases_refresh(const char *devname)
+int leases_refresh()
{
time_t cur_time = get_monotonic_time();
struct allowedips_update updates[WG_DYNAMIC_LEASE_CHUNKSIZE] = { 0 };
@@ -397,15 +331,19 @@ int leases_refresh(const char *devname)
BUG_ON(!lease);
time_t expires = lease->start_mono + lease->leasetime;
if (cur_time >= expires) {
- if (lease->ipv4.s_addr)
- ipp_del_v4(&pool, &lease->ipv4, 32);
+ if (lease->ipv4.s_addr) {
+ ipp_del_v4(&ipns, &lease->ipv4, 32);
+ memset(&lease->ipv4, 0, sizeof(lease->ipv4));
+ }
- if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6))
- ipp_del_v6(&pool, &lease->ipv6, 128);
+ if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) {
+ ipp_del_v6(&ipns, &lease->ipv6, 128);
+ memset(&lease->ipv6, 0, sizeof(lease->ipv6));
+ }
memcpy(updates[i].peer_pubkey, kh_key(leases_ht, k),
sizeof(wg_key));
- updates[i].lladdr = lease->lladdr;
+ updates[i].lease = lease;
wg_key_b64_string pubkey_asc;
wg_key_to_base64(pubkey_asc, updates[i].peer_pubkey);
@@ -413,12 +351,12 @@ int leases_refresh(const char *devname)
++i;
if (i == WG_DYNAMIC_LEASE_CHUNKSIZE) {
- update_allowed_ips_bulk(devname, updates, i);
- i = 0;
+ update_allowed_ips_bulk(updates, i);
+ while (i)
+ free(updates[--i].lease);
memset(updates, 0, sizeof updates);
}
- free(lease);
free((char *)kh_key(leases_ht, k));
kh_del(leaseht, leases_ht, k);
} else {
@@ -427,8 +365,11 @@ int leases_refresh(const char *devname)
}
}
- if (i)
- update_allowed_ips_bulk(devname, updates, i);
+ if (i) {
+ update_allowed_ips_bulk(updates, i);
+ while (i)
+ free(updates[--i].lease);
+ }
return MIN(INT_MAX / 1000, gexpires - cur_time);
}
@@ -440,7 +381,7 @@ static int data_ipv4_attr_cb(const struct nlattr *attr, void *data)
switch (type) {
case RTA_DST:
- case RTA_GATEWAY:
+ case RTA_OIF:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
log_err("mnl_attr_validate: %s\n", strerror(errno));
return MNL_CB_ERROR;
@@ -460,9 +401,14 @@ static int data_ipv6_attr_cb(const struct nlattr *attr, void *data)
switch (type) {
case RTA_DST:
- case RTA_GATEWAY:
if (mnl_attr_validate2(attr, MNL_TYPE_BINARY,
sizeof(struct in6_addr)) < 0) {
+ log_err("mnl_attr_validate2: %s\n", strerror(errno));
+ return MNL_CB_ERROR;
+ }
+ break;
+ case RTA_OIF:
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
log_err("mnl_attr_validate: %s\n", strerror(errno));
return MNL_CB_ERROR;
}
@@ -478,13 +424,22 @@ static int process_nlpacket_cb(const struct nlmsghdr *nlh, void *data)
{
struct nlattr *tb[RTA_MAX + 1] = {};
struct rtmsg *rm = mnl_nlmsg_get_payload(nlh);
- UNUSED(data);
+ uint32_t ifindex;
+
+ BUG_ON(!data);
+ ifindex = *((int *)data);
if (rm->rtm_family == AF_INET)
mnl_attr_parse(nlh, sizeof(*rm), data_ipv4_attr_cb, tb);
else if (rm->rtm_family == AF_INET6)
mnl_attr_parse(nlh, sizeof(*rm), data_ipv6_attr_cb, tb);
+ if (!tb[RTA_OIF] || mnl_attr_get_u32(tb[RTA_OIF]) != ifindex) {
+ debug("ignoring interface %u (want %u)\n",
+ tb[RTA_OIF] ? mnl_attr_get_u32(tb[RTA_OIF]) : 0, ifindex);
+ return MNL_CB_OK;
+ }
+
if (tb[RTA_GATEWAY])
return MNL_CB_OK;
@@ -500,18 +455,20 @@ static int process_nlpacket_cb(const struct nlmsghdr *nlh, void *data)
if (nlh->nlmsg_type == RTM_NEWROUTE) {
if (rm->rtm_family == AF_INET) {
- if (ipp_addpool_v4(&pool, addr, rm->rtm_dst_len))
+ if (ipp_addpool_v4(&ipns, addr, rm->rtm_dst_len))
die("ipp_addpool_v4()\n");
} else if (rm->rtm_family == AF_INET6) {
- if (ipp_addpool_v6(&pool, addr, rm->rtm_dst_len))
+ if (ipp_addpool_v6(&ipns, addr, rm->rtm_dst_len))
die("ipp_addpool_v6()\n");
}
} else if (nlh->nlmsg_type == RTM_DELROUTE) {
if (rm->rtm_family == AF_INET) {
- if (ipp_removepool_v4(&pool, addr) && synchronized)
+ if (ipp_removepool_v4(&ipns, addr, rm->rtm_dst_len) &&
+ synchronized)
die("ipp_removepool_v4()\n");
} else if (rm->rtm_family == AF_INET6) {
- if (ipp_removepool_v6(&pool, addr) && synchronized)
+ if (ipp_removepool_v6(&ipns, addr, rm->rtm_dst_len) &&
+ synchronized)
die("ipp_removepool_v6()\n");
}
}
@@ -525,39 +482,11 @@ void leases_update_pools(struct mnl_socket *nlsock)
char buf[MNL_SOCKET_BUFFER_SIZE];
while ((ret = mnl_socket_recvfrom(nlsock, buf, sizeof buf)) > 0) {
- if (mnl_cb_run(buf, ret, 0, 0, process_nlpacket_cb, NULL) == -1)
+ if (mnl_cb_run(buf, ret, 0, 0, process_nlpacket_cb,
+ (void *)&ifindex) == -1)
fatal("mnl_cb_run()");
}
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 ll[INET6_ADDRSTRLEN], v4[INET_ADDRSTRLEN], v6[INET6_ADDRSTRLEN];
-
- if (!l)
- return "(null)";
-
- inet_ntop(AF_INET6, &l->lladdr, ll, sizeof ll);
- 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 [%s]", l, ll, v4, v6);
-
- return buf;
-}
-#endif
diff --git a/lease.h b/lease.h
index 383bac7..5fa878b 100644
--- a/lease.h
+++ b/lease.h
@@ -28,7 +28,8 @@ struct wg_dynamic_lease {
* Initializes internal state, retrieves routes from nlsock and reads leases
* from fname.
*/
-void leases_init(char *fname, struct mnl_socket *nlsock);
+void leases_init(const char *device_name, int interface_index, char *fname,
+ struct mnl_socket *nlsock);
/*
* Frees everything, closes file.
@@ -41,8 +42,7 @@ void leases_free();
* taken. Frees currently held lease, if any. Updates allowedips for
* the peer.
*/
-struct wg_dynamic_lease *set_lease(const char *devname, wg_key pubkey,
- uint32_t leasetime,
+struct wg_dynamic_lease *set_lease(wg_key pubkey, uint32_t leasetime,
const struct in6_addr *lladdr,
const struct in_addr *ipv4,
const struct in6_addr *ipv6);
@@ -55,25 +55,11 @@ struct wg_dynamic_lease *get_leases(wg_key pubkey);
/* 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(const char *devname);
-
-/*
- * Updates allowedips for peer_pubkey on devname, adding what's in
- * lease (including lladdr), removing all others.
- */
-void update_allowed_ips(const char *devname, wg_key peer_pubkey,
- const struct wg_dynamic_lease *lease);
+int leases_refresh();
/*
* Updates all pools with information from the mnl socket nlsock.
*/
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);
-
-char *lease_to_str(const struct wg_dynamic_lease *l);
-
#endif
diff --git a/radix-trie.c b/radix-trie.c
index 25bdd75..678f3be 100644
--- a/radix-trie.c
+++ b/radix-trie.c
@@ -20,19 +20,23 @@
#define __aligned(x) __attribute__((aligned(x)))
#endif
+enum radix_node_flags {
+ RNODE_IS_LEAF = 1U << 0,
+ RNODE_IS_POOLNODE = 1U << 1,
+ RNODE_IS_SHADOWED = 1U << 2,
+};
+
struct radix_node {
struct radix_node *bit[2];
uint64_t left;
uint64_t right;
uint8_t bits[16];
- uint8_t cidr, bit_at_a, bit_at_b;
- bool is_leaf;
+ uint8_t cidr, bit_at_a, bit_at_b, flags;
};
struct radix_pool {
struct radix_node *node;
struct radix_pool *next;
- bool shadowed;
};
static unsigned int fls64(uint64_t x)
@@ -92,7 +96,7 @@ static struct radix_node *new_node(const uint8_t *key, uint8_t cidr,
node->bit_at_a ^= (bits / 8U - 1U) % 8U;
#endif
node->bit_at_b = 7U - (cidr % 8U);
- node->is_leaf = false;
+ node->flags = 0;
if (bits - cidr > 0 && bits - cidr - 1 < 64)
node->left = node->right = 1ULL << (bits - cidr - 1);
else
@@ -117,26 +121,6 @@ static bool prefix_matches(const struct radix_node *node, const uint8_t *key,
#define CHOOSE_NODE(parent, key) \
(parent)->bit[(key[(parent)->bit_at_a] >> (parent)->bit_at_b) & 1]
-static bool node_placement(struct radix_node *trie, const uint8_t *key,
- uint8_t cidr, uint8_t bits,
- struct radix_node **rnode)
-{
- struct radix_node *node = trie, *parent = NULL;
- bool exact = false;
-
- while (node && node->cidr <= cidr && prefix_matches(node, key, bits)) {
- parent = node;
- if (parent->cidr == cidr) {
- exact = true;
- break;
- }
-
- node = CHOOSE_NODE(parent, key);
- }
- *rnode = parent;
- return exact;
-}
-
static uint64_t subnet_diff(uint8_t *ip1, uint8_t *ip2, uint8_t bits)
{
if (bits == 32)
@@ -145,6 +129,14 @@ static uint64_t subnet_diff(uint8_t *ip1, uint8_t *ip2, uint8_t bits)
return *(const uint64_t *)&ip1[8] - *(const uint64_t *)&ip2[8];
}
+static uint64_t taken_ips(struct radix_node *node, uint8_t bits)
+{
+ if ((bits - node->cidr) >= 64)
+ return 0;
+
+ return (1ULL << (bits - node->cidr)) - (node->left + node->right);
+}
+
static void add_nth(struct radix_node *start, uint8_t bits, uint64_t n,
uint8_t *dest)
{
@@ -201,7 +193,7 @@ static void add_nth(struct radix_node *start, uint8_t bits, uint64_t n,
}
newnode = new_node(ip, cidr, bits);
- newnode->is_leaf = true;
+ newnode->flags |= RNODE_IS_LEAF;
swap_endian(dest, (const uint8_t *)ip, bits);
if (!target) {
@@ -214,43 +206,64 @@ static void add_nth(struct radix_node *start, uint8_t bits, uint64_t n,
CHOOSE_NODE(between, newnode->bits) = newnode;
CHOOSE_NODE(parent, between->bits) = between;
- between->left -=
- (1ULL << (bits - between->bit[0]->cidr)) -
- (between->bit[0]->left + between->bit[0]->right);
- between->right -=
- (1ULL << (bits - between->bit[1]->cidr)) -
- (between->bit[1]->left + between->bit[1]->right);
+ between->left -= taken_ips(between->bit[0], bits);
+ between->right -= taken_ips(between->bit[1], bits);
}
}
-static int add(struct radix_node **trie, uint8_t bits, const uint8_t *key,
- uint8_t cidr, bool is_leaf)
+static struct radix_node *add(struct radix_node **trie, uint8_t bits,
+ const uint8_t *key, uint8_t cidr, uint8_t type)
{
- struct radix_node *node, *newnode, *down, *parent;
+ struct radix_node *node = NULL, *newnode, *down, *parent, *tmp = *trie;
+ bool exact = false, in_pool = false;
- if (cidr > bits)
- return -EINVAL;
+ if (cidr > bits) {
+ errno = EINVAL;
+ return NULL;
+ }
if (!*trie) {
+ if (type & RNODE_IS_LEAF) {
+ errno = ENOENT;
+ return NULL;
+ }
+
*trie = new_node(key, cidr, bits);
- (*trie)->is_leaf = is_leaf;
- return 0;
+ (*trie)->flags = type;
+ return *trie;
}
- if (node_placement(*trie, key, cidr, bits, &node)) {
- /* exact match, so use the existing node */
- if (node->is_leaf)
- return 1;
+ while (tmp && tmp->cidr <= cidr && prefix_matches(tmp, key, bits)) {
+ node = tmp;
+ if (tmp->flags & RNODE_IS_POOLNODE)
+ in_pool = true;
- node->is_leaf = is_leaf;
- return 0;
+ if (node->cidr == cidr) {
+ exact = true;
+ break;
+ }
+
+ tmp = CHOOSE_NODE(node, key);
}
- if (node && node->is_leaf)
- return 1;
+ if (!in_pool && (type & RNODE_IS_LEAF)) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (exact) {
+ /* exact match, so use the existing node */
+ if (node->flags & type) {
+ errno = EEXIST;
+ return NULL;
+ }
+
+ node->flags = type;
+ return node;
+ }
newnode = new_node(key, cidr, bits);
- newnode->is_leaf = is_leaf;
+ newnode->flags = type;
if (!node) {
down = *trie;
@@ -259,7 +272,7 @@ static int add(struct radix_node **trie, uint8_t bits, const uint8_t *key,
if (!down) {
CHOOSE_NODE(node, key) = newnode;
- return 0;
+ return newnode;
}
}
cidr = MIN(cidr, common_bits(down, key, bits));
@@ -276,13 +289,19 @@ static int add(struct radix_node **trie, uint8_t bits, const uint8_t *key,
CHOOSE_NODE(node, down->bits) = down;
CHOOSE_NODE(node, newnode->bits) = newnode;
+
+ if (CHOOSE_NODE(node, down->bits) == node->bit[0])
+ node->left -= taken_ips(down, bits);
+ else
+ node->right -= taken_ips(down, bits);
+
if (!parent)
*trie = node;
else
CHOOSE_NODE(parent, node->bits) = node;
}
- return 0;
+ return newnode;
}
static void radix_free_nodes(struct radix_node *node)
@@ -322,15 +341,15 @@ static int insert_v4(struct radix_node **root, const struct in_addr *ip,
{
/* Aligned so it can be passed to fls */
uint8_t key[4] __aligned(__alignof(uint32_t));
- int ret;
swap_endian(key, (const uint8_t *)ip, 32);
- ret = add(root, 32, key, cidr, true);
- if (!ret)
+ if (add(root, 32, key, cidr, RNODE_IS_LEAF)) {
decrement_radix(*root, 32, (uint8_t *)key);
+ return 0;
+ }
- return ret;
+ return -1;
}
static int insert_v6(struct radix_node **root, const struct in6_addr *ip,
@@ -338,99 +357,128 @@ static int insert_v6(struct radix_node **root, const struct in6_addr *ip,
{
/* Aligned so it can be passed to fls64 */
uint8_t key[16] __aligned(__alignof(uint64_t));
- int ret;
swap_endian(key, (const uint8_t *)ip, 128);
- ret = add(root, 128, key, cidr, true);
- if (!ret)
+ if (add(root, 128, key, cidr, RNODE_IS_LEAF)) {
decrement_radix(*root, 128, (uint8_t *)key);
+ return 0;
+ }
- return ret;
+ return -1;
}
-static int remove_node(struct radix_node *trie, const uint8_t *key,
+static int remove_node(struct radix_node **trie, const uint8_t *key,
uint8_t bits)
{
- struct radix_node **node = &trie, **target = NULL;
+ struct radix_node **node = trie, **target = NULL;
+ uint64_t *pnodes[127];
+ int i = 0;
while (*node && prefix_matches(*node, key, bits)) {
- if ((*node)->is_leaf) {
+ if ((*node)->flags & RNODE_IS_LEAF) {
target = node;
break;
}
if (CHOOSE_NODE(*node, key) == (*node)->bit[0])
- ++((*node)->left);
+ pnodes[i++] = &((*node)->left);
else
- ++((*node)->right);
+ pnodes[i++] = &((*node)->right);
+ BUG_ON(i >= 127);
node = &CHOOSE_NODE(*node, key);
}
if (!target)
return 1; /* key not found in trie */
+ for (int j = 0; j < i; ++j)
+ ++(*(pnodes[j]));
+
+ free(*node);
*target = NULL;
- radix_free_nodes(*node);
return 0;
}
-static void totalip_inc(struct ip_pool *ipp, uint8_t bits, uint8_t val)
+static void totalip_inc(struct ipns *ns, uint8_t bits, uint8_t val)
{
if (bits == 32) {
- BUG_ON(val >= 32);
- ipp->total_ipv4 += 1ULL << val;
+ BUG_ON(val > 32);
+ ns->total_ipv4 += 1ULL << val;
} else if (bits == 128) {
- uint64_t tmp = ipp->totall_ipv6;
+ uint64_t tmp = ns->totall_ipv6;
BUG_ON(val > 64);
- ipp->totall_ipv6 += (val == 64) ? 0 : 1ULL << val;
- if (ipp->totall_ipv6 <= tmp)
- ++ipp->totalh_ipv6;
+ ns->totall_ipv6 += (val == 64) ? 0 : 1ULL << val;
+ if (ns->totall_ipv6 <= tmp)
+ ++ns->totalh_ipv6;
}
}
-static void totalip_dec(struct ip_pool *ipp, uint8_t bits, uint8_t val)
+static void shadow_nodes(struct radix_node *node)
{
- if (bits == 32) {
- BUG_ON(val >= 32);
- ipp->total_ipv4 -= 1ULL << val;
- } else if (bits == 128) {
- uint64_t tmp = ipp->totall_ipv6;
- BUG_ON(val > 64);
- ipp->totall_ipv6 -= (val == 64) ? 0 : 1ULL << val;
- if (ipp->totall_ipv6 >= tmp)
- --ipp->totalh_ipv6;
+ if (!node)
+ return;
+
+ if (node->flags & RNODE_IS_POOLNODE) {
+ BUG_ON(node->flags & RNODE_IS_SHADOWED);
+ node->flags |= RNODE_IS_SHADOWED;
+ return;
}
+
+ if (node->flags & RNODE_IS_LEAF)
+ return;
+
+ shadow_nodes(node->bit[0]);
+ shadow_nodes(node->bit[1]);
}
-static int ipp_addpool(struct ip_pool *ipp, struct radix_pool **pool,
+static int ipp_addpool(struct ipns *ns, struct radix_pool **pool,
struct radix_node **root, uint8_t bits,
const uint8_t *key, uint8_t cidr)
{
+ struct radix_node **node = root, *newnode;
struct radix_pool *newpool;
- struct radix_node *node;
- bool shadowed = false;
-
- while (*pool) {
- node = (*pool)->node;
-
- if (common_bits(node, key, bits) >= MIN(cidr, node->cidr)) {
- if (cidr > node->cidr) {
- shadowed = true;
- } else if (cidr < node->cidr && !(*pool)->shadowed) {
- (*pool)->shadowed = true;
- totalip_dec(ipp, bits, bits - cidr);
- } else {
- return -1;
- }
+ bool shadow = false, good_match = false;
+ uint8_t flags;
+
+ while (*node && (*node)->cidr <= cidr &&
+ prefix_matches(*node, key, bits)) {
+ if ((*node)->cidr == cidr) {
+ good_match = true;
+ break;
}
- pool = &(*pool)->next;
+ if ((*node)->flags & RNODE_IS_POOLNODE)
+ shadow = true;
+
+ node = &CHOOSE_NODE(*node, key);
}
- BUG_ON(add(root, bits, key, cidr, false));
+ flags = RNODE_IS_POOLNODE | (shadow ? RNODE_IS_SHADOWED : 0);
+
+ if (good_match) {
+ if ((*node)->flags & RNODE_IS_POOLNODE)
+ return -1; /* already exists */
+
+ BUG_ON((*node)->flags & RNODE_IS_SHADOWED);
+ (*node)->flags |= flags;
+
+ newnode = *node;
+ } else {
+ newnode = add(node, bits, key, cidr, flags);
+ if (newnode->bit[0])
+ newnode->left -= taken_ips(newnode->bit[0], bits);
+
+ if (newnode->bit[1])
+ newnode->right -= taken_ips(newnode->bit[1], bits);
+ }
+
+ if (!shadow) {
+ shadow_nodes(newnode->bit[0]);
+ shadow_nodes(newnode->bit[1]);
+ }
if (bits == 32) {
/* TODO: insert network address (0) and broadcast address (255)
@@ -438,23 +486,103 @@ static int ipp_addpool(struct ip_pool *ipp, struct radix_pool **pool,
/* TODO: special case /31 ?, see RFC 3021 */
}
- if (!shadowed)
- totalip_inc(ipp, bits, bits - cidr);
+ if (!shadow)
+ totalip_inc(ns, bits, bits - cidr);
newpool = malloc(sizeof *newpool);
if (!newpool)
fatal("malloc()");
- node = *root;
- while (node->cidr != cidr) {
- node = CHOOSE_NODE(node, key);
+ newpool->node = newnode;
+ newpool->next = *pool;
+ *pool = newpool;
+
+ return 0;
+}
+
+static int orphan_nodes(struct radix_node *node, uint64_t *val)
+{
+ uint64_t v1 = 0, v2 = 0;
+
+ if (!node)
+ return 0;
- BUG_ON(!node || !prefix_matches(node, key, bits));
+ if (node->flags & RNODE_IS_POOLNODE) {
+ BUG_ON(!(node->flags & RNODE_IS_SHADOWED));
+ node->flags &= ~RNODE_IS_SHADOWED;
+ return 0;
}
- newpool->node = node;
- newpool->shadowed = shadowed;
- newpool->next = NULL;
- *pool = newpool;
+
+ if (node->flags & RNODE_IS_LEAF) {
+ BUG_ON(node->bit[0] || node->bit[1]);
+ *val = 1;
+ free(node);
+ return 1;
+ }
+
+ if (orphan_nodes(node->bit[0], &v1))
+ node->bit[0] = NULL;
+
+ if (orphan_nodes(node->bit[1], &v2))
+ node->bit[1] = NULL;
+
+ node->left += v1;
+ node->right += v2;
+ *val = v1 + v2;
+
+ if (node->bit[0] || node->bit[1])
+ return 0; /* still need this node */
+
+ free(node);
+ return 1;
+}
+
+static int ipp_removepool(struct ipns *ns, uint8_t bits, const uint8_t *key,
+ uint8_t cidr)
+{
+ struct radix_pool **current, *next;
+ struct radix_node *node;
+
+ for (current = &ns->ip4_pools; *current; current = &(*current)->next) {
+ struct radix_node *node = (*current)->node;
+ if (node->cidr == cidr && common_bits(node, key, bits) >= cidr)
+ break;
+ }
+
+ if (!*current)
+ return -1;
+
+ node = (*current)->node;
+
+ if (node->flags & RNODE_IS_SHADOWED) {
+ node->flags &= ~RNODE_IS_SHADOWED;
+ } else {
+ struct radix_node *n = ns->ip4_root;
+ uint64_t v1 = 0, v2 = 0;
+
+ if (orphan_nodes(node->bit[0], &v1))
+ node->bit[0] = NULL;
+
+ if (orphan_nodes(node->bit[1], &v2))
+ node->bit[1] = NULL;
+
+ node->left += v1;
+ node->right += v2;
+
+ while (n && n->cidr < cidr && prefix_matches(n, key, bits)) {
+ if (n->bit[0] == CHOOSE_NODE(n, key))
+ n->left += v1 + v2;
+ else
+ n->right += v1 + v2;
+
+ n = CHOOSE_NODE(n, key);
+ }
+ }
+
+ node->flags &= ~RNODE_IS_POOLNODE;
+ next = (*current)->next;
+ free(*current);
+ *current = next;
return 0;
}
@@ -498,101 +626,103 @@ static void debug_print_trie(struct radix_node *root, uint8_t bits)
node_to_str(root->bit[0], child1, bits);
node_to_str(root->bit[1], child2, bits);
- debug("%s (%zu, %zu) -> %s, %s\n", parent, root->left, root->right,
- child1, child2);
+ debug("%s (%zu, %zu, %c%c%c) -> %s, %s\n", parent, root->left,
+ root->right, root->flags & RNODE_IS_LEAF ? 'l' : '-',
+ root->flags & RNODE_IS_POOLNODE ? 'p' : '-',
+ root->flags & RNODE_IS_SHADOWED ? 's' : '-', child1, child2);
debug_print_trie(root->bit[0], bits);
debug_print_trie(root->bit[1], bits);
}
-void debug_print_trie_v4(struct ip_pool *pool)
+void debug_print_trie_v4(struct ipns *ns)
{
- debug_print_trie(pool->ip4_root, 32);
+ debug_print_trie(ns->ip4_root, 32);
}
-void debug_print_trie_v6(struct ip_pool *pool)
+void debug_print_trie_v6(struct ipns *ns)
{
- debug_print_trie(pool->ip6_root, 128);
+ debug_print_trie(ns->ip6_root, 128);
}
#endif
-void ipp_init(struct ip_pool *pool)
+void ipp_init(struct ipns *ns)
{
- pool->ip4_root = pool->ip6_root = NULL;
- pool->ip4_pool = pool->ip6_pool = NULL;
- pool->totall_ipv6 = pool->totalh_ipv6 = pool->total_ipv4 = 0;
+ ns->ip4_root = ns->ip6_root = NULL;
+ ns->ip4_pools = ns->ip6_pools = NULL;
+ ns->totall_ipv6 = ns->totalh_ipv6 = ns->total_ipv4 = 0;
}
-void ipp_free(struct ip_pool *pool)
+void ipp_free(struct ipns *ns)
{
struct radix_pool *next;
- radix_free_nodes(pool->ip4_root);
- radix_free_nodes(pool->ip6_root);
+ radix_free_nodes(ns->ip4_root);
+ radix_free_nodes(ns->ip6_root);
- for (struct radix_pool *cur = pool->ip4_pool; cur; cur = next) {
+ for (struct radix_pool *cur = ns->ip4_pools; cur; cur = next) {
next = cur->next;
free(cur);
}
- for (struct radix_pool *cur = pool->ip6_pool; cur; cur = next) {
+ for (struct radix_pool *cur = ns->ip6_pools; cur; cur = next) {
next = cur->next;
free(cur);
}
}
-int ipp_add_v4(struct ip_pool *pool, const struct in_addr *ip, uint8_t cidr)
+int ipp_add_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr)
{
- int ret = insert_v4(&pool->ip4_root, ip, cidr);
+ int ret = insert_v4(&ns->ip4_root, ip, cidr);
if (!ret)
- --pool->total_ipv4;
+ --ns->total_ipv4;
return ret;
}
-int ipp_add_v6(struct ip_pool *pool, const struct in6_addr *ip, uint8_t cidr)
+int ipp_add_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr)
{
- int ret = insert_v6(&pool->ip6_root, ip, cidr);
+ int ret = insert_v6(&ns->ip6_root, ip, cidr);
if (!ret) {
- if (pool->totall_ipv6 == 0)
- --pool->totalh_ipv6;
+ if (ns->totall_ipv6 == 0)
+ --ns->totalh_ipv6;
- --pool->totall_ipv6;
+ --ns->totall_ipv6;
}
return ret;
}
-int ipp_del_v4(struct ip_pool *pool, const struct in_addr *ip, uint8_t cidr)
+int ipp_del_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr)
{
uint8_t key[4] __aligned(__alignof(uint32_t));
int ret;
swap_endian(key, (const uint8_t *)ip, 32);
- ret = remove_node(pool->ip4_root, key, cidr);
+ ret = remove_node(&ns->ip4_root, key, cidr);
if (!ret)
- ++pool->total_ipv4;
+ ++ns->total_ipv4;
return ret;
}
-int ipp_del_v6(struct ip_pool *pool, const struct in6_addr *ip, uint8_t cidr)
+int ipp_del_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr)
{
uint8_t key[16] __aligned(__alignof(uint64_t));
int ret;
swap_endian(key, (const uint8_t *)ip, 128);
- ret = remove_node(pool->ip6_root, key, cidr);
+ ret = remove_node(&ns->ip6_root, key, cidr);
if (!ret) {
- ++pool->totall_ipv6;
- if (pool->totall_ipv6 == 0)
- ++pool->totalh_ipv6;
+ ++ns->totall_ipv6;
+ if (ns->totall_ipv6 == 0)
+ ++ns->totalh_ipv6;
}
return ret;
}
-int ipp_addpool_v4(struct ip_pool *ipp, const struct in_addr *ip, uint8_t cidr)
+int ipp_addpool_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr)
{
uint8_t key[4] __aligned(__alignof(uint32_t));
@@ -600,10 +730,10 @@ int ipp_addpool_v4(struct ip_pool *ipp, const struct in_addr *ip, uint8_t cidr)
return -1;
swap_endian(key, (const uint8_t *)ip, 32);
- return ipp_addpool(ipp, &ipp->ip4_pool, &ipp->ip4_root, 32, key, cidr);
+ return ipp_addpool(ns, &ns->ip4_pools, &ns->ip4_root, 32, key, cidr);
}
-int ipp_addpool_v6(struct ip_pool *ipp, const struct in6_addr *ip, uint8_t cidr)
+int ipp_addpool_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr)
{
uint8_t key[16] __aligned(__alignof(uint64_t));
@@ -611,27 +741,37 @@ int ipp_addpool_v6(struct ip_pool *ipp, const struct in6_addr *ip, uint8_t cidr)
return -1;
swap_endian(key, (const uint8_t *)ip, 128);
- return ipp_addpool(ipp, &ipp->ip6_pool, &ipp->ip6_root, 128, key, cidr);
+ return ipp_addpool(ns, &ns->ip6_pools, &ns->ip6_root, 128, key, cidr);
}
-/* TODO: implement */
-int ipp_removepool_v4(struct ip_pool *pool, const struct in_addr *ip)
+int ipp_removepool_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr)
{
- return 0;
+ uint8_t key[4] __aligned(__alignof(uint32_t));
+
+ if (cidr <= 0 || cidr >= 32)
+ return -1;
+
+ swap_endian(key, (const uint8_t *)ip, 32);
+ return ipp_removepool(ns, 32, key, cidr);
}
-/* TODO: implement */
-int ipp_removepool_v6(struct ip_pool *pool, const struct in6_addr *ip)
+int ipp_removepool_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr)
{
- return 0;
+ uint8_t key[16] __aligned(__alignof(uint64_t));
+
+ if (cidr < 64 || cidr >= 128)
+ return -1;
+
+ swap_endian(key, (const uint8_t *)ip, 128);
+ return ipp_removepool(ns, 128, key, cidr);
}
-void ipp_addnth_v4(struct ip_pool *pool, struct in_addr *dest, uint32_t index)
+void ipp_addnth_v4(struct ipns *ns, struct in_addr *dest, uint32_t index)
{
- struct radix_pool *current = pool->ip4_pool;
+ struct radix_pool *current;
- for (current = pool->ip4_pool; current; current = current->next) {
- if (current->shadowed)
+ for (current = ns->ip4_pools; current; current = current->next) {
+ if (current->node->flags & RNODE_IS_SHADOWED)
continue;
if (index < current->node->left + current->node->right)
@@ -643,24 +783,24 @@ void ipp_addnth_v4(struct ip_pool *pool, struct in_addr *dest, uint32_t index)
BUG_ON(!current);
add_nth(current->node, 32, index, (uint8_t *)&dest->s_addr);
- --pool->total_ipv4;
+ --ns->total_ipv4;
}
-void ipp_addnth_v6(struct ip_pool *pool, struct in6_addr *dest,
- uint32_t index_low, uint64_t index_high)
+void ipp_addnth_v6(struct ipns *ns, struct in6_addr *dest, uint32_t index_low,
+ uint64_t index_high)
{
- struct radix_pool *current = pool->ip6_pool;
+ struct radix_pool *current;
uint64_t tmp;
- while (current) {
- if (current->shadowed ||
- (current->node->left == 0 && current->node->right == 0)) {
- current = current->next;
+ for (current = ns->ip6_pools; current; current = current->next) {
+ if (current->node->flags & RNODE_IS_SHADOWED ||
+ (current->node->left == 0 && current->node->right == 0))
continue;
- }
- if (index_high == 0 &&
- index_low < (current->node->left + current->node->right))
+ /* left + right may overflow, so we substract 1 which is safe
+ * since we ensured it's > 0 except when the total is 2^64 */
+ if (index_high == 0 && index_low <= (current->node->left +
+ current->node->right - 1))
break;
tmp = index_low - (current->node->left + current->node->right);
@@ -669,15 +809,13 @@ void ipp_addnth_v6(struct ip_pool *pool, struct in6_addr *dest,
--index_high;
}
index_low = tmp;
-
- current = current->next;
}
BUG_ON(!current || index_high);
add_nth(current->node, 128, index_low, (uint8_t *)&dest->s6_addr);
- if (pool->totall_ipv6 == 0)
- --pool->totalh_ipv6;
+ if (ns->totall_ipv6 == 0)
+ --ns->totalh_ipv6;
- --pool->totall_ipv6;
+ --ns->totall_ipv6;
}
diff --git a/radix-trie.h b/radix-trie.h
index a72ee50..ffaf7ad 100644
--- a/radix-trie.h
+++ b/radix-trie.h
@@ -10,37 +10,38 @@
#include <stdbool.h>
#include <stdint.h>
-struct ip_pool {
- uint64_t totall_ipv6;
- uint32_t totalh_ipv6, total_ipv4;
+struct ipns {
+ /* Total amount of available addresses over all pools */
+ uint64_t totall_ipv6, total_ipv4;
+ uint32_t totalh_ipv6;
+
struct radix_node *ip4_root, *ip6_root;
- struct radix_pool *ip4_pool, *ip6_pool;
+ struct radix_pool *ip4_pools, *ip6_pools;
};
-void ipp_init(struct ip_pool *pool);
-void ipp_free(struct ip_pool *pool);
+void ipp_init(struct ipns *ns);
+void ipp_free(struct ipns *ns);
-int ipp_add_v4(struct ip_pool *pool, const struct in_addr *ip, uint8_t cidr);
-int ipp_add_v6(struct ip_pool *pool, const struct in6_addr *ip, uint8_t cidr);
+int ipp_add_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr);
+int ipp_add_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr);
-int ipp_del_v4(struct ip_pool *pool, const struct in_addr *ip, uint8_t cidr);
-int ipp_del_v6(struct ip_pool *pool, const struct in6_addr *ip, uint8_t cidr);
+int ipp_del_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr);
+int ipp_del_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr);
-void ipp_addnth_v4(struct ip_pool *pool, struct in_addr *dest, uint32_t index);
-void ipp_addnth_v6(struct ip_pool *pool, struct in6_addr *dest,
- uint32_t index_low, uint64_t index_high);
+void ipp_addnth_v4(struct ipns *ns, struct in_addr *dest, uint32_t index);
+void ipp_addnth_v6(struct ipns *ns, struct in6_addr *dest, uint32_t index_low,
+ uint64_t index_high);
-int ipp_addpool_v4(struct ip_pool *ipp, const struct in_addr *ip, uint8_t cidr);
-int ipp_addpool_v6(struct ip_pool *ipp, const struct in6_addr *ip,
- uint8_t cidr);
+int ipp_addpool_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr);
+int ipp_addpool_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr);
-int ipp_removepool_v4(struct ip_pool *pool, const struct in_addr *ip);
-int ipp_removepool_v6(struct ip_pool *pool, const struct in6_addr *ip);
+int ipp_removepool_v4(struct ipns *ns, const struct in_addr *ip, uint8_t cidr);
+int ipp_removepool_v6(struct ipns *ns, const struct in6_addr *ip, uint8_t cidr);
#ifdef DEBUG
void node_to_str(struct radix_node *node, char *buf, uint8_t bits);
-void debug_print_trie_v4(struct ip_pool *pool);
-void debug_print_trie_v6(struct ip_pool *pool);
+void debug_print_trie_v4(struct ipns *ns);
+void debug_print_trie_v6(struct ipns *ns);
#endif
#endif
diff --git a/random.c b/random.c
index eedb52f..e5982db 100644
--- a/random.c
+++ b/random.c
@@ -27,7 +27,7 @@
#endif
#endif
-static inline bool __attribute__((__warn_unused_result__))
+bool __attribute__((__warn_unused_result__))
get_random_bytes(uint8_t *out, size_t len)
{
ssize_t ret = 0;
@@ -68,24 +68,27 @@ get_random_bytes(uint8_t *out, size_t len)
return i == len;
}
-uint64_t random_bounded(uint64_t bound)
+uint64_t random_u64()
{
uint64_t ret;
+ if (!get_random_bytes((uint8_t *)&ret, sizeof(ret)))
+ fatal("get_random_bytes()");
- if (bound == 0)
- return 0;
+ return ret;
+}
- if (bound == 1) {
- if (!get_random_bytes((uint8_t *)&ret, sizeof(ret)))
- fatal("get_random_bytes()");
- return (ret > 0x7FFFFFFFFFFFFFFF) ? 1 : 0;
- }
+/* Returns a random number [0, bound) (exclusive) */
+uint64_t random_bounded(uint64_t bound)
+{
+ uint64_t ret, max_mod_bound;
+
+ if (bound < 2)
+ return 0;
- const uint64_t max_mod_bound = (1 + ~bound) % bound;
+ max_mod_bound = (1 + ~bound) % bound;
do {
- if (!get_random_bytes((uint8_t *)&ret, sizeof(ret)))
- fatal("get_random_bytes()");
+ ret = random_u64();
} while (ret < max_mod_bound);
return ret % bound;
diff --git a/random.h b/random.h
index 04271a4..a6608da 100644
--- a/random.h
+++ b/random.h
@@ -6,8 +6,12 @@
#ifndef __RANDOM_H__
#define __RANDOM_H__
+#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
+bool get_random_bytes(uint8_t *out, size_t len);
+uint64_t random_u64();
uint64_t random_bounded(uint64_t bound);
#endif
diff --git a/siphash.c b/siphash.c
new file mode 100644
index 0000000..63cc2c6
--- /dev/null
+++ b/siphash.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2016-2019 WireGuard LLC. All Rights Reserved.
+ *
+ * SipHash: a fast short-input PRF
+ * https://131002.net/siphash/
+ */
+
+#include "siphash.h"
+
+static inline uint64_t rol64(uint64_t word, unsigned int shift)
+{
+ return (word << shift) | (word >> (64 - shift));
+}
+
+#define SIPROUND \
+ do { \
+ v0 += v1; \
+ v1 = rol64(v1, 13); \
+ v1 ^= v0; \
+ v0 = rol64(v0, 32); \
+ v2 += v3; \
+ v3 = rol64(v3, 16); \
+ v3 ^= v2; \
+ v0 += v3; \
+ v3 = rol64(v3, 21); \
+ v3 ^= v0; \
+ v2 += v1; \
+ v1 = rol64(v1, 17); \
+ v1 ^= v2; \
+ v2 = rol64(v2, 32); \
+ } while (0)
+
+#define PREAMBLE(len) \
+ uint64_t v0 = 0x736f6d6570736575ULL; \
+ uint64_t v1 = 0x646f72616e646f6dULL; \
+ uint64_t v2 = 0x6c7967656e657261ULL; \
+ uint64_t v3 = 0x7465646279746573ULL; \
+ uint64_t b = ((uint64_t)(len)) << 56; \
+ v3 ^= key->key[1]; \
+ v2 ^= key->key[0]; \
+ v1 ^= key->key[1]; \
+ v0 ^= key->key[0];
+
+#define POSTAMBLE \
+ v3 ^= b; \
+ SIPROUND; \
+ SIPROUND; \
+ v0 ^= b; \
+ v2 ^= 0xff; \
+ SIPROUND; \
+ SIPROUND; \
+ SIPROUND; \
+ SIPROUND; \
+ return (v0 ^ v1) ^ (v2 ^ v3);
+
+uint64_t __siphash_aligned(const void *data, size_t len,
+ const siphash_key_t *key)
+{
+ const uint8_t *end = data + len - (len % sizeof(uint64_t));
+ const uint8_t left = len & (sizeof(uint64_t) - 1);
+ uint64_t m;
+ PREAMBLE(len)
+ for (; data != end; data += sizeof(uint64_t)) {
+ m = le64toh(*((uint64_t *)data));
+ v3 ^= m;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= m;
+ }
+ switch (left) {
+ case 7:
+ b |= ((uint64_t)end[6]) << 48; /* fall through */
+ case 6:
+ b |= ((uint64_t)end[5]) << 40; /* fall through */
+ case 5:
+ b |= ((uint64_t)end[4]) << 32; /* fall through */
+ case 4:
+ b |= le32toh(*((uint32_t *)data));
+ break;
+ case 3:
+ b |= ((uint64_t)end[2]) << 16; /* fall through */
+ case 2:
+ b |= le16toh(*((uint16_t *)data));
+ break;
+ case 1:
+ b |= end[0];
+ }
+ POSTAMBLE
+}
+
+/**
+ * siphash_1u64 - compute 64-bit siphash PRF value of a u64
+ * @first: first u64
+ * @key: the siphash key
+ */
+uint64_t siphash_1u64(const uint64_t first, const siphash_key_t *key)
+{
+ PREAMBLE(8)
+ v3 ^= first;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= first;
+ POSTAMBLE
+}
+
+/**
+ * siphash_2u64 - compute 64-bit siphash PRF value of 2 u64
+ * @first: first u64
+ * @second: second u64
+ * @key: the siphash key
+ */
+uint64_t siphash_2u64(const uint64_t first, const uint64_t second,
+ const siphash_key_t *key)
+{
+ PREAMBLE(16)
+ v3 ^= first;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= first;
+ v3 ^= second;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= second;
+ POSTAMBLE
+}
+
+/**
+ * siphash_3u64 - compute 64-bit siphash PRF value of 3 u64
+ * @first: first u64
+ * @second: second u64
+ * @third: third u64
+ * @key: the siphash key
+ */
+uint64_t siphash_3u64(const uint64_t first, const uint64_t second,
+ const uint64_t third, const siphash_key_t *key)
+{
+ PREAMBLE(24)
+ v3 ^= first;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= first;
+ v3 ^= second;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= second;
+ v3 ^= third;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= third;
+ POSTAMBLE
+}
+
+/**
+ * siphash_4u64 - compute 64-bit siphash PRF value of 4 u64
+ * @first: first u64
+ * @second: second u64
+ * @third: third u64
+ * @forth: forth u64
+ * @key: the siphash key
+ */
+uint64_t siphash_4u64(const uint64_t first, const uint64_t second,
+ const uint64_t third, const uint64_t forth,
+ const siphash_key_t *key)
+{
+ PREAMBLE(32)
+ v3 ^= first;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= first;
+ v3 ^= second;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= second;
+ v3 ^= third;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= third;
+ v3 ^= forth;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= forth;
+ POSTAMBLE
+}
+
+uint64_t siphash_1u32(const uint32_t first, const siphash_key_t *key)
+{
+ PREAMBLE(4)
+ b |= first;
+ POSTAMBLE
+}
+
+uint64_t siphash_3u32(const uint32_t first, const uint32_t second,
+ const uint32_t third, const siphash_key_t *key)
+{
+ uint64_t combined = (uint64_t)second << 32 | first;
+ PREAMBLE(12)
+ v3 ^= combined;
+ SIPROUND;
+ SIPROUND;
+ v0 ^= combined;
+ b |= third;
+ POSTAMBLE
+}
diff --git a/siphash.h b/siphash.h
new file mode 100644
index 0000000..8fefb31
--- /dev/null
+++ b/siphash.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2016-2019 WireGuard LLC. All Rights Reserved.
+ *
+ * SipHash: a fast short-input PRF
+ * https://131002.net/siphash/
+ */
+
+#ifndef _LINUX_SIPHASH_H
+#define _LINUX_SIPHASH_H
+
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+#include <endian.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct {
+ uint64_t key[2];
+} siphash_key_t;
+
+static inline bool siphash_key_is_zero(const siphash_key_t *key)
+{
+ return !(key->key[0] | key->key[1]);
+}
+
+uint64_t __siphash_aligned(const void *data, size_t len,
+ const siphash_key_t *key);
+
+uint64_t siphash_1u64(const uint64_t a, const siphash_key_t *key);
+uint64_t siphash_2u64(const uint64_t a, const uint64_t b,
+ const siphash_key_t *key);
+uint64_t siphash_3u64(const uint64_t a, const uint64_t b, const uint64_t c,
+ const siphash_key_t *key);
+uint64_t siphash_4u64(const uint64_t a, const uint64_t b, const uint64_t c,
+ const uint64_t d, const siphash_key_t *key);
+uint64_t siphash_1u32(const uint32_t a, const siphash_key_t *key);
+uint64_t siphash_3u32(const uint32_t a, const uint32_t b, const uint32_t c,
+ const siphash_key_t *key);
+
+static inline uint64_t siphash_2u32(const uint32_t a, const uint32_t b,
+ const siphash_key_t *key)
+{
+ return siphash_1u64((uint64_t)b << 32 | a, key);
+}
+static inline uint64_t siphash_4u32(const uint32_t a, const uint32_t b,
+ const uint32_t c, const uint32_t d,
+ const siphash_key_t *key)
+{
+ return siphash_2u64((uint64_t)b << 32 | a, (uint64_t)d << 32 | c, key);
+}
+
+static inline uint64_t ___siphash_aligned(const uint64_t *data, size_t len,
+ const siphash_key_t *key)
+{
+ if (__builtin_constant_p(len) && len == 4)
+ return siphash_1u32(le32toh(*((const uint32_t *)data)), key);
+ if (__builtin_constant_p(len) && len == 8)
+ return siphash_1u64(le64toh(data[0]), key);
+ if (__builtin_constant_p(len) && len == 16)
+ return siphash_2u64(le64toh(data[0]), le64toh(data[1]), key);
+ if (__builtin_constant_p(len) && len == 24)
+ return siphash_3u64(le64toh(data[0]), le64toh(data[1]),
+ le64toh(data[2]), key);
+ if (__builtin_constant_p(len) && len == 32)
+ return siphash_4u64(le64toh(data[0]), le64toh(data[1]),
+ le64toh(data[2]), le64toh(data[3]), key);
+ return __siphash_aligned(data, len, key);
+}
+
+/**
+ * siphash - compute 64-bit siphash PRF value
+ * @data: buffer to hash
+ * @size: size of @data
+ * @key: the siphash key
+ */
+static inline uint64_t siphash(const void *data, size_t len,
+ const siphash_key_t *key)
+{
+ return ___siphash_aligned(data, len, key);
+}
+
+#endif /* _LINUX_SIPHASH_H */
diff --git a/tests/clientsh.bash b/tests/clientsh.bash
deleted file mode 100755
index 3f297c5..0000000
--- a/tests/clientsh.bash
+++ /dev/null
@@ -1,302 +0,0 @@
-#! /bin/bash
-
-# BUG: When running this a second time targeting the same
-# wg-dynamic-server, first ncat is hanging on receiving a response.
-# tcpdump shows that the response is indeed sent by the server.
-
-set -e
-
-exec 3>&1
-
-DEBUG=1
-netnsprefix="$1"; shift # wg-test-$PID
-server_public="$1"; shift
-
-netnsn() { echo $netnsprefix-$1; }
-pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
-pp() { pretty "" "$*"; "$@"; }
-maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
-nn() {
- [[ "$1" != "-q" ]] && pretty $n "$*" || shift
- local netns=$(netnsn $1) n=$1; shift;
- maybe_exec ip netns exec $netns "$@";
-}
-ipn() {
- [[ "$1" != "-q" ]] && pretty $n "ip $*" || shift
- local netns=$(netnsn $1) n=$1; shift;
- ip -n $netns "$@";
-}
-
-ns=
-
-cleanup() {
- set +e
- exec 2>/dev/null
-
- for n in $ns; do ipn $n link del dev wg0; done
-
- local to_kill="$(for n in $ns; do $(ip netns pids $(netnsn $n)); done)"
- [[ -n $to_kill ]] && kill $to_kill
-
- for n in $ns; do pp ip netns del $(netnsn $n); done
-
- exit
-}
-
-trap cleanup EXIT
-
-setup_client_peer() {
- local n=$1; shift
- ns+=" $n"
-
- pp ip netns add $(netnsn $n)
- ipn 0 link add dev wg0 type wireguard
- ipn 0 link set wg0 netns $(netnsn $n)
-
- privkey=$(wg genkey)
- pubkey=$(wg pubkey <<< $privkey)
-
- ipn $n addr add fe80::badc:0ffe:e0dd:$n/64 dev wg0
- nn $n wg set wg0 \
- private-key <(echo $privkey) \
- listen-port $n \
- peer $server_public \
- allowed-ips 0.0.0.0/0,::/0
-
- ipn $n link set up dev wg0
- ipn $n route add fe80::/128 dev wg0
-
- nn $n wg set wg0 peer "$server_public" endpoint [::1]:1
- nn 1 wg set wg0 peer "$pubkey" \
- allowed-ips fe80::badc:0ffe:e0dd:$n/128 \
- endpoint [::1]:$n
-
- nn $n ping6 -c 10 -f -W 1 fe80::%wg0
- nn 1 ping6 -c 10 -f -W 1 fe80::badc:0ffe:e0dd:$n%wg0
-}
-
-# Positive check -- verify that $1 is allowed on server.
-check_alowedips() {
- local n="$1"; shift
- local pubkey="$1"; shift
- local ip="$1"; shift
-
- [[ -z "$ip" ]] && return 0
-
- nn -q 1 wg show wg0 allowed-ips |
- while read -r _pubkey _ips; do
- [[ "$_pubkey" = "$pubkey" ]] || continue
- for _ip in $_ips; do
- [[ "$_ip" = "$ip" ]] && return 0
- done
- done && return 0
-
- pretty $n "Missing $ip in allowedips"
- return 1
-}
-
-declare -a IPV4
-declare -a IPV6
-declare -a LEASESTART
-declare -a LEASETIME
-declare -a ERRNO
-
-send_cmd() {
- local n=$1; shift
- local REQ="$1"; shift
-
- # It would have been nice to use /dev/tcp/fe80::%w0/970 instead of
- # nc, but we need to use a specific source port.
- eval $(
- printf $REQ | nn -q $n ncat -p 970 fe80::%wg0 970 |
- while read -r line; do
- key="${line%%=*}"
- value="${line#*=}"
- case "$key" in
- ipv4) echo IPV4[$n]="$value"; continue ;;
- ipv6) echo IPV6[$n]="$value"; continue ;;
- leasestart) echo LEASESTART[$n]="$value"; continue ;;
- leasetime) echo LEASETIME[$n]="$value"; continue ;;
- errno) echo ERRNO[$n]="$value"; continue ;;
- esac
- done
- )
-}
-
-req() {
- local n=$1; shift
- local ipv4_req=
- [ $# -gt 0 ] && [ -n "$1" ] && { ipv4_req="ipv4=$1\n"; shift; }
- [ "$ipv4_req" = "ipv4=-\n" ] && ipv4_req="ipv4=\n"
- local ipv6_req=
- [ $# -gt 0 ] && [ -n "$1" ] && { ipv6_req="ipv6=$1\n"; shift; }
- [ "$ipv6_req" = "ipv6=-\n" ] && ipv6_req="ipv6=\n"
-
- IPV4[$n]=
- IPV6[$n]=
- LEASESTART[$n]=
- LEASETIME[$n]=
- ERRNO[$n]=
-
- REQ="request_ip=1\n${ipv4_req}${ipv6_req}\n"
- send_cmd $n "$REQ"
-}
-
-req_check() {
- local n=$1
-
- req $*
-
- pubkey=$(nn -q $n wg show wg0 public-key)
- check_alowedips $n "$pubkey" "${IPV4[$n]}"
- check_alowedips $n "$pubkey" "${IPV6[$n]}"
-}
-
-run_k_at_random() {
- local NCLIENTS=$1; shift
- local k=$1; shift
- local n=10
- local i
-
- if [[ $NCLIENTS -gt $k ]]; then
- n=$(( 10 + $RANDOM % ($NCLIENTS - $k) ))
- fi
-
- for i in $(seq $n $(( $n + $k - 1))); do
- case $(( $RANDOM % 9 )) in
- 0) req_check $i "" "" ; continue ;;
- 1) req_check $i "" "-" ; continue ;;
- 2) req_check $i "-" "" ; continue ;;
- 3) req_check $i "-" "-" ; continue ;;
-
- 4) req_check $i ${IPV4[$i]} "" ; continue ;;
- 5) req_check $i ${IPV4[$i]} "-" ; continue ;;
- 6) req_check $i "" ${IPV6[$i]} ; continue ;;
- 7) req_check $i "-" ${IPV6[$i]} ; continue ;;
-
- 8) req_check $i ${IPV4[$i]} ${IPV6[$i]} ; continue;;
- esac
- done
-}
-
-run_k_fill() {
- local nclients=$1; shift
- local k=$1; shift
- local i
-
- for i in $(seq $nclients); do
- req_check $(( 9 + $i ))
- done
-}
-
-test_many() {
- local func=$1; shift
- local NCLIENTS=$1; shift
- local k=$1; shift
- local n
-
- for n in $(seq 10 $(( 9+$NCLIENTS ))); do setup_client_peer $n; done
-
- for n in $(seq 10 $(( 9+$NCLIENTS ))); do
- req_check $n
- #t=$(( 1 + $RANDOM % 2 ))
- #sleep $t
- done
-
- while sleep 1; do
- if [ $(( $RANDOM % 100 )) -lt 50 ]; then
- $func $NCLIENTS $k
- fi
- done
-}
-
-fail() {
- echo "FAIL \"$1\""
- exit 1
-}
-
-test_case_1() {
- # One client -- 3.
- setup_client_peer 3
-
- pretty 3 "Badly formed request => errno=1 -- EXPECTED FAILURE: errno=2"
- send_cmd 3 "ip_request=\n\n"
- [[ ${ERRNO[3]} = 2 ]] || fail "errno: ${ERRNO[3]}"
-
- ## Check disabled 2019-09-27. Enable again when ipp_add_v4() and
- ## ipp_add_v6() have checks.
- #pretty 3 "Request an address we won't get => errno=2"
- #req 3 "1.1.1.0/32" "-"
- #[[ ${ERRNO[3]} = 2 ]] || fail "errno: ${ERRNO[3]}"
-
- pretty "" "SUCCESS\n"
-}
-
-test_case_2() {
- # Two clients -- 4 and 5.
- for i in 4 5; do setup_client_peer $i; done
-
- pretty 4 "Any v4, any v6"
- req_check 4
- [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
- local C4_FIRST_V4=${IPV4[4]}
- local C4_FIRST_V6=${IPV6[4]}
-
- pretty 4 "Extend v4, extend v6"
- req_check 4 $C4_FIRST_V4 $C4_FIRST_V6
- [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
- [[ ${IPV4[4]} = $C4_FIRST_V4 ]] || fail "ipv4: ${IPV4[4]}"
- [[ ${IPV6[4]} = $C4_FIRST_V6 ]] || fail "ipv6: ${IPV6[4]}"
-
- pretty 4 "Extend v4, drop v6"
- req_check 4 $C4_FIRST_V4 "-"
- [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
- [[ ${IPV4[4]} = $C4_FIRST_V4 ]] || fail "ipv4: ${IPV4[4]}"
- [[ -z "${IPV6[4]}" ]] || fail "ipv6: ${IPV6[4]}"
-
- pretty 5 "Requesting the v4 of client 4 and no v6 => errno=0 and no addrs"
- req 5 $C4_FIRST_V4 "-"
- [[ ${ERRNO[5]} = 2 ]] || fail "errno: ${ERRNO[5]}"
- [[ -z "${IPV4[5]}" ]] || fail "ipv4 not empty: ${IPV4[5]}"
- [[ -z "${IPV6[5]}" ]] || fail "ipv6 not empty: ${IPV6[5]}"
-
- pretty 5 "Wait for lease to expire and try again"
- pp sleep ${LEASETIME[4]}
- req_check 5 $C4_FIRST_V4 "-"
- [[ ${ERRNO[5]} = 0 ]] || fail "errno: ${ERRNO[5]}"
- [[ ${IPV4[5]} = $C4_FIRST_V4 ]] || fail "ipv4: ${IPV4[5]} != $C4_FIRST_V4"
- [[ -z "${IPV6[5]}" ]] || fail "ipv6 not empty: ${IPV6[5]}"
-
- pretty "" "SUCCESS\n"
-}
-
-test_case_3() {
- # Two clients -- 6 and 7.
- for i in 6 7; do setup_client_peer $i; done
-
- pretty 6 "Any v4, any v6"
- req_check 6
- [[ ${ERRNO[6]} = 0 ]] || fail "errno: ${ERRNO[6]}"
- local C6_FIRST_V4=${IPV4[6]}
- local C6_FIRST_V6=${IPV6[6]}
-
- pretty 6 "Drop v4, extend v6"
- req_check 6 "-" $C6_FIRST_V6
- [[ ${ERRNO[6]} = 0 ]] || fail "errno: ${ERRNO[6]} != 0"
- [[ ${IPV4[6]} = 0.0.0.0/32 ]] || fail "ipv4: ${IPV4[6]} != 0.0.0.0/32"
- [[ ${IPV6[6]} = $C6_FIRST_V6 ]] || fail "ipv6: ${IPV6[6]} != $C6_FIRST_V6"
-
- pretty "" "SUCCESS\n"
-}
-
-test_case_1
-test_case_2
-test_case_3
-
-N_RANDOM=20
-K_RANDOM=4
-[ $# -gt 0 ] && { N_RANDOM=$1; shift; }
-[ $# -gt 0 ] && { K_RANDOM=$1; shift; }
-
-test_many run_k_at_random $N_RANDOM $K_RANDOM
-#test_many run_k_fill $N_RANDOM $K_RANDOM
diff --git a/tests/netsh.sh b/tests/netsh.sh
index f9e9c1e..dd30c2f 100755
--- a/tests/netsh.sh
+++ b/tests/netsh.sh
@@ -6,84 +6,351 @@
set -e
exec 3>&1
-
-[ $# -ge 1 ] && ( n_clients="$1"; shift; )
-
+netnsprefix="wg-test-$$"
export WG_HIDE_KEYS=never
-netnsn() { echo wg-test-$$-$1; }
+netnsn() { echo $netnsprefix-$1; }
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
pp() { pretty "" "$*"; "$@"; }
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
-nn() { local netns=$(netnsn $1) n=$1; shift; pretty $n "$*"; maybe_exec ip netns exec $netns "$@"; }
-ipn() { local netns=$(netnsn $1) n=$1; shift; pretty $n "ip $*"; ip -n $netns "$@"; }
-
-ns="0 1 2"
+nn() {
+ [[ "$1" != "-q" ]] && pretty $1 "$*" || shift
+ local netns=$(netnsn $1); shift
+ maybe_exec ip netns exec $netns "$@";
+}
+ipn() {
+ [[ "$1" != "-q" ]] && pretty $1 "ip $*" || shift
+ local netns=$(netnsn $1); shift
+ ip -n $netns "$@";
+}
cleanup() {
set +e
exec 2>/dev/null
-
for n in $ns; do ipn $n link del dev wg0; done
-
local to_kill="$(for n in $ns; do $(ip netns pids $(netnsn $n)); done)"
[[ -n $to_kill ]] && kill $to_kill
-
for n in $ns; do pp ip netns del $(netnsn $n); done
-
exit
}
-
trap cleanup EXIT
+### Server
+ip netns del $(netnsn 0) 2>/dev/null || true
pp ip netns add $(netnsn 0)
+ip netns del $(netnsn 1) 2>/dev/null || true
pp ip netns add $(netnsn 1)
-pp ip netns add $(netnsn 2)
ipn 0 link set up dev lo
+ns="0 1"
ipn 0 link add dev wg0 type wireguard
ipn 0 link set wg0 netns $(netnsn 1)
-ipn 0 link add dev wg0 type wireguard
-ipn 0 link set wg0 netns $(netnsn 2)
server_private=$(wg genkey)
server_public=$(wg pubkey <<< $server_private)
-client_private=$(wg genkey)
-client_public=$(wg pubkey <<< $client_private)
-
-configure_peers() {
- ipn 1 addr add fe80::/64 dev wg0
- ipn 2 addr add fe80::badc:0ffe:e0dd:f00d/128 dev wg0
-
- nn 1 wg set wg0 \
- private-key <(echo $server_private) \
- listen-port 1 \
- peer $client_public \
- allowed-ips fe80::badc:0ffe:e0dd:f00d/128
-
- nn 2 wg set wg0 \
- private-key <(echo $client_private) \
- listen-port 2 \
- peer $server_public \
- allowed-ips 0.0.0.0/0,::/0
-
- ipn 1 link set up dev wg0
- ipn 2 link set up dev wg0
-
- ipn 2 route add fe80::/128 dev wg0
- ipn 1 route add 192.168.4.0/28 dev wg0
- ipn 1 route add 192.168.73.0/27 dev wg0
- ipn 1 route add 2001:db8:1234::/124 dev wg0
- ipn 1 route add 2001:db8:7777::/124 dev wg0
+
+ipn 1 addr add fe80::/64 dev wg0
+nn 1 wg set wg0 \
+ private-key <(echo $server_private) \
+ listen-port 1
+ipn 1 link set up dev wg0
+
+# Add prefixes to pool.
+ipn 1 route add 192.168.4.0/28 dev wg0
+ipn 1 route add 192.168.73.0/27 dev wg0
+ipn 1 route add 2001:db8:1234::/124 dev wg0
+ipn 1 route add 2001:db8:7777::/124 dev wg0
+
+# Start server.
+nn 1 ./wg-dynamic-server --leasetime 10 wg0 &
+sleep 1 # FIXME: synchronise with server output instead?
+
+### Clients
+declare -a IPV4
+declare -a IPV6
+declare -a LEASESTART
+declare -a LEASETIME
+declare -a ERRNO
+
+setup_client_peer() {
+ local n=$1; shift
+ ns+=" $n"
+
+ ip netns del $(netnsn $n) 2>/dev/null || true
+ pp ip netns add $(netnsn $n)
+ ipn 0 link add dev wg0 type wireguard
+ ipn 0 link set wg0 netns $(netnsn $n)
+
+ privkey=$(wg genkey)
+ pubkey=$(wg pubkey <<< $privkey)
+
+ ipn $n addr add fe80::badc:0ffe:e0dd:$n/64 dev wg0
+ nn $n wg set wg0 \
+ private-key <(echo $privkey) \
+ listen-port $n \
+ peer $server_public \
+ allowed-ips 0.0.0.0/0,::/0
+
+ ipn $n link set up dev wg0
+ ipn $n route add fe80::/128 dev wg0
+
+ nn $n wg set wg0 peer "$server_public" endpoint [::1]:1
+ nn 1 wg set wg0 peer "$pubkey" \
+ allowed-ips fe80::badc:0ffe:e0dd:$n/128 \
+ endpoint [::1]:$n
+
+ nn $n ping6 -c 10 -f -W 1 fe80::%wg0 > /dev/null
+ nn 1 ping6 -c 10 -f -W 1 fe80::badc:0ffe:e0dd:$n%wg0 > /dev/null
+}
+
+# check_alowedips(PUBKEY, IPV4, IPV6) verifies that IPV4 and IPV6
+# are allowed on server. Empty argument means there must be no address
+# for the given family, except the lladdr for v6.
+check_alowedips() {
+ local pubkey="$1"; shift
+ local ipv4="$1"; shift
+ local ipv6="$1"; shift
+
+ local v4_found=
+ local v6_found=
+ while read -r _pubkey _ips; do
+ [[ "$_pubkey" = "$pubkey" ]] || continue
+ for _ip in $_ips; do
+ case $_ip in
+ fe80:badc:ffe:e0dd:*)
+ continue ;;
+ *:*)
+ [[ -n "$v6_found" ]] && { echo "bad allowedip: $_ip"; return 1; }
+ [[ "$ipv6" = "$_ip" ]] && v6_found=$_ip
+ continue ;;
+ *)
+ [[ -n "$v4_found" ]] && { echo "bad allowedip: $_ip"; return 1; }
+ [[ "$ipv4" = "$_ip" ]] && v4_found=$_ip
+ continue ;;
+ esac
+ done
+ done < <(nn -q 1 wg show wg0 allowed-ips)
+
+ [[ "$ipv4" != "$v4_found" ]] && { echo "missing allowedip: $ipv4"; return 1; }
+ [[ "$ipv6" != "$v6_found" ]] && { echo "missing allowedip: $ipv6"; return 1; }
+
+ return 0
+}
+
+send_cmd() {
+ local n=$1; shift
+ local REQ="$1"; shift
+
+ # It would have been nice to use /dev/tcp/fe80::%w0/970 instead of
+ # ncat, but we need to use a specific source port.
+ while read -r line && [[ -n $line ]] ; do
+ key="${line%%=*}"
+ value="${line#*=}"
+ case "$key" in
+ ip)
+ if [[ "$value" =~ : ]]; then
+ IPV6[$n]="$value"
+ else
+ IPV4[$n]="$value"
+ fi
+ continue ;;
+ leasestart) LEASESTART[$n]="$value"; continue ;;
+ leasetime) LEASETIME[$n]="$value"; continue ;;
+ errno) ERRNO[$n]="$value"; continue ;;
+ esac
+ done < <(printf $REQ | nn -q $n ncat -p 970 fe80::%wg0 970 2>/dev/null)
+}
+
+# req(N, IPV4, IPV6) sends a request for client N, asking for IPV4 and
+# IPv6. An empty string or "-" results in a request for releasing an
+# address rather than allocating one. NOTE: Asking for 0.0.0.0 or ::0
+# means asking for any address in the pool.
+req() {
+ local n=$1; shift
+ local ipv4_req=
+ [ $# -gt 0 ] && [ -n "$1" ] && { ipv4_req="ip=$1\n"; shift; }
+ [ "$ipv4_req" = "ip=-\n" ] && ipv4_req=
+ local ipv6_req=
+ [ $# -gt 0 ] && [ -n "$1" ] && { ipv6_req="ip=$1\n"; shift; }
+ [ "$ipv6_req" = "ip=-\n" ] && ipv6_req=
+
+ IPV4[$n]=
+ IPV6[$n]=
+ LEASESTART[$n]=
+ LEASETIME[$n]=
+ ERRNO[$n]=
+
+ REQ="request_ip=1\n${ipv4_req}${ipv6_req}\n"
+ send_cmd $n "$REQ"
+}
+
+req_check() {
+ local n=$1
+
+ req $*
+
+ pubkey=$(nn -q $n wg show wg0 public-key)
+ check_alowedips "$pubkey" "${IPV4[$n]}" "${IPV6[$n]}"
+}
+
+fail() {
+ echo "FAIL \"$1\""
+ exit 1
+}
+
+test_case_1() {
+ # One client -- 3.
+ setup_client_peer 3
+
+ pretty 3 "Badly formed request => errno=1 -- EXPECTED FAILURE: errno=2"
+ send_cmd 3 "ip_request=\n\n"
+ [[ ${ERRNO[3]} = 2 ]] || fail "errno: ${ERRNO[3]} != 2"
+
+ pretty 3 "Request addresses not in the pool"
+ req 3 "1.1.1.1" "fd00::1"
+ [[ ${ERRNO[3]} = 3 ]] || fail "errno: ${ERRNO[3]} != 3"
+ [[ -z ${IPV4[3]} ]] || fail "ipv4 not empty: ${IPV4[3]}"
+ [[ -z ${IPV6[3]} ]] || fail "ipv6 not empty: ${IPV6[3]}"
+
+ pretty "" "SUCCESS\n"
+}
+
+test_case_2() {
+ # Two clients -- 4 and 5.
+ for i in 4 5; do setup_client_peer $i; done
+
+ pretty 4 "Any v4, any v6"
+ req_check 4 0.0.0.0 ::
+ [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
+ local C4_FIRST_V4=${IPV4[4]}
+ [[ -z $C4_FIRST_V4 ]] && fail "no ipv4"
+ local C4_FIRST_V6=${IPV6[4]}
+ [[ -z $C4_FIRST_V6 ]] && fail "no ipv6"
+
+ pretty 4 "Extend v4, extend v6"
+ req_check 4 $C4_FIRST_V4 $C4_FIRST_V6
+ [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
+ [[ ${IPV4[4]} = $C4_FIRST_V4 ]] || fail "${IPV4[4]} != $C4_FIRST_V4"
+ [[ ${IPV6[4]} = $C4_FIRST_V6 ]] || fail "${IPV6[4]} != $C4_FIRST_V6"
+
+ pretty 4 "Extend v4, drop v6"
+ req_check 4 $C4_FIRST_V4 -
+ [[ ${ERRNO[4]} = 0 ]] || fail "errno: ${ERRNO[4]}"
+ [[ ${IPV4[4]} = $C4_FIRST_V4 ]] || fail "${IPV4[4]} != $C4_FIRST_V4"
+ [[ -z ${IPV6[4]} ]] || fail "ipv6 not empty: ${IPV6[4]}"
+
+ pretty 5 "Requesting the v4 of client 4 and no v6 => errno=3 and no addrs"
+ req 5 $C4_FIRST_V4 -
+ [[ ${ERRNO[5]} = 3 ]] || fail "errno: ${ERRNO[5]} != 3"
+ [[ -z ${IPV4[5]} ]] || fail "ipv4 not empty: ${IPV4[5]}"
+ [[ -z ${IPV6[5]} ]] || fail "ipv6 not empty: ${IPV6[5]}"
+
+ pretty 5 "Wait for lease to expire and try again"
+ pp sleep ${LEASETIME[4]}
+ req_check 5 $C4_FIRST_V4 -
+ [[ ${ERRNO[5]} = 0 ]] || fail "errno: ${ERRNO[5]}"
+ [[ ${IPV4[5]} = $C4_FIRST_V4 ]] || fail "${IPV4[5]} != $C4_FIRST_V4"
+ [[ -z ${IPV6[5]} ]] || fail "ipv6 not empty: ${IPV6[5]}"
+
+ pretty "" "SUCCESS\n"
+}
+
+test_case_3() {
+ # Two clients -- 6 and 7.
+ for i in 6 7; do setup_client_peer $i; done
+
+ pretty 6 "Any v4, any v6"
+ req_check 6 0.0.0.0 ::
+ [[ ${ERRNO[6]} = 0 ]] || fail "errno: ${ERRNO[6]}"
+ local C6_FIRST_V4=${IPV4[6]}
+ [[ -z $C6_FIRST_V4 ]] && fail "no ipv4"
+ local C6_FIRST_V6=${IPV6[6]}
+ [[ -z $C6_FIRST_V6 ]] && fail "no ipv6"
+
+ pretty 6 "Drop v4, extend v6"
+ req_check 6 - $C6_FIRST_V6
+ [[ ${ERRNO[6]} = 0 ]] || fail "errno: ${ERRNO[6]}"
+ [[ -z ${IPV4[6]} ]] || fail "ipv4 not empty: ${IPV4[6]}"
+ [[ ${IPV6[6]} = $C6_FIRST_V6 ]] || fail "${IPV6[6]} != $C6_FIRST_V6"
+
+ pretty "" "SUCCESS\n"
+}
+
+# run_k_at_random(NCLIENTS, K) is invoked by test_forever() and runs
+# req_check() for a random number of clients (the up to K first
+# ones). The check performed is one of nine possible combinations of
+# requesting a specific address (ipv4 and ipv6), requesting any type
+# of address and releasing one or both addresses allocated.
+run_k_at_random() {
+ local nclients=$1; shift
+ local k=$1; shift
+ local n=10 # First client.
+ local i
+
+ if [[ $nclients -gt $k ]]; then
+ n=$(( $n + $RANDOM % ($n - $k) ))
+ fi
+
+ for i in $(seq $n $(( $n + $k - 1 ))); do
+ case $(( $RANDOM % 9 )) in
+ 0) req_check $i 0.0.0.0 ::0 ; continue ;; # any v4, any v6
+ 1) req_check $i 0.0.0.0 - ; continue ;; # any v4, drop v6
+ 2) req_check $i - ::0 ; continue ;; # drop v4, any v6
+ 3) req_check $i - - ; continue ;; # drop v4, drop v6
+
+ 4) req_check $i ${IPV4[$i]} ::0 ; continue ;; # extend v4, any v6
+ 5) req_check $i ${IPV4[$i]} - ; continue ;; # extend v4, drop v6
+ 6) req_check $i 0.0.0.0 ${IPV6[$i]} ; continue ;; # any v4, extend v6
+ 7) req_check $i - ${IPV6[$i]} ; continue ;; # drop v4, extend v6
+
+ 8) req_check $i ${IPV4[$i]} ${IPV6[$i]} ; continue ;; # extend v4, extend v6
+ esac
+ done
+}
+
+# run_k_fill(NCLIENTS, _ignored) is invoked by test_forever() and runs
+# req_check() for NCLIENTS, asking for any IPv4 and any IPv6 address.
+run_k_fill() {
+ local nclients=$1; shift
+ shift
+ local i
+
+ for i in $(seq $nclients); do
+ req_check $(( 10 + $i - 1 )) 0.0.0.0 ::0
+ done
+}
+
+# test_forever(NCLIENTS, FUNC, ARG) sets up NCLIENTS clients, numbered
+# from 10 and upwards, runs one req_check() per client and enters an
+# infinite loop invoking FUNC with NCLIENTS and ARGS.
+test_forever() {
+ local nclients=$1; shift
+ local func=$1; shift
+ local arg=$1; shift
+ local i
+
+ for i in $(seq 10 $(( 10 + $nclients - 1 ))); do
+ setup_client_peer $i
+ done
+
+ for i in $(seq 10 $(( 10 + $nclients - 1 ))); do
+ req_check $i
+ done
+
+ while sleep 1; do
+ if [ $(( $RANDOM % 100 )) -lt 50 ]; then
+ $func $nclients $arg
+ fi
+ done
}
-configure_peers
-nn 1 wg set wg0 peer "$client_public" endpoint [::1]:2
-nn 2 wg set wg0 peer "$server_public" endpoint [::1]:1
-nn 2 ping6 -c 10 -f -W 1 fe80::%wg0
-nn 1 ping6 -c 10 -f -W 1 fe80::badc:0ffe:e0dd:f00d%wg0
+### Tests.
-pretty "" "clientsh.bash can be run with the following arguments:"
-echo
-echo wg-test-$$ $server_public
-echo
+# Ordinary test cases.
+test_case_1
+test_case_2
+test_case_3
-nn 1 ./wg-dynamic-server --leasetime 10 wg0
+# Long running test cases, forever actually.
+[ $# -gt 0 ] && { NCLIENTS=$1; shift; } || NCLIENTS=20
+[ $# -gt 0 ] && { ARGS=$1; shift; } || ARGS=4
+#test_forever $NCLIENTS run_k_at_random $ARGS
+#test_forever $NCLIENTS run_k_fill $ARGS
diff --git a/wg-dynamic-client.c b/wg-dynamic-client.c
index f3e3274..7839210 100644
--- a/wg-dynamic-client.c
+++ b/wg-dynamic-client.c
@@ -2,446 +2,311 @@
*
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/
+#define _POSIX_C_SOURCE 200112L
-#include <fcntl.h>
-#include <poll.h>
+#include <arpa/inet.h>
#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
#include <unistd.h>
-#include <arpa/inet.h>
-#include <libmnl/libmnl.h>
-#include <linux/rtnetlink.h>
-
#include "common.h"
#include "dbg.h"
+#include "ipm.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 struct in6_addr well_known;
+static struct in6_addr lladdr;
+
+static struct in_addr ipv4;
+static struct in6_addr ipv6;
+static bool ipv4_assigned = false, ipv6_assigned = false;
+
static wg_device *device = NULL;
-static int our_fd = -1;
-static struct in6_addr our_lladdr = { 0 };
-static struct wg_combined_ip our_gaddr4 = { 0 };
-static struct wg_combined_ip our_gaddr6 = { 0 };
-static struct wg_dynamic_lease our_lease = { 0 };
-
-struct mnl_cb_data {
- uint32_t ifindex;
- struct in6_addr *lladdr;
- struct wg_combined_ip *gaddr4;
- struct wg_combined_ip *gaddr6;
-};
+static int sockfd = -1;
+
+static volatile sig_atomic_t should_exit = 0;
static void usage()
{
die("usage: %s <wg-interface>\n", progname);
}
-int data_cb(const struct nlmsghdr *nlh, void *data)
-{
- struct nlattr *tb[IFA_MAX + 1] = {};
- struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh);
- struct mnl_cb_data *cb_data = (struct mnl_cb_data *)data;
- unsigned char *addr;
-
- if (ifa->ifa_index != cb_data->ifindex)
- return MNL_CB_OK;
-
- mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb);
-
- if (!tb[IFA_ADDRESS])
- return MNL_CB_OK;
-
- addr = mnl_attr_get_payload(tb[IFA_ADDRESS]);
- char out[INET6_ADDRSTRLEN];
- inet_ntop(ifa->ifa_family, addr, out, sizeof(out));
- debug("index=%d, family=%d, addr=%s\n", ifa->ifa_index, ifa->ifa_family,
- out);
-
- if (ifa->ifa_scope == RT_SCOPE_LINK) {
- if (ifa->ifa_prefixlen != 128)
- return MNL_CB_OK;
- memcpy(cb_data->lladdr, addr, 16);
- } else if (ifa->ifa_scope == RT_SCOPE_UNIVERSE) {
- switch (ifa->ifa_family) {
- case AF_INET:
- cb_data->gaddr4->family = ifa->ifa_family;
- 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->ip6, addr, 16);
- cb_data->gaddr6->cidr = ifa->ifa_prefixlen;
- break;
- default:
- die("Unknown address family: %u\n", ifa->ifa_family);
- }
- }
-
- return MNL_CB_OK;
-}
-
-static void iface_update(uint16_t cmd, uint16_t flags, uint32_t ifindex,
- const struct wg_combined_ip *addr)
+/* NOTE: do NOT call exit() in here */
+static void cleanup()
{
- struct mnl_socket *nl;
- char buf[MNL_SOCKET_BUFFER_SIZE];
- struct nlmsghdr *nlh;
- unsigned int seq, portid;
- struct ifaddrmsg *ifaddr; /* linux/if_addr.h */
- int ret;
-
- nl = mnl_socket_open(NETLINK_ROUTE);
- if (nl == NULL)
- fatal("mnl_socket_open");
-
- if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
- fatal("mnl_socket_bind");
-
- portid = mnl_socket_get_portid(nl);
- seq = time(NULL);
- nlh = mnl_nlmsg_put_header(buf);
- nlh->nlmsg_seq = seq;
- nlh->nlmsg_type = cmd;
- nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
- ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
- ifaddr->ifa_family = addr->family;
- 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);
-
- if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
- fatal("mnl_socket_sendto");
-
- do {
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- if (ret <= MNL_CB_STOP)
- break;
- ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
- } while (ret > 0);
-
- if (ret == -1)
- fatal("mnl_cb_run/mnl_socket_recvfrom");
+ if (ipv4_assigned && ipm_deladdr_v4(device->ifindex, &ipv4))
+ debug("Failed to cleanup ipv4 address\n");
+ if (ipv6_assigned && ipm_deladdr_v6(device->ifindex, &ipv6))
+ debug("Failed to cleanup ipv6 address\n");
- mnl_socket_close(nl);
-}
+ if (sockfd >= 0)
+ close(sockfd);
-static void iface_remove_addr(uint32_t ifindex,
- const struct wg_combined_ip *addr)
-{
- char ipstr[INET6_ADDRSTRLEN];
- debug("removing %s/%u from interface %u\n",
- inet_ntop(addr->family, &addr, ipstr, sizeof ipstr), addr->cidr,
- ifindex);
- iface_update(RTM_DELADDR, 0, ifindex, addr);
+ ipm_free();
+ wg_free_device(device);
}
-static void iface_add_addr(uint32_t ifindex, const struct wg_combined_ip *addr)
+static void handler(int signum)
{
- char ipstr[INET6_ADDRSTRLEN];
- debug("adding %s/%u to interface %u\n",
- inet_ntop(addr->family, &addr, ipstr, sizeof ipstr), addr->cidr,
- ifindex);
- iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex, addr);
+ UNUSED(signum);
+ should_exit = 1;
}
-static bool get_and_validate_local_addrs(uint32_t ifindex,
- struct in6_addr *lladdr,
- struct wg_combined_ip *gaddr4,
- struct wg_combined_ip *gaddr6)
+static void check_signal()
{
- struct mnl_cb_data cb_data = {
- .ifindex = ifindex,
- .lladdr = lladdr,
- .gaddr4 = gaddr4,
- .gaddr6 = gaddr6,
- };
-
- iface_get_all_addrs(AF_INET, data_cb, &cb_data);
- iface_get_all_addrs(AF_INET6, data_cb, &cb_data);
-
- return !IN6_IS_ADDR_UNSPECIFIED(cb_data.lladdr);
+ if (should_exit)
+ exit(EXIT_FAILURE);
}
-static int try_connect(int *fd)
+static int request_ip(struct wg_dynamic_request_ip *rip)
{
- struct timeval tval = { .tv_sec = 1, .tv_usec = 0 };
- struct sockaddr_in6 our_addr = {
+ unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE];
+ size_t msglen, remaining = 0, off = 0;
+ struct sockaddr_in6 dstaddr = {
.sin6_family = AF_INET6,
- .sin6_addr = our_lladdr,
+ .sin6_addr = well_known,
.sin6_port = htons(WG_DYNAMIC_PORT),
.sin6_scope_id = device->ifindex,
};
- struct sockaddr_in6 their_addr = {
+ struct sockaddr_in6 srcaddr = {
.sin6_family = AF_INET6,
+ .sin6_addr = lladdr,
.sin6_port = htons(WG_DYNAMIC_PORT),
.sin6_scope_id = device->ifindex,
};
+ struct wg_dynamic_request req = {
+ .cmd = WGKEY_REQUEST_IP,
+ .version = 1,
+ .result = rip,
+ };
+ struct timeval timeout = { .tv_sec = 30 };
+ ssize_t ret;
+ int val = 1;
- *fd = socket(AF_INET6, SOCK_STREAM, 0);
- if (*fd < 0)
+ sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (sockfd < 0)
fatal("Creating a socket failed");
- if (setsockopt(*fd, SOL_SOCKET, SO_RCVTIMEO, &tval, sizeof tval) == -1)
- fatal("Setting socket option failed");
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val))
+ fatal("setsockopt(SO_REUSEADDR)");
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
+ sizeof timeout))
+ fatal("setsockopt(SO_RCVTIMEO)");
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
+ sizeof timeout))
+ fatal("setsockopt(SO_SNDTIMEO)");
- if (bind(*fd, (struct sockaddr *)&our_addr, sizeof(our_addr)))
+ if (bind(sockfd, (struct sockaddr *)&srcaddr, sizeof(srcaddr)))
fatal("Binding socket failed");
- if (inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &their_addr.sin6_addr) != 1)
- fatal("inet_pton()");
+ if (connect(sockfd, (struct sockaddr *)&dstaddr, sizeof(dstaddr)))
+ fatal("connect()");
- if (connect(*fd, (struct sockaddr *)&their_addr,
- sizeof(struct sockaddr_in6))) {
- char out[INET6_ADDRSTRLEN];
+ rip->has_ipv4 = rip->has_ipv6 = true;
+ if (ipv4_assigned)
+ memcpy(&rip->ipv4, &ipv4, sizeof rip->ipv4);
- if (!inet_ntop(their_addr.sin6_family, &their_addr.sin6_addr,
- out, sizeof out))
- fatal("inet_ntop()");
- debug("Connecting to [%s]:%u failed: %s\n", out,
- ntohs(their_addr.sin6_port), strerror(errno));
+ if (ipv6_assigned)
+ memcpy(&rip->ipv6, &ipv6, sizeof rip->ipv6);
- if (close(*fd))
- debug("Closing socket failed: %s\n", strerror(errno));
- *fd = -1;
+ msglen = serialize_request_ip(true, (char *)buf, RECV_BUFSIZE, rip);
+ do {
+ ssize_t written = write(sockfd, buf + off, msglen - off);
+ if (written == -1) {
+ if (errno == EINTR) {
+ check_signal();
+ continue;
+ }
+
+ fatal("write()");
+ }
+
+ off += written;
+ } while (off < msglen);
+
+ memset(rip, 0, sizeof *rip);
+
+ while ((ret = handle_request(sockfd, &req, buf, &remaining)) <= 0) {
+ if (ret == 0) {
+ check_signal();
+ continue;
+ }
+
+ if (close(sockfd))
+ debug("Failed to close socket: %s\n", strerror(errno));
+
+ log_err("Server communication error.\n");
return -1;
}
- return 0;
-}
+ if (remaining > 0)
+ log_err("Warning: discarding %zu extra bytes sent by the server\n",
+ remaining);
+
+ if (rip->wg_errno && !rip->has_ipv4 && !rip->has_ipv6) {
+ if (rip->errmsg) {
+ log_err("Server refused request: %s\n", rip->errmsg);
+ return -1;
+ } else if (rip->wg_errno <= ARRAY_SIZE(WG_DYNAMIC_ERR) - 1) {
+ log_err("Server refused request: %s\n",
+ WG_DYNAMIC_ERR[rip->wg_errno]);
+ } else {
+ log_err("Server refused request: unknown error code %u\n",
+ rip->wg_errno);
+ }
-static void request_ip(int fd, const struct wg_dynamic_lease *lease)
-{
- unsigned char buf[MAX_RESPONSE_SIZE + 1];
- char addrstr[INET6_ADDRSTRLEN];
- size_t msglen;
-
- msglen = 0;
- msglen += print_to_buf((char *)buf, sizeof buf, msglen, "%s=%d\n",
- WG_DYNAMIC_KEY[WGKEY_REQUEST_IP], 1);
-
- 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);
+ free(rip->errmsg); /* TODO: this could be done cleaner */
+ return -1;
+ }
+ free(rip->errmsg);
+
+ if (!ipv4_assigned || memcmp(&ipv4, &rip->ipv4, sizeof ipv4)) {
+ if (ipv4_assigned && ipm_deladdr_v4(device->ifindex, &ipv4))
+ fatal("ipm_deladdr_v4()");
+
+ if (rip->has_ipv4) {
+ memcpy(&ipv4, &rip->ipv4, sizeof ipv4);
+ if (ipm_newaddr_v4(device->ifindex, &ipv4))
+ fatal("ipm_newaddr_v4()");
+ ipv4_assigned = true;
+ } else {
+ memset(&ipv4, 0, sizeof ipv4);
+ ipv4_assigned = false;
+ }
}
- 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,
- "ipv6=%s/128\n", addrstr);
+
+ if (!ipv6_assigned || memcmp(&ipv6, &rip->ipv6, sizeof ipv6)) {
+ if (ipv6_assigned && ipm_deladdr_v6(device->ifindex, &ipv6))
+ fatal("ipm_deladdr_v6()");
+
+ if (rip->has_ipv6) {
+ memcpy(&ipv6, &rip->ipv6, sizeof ipv6);
+ if (ipm_newaddr_v6(device->ifindex, &ipv6))
+ fatal("ipm_newaddr_v6()");
+ ipv6_assigned = true;
+ } else {
+ memset(&ipv6, 0, sizeof ipv6);
+ ipv6_assigned = false;
+ }
}
- /* nmsglen += print_to_buf((char *)buf, sizeof buf, msglen,
- "leasetime=%u\n", fixme); */
- msglen += print_to_buf((char *)buf, sizeof buf, msglen, "\n");
+ if (close(sockfd))
+ debug("Failed to close socket: %s\n", strerror(errno));
- send_message(fd, buf, &msglen);
+ return 0;
}
-static uint32_t time_until_refresh(uint32_t now, struct wg_dynamic_lease *lease)
+static void setup()
{
- uint32_t refresh_at;
+ struct sigaction sa = { .sa_handler = handler, .sa_flags = 0 };
+ struct wg_combined_ip ip;
+ int ret;
- if (lease->leasetime == 0)
- return 0;
- refresh_at = lease->start + (lease->leasetime * 8) / 10;
+ if (atexit(cleanup))
+ die("Failed to set exit function\n");
- if (refresh_at < now)
- return 0;
- return refresh_at - now;
-}
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ fatal("sigaction()");
-static int handle_received_lease(const struct wg_dynamic_request *req)
-{
- uint32_t ret;
- struct wg_dynamic_attr *attr;
- struct wg_dynamic_lease *lease = &our_lease;
- uint32_t now = current_time();
- uint32_t lease_start = 0;
- uint32_t curleasetime = lease->start + lease->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_LEASESTART:
- memcpy(&lease_start, attr->value, sizeof(uint32_t));
- break;
- case WGKEY_LEASETIME:
- memcpy(&lease->leasetime, attr->value,
- sizeof(uint32_t));
- break;
- case WGKEY_ERRNO:
- memcpy(&ret, attr->value, sizeof(uint32_t));
- if (ret) {
- debug("Request IP failed with %ud from server\n",
- ret);
- return -ret;
- }
- break;
- case WGKEY_ERRMSG:
- /* TODO: do something with the error message */
- break;
- default:
- debug("Ignoring invalid attribute for request_ip: %d\n",
- attr->key);
- }
- attr = attr->next;
- }
-
- if (lease->leasetime == 0 || (lease->ip4.ip4.s_addr == 0 &&
- IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip6)))
- return -EINVAL;
+ if (wg_get_device(&device, wg_interface))
+ fatal("Unable to access interface %s", wg_interface);
- if (abs(now - lease_start) < 15)
- lease->start = lease_start;
- else
- lease->start = now;
+ if (inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known) != 1)
+ fatal("inet_pton()");
- debug("Replacing lease %u -> %u\n", curleasetime,
- lease->start + lease->leasetime);
+ ipm_init();
- return 0;
-}
+ ret = ipm_getlladdr(device->ifindex, &ip);
+ if (ret == -1)
+ fatal("ipm_getlladdr()");
-static void cleanup()
-{
- wg_free_device(device);
- if (our_fd != -1 && close(our_fd))
- debug("Failed to close fd %d\n", our_fd);
-}
+ if (ret == -2 || ip.family != AF_INET6)
+ die("%s needs to be assigned an IPv6 link local address\n",
+ wg_interface);
-static bool handle_error(int fd, int ret)
-{
- UNUSED(fd);
- UNUSED(ret);
+ if (ret == -3)
+ die("Interface must not have multiple link-local addresses assigned\n");
- debug("Unable to parse response: %s\n", strerror(ret));
+ if (ip.cidr != 128)
+ die("Link-local address must have a CIDR of 128\n");
- return true;
+ memcpy(&lladdr, &ip, 16);
}
-static void maybe_update_iface()
+static void xnanosleep(time_t duration)
{
- if (memcmp(&our_gaddr4, &our_lease.ip4, sizeof our_gaddr4) ||
- our_gaddr4.cidr != our_lease.ip4.cidr) {
- 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, &our_lease.ip6, sizeof our_gaddr6) ||
- our_gaddr6.cidr != our_lease.ip6.cidr) {
- 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);
+ struct timespec rem, timeout = { .tv_sec = duration };
+ int ret;
+
+ while ((ret = clock_nanosleep(CLOCK_BOOTTIME, 0, &timeout, &rem))) {
+ if (ret == EINTR) {
+ check_signal();
+ memcpy(&timeout, &rem, sizeof timeout);
+ continue;
+ }
+
+ die("clock_nanosleep(): %s\n", strerror(ret));
}
}
-static bool handle_response(int fd, struct wg_dynamic_request *req)
+static void loop()
{
- UNUSED(fd);
-
-#if 0
- printf("Recieved response of type %s.\n", WG_DYNAMIC_KEY[req->cmd]);
- struct wg_dynamic_attr *cur = req->first;
- while (cur) {
- printf(" with attr %s.\n", WG_DYNAMIC_KEY[cur->key]);
- cur = cur->next;
+ struct wg_dynamic_request_ip rip = { 0 };
+ struct timespec tsend, trecv;
+ time_t expires, timeout;
+
+ if (clock_gettime(CLOCK_REALTIME, &tsend))
+ fatal("clock_gettime(CLOCK_REALTIME)");
+
+ if (request_ip(&rip)) {
+ /* TODO: implement some sort of exponential backoff */
+ log_err("Trying again in 30s.\n");
+ xnanosleep(30);
+ return;
}
-#endif
-
- switch (req->cmd) {
- case WGKEY_REQUEST_IP:
- if (handle_received_lease(req) == 0)
- maybe_update_iface();
- break;
- default:
- debug("Unknown command: %d\n", req->cmd);
- return true;
+
+ if (clock_gettime(CLOCK_REALTIME, &trecv))
+ fatal("clock_gettime(CLOCK_REALTIME)");
+
+ if (tsend.tv_sec < rip.start + 5 || rip.start > trecv.tv_sec + 5)
+ expires = tsend.tv_sec + rip.leasetime;
+ else
+ expires = MIN(rip.leasetime, trecv.tv_sec) + rip.leasetime;
+
+ if (expires <= trecv.tv_sec) {
+ log_err("Warning: lease we tried to aquire already expired\n");
+ return;
}
- return true;
+ /* TODO: implement random jitter */
+ timeout = (expires - trecv.tv_sec);
+ timeout -= MIN(30, timeout * 0.5);
+
+ debug("Sleeping for %zus\n", timeout);
+ xnanosleep(timeout);
}
-int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)))
+int main(int argc, char *argv[])
{
- int *fd = &our_fd;
- struct wg_dynamic_request req = { 0 };
-
progname = argv[0];
if (argc != 2)
usage();
wg_interface = argv[1];
+ setup();
- if (wg_get_device(&device, wg_interface))
- fatal("Unable to access interface %s", wg_interface);
-
- if (atexit(cleanup))
- die("Failed to set exit function\n");
-
- if (!get_and_validate_local_addrs(device->ifindex, &our_lladdr,
- &our_gaddr4, &our_gaddr6))
- die("%s needs to have an IPv6 link local address with prefixlen 128 assigned\n",
- wg_interface);
- // TODO: verify that we have a peer with an allowed-ips including fe80::/128
-
- char lladr_str[INET6_ADDRSTRLEN];
- debug("%s: %s\n", wg_interface,
- inet_ntop(AF_INET6, &our_lladdr, lladr_str, sizeof lladr_str));
-
- /* If we have an address configured, let's assume it's from a
- * lease in order to get renewal done. */
- 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,
- sizeof(struct wg_combined_ip));
- memcpy(&our_lease.ip6, &our_gaddr6,
- sizeof(struct wg_combined_ip));
- }
-
- while (1) {
- sleep(time_until_refresh(current_time(), &our_lease));
-
- if (*fd == -1 && try_connect(fd)) {
- sleep(1);
- continue;
- }
-
- request_ip(*fd, &our_lease);
-
- while (!handle_request(&req, handle_response, handle_error))
- ;
- close_connection(&req);
- }
+ while (1)
+ loop();
return 0;
}
diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c
index 27f4054..566559b 100644
--- a/wg-dynamic-server.c
+++ b/wg-dynamic-server.c
@@ -6,6 +6,7 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200112L
+#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -23,6 +24,7 @@
#include "common.h"
#include "dbg.h"
+#include "ipm.h"
#include "khash.h"
#include "lease.h"
#include "netlink.h"
@@ -32,68 +34,31 @@ static const char *wg_interface = NULL;
static struct in6_addr well_known;
static wg_device *device = NULL;
-static struct wg_dynamic_request requests[MAX_CONNECTIONS] = { 0 };
-static uint32_t leasetime = WG_DYNAMIC_DEFAULT_LEASETIME;
+static uint32_t leasetime = 3600;
static int sockfd = -1;
static int epollfd = -1;
static struct mnl_socket *nlsock = NULL;
-KHASH_MAP_INIT_INT64(allowedht, wg_key *)
+KHASH_MAP_INIT_SECURE_INT64(allowedht, wg_key *)
khash_t(allowedht) * allowedips_ht;
-struct mnl_cb_data {
- uint32_t ifindex;
- bool valid_ip_found;
+struct wg_dynamic_connection {
+ struct wg_dynamic_request req;
+ int fd;
+ wg_key pubkey;
+ struct in6_addr lladdr;
+ unsigned char *outbuf;
+ size_t buflen;
};
-static void usage()
-{
- die("usage: %s [--leasetime <leasetime>] <wg-interface>\n", progname);
-}
-
-static int data_cb(const struct nlmsghdr *nlh, void *data)
-{
- struct nlattr *tb[IFA_MAX + 1] = {};
- struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh);
- struct mnl_cb_data *cb_data = (struct mnl_cb_data *)data;
- unsigned char *addr;
-
- if (ifa->ifa_index != cb_data->ifindex)
- return MNL_CB_OK;
-
- if (ifa->ifa_scope != RT_SCOPE_LINK)
- return MNL_CB_OK;
-
- mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb);
+static struct wg_dynamic_connection connections[MAX_CONNECTIONS] = { 0 };
- if (!tb[IFA_ADDRESS])
- return MNL_CB_OK;
-
- addr = mnl_attr_get_payload(tb[IFA_ADDRESS]);
- char out[INET6_ADDRSTRLEN];
- inet_ntop(ifa->ifa_family, addr, out, sizeof(out));
- debug("index=%d, family=%d, addr=%s\n", ifa->ifa_index, ifa->ifa_family,
- out);
-
- if (ifa->ifa_prefixlen != 64 || memcmp(addr, well_known.s6_addr, 16))
- return MNL_CB_OK;
-
- cb_data->valid_ip_found = true;
-
- return MNL_CB_OK;
-}
-
-static bool validate_link_local_ip(uint32_t ifindex)
+static void usage()
{
- struct mnl_cb_data cb_data = {
- .ifindex = ifindex,
- .valid_ip_found = false,
- };
-
- iface_get_all_addrs(AF_INET6, data_cb, &cb_data);
-
- return cb_data.valid_ip_found;
+ fprintf(stderr, "usage: %s [--leasetime <leasetime>] <wg-interface>\n",
+ progname);
+ exit(EXIT_FAILURE);
}
static bool valid_peer_found(wg_device *device)
@@ -167,8 +132,7 @@ static wg_key *addr_to_pubkey(struct sockaddr_storage *addr)
return NULL;
}
-static int accept_connection(int sockfd, wg_key *dest_pubkey,
- struct in6_addr *dest_lladdr)
+static int accept_connection(wg_key *dest_pubkey, struct in6_addr *dest_lladdr)
{
int fd;
wg_key *pubkey;
@@ -227,101 +191,131 @@ static int accept_connection(int sockfd, wg_key *dest_pubkey,
return fd;
}
-static bool send_error(struct wg_dynamic_request *req, int error)
+static bool send_message(struct wg_dynamic_connection *con,
+ const unsigned char *buf, size_t len)
{
- 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]);
+ size_t offset = 0;
- return send_message(req, buf, msglen);
-}
+ while (1) {
+ ssize_t written = write(con->fd, buf + offset, len - offset);
+ if (written < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ break;
-static size_t serialize_lease(char *buf, size_t len,
- const struct wg_dynamic_lease *lease)
-{
- char addrbuf[INET6_ADDRSTRLEN];
- size_t off = 0;
+ if (errno == EINTR)
+ continue;
- if (lease->ipv4.s_addr) {
- if (!inet_ntop(AF_INET, &lease->ipv4, addrbuf, sizeof addrbuf))
- fatal("inet_ntop()");
+ debug("Writing to socket %d failed: %s\n", con->fd,
+ strerror(errno));
+ return false;
+ }
- print_to_buf(buf, len, &off, "ipv4=%s/%d\n", addrbuf, 32);
+ offset += written;
+ if (offset == len)
+ return true;
}
- if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) {
- if (!inet_ntop(AF_INET6, &lease->ipv6, addrbuf, sizeof addrbuf))
- fatal("inet_ntop()");
+ debug("Socket %d blocking on write with %lu bytes left, postponing\n",
+ con->fd, len - offset);
- print_to_buf(buf, len, &off, "ipv6=%s/%d\n", addrbuf, 128);
- }
+ if (!con->outbuf) {
+ con->buflen = len - offset;
+ con->outbuf = malloc(con->buflen);
+ if (!con->outbuf)
+ fatal("malloc()");
- print_to_buf(buf, len, &off, "leasestart=%u\nleasetime=%u\nerrno=0\n\n",
- lease->start_real, lease->leasetime);
+ memcpy(con->outbuf, buf + offset, con->buflen);
+ } else {
+ con->buflen = len - offset;
+ memmove(con->outbuf, buf + offset, con->buflen);
+ }
- return off;
+ return true;
}
-static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey,
- const struct in6_addr *lladdr,
- struct wg_dynamic_lease **lease)
+void close_connection(struct wg_dynamic_connection *con)
{
- struct in_addr *ipv4 = NULL;
- struct in6_addr *ipv6 = NULL;
-
- 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;
- }
+ free_wg_dynamic_request(&con->req);
- *lease = set_lease(wg_interface, pubkey, leasetime, lladdr, ipv4, ipv6);
- if (!*lease)
- return E_IP_UNAVAIL;
+ if (close(con->fd))
+ debug("Failed to close socket\n");
- return E_NO_ERROR;
+ con->fd = -1;
+ memset(con->pubkey, 0, sizeof con->pubkey);
+ free(con->outbuf);
+ con->outbuf = NULL;
+ con->buflen = 0;
}
-static bool send_response(struct wg_dynamic_request *req)
+static bool send_response(struct wg_dynamic_connection *con)
{
char buf[MAX_RESPONSE_SIZE];
- struct wg_dynamic_attr *cur = req->first;
- struct wg_dynamic_lease *lease;
size_t msglen;
- int ret;
- switch (req->cmd) {
- case WGKEY_REQUEST_IP:
- ret = response_request_ip(cur, req->pubkey, &req->lladdr,
- &lease);
- if (ret)
- break;
+ switch (con->req.cmd) {
+ case WGKEY_REQUEST_IP:;
+ struct wg_dynamic_request_ip *rip = con->req.result;
+ struct in_addr *ip4 = rip->has_ipv4 ? &rip->ipv4 : NULL;
+ struct in6_addr *ip6 = rip->has_ipv6 ? &rip->ipv6 : NULL;
+ struct wg_dynamic_lease *lease;
+ struct wg_dynamic_request_ip ans = { 0 };
+
+ lease = set_lease(con->pubkey, leasetime, &con->lladdr, ip4,
+ ip6);
+
+ if (lease->ipv4.s_addr) {
+ ans.has_ipv4 = true;
+ memcpy(&ans.ipv4, &lease->ipv4, sizeof ans.ipv4);
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ipv6)) {
+ ans.has_ipv6 = true;
+ memcpy(&ans.ipv6, &lease->ipv6, sizeof ans.ipv6);
+ }
- msglen = serialize_lease(buf, sizeof buf, lease);
+ if ((!ans.has_ipv4 && rip->has_ipv4) ||
+ (!ans.has_ipv6 && rip->has_ipv6))
+ ans.wg_errno = E_IP_UNAVAIL;
+
+ ans.start = lease->start_real;
+ ans.leasetime = lease->leasetime;
+
+ msglen = serialize_request_ip(false, buf, sizeof buf, &ans);
break;
default:
- debug("Unknown command: %d\n", req->cmd);
+ debug("Unknown command: %d\n", con->req.cmd);
BUG();
}
- if (ret)
- return send_error(req, ret);
+ return send_message(con, (unsigned char *)buf, msglen);
+}
+
+static void handle_client(struct wg_dynamic_connection *con)
+{
+ unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE];
+ size_t rem = 0;
+ ssize_t ret;
+
+ while ((ret = handle_request(con->fd, &con->req, buf, &rem)) > 0) {
+ if (!send_response(con)) {
+ close_connection(con);
+ break;
+ }
+
+ free_wg_dynamic_request(&con->req);
+ }
- return send_message(req, buf, msglen);
+ if (ret < 0) {
+ size_t len = 0;
+ uint32_t err = E_INVALID_REQ;
+ if (-ret == EPROTONOSUPPORT)
+ err = E_UNSUPP_PROTO;
+
+ print_to_buf((char *)buf, sizeof buf, &len,
+ "errno=%u\nerrmsg=%s\n\n", err,
+ WG_DYNAMIC_ERR[err]);
+ send_message(con, buf, len);
+ close_connection(con);
+ }
}
static void setup_sockets()
@@ -391,14 +385,14 @@ static void cleanup()
close(epollfd);
for (int i = 0; i < MAX_CONNECTIONS; ++i) {
- if (requests[i].fd < 0)
+ if (connections[i].fd < 0)
continue;
- close_connection(&requests[i]);
+ close_connection(&connections[i]);
}
}
-static void init_leaess_from_peers()
+static void init_leases_from_peers()
{
wg_peer *peer;
@@ -420,38 +414,54 @@ static void init_leaess_from_peers()
if (!ipv4 && !ipv6)
continue;
- set_lease(wg_interface, peer->public_key, leasetime, lladdr,
- ipv4, ipv6);
+ set_lease(peer->public_key, leasetime, lladdr, ipv4, ipv6);
}
}
static void setup()
{
+ struct wg_combined_ip ip;
+ int ret;
+
if (inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known) != 1)
fatal("inet_pton()");
allowedips_ht = kh_init(allowedht);
+ if (!allowedips_ht)
+ fatal("kh_init()");
for (int i = 0; i < MAX_CONNECTIONS; ++i)
- requests[i].fd = -1;
+ connections[i].fd = -1;
if (atexit(cleanup))
die("Failed to set exit function\n");
rebuild_allowedips_ht();
- if (!validate_link_local_ip(device->ifindex))
- // TODO: assign IP instead?
+ ipm_init();
+ ret = ipm_getlladdr(device->ifindex, &ip);
+ if (ret == -1)
+ fatal("ipm_getlladdr()");
+ if (ret == -2)
+ die("Interface must not have multiple link-local addresses assigned\n");
+ ipm_free();
+
+ if (ret == -1 || ip.family != AF_INET6 ||
+ memcmp(&ip.ip6, well_known.s6_addr, 16))
+ /* TODO: assign IP instead? */
die("%s needs to have %s assigned\n", wg_interface,
WG_DYNAMIC_ADDR);
+ if (ip.cidr != 64)
+ die("Link-local address must have a CIDR of 64\n");
+
if (!valid_peer_found(device))
die("%s has no peers with link-local allowedips\n",
wg_interface);
setup_sockets();
- leases_init(NULL, nlsock);
- init_leaess_from_peers();
+ leases_init(wg_interface, device->ifindex, NULL, nlsock);
+ init_leases_from_peers();
}
static int get_avail_request()
@@ -460,48 +470,47 @@ static int get_avail_request()
if (nfds >= MAX_CONNECTIONS)
return -1;
- if (requests[nfds].fd < 0)
+ if (connections[nfds].fd < 0)
return nfds;
}
}
-static void accept_incoming(int sockfd, int epollfd,
- struct wg_dynamic_request *requests)
+static void accept_incoming()
{
int n, fd;
struct epoll_event ev;
while ((n = get_avail_request()) >= 0) {
- fd = accept_connection(sockfd, &requests[n].pubkey,
- &requests[n].lladdr);
+ fd = accept_connection(&connections[n].pubkey,
+ &connections[n].lladdr);
if (fd < 0) {
if (fd == -ENOENT) {
debug("Failed to match IP to pubkey\n");
continue;
- } else if (fd != -EAGAIN && fd != -EWOULDBLOCK) {
- debug("Failed to accept connection: %s\n",
- strerror(-fd));
- continue;
+ } else if (fd == -EAGAIN || fd == -EWOULDBLOCK) {
+ return;
}
- break;
+ debug("Failed to accept connection: %s\n",
+ strerror(-fd));
+ continue;
}
ev.events = EPOLLIN | EPOLLET;
- ev.data.ptr = &requests[n];
+ ev.data.ptr = &connections[n];
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1)
fatal("epoll_ctl()");
- requests[n].fd = fd;
+ connections[n].fd = fd;
}
}
static void handle_event(void *ptr, uint32_t events)
{
- struct wg_dynamic_request *req;
+ struct wg_dynamic_connection *con;
if (ptr == &sockfd) {
- accept_incoming(sockfd, epollfd, requests);
+ accept_incoming();
return;
}
@@ -510,15 +519,14 @@ static void handle_event(void *ptr, uint32_t events)
return;
}
- req = (struct wg_dynamic_request *)ptr;
+ con = (struct wg_dynamic_connection *)ptr;
if (events & EPOLLIN) {
- if (handle_request(req, send_response, send_error))
- close_connection(req);
+ handle_client(con);
}
if (events & EPOLLOUT) {
- if (send_message(req, req->buf, req->buflen))
- close_connection(req);
+ if (!send_message(con, con->outbuf, con->buflen))
+ close_connection(con);
}
}
@@ -540,7 +548,7 @@ static void poll_loop()
fatal("epoll_ctl()");
while (1) {
- time_t next = leases_refresh(wg_interface) * 1000;
+ time_t next = leases_refresh() * 1000;
int nfds = epoll_wait(epollfd, events, MAX_CONNECTIONS, next);
if (nfds == -1) {
if (errno == EINTR)
@@ -556,27 +564,37 @@ static void poll_loop()
int main(int argc, char *argv[])
{
- char *endptr = NULL;
-
progname = argv[0];
- ++argv;
- --argc;
- while (argc > 0) {
- if (!strcmp(argv[0], "--leasetime") && argc >= 2) {
- leasetime = (uint32_t) strtoul(argv[1], &endptr, 10);
+ while (1) {
+ int ret, index;
+ char *endptr = NULL;
+ const struct option options[] = {
+ { "leasetime", required_argument, NULL, 0 },
+ { 0, 0, 0, 0 }
+ };
+
+ ret = getopt_long(argc, argv, "", options, &index);
+ if (ret == -1)
+ break;
+
+ switch (ret) {
+ case 0:
+ if (index != 0)
+ usage();
+ leasetime = (uint32_t)strtoul(optarg, &endptr, 10);
if (*endptr)
usage();
- argv += 2;
- argc -= 2;
- } else {
- wg_interface = argv[0];
- argv += 1;
- argc -= 1;
break;
+ default:
+ usage();
}
}
- if (!wg_interface || argc > 0)
+
+ if (optind < argc)
+ wg_interface = argv[optind++];
+
+ if (!wg_interface || optind < argc)
usage();
setup();