// 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(); }