diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | common.c | 93 | ||||
-rw-r--r-- | common.h | 12 | ||||
-rw-r--r-- | ipm.c | 178 | ||||
-rw-r--r-- | ipm.h | 19 | ||||
-rw-r--r-- | wg-dynamic-server.c | 188 |
6 files changed, 329 insertions, 165 deletions
@@ -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: @@ -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 */ @@ -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); @@ -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; +} @@ -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); } } |