diff --git a/app/vpn/tunnelmanager_win.cpp b/app/vpn/tunnelmanager_win.cpp new file mode 100644 index 0000000..eb3416e --- /dev/null +++ b/app/vpn/tunnelmanager_win.cpp @@ -0,0 +1,370 @@ +// tunnelmanager_win.cpp — Windows WireGuard tunnel implementation. +// +// Uses Wintun (kernel TUN driver) for the virtual NIC and boringtun (via FFI) +// for the WireGuard crypto. Requires elevation: the process must run as +// Administrator (see DragonMoonlight.manifest) so that WintunCreateAdapter +// can install the kernel driver. +// +// Packet format on Windows: raw IP (no 4-byte AF header unlike macOS utun). +// +// I/O model +// --------- +// TUN → UDP thread: WintunReceivePacket → wireguard_write → sendto +// UDP → TUN thread: recvfrom → wireguard_read → WintunAllocSendPacket/Send +// Ticker thread: wireguard_tick every 100 ms (keepalives / handshake) +// +// Shutdown: SetEvent(stopEvent) unblocks WaitForMultipleObjects in all threads. + +#include "tunnelmanager.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "boringtun_ffi.h" +#include "wireguardconfig.h" +#include "wintun.h" + +// ── helpers ────────────────────────────────────────────────────────────────── + +static std::string lastWinError(const char *context) { + char buf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), 0, buf, sizeof(buf), nullptr); + return std::string(context) + ": " + buf; +} + +// Parse "x.x.x.x/prefix" into binary address + prefix length. +static bool parseCIDR(const std::string &cidr, IN_ADDR *addr, UINT8 *prefix) { + auto slash = cidr.find('/'); + std::string ip = (slash != std::string::npos) ? cidr.substr(0, slash) : cidr; + *prefix = (slash != std::string::npos) ? (UINT8)std::stoi(cidr.substr(slash + 1)) : 32; + return InetPtonA(AF_INET, ip.c_str(), addr) == 1; +} + +// ── private state ───────────────────────────────────────────────────────────── + +struct WinTunnelPriv { + WintunLib wintun; + WINTUN_ADAPTER_HANDLE adapter = nullptr; + WINTUN_SESSION_HANDLE session = nullptr; + wireguard_tunnel *wg = nullptr; + SOCKET udpSock = INVALID_SOCKET; + HANDLE stopEvent = nullptr; + + std::atomic running {false}; + std::thread thdTunToUdp; + std::thread thdUdpToTun; + std::thread thdTicker; + + WireGuardConfig cfg; + NET_LUID adapterLuid {}; +}; + +// ── TUN → UDP thread ────────────────────────────────────────────────────────── + +static void tunToUdpThread(WinTunnelPriv *p) { + // Resolve peer endpoint once + struct sockaddr_in peer {}; + peer.sin_family = AF_INET; + InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); + peer.sin_port = htons((u_short)p->cfg.endpointPort()); + + HANDLE readEvent = p->wintun.GetReadWaitEvent(p->session); + HANDLE events[2] = { readEvent, p->stopEvent }; + + static constexpr size_t BUF = 65536; + std::vector dst(BUF); + + while (p->running.load()) { + // Wait for a packet or stop signal + DWORD wait = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait != WAIT_OBJECT_0) // stopEvent or error + break; + + DWORD pktSize = 0; + BYTE *pkt = p->wintun.ReceivePacket(p->session, &pktSize); + if (!pkt) + continue; // spurious wakeup + + size_t dstLen = dst.size(); + wireguard_result r = wireguard_write(p->wg, pkt, pktSize, + dst.data(), &dstLen); + p->wintun.ReleaseReceivePacket(p->session, pkt); + + if (r.op == WRITE_TO_NETWORK && dstLen > 0) { + sendto(p->udpSock, + reinterpret_cast(dst.data()), (int)dstLen, + 0, reinterpret_cast(&peer), sizeof(peer)); + } + } +} + +// ── UDP → TUN thread ────────────────────────────────────────────────────────── + +static void udpToTunThread(WinTunnelPriv *p) { + static constexpr size_t BUF = 65536; + std::vector enc(BUF), plain(BUF); + + // Make the socket non-blocking via select() with stopEvent polling + // Strategy: use WSAEventSelect so we can WaitForMultipleObjects on the socket too. + WSAEVENT sockEvent = WSACreateEvent(); + WSAEventSelect(p->udpSock, sockEvent, FD_READ); + HANDLE events[2] = { sockEvent, p->stopEvent }; + + while (p->running.load()) { + DWORD wait = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait != WAIT_OBJECT_0) + break; + WSAResetEvent(sockEvent); + + struct sockaddr_in from {}; + int fromLen = sizeof(from); + int n = recvfrom(p->udpSock, + reinterpret_cast(enc.data()), (int)enc.size(), + 0, reinterpret_cast(&from), &fromLen); + if (n <= 0) + continue; + + size_t plainLen = plain.size(); + wireguard_result r = wireguard_read(p->wg, enc.data(), (size_t)n, + plain.data(), &plainLen); + + if ((r.op == WRITE_TO_TUNNEL_IPV4 || r.op == WRITE_TO_TUNNEL_IPV6) && plainLen > 0) { + BYTE *pkt = p->wintun.AllocateSendPacket(p->session, (DWORD)plainLen); + if (pkt) { + memcpy(pkt, plain.data(), plainLen); + p->wintun.SendPacket(p->session, pkt); + } + } else if (r.op == WRITE_TO_NETWORK) { + // Handshake response — send back to peer + struct sockaddr_in peer {}; + peer.sin_family = AF_INET; + InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); + peer.sin_port = htons((u_short)p->cfg.endpointPort()); + sendto(p->udpSock, + reinterpret_cast(plain.data()), (int)plainLen, + 0, reinterpret_cast(&peer), sizeof(peer)); + } + } + WSACloseEvent(sockEvent); +} + +// ── Ticker thread (keepalive / re-handshake) ────────────────────────────────── + +static void tickerThread(WinTunnelPriv *p) { + struct sockaddr_in peer {}; + peer.sin_family = AF_INET; + InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); + peer.sin_port = htons((u_short)p->cfg.endpointPort()); + + static constexpr size_t BUF = 512; + std::vector dst(BUF); + + while (p->running.load()) { + // Wait 100 ms or until stop + if (WaitForSingleObject(p->stopEvent, 100) != WAIT_TIMEOUT) + break; + + size_t dstLen = dst.size(); + wireguard_result r = wireguard_tick(p->wg, dst.data(), &dstLen); + if (r.op == WRITE_TO_NETWORK && dstLen > 0) { + sendto(p->udpSock, + reinterpret_cast(dst.data()), (int)dstLen, + 0, reinterpret_cast(&peer), sizeof(peer)); + } + } +} + +// ── Route setup helpers ─────────────────────────────────────────────────────── + +// Configure the Wintun adapter's unicast address via the IP Helper API. +static void setAdapterAddress(const NET_LUID &luid, const std::string &cidr) { + IN_ADDR addr {}; + UINT8 prefix = 32; + if (!parseCIDR(cidr, &addr, &prefix)) + return; + + MIB_UNICASTIPADDRESS_ROW row {}; + InitializeUnicastIpAddressEntry(&row); + row.InterfaceLuid = luid; + row.Address.si_family = AF_INET; + row.Address.Ipv4.sin_family = AF_INET; + row.Address.Ipv4.sin_addr = addr; + row.OnLinkPrefixLength = prefix; + row.DadState = IpDadStatePreferred; + + CreateUnicastIpAddressEntry(&row); // ignore error: may already exist +} + +// Add a host/network route through the Wintun adapter. +static void addRoute(const NET_LUID &luid, const std::string &cidr) { + IN_ADDR net {}; + UINT8 prefix = 0; + if (!parseCIDR(cidr, &net, &prefix)) + return; + + MIB_IPFORWARD_ROW2 row {}; + InitializeIpForwardEntry(&row); + row.InterfaceLuid = luid; + row.DestinationPrefix.Prefix.si_family = AF_INET; + row.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET; + row.DestinationPrefix.Prefix.Ipv4.sin_addr = net; + row.DestinationPrefix.PrefixLength = prefix; + row.NextHop.si_family = AF_INET; // on-link + row.Metric = 1; + row.Protocol = MIB_IPPROTO_NETMGMT; + + CreateIpForwardEntry2(&row); // ignore duplicate error +} + +// ── TunnelManager implementation ────────────────────────────────────────────── + +void TunnelManager::start(const WireGuardConfig &cfg) { + if (m_priv) return; // already running + + auto *p = new WinTunnelPriv(); + p->cfg = cfg; + + // ── 1. Load wintun.dll ── + if (!p->wintun.load()) { + delete p; + emit error("Failed to load wintun.dll — is it next to the executable?"); + return; + } + + // ── 2. Create WireGuard tunnel (boringtun) ── + p->wg = new_tunnel( + cfg.privateKey().c_str(), + cfg.peerPublicKey().c_str(), + cfg.presharedKey().empty() ? nullptr : cfg.presharedKey().c_str(), + cfg.persistentKeepalive(), + 1 // log level: errors only + ); + if (!p->wg) { + p->wintun.unload(); + delete p; + emit error("boringtun: failed to create WireGuard tunnel"); + return; + } + + // ── 3. Create Wintun adapter ── + GUID guid; + CoCreateGuid(&guid); + p->adapter = p->wintun.CreateAdapter(L"DragonMoonlight", L"WireGuard", &guid); + if (!p->adapter) { + tunnel_free(p->wg); + p->wintun.unload(); + delete p; + emit error(QString::fromStdString(lastWinError("WintunCreateAdapter"))); + return; + } + p->wintun.GetAdapterLUID(p->adapter, &p->adapterLuid); + + // ── 4. Configure IP address + routes ── + setAdapterAddress(p->adapterLuid, cfg.address()); + for (const auto &cidr : cfg.allowedIPs()) { + addRoute(p->adapterLuid, cidr); + } + + // ── 5. Start Wintun session ── + p->session = p->wintun.StartSession(p->adapter, 0x400000); // 4 MiB ring + if (!p->session) { + p->wintun.DeleteAdapter(p->adapter); + tunnel_free(p->wg); + p->wintun.unload(); + delete p; + emit error(QString::fromStdString(lastWinError("WintunStartSession"))); + return; + } + + // ── 6. Create UDP socket ── + WSADATA wsd {}; + WSAStartup(MAKEWORD(2, 2), &wsd); + p->udpSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (p->udpSock == INVALID_SOCKET) { + p->wintun.EndSession(p->session); + p->wintun.DeleteAdapter(p->adapter); + tunnel_free(p->wg); + p->wintun.unload(); + delete p; + emit error("Failed to create UDP socket"); + return; + } + // Bind to any local port + struct sockaddr_in local {}; + local.sin_family = AF_INET; + local.sin_addr.s_addr = INADDR_ANY; + local.sin_port = 0; + bind(p->udpSock, reinterpret_cast(&local), sizeof(local)); + + // ── 7. Stop event for I/O threads ── + p->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + // ── 8. Force initial handshake ── + { + static constexpr size_t BUF = 512; + std::vector hs(BUF); + size_t hsLen = hs.size(); + wireguard_force_handshake(p->wg, hs.data(), &hsLen); + if (hsLen > 0) { + struct sockaddr_in peer {}; + peer.sin_family = AF_INET; + InetPtonA(AF_INET, cfg.endpointHost().c_str(), &peer.sin_addr); + peer.sin_port = htons((u_short)cfg.endpointPort()); + sendto(p->udpSock, + reinterpret_cast(hs.data()), (int)hsLen, + 0, reinterpret_cast(&peer), sizeof(peer)); + } + } + + // ── 9. Start I/O threads ── + p->running = true; + p->thdTunToUdp = std::thread(tunToUdpThread, p); + p->thdUdpToTun = std::thread(udpToTunThread, p); + p->thdTicker = std::thread(tickerThread, p); + + m_priv = p; + emit tunnelUp(QString::fromStdString(cfg.address())); +} + +void TunnelManager::stop() { + if (!m_priv) return; + auto *p = static_cast(m_priv); + + // Signal all threads to stop + p->running = false; + SetEvent(p->stopEvent); + + // Wait for threads + if (p->thdTunToUdp.joinable()) p->thdTunToUdp.join(); + if (p->thdUdpToTun.joinable()) p->thdUdpToTun.join(); + if (p->thdTicker.joinable()) p->thdTicker.join(); + + // Teardown Wintun + p->wintun.EndSession(p->session); + p->wintun.DeleteAdapter(p->adapter); + + // Teardown boringtun + tunnel_free(p->wg); + + // Close socket & event + closesocket(p->udpSock); + WSACleanup(); + CloseHandle(p->stopEvent); + + p->wintun.unload(); + + delete p; + m_priv = nullptr; + emit tunnelDown(); +}