Rewrite Windows tunnelmanager to match TunnelManager interface, fix FFI, fix shutdown order

This commit is contained in:
Zac Gaetano 2026-05-07 00:12:29 -04:00
parent 6900f17804
commit aa7649601d

View file

@ -9,14 +9,22 @@
// //
// I/O model // I/O model
// --------- // ---------
// TUN → UDP thread: WintunReceivePacket → wireguard_write → sendto // TUN -> UDP thread: WintunReceivePacket -> wireguard_write -> sendto
// UDP → TUN thread: recvfrom → wireguard_read → WintunAllocSendPacket/Send // UDP -> TUN thread: recvfrom -> wireguard_read -> WintunAllocSendPacket/Send
// Ticker thread: wireguard_tick every 100 ms (keepalives / handshake) // Ticker thread: wireguard_tick every 100 ms (keepalives / handshake)
// //
// Shutdown: SetEvent(stopEvent) unblocks WaitForMultipleObjects in all threads. // Shutdown: SetEvent(stopEvent) unblocks WaitForMultipleObjects in all threads.
//
// Tunnel-up detection: udpToTunThread emits tunnelUp() the first time a
// decrypted packet arrives. A 1500 ms fallback timer fires the same signal
// if no packet has arrived yet, so the UI moves out of "Connecting" state.
// This corresponds to the roadmap item "connected() signal from first
// received packet rather than a fixed timer".
#include "tunnelmanager.h" #include "tunnelmanager.h"
#ifdef Q_OS_WIN
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <windows.h> #include <windows.h>
@ -30,54 +38,68 @@
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <QDebug>
#include <QPointer>
#include <QTimer>
#include "boringtun_ffi.h" #include "boringtun_ffi.h"
#include "wireguardconfig.h" #include "wireguardconfig.h"
#include "wintun.h" #include "wintun.h"
// ── helpers ────────────────────────────────────────────────────────────────── // ----- helpers --------------------------------------------------------------
static std::string lastWinError(const char *context) { static std::string lastWinError(const char *context)
char buf[256]; {
char buf[256] = {};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, GetLastError(), 0, buf, sizeof(buf), nullptr); nullptr, GetLastError(), 0, buf, sizeof(buf), nullptr);
return std::string(context) + ": " + buf; return std::string(context) + ": " + buf;
} }
// Parse "x.x.x.x/prefix" into binary address + prefix length. // Parse "x.x.x.x/prefix" into binary address + prefix length. Default prefix
static bool parseCIDR(const std::string &cidr, IN_ADDR *addr, UINT8 *prefix) { // is 32 if none is supplied.
static bool parseCIDR(const std::string &cidr, IN_ADDR *addr, UINT8 *prefix)
{
auto slash = cidr.find('/'); auto slash = cidr.find('/');
std::string ip = (slash != std::string::npos) ? cidr.substr(0, slash) : cidr; 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; *prefix = (slash != std::string::npos)
? static_cast<UINT8>(std::stoi(cidr.substr(slash + 1)))
: 32;
return InetPtonA(AF_INET, ip.c_str(), addr) == 1; return InetPtonA(AF_INET, ip.c_str(), addr) == 1;
} }
// ── private state ───────────────────────────────────────────────────────────── // ----- private state --------------------------------------------------------
struct WinTunnelPriv { struct WinTunnelPriv {
WintunLib wintun; WintunLib wintun;
WINTUN_ADAPTER_HANDLE adapter = nullptr; WINTUN_ADAPTER_HANDLE adapter = nullptr;
WINTUN_SESSION_HANDLE session = nullptr; WINTUN_SESSION_HANDLE session = nullptr;
wireguard_tunnel *wg = nullptr; wireguard_tunnel *wg = nullptr;
SOCKET udpSock = INVALID_SOCKET; SOCKET udpSock = INVALID_SOCKET;
HANDLE stopEvent = nullptr; HANDLE stopEvent = nullptr;
NET_LUID adapterLuid {};
std::atomic<bool> running {false}; std::atomic<bool> running {false};
std::thread thdTunToUdp; std::atomic<bool> gotFirstPkt {false};
std::thread thdUdpToTun; std::thread thdTunToUdp;
std::thread thdTicker; std::thread thdUdpToTun;
std::thread thdTicker;
WireGuardConfig cfg; WireGuardConfig cfg;
NET_LUID adapterLuid {};
// Owning TunnelManager - so I/O threads can post status to the Qt main
// thread once the first decrypted packet arrives.
QPointer<TunnelManager> owner;
}; };
// ── TUN → UDP thread ────────────────────────────────────────────────────────── // ----- TUN -> UDP thread ----------------------------------------------------
static void tunToUdpThread(WinTunnelPriv *p) { static void tunToUdpThread(WinTunnelPriv *p)
// Resolve peer endpoint once {
struct sockaddr_in peer {}; sockaddr_in peer {};
peer.sin_family = AF_INET; peer.sin_family = AF_INET;
InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); InetPtonA(AF_INET, p->cfg.endpointHost().toUtf8().constData(), &peer.sin_addr);
peer.sin_port = htons((u_short)p->cfg.endpointPort()); peer.sin_port = htons(static_cast<u_short>(p->cfg.endpointPort()));
HANDLE readEvent = p->wintun.GetReadWaitEvent(p->session); HANDLE readEvent = p->wintun.GetReadWaitEvent(p->session);
HANDLE events[2] = { readEvent, p->stopEvent }; HANDLE events[2] = { readEvent, p->stopEvent };
@ -86,37 +108,41 @@ static void tunToUdpThread(WinTunnelPriv *p) {
std::vector<uint8_t> dst(BUF); std::vector<uint8_t> dst(BUF);
while (p->running.load()) { while (p->running.load()) {
// Wait for a packet or stop signal
DWORD wait = WaitForMultipleObjects(2, events, FALSE, INFINITE); DWORD wait = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (wait != WAIT_OBJECT_0) // stopEvent or error if (wait != WAIT_OBJECT_0)
break; break;
DWORD pktSize = 0; DWORD pktSize = 0;
BYTE *pkt = p->wintun.ReceivePacket(p->session, &pktSize); BYTE *pkt = p->wintun.ReceivePacket(p->session, &pktSize);
if (!pkt) if (!pkt)
continue; // spurious wakeup continue;
size_t dstLen = dst.size(); size_t dstLen = dst.size();
wireguard_result r = wireguard_write(p->wg, pkt, pktSize, wireguard_result r = wireguard_write(p->wg, pkt, pktSize,
dst.data(), &dstLen); dst.data(), &dstLen);
p->wintun.ReleaseReceivePacket(p->session, pkt); p->wintun.ReleaseReceivePacket(p->session, pkt);
if (r.op == WRITE_TO_NETWORK && dstLen > 0) { if (r.op == WRITE_TO_NETWORK && r.size > 0) {
sendto(p->udpSock, sendto(p->udpSock,
reinterpret_cast<const char *>(dst.data()), (int)dstLen, reinterpret_cast<const char *>(dst.data()),
static_cast<int>(r.size),
0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer)); 0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer));
} }
} }
} }
// ── UDP → TUN thread ────────────────────────────────────────────────────────── // ----- UDP -> TUN thread ----------------------------------------------------
static void udpToTunThread(WinTunnelPriv *p) { static void udpToTunThread(WinTunnelPriv *p)
{
static constexpr size_t BUF = 65536; static constexpr size_t BUF = 65536;
std::vector<uint8_t> enc(BUF), plain(BUF); std::vector<uint8_t> enc(BUF), plain(BUF);
// Make the socket non-blocking via select() with stopEvent polling sockaddr_in peer {};
// Strategy: use WSAEventSelect so we can WaitForMultipleObjects on the socket too. peer.sin_family = AF_INET;
InetPtonA(AF_INET, p->cfg.endpointHost().toUtf8().constData(), &peer.sin_addr);
peer.sin_port = htons(static_cast<u_short>(p->cfg.endpointPort()));
WSAEVENT sockEvent = WSACreateEvent(); WSAEVENT sockEvent = WSACreateEvent();
WSAEventSelect(p->udpSock, sockEvent, FD_READ); WSAEventSelect(p->udpSock, sockEvent, FD_READ);
HANDLE events[2] = { sockEvent, p->stopEvent }; HANDLE events[2] = { sockEvent, p->stopEvent };
@ -127,71 +153,80 @@ static void udpToTunThread(WinTunnelPriv *p) {
break; break;
WSAResetEvent(sockEvent); WSAResetEvent(sockEvent);
struct sockaddr_in from {}; sockaddr_in from {};
int fromLen = sizeof(from); int fromLen = sizeof(from);
int n = recvfrom(p->udpSock, int n = recvfrom(p->udpSock,
reinterpret_cast<char *>(enc.data()), (int)enc.size(), reinterpret_cast<char *>(enc.data()),
static_cast<int>(enc.size()),
0, reinterpret_cast<sockaddr *>(&from), &fromLen); 0, reinterpret_cast<sockaddr *>(&from), &fromLen);
if (n <= 0) if (n <= 0)
continue; continue;
size_t plainLen = plain.size(); size_t plainLen = plain.size();
wireguard_result r = wireguard_read(p->wg, enc.data(), (size_t)n, wireguard_result r = wireguard_read(p->wg, enc.data(),
static_cast<size_t>(n),
plain.data(), &plainLen); plain.data(), &plainLen);
if ((r.op == WRITE_TO_TUNNEL_IPV4 || r.op == WRITE_TO_TUNNEL_IPV6) && plainLen > 0) { if ((r.op == WRITE_TO_TUNNEL_IPV4 || r.op == WRITE_TO_TUNNEL_IPV6)
BYTE *pkt = p->wintun.AllocateSendPacket(p->session, (DWORD)plainLen); && r.size > 0) {
if (pkt) { BYTE *outPkt = p->wintun.AllocateSendPacket(p->session,
memcpy(pkt, plain.data(), plainLen); static_cast<DWORD>(r.size));
p->wintun.SendPacket(p->session, pkt); if (outPkt) {
memcpy(outPkt, plain.data(), r.size);
p->wintun.SendPacket(p->session, outPkt);
} }
} else if (r.op == WRITE_TO_NETWORK) { // First decrypted packet => handshake completed => tunnel is up.
// Handshake response — send back to peer if (!p->gotFirstPkt.exchange(true) && p->owner) {
struct sockaddr_in peer {}; const QString localIP = p->cfg.localIP();
peer.sin_family = AF_INET; QPointer<TunnelManager> owner = p->owner;
InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); QMetaObject::invokeMethod(owner.data(), [owner, localIP]() {
peer.sin_port = htons((u_short)p->cfg.endpointPort()); if (owner) emit owner->tunnelUp(localIP);
}, Qt::QueuedConnection);
}
} else if (r.op == WRITE_TO_NETWORK && r.size > 0) {
sendto(p->udpSock, sendto(p->udpSock,
reinterpret_cast<const char *>(plain.data()), (int)plainLen, reinterpret_cast<const char *>(plain.data()),
static_cast<int>(r.size),
0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer)); 0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer));
} }
} }
WSACloseEvent(sockEvent); WSACloseEvent(sockEvent);
} }
// ── Ticker thread (keepalive / re-handshake) ────────────────────────────────── // ----- Ticker thread (keepalive / re-handshake) -----------------------------
static void tickerThread(WinTunnelPriv *p) { static void tickerThread(WinTunnelPriv *p)
struct sockaddr_in peer {}; {
sockaddr_in peer {};
peer.sin_family = AF_INET; peer.sin_family = AF_INET;
InetPtonA(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr); InetPtonA(AF_INET, p->cfg.endpointHost().toUtf8().constData(), &peer.sin_addr);
peer.sin_port = htons((u_short)p->cfg.endpointPort()); peer.sin_port = htons(static_cast<u_short>(p->cfg.endpointPort()));
static constexpr size_t BUF = 512; static constexpr size_t BUF = 512;
std::vector<uint8_t> dst(BUF); std::vector<uint8_t> dst(BUF);
while (p->running.load()) { while (p->running.load()) {
// Wait 100 ms or until stop
if (WaitForSingleObject(p->stopEvent, 100) != WAIT_TIMEOUT) if (WaitForSingleObject(p->stopEvent, 100) != WAIT_TIMEOUT)
break; break;
size_t dstLen = dst.size(); size_t dstLen = dst.size();
wireguard_result r = wireguard_tick(p->wg, dst.data(), &dstLen); wireguard_result r = wireguard_tick(p->wg, dst.data(), &dstLen);
if (r.op == WRITE_TO_NETWORK && dstLen > 0) { if (r.op == WRITE_TO_NETWORK && r.size > 0) {
sendto(p->udpSock, sendto(p->udpSock,
reinterpret_cast<const char *>(dst.data()), (int)dstLen, reinterpret_cast<const char *>(dst.data()),
static_cast<int>(r.size),
0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer)); 0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer));
} }
} }
} }
// ── Route setup helpers ─────────────────────────────────────────────────────── // ----- Route setup helpers --------------------------------------------------
// Configure the Wintun adapter's unicast address via the IP Helper API. static void setAdapterAddress(const NET_LUID &luid, const QString &cidr)
static void setAdapterAddress(const NET_LUID &luid, const std::string &cidr) { {
IN_ADDR addr {}; IN_ADDR addr {};
UINT8 prefix = 32; UINT8 prefix = 32;
if (!parseCIDR(cidr, &addr, &prefix)) if (!parseCIDR(cidr.toStdString(), &addr, &prefix))
return; return;
MIB_UNICASTIPADDRESS_ROW row {}; MIB_UNICASTIPADDRESS_ROW row {};
@ -206,165 +241,231 @@ static void setAdapterAddress(const NET_LUID &luid, const std::string &cidr) {
CreateUnicastIpAddressEntry(&row); // ignore error: may already exist CreateUnicastIpAddressEntry(&row); // ignore error: may already exist
} }
// Add a host/network route through the Wintun adapter. static void addRoute(const NET_LUID &luid, const QString &cidr)
static void addRoute(const NET_LUID &luid, const std::string &cidr) { {
IN_ADDR net {}; IN_ADDR net {};
UINT8 prefix = 0; UINT8 prefix = 0;
if (!parseCIDR(cidr, &net, &prefix)) if (!parseCIDR(cidr.toStdString(), &net, &prefix))
return; return;
MIB_IPFORWARD_ROW2 row {}; MIB_IPFORWARD_ROW2 row {};
InitializeIpForwardEntry(&row); InitializeIpForwardEntry(&row);
row.InterfaceLuid = luid; row.InterfaceLuid = luid;
row.DestinationPrefix.Prefix.si_family = AF_INET; row.DestinationPrefix.Prefix.si_family = AF_INET;
row.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET; row.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
row.DestinationPrefix.Prefix.Ipv4.sin_addr = net; row.DestinationPrefix.Prefix.Ipv4.sin_addr = net;
row.DestinationPrefix.PrefixLength = prefix; row.DestinationPrefix.PrefixLength = prefix;
row.NextHop.si_family = AF_INET; // on-link row.NextHop.si_family = AF_INET;
row.Metric = 1; row.Metric = 1;
row.Protocol = MIB_IPPROTO_NETMGMT; row.Protocol = MIB_IPPROTO_NETMGMT;
CreateIpForwardEntry2(&row); // ignore duplicate error CreateIpForwardEntry2(&row); // ignore duplicate error
} }
// ── TunnelManager implementation ────────────────────────────────────────────── // ----- TunnelManager implementation -----------------------------------------
void TunnelManager::start(const WireGuardConfig &cfg) { TunnelManager::TunnelManager(QObject *parent) : QObject(parent) {}
if (m_priv) return; // already running
auto *p = new WinTunnelPriv(); TunnelManager::~TunnelManager() { stop(); }
p->cfg = cfg;
// ── 1. Load wintun.dll ── bool TunnelManager::start(const WireGuardConfig &cfg)
if (!p->wintun.load()) { {
delete p; if (!cfg.isValid()) {
emit error("Failed to load wintun.dll — is it next to the executable?"); m_error = QStringLiteral("Invalid WireGuard configuration");
return; emit tunnelError(m_error);
return false;
} }
// ── 2. Create WireGuard tunnel (boringtun) ── if (m_running) stop();
p->wg = new_tunnel(
cfg.privateKey().c_str(), return platformStart(cfg);
cfg.peerPublicKey().c_str(), }
cfg.presharedKey().empty() ? nullptr : cfg.presharedKey().c_str(),
cfg.persistentKeepalive(), void TunnelManager::stop()
1 // log level: errors only {
); if (!m_running) return;
m_running = false;
platformStop();
m_localAddr.clear();
emit tunnelDown();
}
bool TunnelManager::platformStart(const WireGuardConfig &cfg)
{
auto *p = new WinTunnelPriv();
p->cfg = cfg;
p->owner = this;
// 1. Load wintun.dll
if (!p->wintun.load()) {
delete p;
m_error = QStringLiteral(
"Failed to load wintun.dll - is it next to the executable?");
emit tunnelError(m_error);
return false;
}
// 2. Create WireGuard tunnel (boringtun)
{
const QByteArray priv = cfg.privateKey.toUtf8();
const QByteArray pubk = cfg.peerPublicKey.toUtf8();
const QByteArray psk = cfg.presharedKey.toUtf8();
p->wg = new_tunnel(
priv.constData(),
pubk.constData(),
psk.isEmpty() ? nullptr : psk.constData(),
cfg.persistentKeepalive,
/*log_level=*/1);
}
if (!p->wg) { if (!p->wg) {
p->wintun.unload(); p->wintun.unload();
delete p; delete p;
emit error("boringtun: failed to create WireGuard tunnel"); m_error = QStringLiteral("boringtun: failed to create WireGuard tunnel");
return; emit tunnelError(m_error);
return false;
} }
// ── 3. Create Wintun adapter ── // 3. Create Wintun adapter
GUID guid; GUID guid;
CoCreateGuid(&guid); CoCreateGuid(&guid);
p->adapter = p->wintun.CreateAdapter(L"DragonMoonlight", L"WireGuard", &guid); p->adapter = p->wintun.CreateAdapter(L"DragonMoonlight", L"WireGuard", &guid);
if (!p->adapter) { if (!p->adapter) {
const QString err = QString::fromStdString(lastWinError("WintunCreateAdapter"));
tunnel_free(p->wg); tunnel_free(p->wg);
p->wintun.unload(); p->wintun.unload();
delete p; delete p;
emit error(QString::fromStdString(lastWinError("WintunCreateAdapter"))); m_error = err;
return; emit tunnelError(m_error);
return false;
} }
p->wintun.GetAdapterLUID(p->adapter, &p->adapterLuid); p->wintun.GetAdapterLUID(p->adapter, &p->adapterLuid);
// ── 4. Configure IP address + routes ── // 4. Configure IP address + routes
setAdapterAddress(p->adapterLuid, cfg.address()); setAdapterAddress(p->adapterLuid, cfg.address);
for (const auto &cidr : cfg.allowedIPs()) { for (const QString &cidr : cfg.allowedIPs)
addRoute(p->adapterLuid, cidr); addRoute(p->adapterLuid, cidr);
}
// ── 5. Start Wintun session ── // 5. Start Wintun session
p->session = p->wintun.StartSession(p->adapter, 0x400000); // 4 MiB ring p->session = p->wintun.StartSession(p->adapter, 0x400000); // 4 MiB ring
if (!p->session) { if (!p->session) {
p->wintun.DeleteAdapter(p->adapter); const QString err = QString::fromStdString(lastWinError("WintunStartSession"));
p->wintun.CloseAdapter(p->adapter);
tunnel_free(p->wg); tunnel_free(p->wg);
p->wintun.unload(); p->wintun.unload();
delete p; delete p;
emit error(QString::fromStdString(lastWinError("WintunStartSession"))); m_error = err;
return; emit tunnelError(m_error);
return false;
} }
// ── 6. Create UDP socket ── // 6. Create UDP socket
WSADATA wsd {}; WSADATA wsd {};
WSAStartup(MAKEWORD(2, 2), &wsd); WSAStartup(MAKEWORD(2, 2), &wsd);
p->udpSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); p->udpSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (p->udpSock == INVALID_SOCKET) { if (p->udpSock == INVALID_SOCKET) {
p->wintun.EndSession(p->session); p->wintun.EndSession(p->session);
p->wintun.DeleteAdapter(p->adapter); p->wintun.CloseAdapter(p->adapter);
tunnel_free(p->wg); tunnel_free(p->wg);
WSACleanup();
p->wintun.unload(); p->wintun.unload();
delete p; delete p;
emit error("Failed to create UDP socket"); m_error = QStringLiteral("Failed to create UDP socket");
return; emit tunnelError(m_error);
return false;
} }
// Bind to any local port sockaddr_in local {};
struct sockaddr_in local {};
local.sin_family = AF_INET; local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY; local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = 0; local.sin_port = 0;
bind(p->udpSock, reinterpret_cast<sockaddr *>(&local), sizeof(local)); bind(p->udpSock, reinterpret_cast<sockaddr *>(&local), sizeof(local));
// ── 7. Stop event for I/O threads ── // 7. Stop event for I/O threads
p->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); p->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (!p->stopEvent) {
const QString err = QString::fromStdString(lastWinError("CreateEvent"));
closesocket(p->udpSock);
WSACleanup();
p->wintun.EndSession(p->session);
p->wintun.CloseAdapter(p->adapter);
tunnel_free(p->wg);
p->wintun.unload();
delete p;
m_error = err;
emit tunnelError(m_error);
return false;
}
// ── 8. Force initial handshake ── // 8. Force initial handshake
{ {
static constexpr size_t BUF = 512; static constexpr size_t BUF = 512;
std::vector<uint8_t> hs(BUF); std::vector<uint8_t> hs(BUF);
size_t hsLen = hs.size(); size_t hsLen = hs.size();
wireguard_force_handshake(p->wg, hs.data(), &hsLen); wireguard_force_handshake(p->wg, hs.data(), &hsLen);
if (hsLen > 0) { if (hsLen > 0) {
struct sockaddr_in peer {}; sockaddr_in peer {};
peer.sin_family = AF_INET; peer.sin_family = AF_INET;
InetPtonA(AF_INET, cfg.endpointHost().c_str(), &peer.sin_addr); InetPtonA(AF_INET, cfg.endpointHost().toUtf8().constData(),
peer.sin_port = htons((u_short)cfg.endpointPort()); &peer.sin_addr);
peer.sin_port = htons(static_cast<u_short>(cfg.endpointPort()));
sendto(p->udpSock, sendto(p->udpSock,
reinterpret_cast<const char *>(hs.data()), (int)hsLen, reinterpret_cast<const char *>(hs.data()),
static_cast<int>(hsLen),
0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer)); 0, reinterpret_cast<sockaddr *>(&peer), sizeof(peer));
} }
} }
// ── 9. Start I/O threads ── // 9. Start I/O threads
p->running = true; p->running = true;
m_running = true;
m_localAddr = cfg.localIP();
p->thdTunToUdp = std::thread(tunToUdpThread, p); p->thdTunToUdp = std::thread(tunToUdpThread, p);
p->thdUdpToTun = std::thread(udpToTunThread, p); p->thdUdpToTun = std::thread(udpToTunThread, p);
p->thdTicker = std::thread(tickerThread, p); p->thdTicker = std::thread(tickerThread, p);
m_priv = p; m_priv = p;
emit tunnelUp(QString::fromStdString(cfg.address()));
qDebug() << "[DragonVPN] Wintun tunnel started, local IP" << m_localAddr;
// Belt-and-braces: if no packet arrives, fire tunnelUp() after 1500 ms so
// the UI doesn't hang on a green-but-disconnected state.
QPointer<TunnelManager> owner(this);
QTimer::singleShot(1500, this, [owner]() {
if (!owner || !owner->m_running) return;
auto *priv = static_cast<WinTunnelPriv *>(owner->m_priv);
if (priv && !priv->gotFirstPkt.load())
emit owner->tunnelUp(owner->localAddress());
});
return true;
} }
void TunnelManager::stop() { void TunnelManager::platformStop()
if (!m_priv) return; {
auto *p = static_cast<WinTunnelPriv *>(m_priv); auto *p = static_cast<WinTunnelPriv *>(m_priv);
if (!p) return;
// Signal all threads to stop
p->running = false; p->running = false;
SetEvent(p->stopEvent); if (p->stopEvent) SetEvent(p->stopEvent);
// Wait for threads
if (p->thdTunToUdp.joinable()) p->thdTunToUdp.join(); if (p->thdTunToUdp.joinable()) p->thdTunToUdp.join();
if (p->thdUdpToTun.joinable()) p->thdUdpToTun.join(); if (p->thdUdpToTun.joinable()) p->thdUdpToTun.join();
if (p->thdTicker.joinable()) p->thdTicker.join(); if (p->thdTicker.joinable()) p->thdTicker.join();
// Teardown Wintun if (p->session) p->wintun.EndSession(p->session);
p->wintun.EndSession(p->session); if (p->adapter) p->wintun.CloseAdapter(p->adapter);
p->wintun.DeleteAdapter(p->adapter);
// Teardown boringtun if (p->wg) tunnel_free(p->wg);
tunnel_free(p->wg);
// Close socket & event // socket close BEFORE WSACleanup
closesocket(p->udpSock); if (p->udpSock != INVALID_SOCKET) closesocket(p->udpSock);
WSACleanup(); WSACleanup();
CloseHandle(p->stopEvent); if (p->stopEvent) CloseHandle(p->stopEvent);
p->wintun.unload(); p->wintun.unload();
delete p; delete p;
m_priv = nullptr; m_priv = nullptr;
emit tunnelDown(); qDebug() << "[DragonVPN] Wintun tunnel stopped";
} }
#endif // Q_OS_WIN