diff options
Diffstat (limited to 'sysdep/unix/wg_user.c')
-rw-r--r-- | sysdep/unix/wg_user.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/sysdep/unix/wg_user.c b/sysdep/unix/wg_user.c new file mode 100644 index 00000000..a3a1067f --- /dev/null +++ b/sysdep/unix/wg_user.c @@ -0,0 +1,281 @@ +#include <stdio.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> +#include "lib/lists.h" +#include "lib/ip.h" +#include "lib/socket.h" +#include "nest/iface.h" +#include "sysdep/unix/unix.h" +#include "sysdep/unix/wg_user.h" +#include "sysdep/linux/wireguard.h" + +static +socklen_t get_socket_path(const char *ifname, char *buf, uint size) +{ + int pos = 0; + + if (size < 1) + return 0; + +#ifdef ANDROID + /* Abstract socket */ + buf[pos++] = 0; +#endif + bsnprintf(buf+pos, size-pos, SOCKET_PATH "%s.sock", ifname); + return pos + strlen(buf+pos); +} + +bool wg_has_userspace(const char *ifname) +{ + struct stat sb; + char tmp[sizeof(struct sockaddr_un)]; + socklen_t tmplen = get_socket_path(ifname, tmp, sizeof(tmp)); + + if (tmplen > 0 && tmp[0] == 0) + /* System with abstract socket (Android) always use WireGuard's userspace + implementation. */ + return true; + else if (stat(tmp, &sb) == 0) + return (sb.st_mode & S_IFMT) == S_IFSOCK; + else + { + DBG(L_TRACE "WG: no socket %s", tmp); + return false; + } +} + +/* NULL=receiving turned off, returns 1 to clear rx buffer */ +static +int user_rx_hook(struct birdsock *sk UNUSED, uint size UNUSED) +{ + DBG(L_TRACE "WG: RX %p %d", sk, size); + rfree(sk); + return 1; +} + +static +void user_tx_hook(struct birdsock *bs) +{ + DBG(L_TRACE "WG: TX %p %d", bs, bs->tpos - bs->ttx); + + uint size = (uintptr_t)bs->data; + + if (size > 0) + { + int res = sk_send(bs, bs->tbsize - size); + + /* Send data, <0=err, >0=ok, 0=sleep */ + log(L_TRACE "WG: send %d", res); + + if (res != 0) + { + //rfree(sock); + //shutdown(sock->fd, SHUT_WR); + } + + bs->data = NULL; + } +} + +/* errno or zero if EOF */ +static +void user_err_hook(struct birdsock *bs, int err) +{ + /* if (err == 0) */ + /* return; */ + + log(L_TRACE "WG: ERR %p %d %s", bs, err, bs->err); + rfree(bs); +} + +static void +wg_puts(const char *str, byte **buf, uint *size) +{ + int len = strlen(str); + if (0 < len && len < (int)*size) + { + strcpy(*buf, str); + *size -= len; + *buf += len; + } + else + *size = 0; +} + +static void +wg_put_str(const char *key, const char *value, byte **buf, uint *size) +{ + char tmp[128]; + + int len = snprintf(tmp, sizeof(tmp), "%s=%s\n", key, value); + if (0 < len && len < (int)*size) + { + strcpy(*buf, tmp); + *size -= len; + *buf += len; + } + else + *size = 0; +} + +static void +wg_put_u16(const char *key, u16 value, byte **buf, uint *size) +{ + char tmp[64]; + + int len = snprintf(tmp, sizeof(tmp), "%u", value); + if (len > 0) + wg_put_str(key, tmp, buf, size); + else + *size = 0; +} + +static void +wg_put_bool(const char *key, bool value, byte **buf, uint *size) +{ + if (value) + wg_put_str(key, "true", buf, size); +} + +static void +wg_put_key(const char *key, wg_key value, byte **buf, uint *size) +{ + char tmp[128]; + + for (uint i=0; i < sizeof(wg_key); i++) + bsnprintf(tmp+2*i, sizeof(tmp)-2*i, "%02x", value[i]); + + wg_put_str(key, tmp, buf, size); +} + +static void +wg_put_endpoint(const wg_endpoint *endpoint, byte **buf, uint *size) +{ + char tmp[INET6_ADDRSTRLEN + 16]; + ip_addr ip; + struct iface *ifa = NULL; + uint port = 0; + + if (sockaddr_read((sockaddr*)&endpoint->addr, endpoint->addr.sa_family, + &ip, &ifa, &port) == 0) + { + char *pos = NULL; + + if (ipa_is_ip4(ip)) + pos = ip4_ntop(ipa_to_ip4(ip), tmp); + else + { + tmp[0] = '['; + pos = ip6_ntop(ipa_to_ip6(ip), tmp + 1); + if (ifa) + pos += bsprintf(pos, "%%%u", ifa->index); + *pos++ = ']'; + } + + bsprintf(pos, ":%u", port); + wg_put_str("endpoint", tmp, buf, size); + } +} + +static void +wg_put_allowedip(wg_allowedip *allowedip, byte **buf, uint *size) +{ + char tmp[INET6_ADDRSTRLEN + 10]; + ip_addr ip = IP6_NONE; + + switch (allowedip->family) + { + case AF_INET: + ip = ipa_from_in4(allowedip->ip4); + break; + case AF_INET6: + ip = ipa_from_in6(allowedip->ip6); + break; + default: + return; + } + + int res = bsnprintf(tmp, sizeof(tmp), "%I/%u", ip, allowedip->cidr); + + if (res < 0) + { + *size = 0; + return; + } + + wg_put_str("allowed_ip", tmp, buf, size); +} + +static int +user_put_device(wg_device *dev, byte **buf, uint *size) +{ + wg_put_u16("set", 1, buf, size); + wg_put_key("private_key", dev->private_key, buf, size); + wg_put_u16("listen_port", dev->listen_port, buf, size); + wg_put_bool("replace_peers", dev->flags & WGDEVICE_REPLACE_PEERS, buf, size); + + wg_peer *peer = NULL; + wg_for_each_peer(dev, peer) + { + wg_put_key("public_key", peer->public_key, buf, size); + wg_put_endpoint(&peer->endpoint, buf, size); + wg_put_bool("replace_allowed_ips", peer->flags & WGPEER_REPLACE_ALLOWEDIPS, buf, size); + + wg_allowedip *allowedip = NULL; + wg_for_each_allowedip(peer, allowedip) + { + wg_put_allowedip(allowedip, buf, size); + } + } + wg_puts("\n", buf, size); + + if (*size > 0) + return 0; + else + return -1; +} + +int +wg_user_set_device(struct pool *pool, + const char *ifname, + struct wg_device *dev) +{ + char path[sizeof(struct sockaddr_un)]; + socklen_t pathlen = get_socket_path(ifname, path, sizeof(path)); + + struct birdsock *sock = sk_new(pool); + sock->rx_hook = user_rx_hook; + sock->tx_hook = user_tx_hook; + sock->err_hook = user_err_hook; + sock->fast_rx = 1; + + uint tbsize = 8192; + sk_set_tbsize(sock, tbsize); + sk_set_rbsize(sock, 16); + byte *pos = sock->tbuf; + uint size = tbsize; + int len = user_put_device(dev, &pos, &size); + + DBG(L_TRACE "WG: put %d %s", size, sock->tbuf); + + if (len < 0) + { + rfree(sock); + return -1; + } + + sock->data = (void*)(uintptr_t)size; + + int res = sk_connect_unix(sock, path, pathlen); + DBG(L_TRACE "WG: socket %d %d %s", res, sock->fd, path); + if (res < 0) + { + rfree(sock); + return -1; + } + + /* abort(); */ + return -1; +} |