From 9a9c3eaf6fedb23f26ea4ceb95d124a66c3c9d29 Mon Sep 17 00:00:00 2001 From: Thomas Gschwantner Date: Sat, 14 Sep 2019 19:43:33 +0200 Subject: WIP2 --- Makefile | 2 +- wg-dynamic-client.c | 512 +++++++++++++++++----------------------------------- 2 files changed, 165 insertions(+), 349 deletions(-) diff --git a/Makefile b/Makefile index 9d6651d..e5c4fe2 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ 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.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 diff --git a/wg-dynamic-client.c b/wg-dynamic-client.c index f3e3274..83ce392 100644 --- a/wg-dynamic-client.c +++ b/wg-dynamic-client.c @@ -2,446 +2,262 @@ * * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. */ - -#include -#include -#include -#include -#include -#include -#include +#define _POSIX_C_SOURCE 200112L #include -#include -#include +#include +#include +#include +#include +#include +#include #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 wg_combined_ip ipv4, ipv6; // TODO: +static bool ipv4_assign = false, ipv6_assign = 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 void usage() { die("usage: %s \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) -{ - 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"); - - mnl_socket_close(nl); -} - -static void iface_remove_addr(uint32_t ifindex, - const struct wg_combined_ip *addr) +static void cleanup() { - 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); -} + if (ipv4_assign) + ipm_deladdr(device->ifindex, &ipv4); + if (ipv6_assign) + ipm_deladdr(device->ifindex, &ipv6); -static void iface_add_addr(uint32_t ifindex, const struct wg_combined_ip *addr) -{ - 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); + ipm_free(); + wg_free_device(device); } -static bool get_and_validate_local_addrs(uint32_t ifindex, - struct in6_addr *lladdr, - struct wg_combined_ip *gaddr4, - struct wg_combined_ip *gaddr6) -{ - 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); -} +#include "ip_util.h" +static int sockfd = -1; -static int try_connect(int *fd) +static void preamble() { - struct timeval tval = { .tv_sec = 1, .tv_usec = 0 }; - struct sockaddr_in6 our_addr = { + struct sockaddr_in6 dstaddr = { .sin6_family = AF_INET6, - .sin6_addr = our_lladdr, .sin6_port = htons(WG_DYNAMIC_PORT), + .sin6_addr = well_known, .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, }; - *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 (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(*fd, (struct sockaddr *)&their_addr, - sizeof(struct sockaddr_in6))) { - char out[INET6_ADDRSTRLEN]; - - 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 (close(*fd)) - debug("Closing socket failed: %s\n", strerror(errno)); - *fd = -1; - return -1; - } + if (connect(sockfd, (struct sockaddr *)&dstaddr, sizeof(dstaddr))) + fatal("connect()"); +} - return 0; +static void postamble() +{ + close(sockfd); // TODO: } -static void request_ip(int fd, const struct wg_dynamic_lease *lease) +static int request_ip() { - unsigned char buf[MAX_RESPONSE_SIZE + 1]; - char addrstr[INET6_ADDRSTRLEN]; - size_t msglen; + char buf[4096]; + struct wg_dynamic_request req = { + .cmd = WGKEY_REQUEST_IP + }; // TODO: version + struct wg_combined_ip new_ipv4, new_ipv6; + struct wg_dynamic_attr *attr = req.first; + size_t msglen = 0, off = 0; + char ip4str[INET_ADDRSTRLEN], ip6str[INET6_ADDRSTRLEN]; + uint32_t err; + int ret; - msglen = 0; - msglen += print_to_buf((char *)buf, sizeof buf, msglen, "%s=%d\n", - WG_DYNAMIC_KEY[WGKEY_REQUEST_IP], 1); + strcpy(buf, "request_ip=1\n"); + msglen = strlen(buf); - if (lease && lease->ip4.ip4.s_addr) { - if (!inet_ntop(AF_INET, &lease->ip4.ip4, addrstr, - sizeof addrstr)) + if (ipv4_assign) { + if (!inet_ntop(AF_INET, &ipv4.ip4, ip4str, sizeof ip4str)) fatal("inet_ntop()"); - msglen += print_to_buf((char *)buf, sizeof buf, msglen, - "ipv4=%s/32\n", addrstr); + + print_to_buf(buf, sizeof buf, &msglen, "ipv4=%s/32\n", ip4str); } - if (lease && !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip6)) { - if (!inet_ntop(AF_INET6, &lease->ip6.ip6, addrstr, - sizeof addrstr)) + + if (ipv6_assign) { + if (!inet_ntop(AF_INET6, &ipv6.ip6, ip6str, sizeof ip6str)) fatal("inet_ntop()"); - msglen += print_to_buf((char *)buf, sizeof buf, msglen, - "ipv6=%s/128\n", addrstr); + + print_to_buf(buf, sizeof buf, &msglen, "ipv6=%s/128\n", ip6str); } - /* nmsglen += print_to_buf((char *)buf, sizeof buf, msglen, - "leasetime=%u\n", fixme); */ - msglen += print_to_buf((char *)buf, sizeof buf, msglen, "\n"); + buf[msglen++] = '\n'; - send_message(fd, buf, &msglen); -} + do { + ssize_t written = write(sockfd, buf + off, msglen - off); + if (written == -1) { + if (errno == EINTR) + continue; -static uint32_t time_until_refresh(uint32_t now, struct wg_dynamic_lease *lease) -{ - uint32_t refresh_at; + fatal("write()"); + } - if (lease->leasetime == 0) - return 0; - refresh_at = lease->start + (lease->leasetime * 8) / 10; + off += written; + } while (off < msglen); - if (refresh_at < now) - return 0; - return refresh_at - now; -} + ret = handle_request(sockfd, &req); + if (ret != 0) { + free_wg_dynamic_request(&req); + return ret; + } -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)); + memcpy(&new_ipv4, attr->value, sizeof new_ipv4); + ipv4.cidr = 32; + debug("Recieved addr: %y/%d\n", &new_ipv4.ip4, + new_ipv4.cidr); 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)); + memcpy(&new_ipv6, attr->value, sizeof new_ipv6); + ipv6.cidr = 128; 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; - } + err = *attr->value; break; case WGKEY_ERRMSG: - /* TODO: do something with the error message */ break; default: - debug("Ignoring invalid attribute for request_ip: %d\n", + debug("Ignoring unknown 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 (err != 0) { + free_wg_dynamic_request(&req); + return -err; + } + + if (ipv4_assign) + ipm_deladdr(device->ifindex, &ipv4); - if (abs(now - lease_start) < 15) - lease->start = lease_start; - else - lease->start = now; + if (ipv6_assign) + ipm_deladdr(device->ifindex, &ipv6); - debug("Replacing lease %u -> %u\n", curleasetime, - lease->start + lease->leasetime); + memcpy(&ipv4, &new_ipv4, sizeof ipv4); + memcpy(&ipv6, &new_ipv6, sizeof ipv6); - return 0; -} + ipm_newaddr(device->ifindex, &ipv4); + ipm_newaddr(device->ifindex, &ipv6); + ipv4_assign = ipv6_assign = true; -static void cleanup() -{ - wg_free_device(device); - if (our_fd != -1 && close(our_fd)) - debug("Failed to close fd %d\n", our_fd); + free_wg_dynamic_request(&req); } -static bool handle_error(int fd, int ret) +static void setup() { - UNUSED(fd); - UNUSED(ret); + struct wg_combined_ip ip; + int ret; - debug("Unable to parse response: %s\n", strerror(ret)); + if (atexit(cleanup)) + die("Failed to set exit function\n"); - return true; -} + if (wg_get_device(&device, wg_interface)) + fatal("Unable to access interface %s", wg_interface); -static void maybe_update_iface() -{ - 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); - } -} + if (inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known) != 1) + fatal("inet_pton()"); -static bool handle_response(int fd, struct wg_dynamic_request *req) -{ - 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; - } -#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; - } + ipm_init(); - return true; + ret = ipm_getlladdr(device->ifindex, &ip); + if (ret == -1 || ip.family != AF_INET6) + die("%s needs to be assigned an IPv6 link local address\n", + wg_interface); + + if (ret == -2) + die("Interface must not have multiple link-local addresses assigned\n"); + + if (ip.cidr != 128) + die("Link-local address must have a CIDR of 128\n"); + + memcpy(&lladdr, &ip, 16); } -int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +static void loop() { - int *fd = &our_fd; - struct wg_dynamic_request req = { 0 }; + int ret; - progname = argv[0]; - if (argc != 2) - usage(); + preamble(); + ret = request_ip(); + if (ret < 0) { + /* TODO: differentiate between errors */ + die("Server error: %d\n", -ret); + } else if (ret == 1) { + /* TODO: network error, retry later */ + die("Network error"); + } else if (ret == 2) { + die("Invalid server response"); + } - wg_interface = argv[1]; + postamble(); + // figure out last handshake time + // sleep until then + wg_free_device(device); 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)); - } + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts)) + fatal("clock_gettime(CLOCK_REALTIME)"); - while (1) { - sleep(time_until_refresh(current_time(), &our_lease)); + debug("Last handshake: %zu (%zu)\n", + device->first_peer->last_handshake_time.tv_sec, + ts.tv_sec - device->first_peer->last_handshake_time.tv_sec); - if (*fd == -1 && try_connect(fd)) { - sleep(1); - continue; - } + time_t test = 180 + device->first_peer->last_handshake_time.tv_sec; - request_ip(*fd, &our_lease); + if (test > ts.tv_sec) { + struct timespec timeout = { + .tv_sec = test - ts.tv_sec, + }, remain; + debug("Sleeping for %zus\n", timeout.tv_sec); - while (!handle_request(&req, handle_response, handle_error)) - ; - close_connection(&req); + if (clock_nanosleep(CLOCK_BOOTTIME, 0, &timeout, &remain)) + fatal("clock_nanosleep()"); // TODO: does not set errno } +} + +int main(int argc, char *argv[]) +{ + progname = argv[0]; + if (argc != 2) + usage(); + + wg_interface = argv[1]; + setup(); + + while (1) + loop(); return 0; } -- cgit v1.2.3-59-g8ed1b