#define LOCAL_DEBUG #include #include #include #include #include #include #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); log(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) { char buf[1024]=""; strncpy(buf, sk->rbuf, size); log(L_TRACE "WG: RX %p %d '%s'", sk, size, buf); rfree(sk); return 1; } static void user_tx_hook(struct birdsock *bs) { log(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); if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) wg_put_key("private_key", dev->private_key, buf, size); #if 0 /* Setting listen_port causes dead-lock in wireguard-go. */ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) wg_put_u16("listen_port", dev->listen_port, buf, size); #endif if (dev->flags & WGDEVICE_REPLACE_PEERS) wg_put_bool("replace_peers", true, 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); if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS) wg_put_bool("replace_allowed_ips", true, 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); log(L_TRACE "WG: put %d %d %s", len, size, sock->tbuf); if (len < 0) { rfree(sock); return -1; } sock->data = (void*)(uintptr_t)size; int res = sk_connect_unix(sock, path, pathlen); log(L_TRACE "WG: socket %s %d %d %d %s %d %s %d", res<0?strerror(errno):NULL, res, res<0?errno:0, sock->fd, ifname, path[0], path + 1, pathlen); DBG(L_TRACE "WG: socket %d %d %s", res, sock->fd, path); if (res < 0) { rfree(sock); return -1; } /* abort(); */ return -1; }