aboutsummaryrefslogtreecommitdiffstats
path: root/wg-dynamic-server.c
diff options
context:
space:
mode:
Diffstat (limited to 'wg-dynamic-server.c')
-rw-r--r--wg-dynamic-server.c318
1 files changed, 233 insertions, 85 deletions
diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c
index b63c72d..77eb9a4 100644
--- a/wg-dynamic-server.c
+++ b/wg-dynamic-server.c
@@ -14,6 +14,7 @@
#include <arpa/inet.h>
#include <fcntl.h>
+#include <netdb.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
@@ -44,33 +45,6 @@ static void usage()
die("usage: %s <wg-interface>\n", progname);
}
-static bool is_link_local(unsigned char *addr)
-{
- /* TODO: check if the remaining 48 bits are 0 */
- return addr[0] == 0xFE && addr[1] == 0x80;
-}
-
-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] = {};
@@ -105,52 +79,12 @@ static int data_cb(const struct nlmsghdr *nlh, void *data)
static bool validate_link_local_ip(uint32_t ifindex)
{
- 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;
struct mnl_cb_data cb_data = {
.ifindex = ifindex,
.valid_ip_found = false,
};
- 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 = AF_INET6;
-
- if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
- fatal("mnl_socket_sendto");
-
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- while (ret > 0) {
- ret = mnl_cb_run(buf, ret, seq, portid, data_cb, &cb_data);
- if (ret <= MNL_CB_STOP)
- break;
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- }
- if (ret == -1)
- fatal("mnl_cb_run/mnl_socket_recvfrom");
-
- mnl_socket_close(nl);
+ iface_get_all_addrs(AF_INET6, data_cb, &cb_data);
return cb_data.valid_ip_found;
}
@@ -255,6 +189,20 @@ static int accept_connection(int sockfd, wg_key *dest)
if (res < 0 || fcntl(fd, F_SETFL, res | O_NONBLOCK) < 0)
fatal("Setting socket to nonblocking failed");
#endif
+
+ if (addr.ss_family != AF_INET6) {
+ debug("Rejecting client for not using an IPv6 address\n");
+ return -EINVAL;
+ }
+
+ if (((struct sockaddr_in6 *)&addr)->sin6_port !=
+ htons(WG_DYNAMIC_PORT)) {
+ debug("Rejecting client for using port %u != %u\n",
+ htons(((struct sockaddr_in6 *)&addr)->sin6_port),
+ WG_DYNAMIC_PORT);
+ return -EINVAL;
+ }
+
pubkey = addr_to_pubkey(&addr);
if (!pubkey) {
/* our copy of allowedips is outdated, refresh */
@@ -266,7 +214,7 @@ static int accept_connection(int sockfd, wg_key *dest)
return -ENOENT;
}
}
- memcpy(dest, pubkey, sizeof *pubkey);
+ memcpy(dest, pubkey, sizeof *dest);
wg_key_b64_string key;
char out[INET6_ADDRSTRLEN];
@@ -295,28 +243,214 @@ static void accept_incoming(int sockfd, struct wg_dynamic_request *reqs)
}
}
-static void close_connection(int *fd, struct wg_dynamic_request *req)
+static int allocate_from_pool(struct wg_dynamic_request *const req,
+ struct wg_dynamic_lease *lease)
{
- if (close(*fd))
- debug("Failed to close socket");
+ struct wg_dynamic_attr *attr;
+ struct wg_combined_ip default_v4 = { .family = AF_INET, .cidr = 32 },
+ default_v6 = { .family = AF_INET6, .cidr = 128 };
+
+ /* NOTE: handing out whatever the client asks for */
+ /* TODO: choose an ip address from pool of available
+ * addresses, together with an appropriate lease time */
+ /* NOTE: the pool is to be drawn from what routes are pointing
+ * to the wg interface, and kept up to date as the routing
+ * table changes */
+
+ if (inet_pton(AF_INET, "192.168.47.11", &default_v4.ip.ip4) != 1)
+ fatal("inet_pton()");
+ memcpy(&lease->ip4, &default_v4, sizeof(struct wg_combined_ip));
+
+ if (inet_pton(AF_INET6, "fd00::4711", &default_v6.ip.ip6) != 1)
+ fatal("inet_pton()");
+ memcpy(&lease->ip6, &default_v6, sizeof(struct wg_combined_ip));
+
+ lease->start = current_time();
+ lease->leasetime = WG_DYNAMIC_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_LEASETIME:
+ memcpy(&lease->leasetime, attr->value,
+ sizeof(uint32_t));
+ break;
+ default:
+ debug("Ignoring invalid attribute for request_ip: %d\n",
+ attr->key);
+ }
+
+ attr = attr->next;
+ }
+
+ return 0;
+}
- *fd = -1;
- free_wg_dynamic_request(req);
+static bool send_error(int fd, int ret)
+{
+ UNUSED(fd);
+ debug("Error: %s\n", strerror(ret));
+ return true;
}
-static void send_response(int fd, struct wg_dynamic_request *req)
+static bool serialise_lease(char *buf, size_t bufsize, size_t *offset,
+ const struct wg_dynamic_lease *lease)
{
- printf("Recieved request 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;
+ char addrbuf[INET6_ADDRSTRLEN];
+ bool ret = false;
+
+ if (lease->ip4.ip.ip4.s_addr) {
+ if (!inet_ntop(AF_INET, &lease->ip4.ip.ip4, addrbuf,
+ sizeof addrbuf))
+ fatal("inet_ntop()");
+ *offset += print_to_buf(buf, bufsize, *offset, "ipv4=%s/%d\n",
+ addrbuf, lease->ip4.cidr);
+ ret = true;
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)) {
+ if (!inet_ntop(AF_INET6, &lease->ip6.ip.ip6, addrbuf,
+ sizeof addrbuf))
+ fatal("inet_ntop()");
+ *offset += print_to_buf(buf, bufsize, *offset, "ipv6=%s/%d\n",
+ addrbuf, lease->ip6.cidr);
+ ret = true;
+ }
+
+ if (ret) {
+ *offset += print_to_buf(buf, bufsize, *offset,
+ "leasestart=%u\n", lease->start);
+ *offset += print_to_buf(buf, bufsize, *offset, "leasetime=%u\n",
+ lease->leasetime);
}
+
+ return ret;
}
-static void send_error(int fd, int ret)
+static struct wg_peer *current_peer(struct wg_dynamic_request *req)
{
- debug("Error: %s\n", strerror(ret));
+ struct wg_peer *peer;
+
+ wg_for_each_peer (device, peer) {
+ if (!memcmp(peer->public_key, req->pubkey, sizeof(wg_key)))
+ return peer;
+ }
+
+ return NULL;
+}
+
+static void insert_allowed_ip(struct wg_peer *peer,
+ const struct wg_combined_ip *ip)
+{
+ struct wg_allowedip *newip;
+
+ newip = calloc(1, sizeof(struct wg_allowedip));
+ if (!newip)
+ fatal("calloc()");
+
+ newip->family = ip->family;
+ switch (newip->family) {
+ case AF_INET:
+ memcpy(&newip->ip4, &ip->ip.ip4, sizeof(struct in_addr));
+ break;
+ case AF_INET6:
+ memcpy(&newip->ip6, &ip->ip.ip6, sizeof(struct in6_addr));
+ break;
+ }
+ newip->cidr = ip->cidr;
+
+ if (!peer->first_allowedip)
+ peer->first_allowedip = newip;
+ else
+ peer->last_allowedip->next_allowedip = newip;
+ peer->last_allowedip = newip;
+}
+
+static int add_allowed_ips(struct wg_peer *peer,
+ const struct wg_dynamic_lease *lease)
+{
+ if (lease->ip4.ip.ip4.s_addr)
+ insert_allowed_ip(peer, &lease->ip4);
+ if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6))
+ insert_allowed_ip(peer, &lease->ip6);
+
+ return wg_set_device(device);
+}
+
+static bool send_response(int fd, struct wg_dynamic_request *req)
+{
+ int ret;
+ char *errmsg = "OK";
+ unsigned char buf[MAX_RESPONSE_SIZE + 1];
+ size_t msglen;
+ size_t written;
+ struct wg_dynamic_lease lease = { 0 };
+ struct wg_peer *peer;
+
+ peer = current_peer(req);
+ if (!peer)
+ die("Unable to find peer\n");
+
+ ret = 0;
+ msglen = 0;
+ switch (req->cmd) {
+ case WGKEY_REQUEST_IP:
+ msglen = print_to_buf((char *)buf, sizeof buf, 0, "%s=%d\n",
+ WG_DYNAMIC_KEY[req->cmd], 1);
+ ret = allocate_from_pool(req, &lease);
+ if (ret) {
+ debug("IP address allocation failing with %d\n", ret);
+ ret = 0x02;
+ errmsg = "Out of IP addresses";
+ break;
+ }
+
+ ret = add_allowed_ips(peer, &lease);
+ if (ret) {
+ debug("Unable to add address(es) to peer: %s\n",
+ strerror(-ret));
+ ret = 0x1;
+ errmsg = "Internal error";
+ break;
+ }
+
+ if (!serialise_lease((char *)buf, sizeof buf, &msglen,
+ &lease)) {
+ die("Nothing to hand out, despite succeeding allocate_from_pool()\n");
+ break;
+ }
+
+ break;
+
+ default:
+ debug("Unknown command: %d\n", req->cmd);
+ return true;
+ }
+
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen, "errno=%d\n",
+ ret);
+ if (ret)
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen,
+ "errmsg=%s\n", errmsg);
+ if (msglen == sizeof buf)
+ fatal("Outbuffer too small");
+ buf[msglen++] = '\n';
+
+ written = send_message(fd, buf, &msglen);
+ if (msglen == 0)
+ return true;
+
+ debug("Socket %d blocking on write with %lu bytes left, postponing\n",
+ fd, msglen);
+ send_later(req, buf + written, msglen);
+ return false;
}
static void setup_socket(int *fd)
@@ -367,7 +501,8 @@ int main(int argc, char *argv[])
int *sockfd = &pollfds[0].fd;
progname = argv[0];
- inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known);
+ if (inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known) != 1)
+ fatal("inet_pton()");
for (int i = 0; i < MAX_CONNECTIONS + 1; ++i) {
pollfds[i] = (struct pollfd){
@@ -408,13 +543,26 @@ int main(int argc, char *argv[])
}
for (int i = 1; i < MAX_CONNECTIONS + 1; ++i) {
- if (!(pollfds[i].revents & POLLIN))
+ if (!(pollfds[i].revents & POLLOUT))
+ continue;
+
+ pollfds[i].revents &= ~POLLOUT;
+ send_message(pollfds[i].fd, reqs[i - 1].buf,
+ &reqs[i - 1].buflen);
+ if (!reqs[i - 1].buflen)
+ close_connection(&pollfds[i].fd, &reqs[i - 1]);
+ }
+
+ for (int i = 1; i < MAX_CONNECTIONS + 1; ++i) {
+ if (pollfds[i].fd < 0 || !pollfds[i].revents & POLLIN)
continue;
- pollfds[i].revents = 0;
+ pollfds[i].revents &= ~POLLIN;
if (handle_request(pollfds[i].fd, &reqs[i - 1],
send_response, send_error))
close_connection(&pollfds[i].fd, &reqs[i - 1]);
+ else if (reqs[i - 1].buf)
+ pollfds[i].events |= POLLOUT;
}
}