aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--common.c93
-rw-r--r--common.h12
-rw-r--r--ipm.c178
-rw-r--r--ipm.h19
-rw-r--r--wg-dynamic-server.c188
6 files changed, 329 insertions, 165 deletions
diff --git a/Makefile b/Makefile
index 7d7a327..9d6651d 100644
--- a/Makefile
+++ b/Makefile
@@ -45,8 +45,8 @@ endif
all: wg-dynamic-server
-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.c 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
ifneq ($(V),1)
clean:
diff --git a/common.c b/common.c
index 3edf872..cbb38a9 100644
--- a/common.c
+++ b/common.c
@@ -253,78 +253,52 @@ static int parse_request(struct wg_dynamic_request *req, unsigned char *buf,
return 1;
}
-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)
{
ssize_t bytes;
int ret;
unsigned char buf[RECV_BUFSIZE + MAX_LINESIZE];
while (1) {
- bytes = read(req->fd, buf, RECV_BUFSIZE);
+ bytes = read(fd, buf, RECV_BUFSIZE);
if (bytes < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
// TODO: handle EINTR
- debug("Reading from socket %d failed: %s\n", req->fd,
+ 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);
+ return 2;
else if (ret == 0)
- return success(req);
+ return 0;
}
- return false;
+ return 0;
}
-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;
-
- while (1) {
- ssize_t written = write(req->fd, buf + offset, len - offset);
- if (written < 0) {
- if (errno == EWOULDBLOCK || errno == EAGAIN)
- break;
-
- // TODO: handle EINTR
-
- debug("Writing to socket %d failed: %s\n", req->fd,
- strerror(errno));
- return true;
- }
-
- offset += written;
- if (offset == len)
- return true;
- }
-
- debug("Socket %d blocking on write with %lu bytes left, postponing\n",
- req->fd, len - offset);
-
- if (!req->buf) {
- req->buflen = len - offset;
- req->buf = malloc(req->buflen);
- if (!req->buf)
- fatal("malloc()");
+ struct wg_dynamic_attr *prev, *cur = req->first;
- memcpy(req->buf, buf + offset, req->buflen);
- } else {
- req->buflen = len - offset;
- memmove(req->buf, buf + offset, req->buflen);
+ while (cur) {
+ prev = cur;
+ cur = cur->next;
+ free(prev);
}
- return false;
+ req->cmd = WGKEY_UNKNOWN;
+ req->version = 0;
+ req->first = NULL;
+ req->last = NULL;
}
void print_to_buf(char *buf, size_t bufsize, size_t *offset, char *fmt, ...)
@@ -345,37 +319,6 @@ 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 */
diff --git a/common.h b/common.h
index fcb0f0e..f5622bc 100644
--- a/common.h
+++ b/common.h
@@ -72,10 +72,6 @@ struct wg_dynamic_attr {
struct wg_dynamic_request {
enum wg_dynamic_key cmd;
uint32_t version;
- int fd;
- wg_key pubkey;
- unsigned char *buf;
- size_t buflen;
struct wg_dynamic_attr *first, *last;
};
@@ -90,14 +86,10 @@ struct wg_combined_ip {
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+int handle_request(int fd, struct wg_dynamic_request *req);
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);
+
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);
diff --git a/ipm.c b/ipm.c
new file mode 100644
index 0000000..954b5e3
--- /dev/null
+++ b/ipm.c
@@ -0,0 +1,178 @@
+/* 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 void iface_update(uint16_t cmd, uint16_t flags, uint32_t ifindex,
+ const struct wg_combined_ip *addr)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ unsigned int seq, portid;
+ struct ifaddrmsg *ifaddr; /* linux/if_addr.h */
+ int ret;
+
+ 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");
+}
+
+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 void iface_get_all_addrs2(uint8_t family, mnl_cb_t data_cb,
+ 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)
+ 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");
+}
+
+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);
+}
+
+void ipm_newaddr(uint32_t ifindex, const struct wg_combined_ip *addr)
+{
+ iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex, addr);
+}
+
+void ipm_deladdr(uint32_t ifindex, const struct wg_combined_ip *addr)
+{
+ iface_update(RTM_DELADDR, 0, ifindex, addr);
+}
+
+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,
+ };
+
+ iface_get_all_addrs2(AF_INET6, data_cb, &cb_data);
+
+ if (!cb_data.ip_found)
+ return -1;
+
+ if (cb_data.duplicate)
+ return -2;
+
+ return 0;
+}
diff --git a/ipm.h b/ipm.h
new file mode 100644
index 0000000..6e5994d
--- /dev/null
+++ b/ipm.h
@@ -0,0 +1,19 @@
+/* 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();
+void ipm_newaddr(uint32_t ifindex, const struct wg_combined_ip *addr);
+void ipm_deladdr(uint32_t ifindex, const struct wg_combined_ip *addr);
+int ipm_getlladdr(uint32_t ifindex, struct wg_combined_ip *addr);
+
+#endif
diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c
index fca1dfc..81031dc 100644
--- a/wg-dynamic-server.c
+++ b/wg-dynamic-server.c
@@ -24,6 +24,7 @@
#include "common.h"
#include "dbg.h"
+#include "ipm.h"
#include "khash.h"
#include "lease.h"
#include "netlink.h"
@@ -33,8 +34,6 @@ static const char *wg_interface;
static struct in6_addr well_known;
static wg_device *device = NULL;
-static struct wg_dynamic_request requests[MAX_CONNECTIONS] = { 0 };
-
static int sockfd = -1;
static int epollfd = -1;
static struct mnl_socket *nlsock = NULL;
@@ -42,60 +41,21 @@ static struct mnl_socket *nlsock = NULL;
KHASH_MAP_INIT_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;
+ unsigned char *buf;
+ size_t buflen;
};
+static struct wg_dynamic_connection connections[MAX_CONNECTIONS] = { 0 };
+
static void usage()
{
die("usage: %s <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);
-
- 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)
-{
- 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;
-}
-
static bool valid_peer_found(wg_device *device)
{
wg_peer *peer;
@@ -167,7 +127,7 @@ static wg_key *addr_to_pubkey(struct sockaddr_storage *addr)
return NULL;
}
-static int accept_connection(int sockfd, wg_key *dest)
+static int accept_connection(wg_key *dest)
{
int fd;
wg_key *pubkey;
@@ -223,7 +183,48 @@ static int accept_connection(int sockfd, wg_key *dest)
return fd;
}
-static bool send_error(struct wg_dynamic_request *req, int error)
+static bool send_message(struct wg_dynamic_connection *con, const void *buf,
+ size_t len)
+{
+ size_t offset = 0;
+
+ while (1) {
+ ssize_t written = write(con->fd, buf + offset, len - offset);
+ if (written < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ break;
+
+ // TODO: handle EINTR
+
+ debug("Writing to socket %d failed: %s\n", con->fd,
+ strerror(errno));
+ return true;
+ }
+
+ offset += written;
+ if (offset == len)
+ return true;
+ }
+
+ debug("Socket %d blocking on write with %lu bytes left, postponing\n",
+ con->fd, len - offset);
+
+ if (!con->buf) {
+ con->buflen = len - offset;
+ con->buf = malloc(con->buflen);
+ if (!con->buf)
+ fatal("malloc()");
+
+ memcpy(con->buf, buf + offset, con->buflen);
+ } else {
+ con->buflen = len - offset;
+ memmove(con->buf, buf + offset, con->buflen);
+ }
+
+ return false;
+}
+
+static bool send_error(struct wg_dynamic_connection *con, int error)
{
char buf[MAX_RESPONSE_SIZE];
size_t msglen = 0;
@@ -231,7 +232,7 @@ static bool send_error(struct wg_dynamic_request *req, int error)
print_to_buf(buf, sizeof buf, &msglen, "errno=%d\nerrmsg=%s\n\n", error,
WG_DYNAMIC_ERR[error]);
- return send_message(req, buf, msglen);
+ return send_message(con, buf, msglen);
}
static size_t serialize_lease(char *buf, size_t len,
@@ -331,32 +332,32 @@ static int response_request_ip(struct wg_dynamic_attr *cur, wg_key pubkey,
return E_NO_ERROR;
}
-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_attr *cur = con->req.first;
struct wg_dynamic_lease *lease;
size_t msglen;
int ret;
- switch (req->cmd) {
+ switch (con->req.cmd) {
case WGKEY_REQUEST_IP:
- ret = response_request_ip(cur, req->pubkey, &lease);
+ ret = response_request_ip(cur, con->pubkey, &lease);
if (ret)
break;
- add_allowed_ips(req->pubkey, &lease->ipv4, &lease->ipv6);
+ add_allowed_ips(con->pubkey, &lease->ipv4, &lease->ipv6);
msglen = serialize_lease(buf, sizeof buf, lease);
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_error(con, ret);
- return send_message(req, buf, msglen);
+ return send_message(con, buf, msglen);
}
static void setup_sockets()
@@ -410,6 +411,20 @@ static void setup_sockets()
fatal("mnl_socket_setsockopt()");
}
+void close_connection(struct wg_dynamic_connection *con)
+{
+ free_wg_dynamic_request(&con->req);
+
+ if (close(con->fd))
+ debug("Failed to close socket\n");
+
+ con->fd = -1;
+ memset(con->pubkey, 0, sizeof con->pubkey);
+ free(con->buf);
+ con->buf = NULL;
+ con->buflen = 0;
+}
+
static void cleanup()
{
leases_free();
@@ -426,33 +441,46 @@ 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 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);
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);
+ ipm_free();
+ if (ret == -2)
+ die("Interface must not have multiple link-local addresses assigned\n");
+
+ 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) // TODO:
+ die("Link-local address must have a CIDR of 128\n");
+
if (!valid_peer_found(device))
die("%s has no peers with link-local allowedips\n",
wg_interface);
@@ -467,19 +495,18 @@ 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);
+ fd = accept_connection(&connections[n].pubkey);
if (fd < 0) {
if (fd == -ENOENT) {
debug("Failed to match IP to pubkey\n");
@@ -494,20 +521,20 @@ static void accept_incoming(int sockfd, int epollfd,
}
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;
}
@@ -516,15 +543,20 @@ 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);
+ int ret = handle_request(con->fd, &con->req);
+ debug("handle_request(): %d\n", ret);
+
+ if (ret < 0 && send_error(con, -ret))
+ close_connection(con);
+ else if ((ret == 0 && send_response(con)) || ret == 1)
+ close_connection(con);
}
if (events & EPOLLOUT) {
- if (send_message(req, req->buf, req->buflen))
- close_connection(req);
+ if (send_message(con, con->buf, con->buflen))
+ close_connection(con);
}
}