From b43de5932ba01c8acbcdfc7aebd48b87514f4af2 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 4 Apr 2019 11:54:42 +0200 Subject: WIP poc0 --- wg-dynamic-server.c | 349 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 266 insertions(+), 83 deletions(-) (limited to 'wg-dynamic-server.c') diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c index b63c72d..fc8098c 100644 --- a/wg-dynamic-server.c +++ b/wg-dynamic-server.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,7 @@ #include #include #include - +#include #include #include @@ -29,6 +30,7 @@ static const char *progname; static const char *wg_interface; static struct in6_addr well_known; +/* static in_addr_t inaddr_any = INADDR_ANY; */ static wg_device *device = NULL; static struct radix_trie allowedips_trie; @@ -36,6 +38,7 @@ static struct pollfd pollfds[MAX_CONNECTIONS + 1]; struct mnl_cb_data { uint32_t ifindex; + struct in6_addr addr; bool valid_ip_found; }; @@ -44,12 +47,6 @@ static void usage() die("usage: %s \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; @@ -105,56 +102,26 @@ 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, + .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; } +static int get_avail_pollfds() +{ + for (int nfds = 1;; ++nfds) { + if (nfds >= MAX_CONNECTIONS + 1) + return -1; + + if (pollfds[nfds].fd < 0) + return nfds; + } +} + static bool valid_peer_found(wg_device *device) { wg_peer *peer; @@ -180,17 +147,6 @@ static bool valid_peer_found(wg_device *device) return false; } -static int get_avail_pollfds() -{ - for (int nfds = 1;; ++nfds) { - if (nfds >= MAX_CONNECTIONS + 1) - return -1; - - if (pollfds[nfds].fd < 0) - return nfds; - } -} - static void rebuild_allowedips_trie() { int ret; @@ -242,6 +198,7 @@ static int accept_connection(int sockfd, wg_key *dest) wg_key *pubkey; struct sockaddr_storage addr; socklen_t size = sizeof addr; + #ifdef __linux__ fd = accept4(sockfd, (struct sockaddr *)&addr, &size, SOCK_NONBLOCK); if (fd < 0) @@ -255,6 +212,27 @@ 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 + + char addrstring[INET6_ADDRSTRLEN]; + char serv[10]; + if (getnameinfo((struct sockaddr *)&addr, sizeof addr, addrstring, + sizeof addrstring, serv, sizeof serv, + NI_NUMERICHOST | NI_NUMERICSERV)) + fatal("getnameinfo"); + + if (addr.ss_family != AF_INET6) { + debug("%s: rejecting client for not using an IPv6 address\n", + addrstring); + return -EINVAL; + } + unsigned long port = strtoul(serv, NULL, 10); + if (port >= 1024) { + debug("%s: rejecting client for not using a low port: %lu\n", + addrstring, port); + return -EINVAL; + } + //debug("Client connection from [%s]:%lu\n", addrstring, port); + pubkey = addr_to_pubkey(&addr); if (!pubkey) { /* our copy of allowedips is outdated, refresh */ @@ -266,14 +244,11 @@ 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]; wg_key_to_base64(key, *pubkey); - inet_ntop(addr.ss_family, &((struct sockaddr_in6 *)&addr)->sin6_addr, - out, sizeof(out)); - debug("%s has pubkey: %s\n", out, key); + debug("%s has pubkey: %s\n", addrstring, key); return fd; } @@ -295,28 +270,225 @@ 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) +{ + 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)) + fatal("inet_pton()"); + memcpy(&lease->ip4, &default_v4, sizeof(struct wg_combined_ip)); + + if (!inet_pton(AF_INET6, "fd00::4711", &default_v6.ip.ip6)) + 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: + break; /* FIXME */ + memcpy(&lease->ip4, attr->value, + sizeof(struct wg_combined_ip)); + break; + case WGKEY_IPV6: + break; /* FIXME */ + 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; +} + +static bool send_error(int fd, int ret) +{ + UNUSED(fd); + debug("Error: %s\n", strerror(ret)); + return true; +} + +static bool serialise_lease(char *buf, size_t bufsize, size_t *offset, + const struct wg_dynamic_lease *lease) +{ + char addrbuf[INET6_ADDRSTRLEN]; + bool ret = false; + + if (lease->ip4.family) { /* FIXME: memcmp(&lease->ip4, &inaddr_any, 4) instead? */ + 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 (lease->ip6.family) { /* FIXME: !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6) instead? */ + 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 struct wg_peer *current_peer(struct wg_dynamic_request *req) +{ + 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) { - if (close(*fd)) - debug("Failed to close socket"); + struct wg_allowedip *newip; + + newip = calloc(1, sizeof(struct wg_allowedip)); + if (!newip) + fatal("calloc()"); - *fd = -1; - free_wg_dynamic_request(req); + 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 void send_response(int fd, struct wg_dynamic_request *req) +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; + +#if 0 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; } -} +#endif -static void send_error(int fd, int ret) -{ - debug("Error: %s\n", strerror(ret)); + 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 = 0x01; + 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 = 0x2; + errmsg = "Internal error"; + break; + } + + if (!serialise_lease((char *)buf, sizeof buf, &msglen, + &lease)) { + die("Nothing to hand out, despite succeeding allocate_from_pool()\n"); + ret = 0x3; + errmsg = "Internal error"; + 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,13 +539,11 @@ 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)) + fatal("inet_pton()"); for (int i = 0; i < MAX_CONNECTIONS + 1; ++i) { - pollfds[i] = (struct pollfd){ - .fd = -1, - .events = POLLIN, - }; + pollfds[i] = (struct pollfd){.fd = -1, .events = POLLIN }; } if (argc != 2) @@ -408,13 +578,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; } } -- cgit v1.2.3-59-g8ed1b