WireGuard rework for MacOS and Windows (#314)
WireGuard rework for MacOS and Windows
This commit is contained in:
parent
421a27ceae
commit
07c38e9b6c
60 changed files with 4779 additions and 434 deletions
345
client/platforms/windows/daemon/windowstunnelservice.cpp
Normal file
345
client/platforms/windows/daemon/windowstunnelservice.cpp
Normal file
|
@ -0,0 +1,345 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WindowsTunnelService.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowscommons.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "windowsdaemon.h"
|
||||
|
||||
#define TUNNEL_NAMED_PIPE \
|
||||
"\\\\." \
|
||||
"\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\AmneziaVPN"
|
||||
|
||||
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsTunnelService");
|
||||
} // namespace
|
||||
|
||||
static bool stopAndDeleteTunnelService(SC_HANDLE service);
|
||||
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
|
||||
|
||||
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(WindowsTunnelService);
|
||||
|
||||
m_scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (m_scm == nullptr) {
|
||||
WindowsUtils::windowsLog("Failed to open SCManager");
|
||||
}
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &WindowsTunnelService::timeout);
|
||||
}
|
||||
|
||||
WindowsTunnelService::~WindowsTunnelService() {
|
||||
MZ_COUNT_CTOR(WindowsTunnelService);
|
||||
stop();
|
||||
CloseServiceHandle((SC_HANDLE)m_scm);
|
||||
}
|
||||
|
||||
void WindowsTunnelService::stop() {
|
||||
SC_HANDLE service = (SC_HANDLE)m_service;
|
||||
if (service) {
|
||||
stopAndDeleteTunnelService(service);
|
||||
CloseServiceHandle(service);
|
||||
m_service = nullptr;
|
||||
}
|
||||
|
||||
m_timer.stop();
|
||||
|
||||
if (m_logworker) {
|
||||
m_logthread.quit();
|
||||
m_logthread.wait();
|
||||
delete m_logworker;
|
||||
m_logworker = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowsTunnelService::isRunning() {
|
||||
if (m_service == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus((SC_HANDLE)m_service, &status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return status.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
void WindowsTunnelService::timeout() {
|
||||
if (m_service == nullptr) {
|
||||
logger.error() << "The service doesn't exist";
|
||||
emit backendFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus((SC_HANDLE)m_service, &status)) {
|
||||
WindowsUtils::windowsLog("Failed to retrieve the service status");
|
||||
emit backendFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.dwCurrentState == SERVICE_RUNNING) {
|
||||
// The service is active
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug() << "The service is not active";
|
||||
emit backendFailure();
|
||||
}
|
||||
|
||||
bool WindowsTunnelService::start(const QString& configData) {
|
||||
logger.debug() << "Starting the tunnel service";
|
||||
|
||||
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
|
||||
m_logworker->moveToThread(&m_logthread);
|
||||
m_logthread.start();
|
||||
|
||||
SC_HANDLE scm = (SC_HANDLE)m_scm;
|
||||
SC_HANDLE service = nullptr;
|
||||
auto guard = qScopeGuard([&] {
|
||||
if (service) {
|
||||
CloseServiceHandle(service);
|
||||
}
|
||||
m_logthread.quit();
|
||||
m_logthread.wait();
|
||||
delete m_logworker;
|
||||
m_logworker = nullptr;
|
||||
});
|
||||
|
||||
// Let's see if we have to delete a previous instance.
|
||||
service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||
if (service) {
|
||||
logger.debug() << "An existing service has been detected. Let's close it.";
|
||||
if (!stopAndDeleteTunnelService(service)) {
|
||||
return false;
|
||||
}
|
||||
CloseServiceHandle(service);
|
||||
service = nullptr;
|
||||
}
|
||||
|
||||
QString serviceCmdline;
|
||||
{
|
||||
QTextStream out(&serviceCmdline);
|
||||
out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \""
|
||||
<< configData << "\"";
|
||||
}
|
||||
|
||||
logger.debug() << "Service:" << qApp->applicationFilePath();
|
||||
|
||||
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amezia VPN (tunnel)",
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
|
||||
TEXT("Nsi\0TcpIp\0"), nullptr, nullptr);
|
||||
if (!service) {
|
||||
WindowsUtils::windowsLog("Failed to create the tunnel service");
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_DESCRIPTION sd = {
|
||||
(wchar_t*)L"Manages the Amnezia VPN tunnel connection"};
|
||||
|
||||
if (!ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &sd)) {
|
||||
WindowsUtils::windowsLog(
|
||||
"Failed to set the description to the tunnel service");
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_SID_INFO ssi;
|
||||
ssi.dwServiceSidType = SERVICE_SID_TYPE_UNRESTRICTED;
|
||||
if (!ChangeServiceConfig2(service, SERVICE_CONFIG_SERVICE_SID_INFO, &ssi)) {
|
||||
WindowsUtils::windowsLog("Failed to set the SID to the tunnel service");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StartService(service, 0, nullptr)) {
|
||||
WindowsUtils::windowsLog("Failed to start the service");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (waitForServiceStatus(service, SERVICE_RUNNING)) {
|
||||
logger.debug() << "The tunnel service is up and running";
|
||||
guard.dismiss();
|
||||
m_service = service;
|
||||
m_timer.start(WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC);
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.error() << "Failed to run the tunnel service";
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus(service, &status)) {
|
||||
WindowsUtils::windowsLog("Failed to retrieve the service status");
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug() << "The tunnel service exited with status:"
|
||||
<< status.dwWin32ExitCode << "-" << exitCodeToFailure(&status);
|
||||
|
||||
emit backendFailure();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool stopAndDeleteTunnelService(SC_HANDLE service) {
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus(service, &status)) {
|
||||
WindowsUtils::windowsLog("Failed to retrieve the service status");
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug() << "The current service is stopped:"
|
||||
<< (status.dwCurrentState == SERVICE_STOPPED);
|
||||
|
||||
if (status.dwCurrentState != SERVICE_STOPPED) {
|
||||
logger.debug() << "The service is not stopped yet.";
|
||||
if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) {
|
||||
WindowsUtils::windowsLog("Failed to control the service");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waitForServiceStatus(service, SERVICE_STOPPED)) {
|
||||
logger.error() << "Unable to stop the service";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug() << "Proceeding with the deletion";
|
||||
|
||||
if (!DeleteService(service)) {
|
||||
WindowsUtils::windowsLog("Failed to delete the service");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString WindowsTunnelService::uapiCommand(const QString& command) {
|
||||
// Create a pipe to the tunnel service.
|
||||
LPTSTR tunnelName = (LPTSTR)TEXT(TUNNEL_NAMED_PIPE);
|
||||
HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||
OPEN_EXISTING, 0, nullptr);
|
||||
if (pipe == INVALID_HANDLE_VALUE) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
auto guard = qScopeGuard([&] { CloseHandle(pipe); });
|
||||
if (!WaitNamedPipe(tunnelName, 1000)) {
|
||||
WindowsUtils::windowsLog("Failed to wait for named pipes");
|
||||
return QString();
|
||||
}
|
||||
|
||||
DWORD mode = PIPE_READMODE_BYTE;
|
||||
if (!SetNamedPipeHandleState(pipe, &mode, nullptr, nullptr)) {
|
||||
WindowsUtils::windowsLog("Failed to set the read-mode on pipe");
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Write the UAPI command into the pipe.
|
||||
QByteArray message = command.toLocal8Bit();
|
||||
DWORD written;
|
||||
while (!message.endsWith("\n\n")) {
|
||||
message.append('\n');
|
||||
}
|
||||
if (!WriteFile(pipe, message.constData(), message.length(), &written,
|
||||
nullptr)) {
|
||||
WindowsUtils::windowsLog("Failed to write into the pipe");
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Receive the response from the pipe.
|
||||
QByteArray reply;
|
||||
while (!reply.contains("\n\n")) {
|
||||
char buffer[512];
|
||||
DWORD read = 0;
|
||||
if (!ReadFile(pipe, buffer, sizeof(buffer), &read, nullptr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
reply.append(buffer, read);
|
||||
}
|
||||
|
||||
return QString::fromUtf8(reply).trimmed();
|
||||
}
|
||||
|
||||
// static
|
||||
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus) {
|
||||
int tries = 0;
|
||||
while (tries < 30) {
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus(service, &status)) {
|
||||
WindowsUtils::windowsLog("Failed to retrieve the service status");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.dwCurrentState == expectedStatus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.warning() << "The service is not in the right status yet.";
|
||||
|
||||
Sleep(1000);
|
||||
++tries;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
QString WindowsTunnelService::exitCodeToFailure(const void* status) {
|
||||
const SERVICE_STATUS* st = static_cast<const SERVICE_STATUS*>(status);
|
||||
if (st->dwWin32ExitCode != ERROR_SERVICE_SPECIFIC_ERROR) {
|
||||
return WindowsUtils::getErrorMessage(st->dwWin32ExitCode);
|
||||
}
|
||||
|
||||
// The order of this error code is taken from wireguard.
|
||||
switch (st->dwServiceSpecificExitCode) {
|
||||
case 0:
|
||||
return "No error";
|
||||
case 1:
|
||||
return "Error when opening the ringlogger log file";
|
||||
case 2:
|
||||
return "Error while loading the WireGuard configuration file from "
|
||||
"path.";
|
||||
case 3:
|
||||
return "Error while creating a WinTun device.";
|
||||
case 4:
|
||||
return "Error while listening on a named pipe.";
|
||||
case 5:
|
||||
return "Error while resolving DNS hostname endpoints.";
|
||||
case 6:
|
||||
return "Error while manipulating firewall rules.";
|
||||
case 7:
|
||||
return "Error while setting the device configuration.";
|
||||
case 8:
|
||||
return "Error while binding sockets to default routes.";
|
||||
case 9:
|
||||
return "Unable to set interface addresses, routes, dns, and/or "
|
||||
"interface settings.";
|
||||
case 10:
|
||||
return "Error while determining current executable path.";
|
||||
case 11:
|
||||
return "Error while opening the NUL file.";
|
||||
case 12:
|
||||
return "Error while attempting to track tunnels.";
|
||||
case 13:
|
||||
return "Error while attempting to enumerate current sessions.";
|
||||
case 14:
|
||||
return "Error while dropping privileges.";
|
||||
case 15:
|
||||
return "Windows internal error.";
|
||||
default:
|
||||
return QString("Unknown error (%1)").arg(st->dwServiceSpecificExitCode);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue