wg: Windows WireGuard client — Wintun + boringtun FFI
This commit is contained in:
parent
bf05b78282
commit
77b8d616eb
1 changed files with 317 additions and 0 deletions
317
src/wg/wgclient_win.cpp
Normal file
317
src/wg/wgclient_win.cpp
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// src/wg/wgclient_win.cpp — Windows WireGuard tunnel for Artemis.
|
||||
//
|
||||
// Requires: Administrator privileges (Wintun kernel driver).
|
||||
// Links against: boringtun.lib, iphlpapi.lib, ws2_32.lib, ntdll.lib, ole32.lib
|
||||
//
|
||||
// Packet format on Windows: raw IP (no AF-prefix unlike macOS utun).
|
||||
|
||||
#include "wgclient.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <netioapi.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
// boringtun C ABI (header lives in the same directory — symlinked or copied)
|
||||
#include "boringtun_ffi.h"
|
||||
|
||||
// Wintun typedefs & loader
|
||||
#include "wintun_artemis.h"
|
||||
|
||||
namespace wg {
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static std::string winErr(const char *ctx) {
|
||||
char buf[256] = {};
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, GetLastError(), 0, buf, sizeof(buf), nullptr);
|
||||
return std::string(ctx) + ": " + buf;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ── Wintun function pointer types ─────────────────────────────────────────────
|
||||
// (Mirror of DragonMoonlight's wintun.h — kept minimal to avoid header dep)
|
||||
|
||||
typedef PVOID WINTUN_ADAPTER_HANDLE;
|
||||
typedef PVOID WINTUN_SESSION_HANDLE;
|
||||
typedef DWORD WINTUN_CAPACITY;
|
||||
|
||||
typedef WINTUN_ADAPTER_HANDLE (WINAPI *PFN_WintunCreateAdapter)(LPCWSTR, LPCWSTR, const GUID *);
|
||||
typedef void (WINAPI *PFN_WintunDeleteAdapter)(WINTUN_ADAPTER_HANDLE);
|
||||
typedef void (WINAPI *PFN_WintunGetAdapterLUID)(WINTUN_ADAPTER_HANDLE, NET_LUID *);
|
||||
typedef WINTUN_SESSION_HANDLE (WINAPI *PFN_WintunStartSession)(WINTUN_ADAPTER_HANDLE, DWORD);
|
||||
typedef void (WINAPI *PFN_WintunEndSession)(WINTUN_SESSION_HANDLE);
|
||||
typedef BYTE *(WINAPI *PFN_WintunReceivePacket)(WINTUN_SESSION_HANDLE, DWORD *);
|
||||
typedef void (WINAPI *PFN_WintunReleaseReceivePacket)(WINTUN_SESSION_HANDLE, BYTE *);
|
||||
typedef BYTE *(WINAPI *PFN_WintunAllocateSendPacket)(WINTUN_SESSION_HANDLE, DWORD);
|
||||
typedef void (WINAPI *PFN_WintunSendPacket)(WINTUN_SESSION_HANDLE, BYTE *);
|
||||
typedef HANDLE (WINAPI *PFN_WintunGetReadWaitEvent)(WINTUN_SESSION_HANDLE);
|
||||
|
||||
struct WintunFns {
|
||||
HMODULE hmod = nullptr;
|
||||
PFN_WintunCreateAdapter CreateAdapter = nullptr;
|
||||
PFN_WintunDeleteAdapter DeleteAdapter = nullptr;
|
||||
PFN_WintunGetAdapterLUID GetAdapterLUID = nullptr;
|
||||
PFN_WintunStartSession StartSession = nullptr;
|
||||
PFN_WintunEndSession EndSession = nullptr;
|
||||
PFN_WintunReceivePacket ReceivePacket = nullptr;
|
||||
PFN_WintunReleaseReceivePacket ReleaseReceivePacket = nullptr;
|
||||
PFN_WintunAllocateSendPacket AllocateSendPacket = nullptr;
|
||||
PFN_WintunSendPacket SendPacket = nullptr;
|
||||
PFN_WintunGetReadWaitEvent GetReadWaitEvent = nullptr;
|
||||
|
||||
bool load() {
|
||||
hmod = LoadLibraryExW(L"wintun.dll", nullptr,
|
||||
LOAD_LIBRARY_SEARCH_APPLICATION_DIR |
|
||||
LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if (!hmod) return false;
|
||||
#define R(fn) fn = (PFN_##fn)GetProcAddress(hmod, #fn); if (!fn) { unload(); return false; }
|
||||
R(WintunCreateAdapter)
|
||||
R(WintunDeleteAdapter)
|
||||
R(WintunGetAdapterLUID)
|
||||
R(WintunStartSession)
|
||||
R(WintunEndSession)
|
||||
R(WintunReceivePacket)
|
||||
R(WintunReleaseReceivePacket)
|
||||
R(WintunAllocateSendPacket)
|
||||
R(WintunSendPacket)
|
||||
R(WintunGetReadWaitEvent)
|
||||
#undef R
|
||||
return true;
|
||||
}
|
||||
void unload() {
|
||||
if (hmod) { FreeLibrary(hmod); hmod = nullptr; }
|
||||
}
|
||||
};
|
||||
|
||||
// ── ClientImpl ────────────────────────────────────────────────────────────────
|
||||
|
||||
class ClientImpl {
|
||||
public:
|
||||
WintunFns wt;
|
||||
WINTUN_ADAPTER_HANDLE adapter = nullptr;
|
||||
WINTUN_SESSION_HANDLE session = nullptr;
|
||||
wireguard_tunnel *wg = nullptr;
|
||||
SOCKET udpSock = INVALID_SOCKET;
|
||||
HANDLE stopEvt = nullptr;
|
||||
NET_LUID luid {};
|
||||
std::atomic<bool> live {false};
|
||||
std::thread tTunToUdp, tUdpToTun, tTicker;
|
||||
Config cfg;
|
||||
std::string localIPStr;
|
||||
LogFn log;
|
||||
ErrorFn err;
|
||||
};
|
||||
|
||||
// ── I/O threads ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void tunToUdp(ClientImpl *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());
|
||||
|
||||
HANDLE re = p->wt.GetReadWaitEvent(p->session);
|
||||
HANDLE evs[2] = { re, p->stopEvt };
|
||||
std::vector<uint8_t> dst(65536);
|
||||
|
||||
while (p->live) {
|
||||
if (WaitForMultipleObjects(2, evs, FALSE, INFINITE) != WAIT_OBJECT_0) break;
|
||||
DWORD sz = 0;
|
||||
BYTE *pkt = p->wt.ReceivePacket(p->session, &sz);
|
||||
if (!pkt) continue;
|
||||
size_t dlen = dst.size();
|
||||
wireguard_result r = wireguard_write(p->wg, pkt, sz, dst.data(), &dlen);
|
||||
p->wt.ReleaseReceivePacket(p->session, pkt);
|
||||
if (r.op == WRITE_TO_NETWORK && dlen > 0)
|
||||
sendto(p->udpSock, (char *)dst.data(), (int)dlen, 0,
|
||||
(sockaddr *)&peer, sizeof(peer));
|
||||
}
|
||||
}
|
||||
|
||||
static void udpToTun(ClientImpl *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());
|
||||
|
||||
WSAEVENT se = WSACreateEvent();
|
||||
WSAEventSelect(p->udpSock, se, FD_READ);
|
||||
HANDLE evs[2] = { se, p->stopEvt };
|
||||
std::vector<uint8_t> enc(65536), plain(65536);
|
||||
|
||||
while (p->live) {
|
||||
if (WaitForMultipleObjects(2, evs, FALSE, INFINITE) != WAIT_OBJECT_0) break;
|
||||
WSAResetEvent(se);
|
||||
struct sockaddr_in from {}; int fl = sizeof(from);
|
||||
int n = recvfrom(p->udpSock, (char *)enc.data(), (int)enc.size(),
|
||||
0, (sockaddr *)&from, &fl);
|
||||
if (n <= 0) continue;
|
||||
size_t plen = plain.size();
|
||||
wireguard_result r = wireguard_read(p->wg, enc.data(), n, plain.data(), &plen);
|
||||
if ((r.op == WRITE_TO_TUNNEL_IPV4 || r.op == WRITE_TO_TUNNEL_IPV6) && plen > 0) {
|
||||
BYTE *out = p->wt.AllocateSendPacket(p->session, (DWORD)plen);
|
||||
if (out) { memcpy(out, plain.data(), plen); p->wt.SendPacket(p->session, out); }
|
||||
} else if (r.op == WRITE_TO_NETWORK && plen > 0) {
|
||||
sendto(p->udpSock, (char *)plain.data(), (int)plen, 0,
|
||||
(sockaddr *)&peer, sizeof(peer));
|
||||
}
|
||||
}
|
||||
WSACloseEvent(se);
|
||||
}
|
||||
|
||||
static void ticker(ClientImpl *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());
|
||||
std::vector<uint8_t> buf(512);
|
||||
while (p->live) {
|
||||
if (WaitForSingleObject(p->stopEvt, 100) != WAIT_TIMEOUT) break;
|
||||
size_t len = buf.size();
|
||||
wireguard_result r = wireguard_tick(p->wg, buf.data(), &len);
|
||||
if (r.op == WRITE_TO_NETWORK && len > 0)
|
||||
sendto(p->udpSock, (char *)buf.data(), (int)len, 0,
|
||||
(sockaddr *)&peer, sizeof(peer));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Client public API ─────────────────────────────────────────────────────────
|
||||
|
||||
Client::Client() = default;
|
||||
Client::~Client() { stop(); }
|
||||
|
||||
void Client::start(const Config &cfg) {
|
||||
if (m_impl && m_impl->live) return;
|
||||
|
||||
auto p = std::make_unique<ClientImpl>();
|
||||
p->cfg = cfg;
|
||||
p->log = m_log;
|
||||
p->err = m_error;
|
||||
|
||||
if (!p->wt.load())
|
||||
throw std::runtime_error("Failed to load wintun.dll");
|
||||
|
||||
p->wg = new_tunnel(
|
||||
cfg.privateKey().c_str(),
|
||||
cfg.peerPublicKey().c_str(),
|
||||
cfg.presharedKey().empty() ? nullptr : cfg.presharedKey().c_str(),
|
||||
cfg.persistentKeepalive(), 0);
|
||||
if (!p->wg) throw std::runtime_error("boringtun: tunnel creation failed");
|
||||
|
||||
GUID guid; CoCreateGuid(&guid);
|
||||
p->adapter = p->wt.CreateAdapter(L"Artemis", L"WireGuard", &guid);
|
||||
if (!p->adapter) { tunnel_free(p->wg); p->wt.unload();
|
||||
throw std::runtime_error(winErr("WintunCreateAdapter")); }
|
||||
p->wt.GetAdapterLUID(p->adapter, &p->luid);
|
||||
|
||||
// Configure IP address
|
||||
{
|
||||
IN_ADDR addr {}; UINT8 prefix = 24;
|
||||
parseCIDR(cfg.address(), &addr, &prefix);
|
||||
MIB_UNICASTIPADDRESS_ROW row {}; InitializeUnicastIpAddressEntry(&row);
|
||||
row.InterfaceLuid = p->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);
|
||||
}
|
||||
|
||||
// Configure routes (AllowedIPs)
|
||||
for (const auto &cidr : cfg.allowedIPs()) {
|
||||
IN_ADDR net {}; UINT8 prefix = 24;
|
||||
if (!parseCIDR(cidr, &net, &prefix)) continue;
|
||||
MIB_IPFORWARD_ROW2 row {}; InitializeIpForwardEntry(&row);
|
||||
row.InterfaceLuid = p->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;
|
||||
row.Metric = 1;
|
||||
row.Protocol = MIB_IPPROTO_NETMGMT;
|
||||
CreateIpForwardEntry2(&row);
|
||||
}
|
||||
|
||||
p->session = p->wt.StartSession(p->adapter, 0x400000);
|
||||
if (!p->session) {
|
||||
p->wt.DeleteAdapter(p->adapter); tunnel_free(p->wg); p->wt.unload();
|
||||
throw std::runtime_error(winErr("WintunStartSession"));
|
||||
}
|
||||
|
||||
WSADATA wsd {}; WSAStartup(MAKEWORD(2,2), &wsd);
|
||||
p->udpSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (p->udpSock == INVALID_SOCKET) {
|
||||
p->wt.EndSession(p->session); p->wt.DeleteAdapter(p->adapter);
|
||||
tunnel_free(p->wg); p->wt.unload();
|
||||
throw std::runtime_error("socket() failed");
|
||||
}
|
||||
sockaddr_in local {}; local.sin_family = AF_INET; local.sin_port = 0;
|
||||
bind(p->udpSock, (sockaddr *)&local, sizeof(local));
|
||||
|
||||
p->stopEvt = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
p->localIPStr = cfg.addressIP();
|
||||
|
||||
// Initial handshake
|
||||
{
|
||||
std::vector<uint8_t> hs(512); size_t len = hs.size();
|
||||
wireguard_force_handshake(p->wg, hs.data(), &len);
|
||||
if (len > 0) {
|
||||
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, (char *)hs.data(), (int)len, 0,
|
||||
(sockaddr *)&peer, sizeof(peer));
|
||||
}
|
||||
}
|
||||
|
||||
p->live = true;
|
||||
p->tTunToUdp = std::thread(tunToUdp, p.get());
|
||||
p->tUdpToTun = std::thread(udpToTun, p.get());
|
||||
p->tTicker = std::thread(ticker, p.get());
|
||||
|
||||
m_impl = std::move(p);
|
||||
}
|
||||
|
||||
void Client::stop() {
|
||||
if (!m_impl) return;
|
||||
auto *p = m_impl.get();
|
||||
p->live = false;
|
||||
SetEvent(p->stopEvt);
|
||||
if (p->tTunToUdp.joinable()) p->tTunToUdp.join();
|
||||
if (p->tUdpToTun.joinable()) p->tUdpToTun.join();
|
||||
if (p->tTicker.joinable()) p->tTicker.join();
|
||||
p->wt.EndSession(p->session);
|
||||
p->wt.DeleteAdapter(p->adapter);
|
||||
tunnel_free(p->wg);
|
||||
closesocket(p->udpSock);
|
||||
WSACleanup();
|
||||
CloseHandle(p->stopEvt);
|
||||
p->wt.unload();
|
||||
m_impl.reset();
|
||||
}
|
||||
|
||||
bool Client::running() const { return m_impl && m_impl->live; }
|
||||
std::string Client::localIP() const {
|
||||
return (m_impl && m_impl->live) ? m_impl->localIPStr : "";
|
||||
}
|
||||
|
||||
} // namespace wg
|
||||
|
||||
#endif // _WIN32
|
||||
Loading…
Reference in a new issue