aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/tools/ipc.c
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2016-07-20 21:24:27 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2016-07-21 11:26:52 +0200
commit0a494a01a97e51cca2f889ad2cd46e413927f979 (patch)
tree534493404f1c0cb4716c3bfac0d3130fe60c5474 /src/tools/ipc.c
parenttools: support horrible freebsd/osx/unix semantics (diff)
downloadwireguard-monolithic-historical-0a494a01a97e51cca2f889ad2cd46e413927f979.tar.xz
wireguard-monolithic-historical-0a494a01a97e51cca2f889ad2cd46e413927f979.zip
tools: rename kernel to ipc
Diffstat (limited to 'src/tools/ipc.c')
-rw-r--r--src/tools/ipc.c516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/tools/ipc.c b/src/tools/ipc.c
new file mode 100644
index 0000000..3243fcb
--- /dev/null
+++ b/src/tools/ipc.c
@@ -0,0 +1,516 @@
+/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+#ifdef __linux__
+#include <libmnl/libmnl.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#endif
+#include <netinet/in.h>
+#include <net/if.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <dirent.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/signal.h>
+
+#include "ipc.h"
+#include "../uapi.h"
+
+#define SOCK_PATH RUNSTATEDIR "/wireguard/"
+#define SOCK_SUFFIX ".sock"
+
+struct inflatable_buffer {
+ char *buffer;
+ char *next;
+ bool good;
+ size_t len;
+ size_t pos;
+};
+
+#define max(a, b) (a > b ? a : b)
+
+static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
+{
+ size_t len, expand_to;
+ char *new_buffer;
+
+ if (!buffer->good || !buffer->next) {
+ free(buffer->next);
+ buffer->good = false;
+ return 0;
+ }
+
+ len = strlen(buffer->next) + 1;
+
+ if (len == 1) {
+ free(buffer->next);
+ buffer->good = false;
+ return 0;
+ }
+
+ if (buffer->len - buffer->pos <= len) {
+ expand_to = max(buffer->len * 2, buffer->len + len + 1);
+ new_buffer = realloc(buffer->buffer, expand_to);
+ if (!new_buffer) {
+ free(buffer->next);
+ buffer->good = false;
+ return -errno;
+ }
+ memset(&new_buffer[buffer->len], 0, expand_to - buffer->len);
+ buffer->buffer = new_buffer;
+ buffer->len = expand_to;
+ }
+ memcpy(&buffer->buffer[buffer->pos], buffer->next, len);
+ free(buffer->next);
+ buffer->good = false;
+ buffer->pos += len;
+ return 0;
+}
+
+#ifndef __linux__
+static void close_and_unlink(int fd)
+{
+ struct sockaddr_un addr;
+ socklen_t len = sizeof(addr);
+
+ if (!getsockname(fd, (struct sockaddr *)&addr, &len))
+ unlink(addr.sun_path);
+ close(fd);
+}
+#endif
+
+static int userspace_interface_fd(const char *interface)
+{
+ struct stat sbuf;
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+#ifndef __linux__
+ struct sockaddr_un bind_addr = { .sun_family = AF_UNIX };
+ mode_t old_umask;
+#endif
+ int fd = -1, ret;
+
+ ret = -EINVAL;
+ if (strchr(interface, '/'))
+ goto out;
+ ret = snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface);
+ if (ret < 0)
+ goto out;
+#ifndef __linux__
+ ret = snprintf(bind_addr.sun_path, sizeof(bind_addr.sun_path) - 1, SOCK_PATH ".wg-tool-%s-%d.client", interface, getpid());
+ if (ret < 0)
+ goto out;
+ unlink(bind_addr.sun_path);
+#endif
+ ret = stat(addr.sun_path, &sbuf);
+ if (ret < 0)
+ goto out;
+ ret = -EBADF;
+ if (!S_ISSOCK(sbuf.st_mode))
+ goto out;
+
+ ret = fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (ret < 0)
+ goto out;
+#ifdef __linux__
+ ret = bind(fd, (struct sockaddr *)&addr, sizeof(sa_family_t));
+#else
+ old_umask = umask(0077);
+ ret = bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
+ umask(old_umask);
+#endif
+ if (ret < 0)
+ goto out;
+
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0) {
+ if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */
+ unlink(addr.sun_path);
+ goto out;
+ }
+out:
+ if (ret && fd >= 0)
+#ifdef __linux__
+ close(fd);
+#else
+ close_and_unlink(fd);
+#endif
+ if (!ret)
+ ret = fd;
+ return ret;
+}
+
+static bool userspace_has_wireguard_interface(const char *interface)
+{
+ int fd = userspace_interface_fd(interface);
+ if (fd < 0)
+ return false;
+#ifdef __linux__
+ close(fd);
+#else
+ close_and_unlink(fd);
+#endif
+ return true;
+}
+
+static int userspace_get_wireguard_interfaces(struct inflatable_buffer *buffer)
+{
+ DIR *dir;
+ struct dirent *ent;
+ size_t len;
+ char *end;
+ int ret = 0;
+
+ dir = opendir(SOCK_PATH);
+ if (!dir)
+ return errno == ENOENT ? 0 : errno;
+ while ((ent = readdir(dir))) {
+ len = strlen(ent->d_name);
+ if (len <= strlen(SOCK_SUFFIX))
+ continue;
+ end = &ent->d_name[len - strlen(SOCK_SUFFIX)];
+ if (strncmp(end, SOCK_SUFFIX, strlen(SOCK_SUFFIX)))
+ continue;
+ *end = '\0';
+ if (!userspace_has_wireguard_interface(ent->d_name))
+ continue;
+ buffer->next = strdup(ent->d_name);
+ buffer->good = true;
+ ret = add_next_to_inflatable_buffer(buffer);
+ if (ret < 0)
+ goto out;
+ }
+out:
+ closedir(dir);
+ return ret;
+}
+
+static int userspace_set_device(struct wgdevice *dev)
+{
+ struct wgpeer *peer;
+ size_t len;
+ ssize_t ret;
+ int ret_code;
+ int fd = userspace_interface_fd(dev->interface);
+ if (fd < 0)
+ return fd;
+ for_each_wgpeer(dev, peer, len);
+ len = (unsigned char *)peer - (unsigned char *)dev;
+ ret = -EBADMSG;
+ if (!len)
+ goto out;
+ ret = send(fd, dev, len, 0);
+ if (ret < 0)
+ goto out;
+ ret = recv(fd, &ret_code, sizeof(ret_code), 0);
+ if (ret < 0)
+ goto out;
+ ret = ret_code;
+out:
+#ifdef __linux__
+ close(fd);
+#else
+ close_and_unlink(fd);
+#endif
+ return (int)ret;
+}
+
+static int userspace_get_device(struct wgdevice **dev, const char *interface)
+{
+#ifdef __linux__
+ ssize_t len;
+#else
+ int len;
+#endif
+ ssize_t ret;
+ int fd = userspace_interface_fd(interface);
+ if (fd < 0)
+ return fd;
+ *dev = NULL;
+ ret = send(fd, NULL, 0, 0);
+ if (ret < 0)
+ goto out;
+
+#ifdef __linux__
+ ret = len = recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC);
+ if (len < 0)
+ goto out;
+#else
+ ret = recv(fd, &ret, 1, MSG_PEEK);
+ if (ret < 0)
+ goto out;
+ ret = ioctl(fd, FIONREAD, &len);
+ if (ret < 0) {
+ ret = -errno;
+ goto out;
+ }
+#endif
+ ret = -EBADMSG;
+ if ((size_t)len < sizeof(struct wgdevice))
+ goto out;
+
+ ret = -ENOMEM;
+ *dev = calloc(len, 1);
+ if (!*dev)
+ goto out;
+
+ ret = recv(fd, *dev, len, 0);
+ if (ret < 0)
+ goto out;
+ ret = 0;
+out:
+ if (*dev && ret)
+ free(*dev);
+#ifdef __linux__
+ close(fd);
+#else
+ close_and_unlink(fd);
+#endif
+ errno = -ret;
+ return ret;
+}
+
+#ifdef __linux__
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+ if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp("wireguard", mnl_attr_get_str(attr)))
+ buffer->good = true;
+ return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+ if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+ return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+ else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+ buffer->next = strdup(mnl_attr_get_str(attr));
+ return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+ buffer->good = false;
+ buffer->next = NULL;
+ int ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, data);
+ if (ret != MNL_CB_OK)
+ return ret;
+ ret = add_next_to_inflatable_buffer(buffer);
+ if (ret < 0)
+ return ret;
+ if (nlh->nlmsg_type != NLMSG_DONE)
+ return MNL_CB_OK + 1;
+ return MNL_CB_OK;
+}
+
+static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer = NULL;
+ size_t message_len;
+ unsigned int portid, seq;
+ ssize_t len;
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+
+ ret = -ENOMEM;
+ rtnl_buffer = calloc(4096, 1);
+ if (!rtnl_buffer)
+ goto cleanup;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = RTM_GETLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ message_len = nlh->nlmsg_len;
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+another:
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, 4096)) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, buffer)) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if (len == MNL_CB_OK + 1)
+ goto another;
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+static bool kernel_has_wireguard_interface(const char *interface)
+{
+ char *this_interface;
+ struct inflatable_buffer buffer = { .len = 4096 };
+
+ buffer.buffer = calloc(buffer.len, 1);
+ if (!buffer.buffer)
+ return false;
+ if (kernel_get_wireguard_interfaces(&buffer) < 0) {
+ free(buffer.buffer);
+ return false;
+ }
+ this_interface = buffer.buffer;
+ for (size_t len = 0; (len = strlen(this_interface)); this_interface += len + 1) {
+ if (!strcmp(interface, this_interface)) {
+ free(buffer.buffer);
+ return true;
+ }
+ }
+ free(buffer.buffer);
+ return false;
+}
+
+static int do_ioctl(int req, struct ifreq *ifreq)
+{
+ static int fd = -1;
+ if (fd < 0) {
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return fd;
+ }
+ return ioctl(fd, req, ifreq);
+}
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ struct ifreq ifreq = { .ifr_data = (char *)dev };
+ memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ);
+ ifreq.ifr_name[IFNAMSIZ - 1] = 0;
+ return do_ioctl(WG_SET_DEVICE, &ifreq);
+}
+
+static int kernel_get_device(struct wgdevice **dev, const char *interface)
+{
+ int ret;
+ struct ifreq ifreq = { 0 };
+ memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
+ ifreq.ifr_name[IFNAMSIZ - 1] = 0;
+ *dev = NULL;
+ do {
+ free(*dev);
+ ret = do_ioctl(WG_GET_DEVICE, &ifreq);
+ if (ret < 0)
+ goto out;
+ *dev = calloc(ret + sizeof(struct wgdevice), 1);
+ if (!*dev) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ (*dev)->peers_size = ret;
+ ifreq.ifr_data = (char *)*dev;
+ memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
+ ifreq.ifr_name[IFNAMSIZ - 1] = 0;
+ ret = do_ioctl(WG_GET_DEVICE, &ifreq);
+ } while (ret == -EMSGSIZE);
+ if (ret < 0) {
+ free(*dev);
+ *dev = NULL;
+ }
+out:
+ errno = -ret;
+ return ret;
+}
+#endif
+
+/* first\0second\0third\0forth\0last\0\0 */
+char *ipc_list_devices(void)
+{
+ struct inflatable_buffer buffer = { .len = 4096 };
+ int ret;
+
+ ret = -ENOMEM;
+ buffer.buffer = calloc(buffer.len, 1);
+ if (!buffer.buffer)
+ goto cleanup;
+
+#ifdef __linux__
+ ret = kernel_get_wireguard_interfaces(&buffer);
+ if (ret < 0)
+ goto cleanup;
+#endif
+ ret = userspace_get_wireguard_interfaces(&buffer);
+ if (ret < 0)
+ goto cleanup;
+
+cleanup:
+ errno = -ret;
+ if (errno) {
+ perror("Error when trying to get a list of WireGuard interfaces");
+ free(buffer.buffer);
+ return NULL;
+ }
+ return buffer.buffer;
+}
+
+int ipc_get_device(struct wgdevice **dev, const char *interface)
+{
+#ifdef __linux__
+ if (userspace_has_wireguard_interface(interface))
+ return userspace_get_device(dev, interface);
+ return kernel_get_device(dev, interface);
+#else
+ return userspace_get_device(dev, interface);
+#endif
+}
+
+int ipc_set_device(struct wgdevice *dev)
+{
+#ifdef __linux__
+ if (userspace_has_wireguard_interface(dev->interface))
+ return userspace_set_device(dev);
+ return kernel_set_device(dev);
+#else
+ return userspace_set_device(dev);
+#endif
+}
+
+bool ipc_has_device(const char *interface)
+{
+#ifdef __linux__
+ return userspace_has_wireguard_interface(interface) || kernel_has_wireguard_interface(interface);
+#else
+ return userspace_has_wireguard_interface(interface);
+#endif
+}