|
|
@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 3.1.0.0
|
project(${PROJECT} VERSION 3.1.0.1
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
set(RELEASE_DATE "2023-08-28")
|
set(RELEASE_DATE "2023-09-21")
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 75ab7e2418b83af7f8ed0a448ec5081b37b54442
|
Subproject commit e8795854a5cf27004fe78caecc90a961688d1d41
|
||||||
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QMetaEnum>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
|
|
@ -64,9 +65,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
// method calls switchServer().
|
// method calls switchServer().
|
||||||
//
|
//
|
||||||
// At the end, if the activation succeds, the `connected` signal is emitted.
|
// At the end, if the activation succeds, the `connected` signal is emitted.
|
||||||
|
// If the activation abort's for any reason `the `activationFailure` signal is
|
||||||
|
// emitted.
|
||||||
logger.debug() << "Activating interface";
|
logger.debug() << "Activating interface";
|
||||||
|
auto emit_failure_guard = qScopeGuard([this] { emit activationFailure(); });
|
||||||
|
|
||||||
if (m_connections.contains(config.m_hopindex)) {
|
if (m_connections.contains(config.m_hopType)) {
|
||||||
if (supportServerSwitching(config)) {
|
if (supportServerSwitching(config)) {
|
||||||
logger.debug() << "Already connected. Server switching supported.";
|
logger.debug() << "Already connected. Server switching supported.";
|
||||||
|
|
||||||
|
|
@ -85,10 +89,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
bool status = run(Switch, config);
|
bool status = run(Switch, config);
|
||||||
logger.debug() << "Connection status:" << status;
|
logger.debug() << "Connection status:" << status;
|
||||||
if (status) {
|
if (status) {
|
||||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
m_connections[config.m_hopType] = ConnectionState(config);
|
||||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||||
|
emit_failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return status;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warning() << "Already connected. Server switching not supported.";
|
logger.warning() << "Already connected. Server switching not supported.";
|
||||||
|
|
@ -96,8 +102,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(!m_connections.contains(config.m_hopindex));
|
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||||
return activate(config);
|
if (activate(config)) {
|
||||||
|
emit_failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareActivation(config);
|
prepareActivation(config);
|
||||||
|
|
@ -112,13 +122,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
|
|
||||||
// Configure routing for excluded addresses.
|
// Configure routing for excluded addresses.
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
QHostAddress address(i);
|
addExclusionRoute(IPAddress(i));
|
||||||
if (m_excludedAddrSet.contains(address)) {
|
|
||||||
m_excludedAddrSet[address]++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
wgutils()->addExclusionRoute(address);
|
|
||||||
m_excludedAddrSet[address] = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the peer to this interface.
|
// Add the peer to this interface.
|
||||||
|
|
@ -142,7 +146,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
|
|
||||||
// set routing
|
// set routing
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
logger.debug() << "Routing configuration failed for"
|
logger.debug() << "Routing configuration failed for"
|
||||||
<< logger.sensitive(ip.toString());
|
<< logger.sensitive(ip.toString());
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -152,15 +156,21 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
bool status = run(Up, config);
|
bool status = run(Up, config);
|
||||||
logger.debug() << "Connection status:" << status;
|
logger.debug() << "Connection status:" << status;
|
||||||
if (status) {
|
if (status) {
|
||||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
m_connections[config.m_hopType] = ConnectionState(config);
|
||||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||||
|
emit_failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||||
if ((config.m_hopindex == 0) && supportDnsUtils()) {
|
if (!supportDnsUtils()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||||
|
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||||
QList<QHostAddress> resolvers;
|
QList<QHostAddress> resolvers;
|
||||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
resolvers.append(QHostAddress(config.m_dnsServer));
|
||||||
|
|
||||||
|
|
@ -199,6 +209,28 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
if (m_excludedAddrSet.contains(prefix)) {
|
||||||
|
m_excludedAddrSet[prefix]++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!wgutils()->addExclusionRoute(prefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_excludedAddrSet[prefix] = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
||||||
|
Q_ASSERT(m_excludedAddrSet.contains(prefix));
|
||||||
|
if (m_excludedAddrSet[prefix] > 1) {
|
||||||
|
m_excludedAddrSet[prefix]--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
m_excludedAddrSet.remove(prefix);
|
||||||
|
return wgutils()->deleteExclusionRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||||
#define GETVALUE(name, where, jsontype) \
|
#define GETVALUE(name, where, jsontype) \
|
||||||
|
|
@ -216,8 +248,8 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||||
|
|
||||||
GETVALUE("privateKey", config.m_privateKey, String);
|
GETVALUE("privateKey", config.m_privateKey, String);
|
||||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
|
||||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
||||||
|
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||||
|
|
||||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||||
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
||||||
|
|
@ -247,15 +279,24 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||||
config.m_dnsServer = value.toString();
|
config.m_dnsServer = value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.contains("hopindex")) {
|
if (!obj.contains("hopType")) {
|
||||||
config.m_hopindex = 0;
|
config.m_hopType = InterfaceConfig::SingleHop;
|
||||||
} else {
|
} else {
|
||||||
QJsonValue value = obj.value("hopindex");
|
QJsonValue value = obj.value("hopType");
|
||||||
if (!value.isDouble()) {
|
if (!value.isString()) {
|
||||||
logger.error() << "hopindex is not a number";
|
logger.error() << "hopType is not a string";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool okay;
|
||||||
|
QByteArray vdata = value.toString().toUtf8();
|
||||||
|
QMetaEnum meta = QMetaEnum::fromType<InterfaceConfig::HopType>();
|
||||||
|
config.m_hopType =
|
||||||
|
InterfaceConfig::HopType(meta.keyToValue(vdata.constData(), &okay));
|
||||||
|
if (!okay) {
|
||||||
|
logger.error() << "hopType" << value.toString() << "is not valid";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
config.m_hopindex = value.toInt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
||||||
|
|
@ -325,8 +366,8 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
|
|
||||||
// Deactivate the main interface.
|
// Deactivate the main interface.
|
||||||
if (m_connections.contains(0)) {
|
if (!m_connections.isEmpty()) {
|
||||||
const ConnectionState& state = m_connections.value(0);
|
const ConnectionState& state = m_connections.first();
|
||||||
if (!run(Down, state.m_config)) {
|
if (!run(Down, state.m_config)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -349,9 +390,9 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||||
// Cleanup peers and routing
|
// Cleanup peers and routing
|
||||||
for (const ConnectionState& state : m_connections) {
|
for (const ConnectionState& state : m_connections) {
|
||||||
const InterfaceConfig& config = state.m_config;
|
const InterfaceConfig& config = state.m_config;
|
||||||
logger.debug() << "Deleting routes for hop" << config.m_hopindex;
|
logger.debug() << "Deleting routes for" << config.m_hopType;
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
wgutils()->deleteRoutePrefix(ip);
|
||||||
}
|
}
|
||||||
wgutils()->deletePeer(config);
|
wgutils()->deletePeer(config);
|
||||||
}
|
}
|
||||||
|
|
@ -376,14 +417,14 @@ QString Daemon::logs() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Daemon::cleanLogs() { }
|
void Daemon::cleanLogs() { }
|
||||||
|
|
||||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||||
if (!m_connections.contains(config.m_hopindex)) {
|
if (!m_connections.contains(config.m_hopType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const InterfaceConfig& current =
|
const InterfaceConfig& current =
|
||||||
m_connections.value(config.m_hopindex).m_config;
|
m_connections.value(config.m_hopType).m_config;
|
||||||
|
|
||||||
return current.m_privateKey == config.m_privateKey &&
|
return current.m_privateKey == config.m_privateKey &&
|
||||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||||
|
|
@ -395,21 +436,15 @@ bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
|
|
||||||
logger.debug() << "Switching server for hop" << config.m_hopindex;
|
logger.debug() << "Switching server for" << config.m_hopType;
|
||||||
|
|
||||||
Q_ASSERT(m_connections.contains(config.m_hopindex));
|
Q_ASSERT(m_connections.contains(config.m_hopType));
|
||||||
const InterfaceConfig& lastConfig =
|
const InterfaceConfig& lastConfig =
|
||||||
m_connections.value(config.m_hopindex).m_config;
|
m_connections.value(config.m_hopType).m_config;
|
||||||
|
|
||||||
// Configure routing for new excluded addresses.
|
// Configure routing for new excluded addresses.
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
QHostAddress address(i);
|
addExclusionRoute(IPAddress(i));
|
||||||
if (m_excludedAddrSet.contains(address)) {
|
|
||||||
m_excludedAddrSet[address]++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
wgutils()->addExclusionRoute(address);
|
|
||||||
m_excludedAddrSet[address] = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate the new peer and its routes.
|
// Activate the new peer and its routes.
|
||||||
|
|
@ -418,7 +453,7 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
logger.error() << "Server switch failed to update the routing table";
|
logger.error() << "Server switch failed to update the routing table";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -426,18 +461,11 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||||
|
|
||||||
// Remove routing entries for the old peer.
|
// Remove routing entries for the old peer.
|
||||||
for (const QString& i : lastConfig.m_excludedAddresses) {
|
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||||
QHostAddress address(i);
|
delExclusionRoute(QHostAddress(i));
|
||||||
Q_ASSERT(m_excludedAddrSet.contains(address));
|
|
||||||
if (m_excludedAddrSet[address] > 1) {
|
|
||||||
m_excludedAddrSet[address]--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
wgutils()->deleteExclusionRoute(address);
|
|
||||||
m_excludedAddrSet.remove(address);
|
|
||||||
}
|
}
|
||||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
wgutils()->deleteRoutePrefix(ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -448,7 +476,7 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
m_connections[config.m_hopType] = ConnectionState(config);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -457,12 +485,12 @@ QJsonObject Daemon::getStatus() {
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
logger.debug() << "Status request";
|
logger.debug() << "Status request";
|
||||||
|
|
||||||
if (!m_connections.contains(0) || !wgutils()->interfaceExists()) {
|
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||||
json.insert("connected", QJsonValue(false));
|
json.insert("connected", QJsonValue(false));
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionState& connection = m_connections.value(0);
|
const ConnectionState& connection = m_connections.first();
|
||||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||||
|
|
@ -495,6 +523,7 @@ void Daemon::checkHandshake() {
|
||||||
if (connection.m_date.isValid()) {
|
if (connection.m_date.isValid()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||||
|
|
||||||
// Check if the handshake has completed.
|
// Check if the handshake has completed.
|
||||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,18 @@ class Daemon : public QObject {
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connected(const QString& pubkey);
|
void connected(const QString& pubkey);
|
||||||
|
/**
|
||||||
|
* Can be fired if a call to activate() was unsucessfull
|
||||||
|
* and connected systems should rollback
|
||||||
|
*/
|
||||||
|
void activationFailure();
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||||
|
bool addExclusionRoute(const IPAddress& address);
|
||||||
|
bool delExclusionRoute(const IPAddress& address);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||||
|
|
@ -75,8 +82,8 @@ class Daemon : public QObject {
|
||||||
QDateTime m_date;
|
QDateTime m_date;
|
||||||
InterfaceConfig m_config;
|
InterfaceConfig m_config;
|
||||||
};
|
};
|
||||||
QMap<int, ConnectionState> m_connections;
|
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||||
QHash<QHostAddress, int> m_excludedAddrSet;
|
QHash<IPAddress, int> m_excludedAddrSet;
|
||||||
QTimer m_handshakeTimer;
|
QTimer m_handshakeTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
#ifdef MZ_MACOS
|
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||||
# include <sys/stat.h>
|
# include <sys/stat.h>
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
|
|
@ -68,7 +68,8 @@ bool DaemonLocalServer::initialize() {
|
||||||
QString DaemonLocalServer::daemonPath() const {
|
QString DaemonLocalServer::daemonPath() const {
|
||||||
#if defined(MZ_WINDOWS)
|
#if defined(MZ_WINDOWS)
|
||||||
return "\\\\.\\pipe\\amneziavpn";
|
return "\\\\.\\pipe\\amneziavpn";
|
||||||
#elif defined(MZ_MACOS)
|
#endif
|
||||||
|
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||||
QDir dir("/var/run");
|
QDir dir("/var/run");
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
||||||
|
|
@ -92,7 +93,5 @@ QString DaemonLocalServer::daemonPath() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
return VAR_PATH;
|
return VAR_PATH;
|
||||||
#else
|
|
||||||
# error Unsupported platform
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
122
client/daemon/interfaceconfig.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
/* 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 "interfaceconfig.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
|
||||||
|
QJsonObject InterfaceConfig::toJson() const {
|
||||||
|
QJsonObject json;
|
||||||
|
QMetaEnum metaEnum = QMetaEnum::fromType<HopType>();
|
||||||
|
|
||||||
|
json.insert("hopType", QJsonValue(metaEnum.valueToKey(m_hopType)));
|
||||||
|
json.insert("privateKey", QJsonValue(m_privateKey));
|
||||||
|
json.insert("deviceIpv4Address", QJsonValue(m_deviceIpv4Address));
|
||||||
|
json.insert("deviceIpv6Address", QJsonValue(m_deviceIpv6Address));
|
||||||
|
json.insert("serverPublicKey", QJsonValue(m_serverPublicKey));
|
||||||
|
json.insert("serverPskKey", QJsonValue(m_serverPskKey));
|
||||||
|
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
|
||||||
|
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
|
||||||
|
json.insert("serverPort", QJsonValue((double)m_serverPort));
|
||||||
|
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||||
|
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||||
|
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||||
|
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
||||||
|
json.insert("dnsServer", QJsonValue(m_dnsServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray allowedIPAddesses;
|
||||||
|
for (const IPAddress& i : m_allowedIPAddressRanges) {
|
||||||
|
QJsonObject range;
|
||||||
|
range.insert("address", QJsonValue(i.address().toString()));
|
||||||
|
range.insert("range", QJsonValue((double)i.prefixLength()));
|
||||||
|
range.insert("isIpv6",
|
||||||
|
QJsonValue(i.type() == QAbstractSocket::IPv6Protocol));
|
||||||
|
allowedIPAddesses.append(range);
|
||||||
|
};
|
||||||
|
json.insert("allowedIPAddressRanges", allowedIPAddesses);
|
||||||
|
|
||||||
|
QJsonArray jsExcludedAddresses;
|
||||||
|
for (const QString& i : m_excludedAddresses) {
|
||||||
|
jsExcludedAddresses.append(QJsonValue(i));
|
||||||
|
}
|
||||||
|
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||||
|
|
||||||
|
QJsonArray disabledApps;
|
||||||
|
for (const QString& i : m_vpnDisabledApps) {
|
||||||
|
disabledApps.append(QJsonValue(i));
|
||||||
|
}
|
||||||
|
json.insert("vpnDisabledApps", disabledApps);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||||
|
#define VALIDATE(x) \
|
||||||
|
if (x.contains("\n")) return "";
|
||||||
|
|
||||||
|
VALIDATE(m_privateKey);
|
||||||
|
VALIDATE(m_deviceIpv4Address);
|
||||||
|
VALIDATE(m_deviceIpv6Address);
|
||||||
|
VALIDATE(m_serverIpv4Gateway);
|
||||||
|
VALIDATE(m_serverIpv6Gateway);
|
||||||
|
VALIDATE(m_serverPublicKey);
|
||||||
|
VALIDATE(m_serverIpv4AddrIn);
|
||||||
|
VALIDATE(m_serverIpv6AddrIn);
|
||||||
|
#undef VALIDATE
|
||||||
|
|
||||||
|
QString content;
|
||||||
|
QTextStream out(&content);
|
||||||
|
out << "[Interface]\n";
|
||||||
|
out << "PrivateKey = " << m_privateKey << "\n";
|
||||||
|
|
||||||
|
QStringList addresses;
|
||||||
|
if (!m_deviceIpv4Address.isNull()) {
|
||||||
|
addresses.append(m_deviceIpv4Address);
|
||||||
|
}
|
||||||
|
if (!m_deviceIpv6Address.isNull()) {
|
||||||
|
addresses.append(m_deviceIpv6Address);
|
||||||
|
}
|
||||||
|
if (addresses.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
out << "Address = " << addresses.join(", ") << "\n";
|
||||||
|
|
||||||
|
if (!m_dnsServer.isNull()) {
|
||||||
|
QStringList dnsServers(m_dnsServer);
|
||||||
|
// If the DNS is not the Gateway, it's a user defined DNS
|
||||||
|
// thus, not add any other :)
|
||||||
|
if (m_dnsServer == m_serverIpv4Gateway) {
|
||||||
|
dnsServers.append(m_serverIpv6Gateway);
|
||||||
|
}
|
||||||
|
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any extra config was provided, append it now.
|
||||||
|
for (const QString& key : extra.keys()) {
|
||||||
|
out << key << " = " << extra[key] << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "\n[Peer]\n";
|
||||||
|
out << "PublicKey = " << m_serverPublicKey << "\n";
|
||||||
|
out << "Endpoint = " << m_serverIpv4AddrIn.toUtf8() << ":" << m_serverPort
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
/* In theory, we should use the ipv6 endpoint, but wireguard doesn't seem
|
||||||
|
* to be happy if there are 2 endpoints.
|
||||||
|
out << "Endpoint = [" << config.m_serverIpv6AddrIn << "]:"
|
||||||
|
<< config.m_serverPort << "\n";
|
||||||
|
*/
|
||||||
|
QStringList ranges;
|
||||||
|
for (const IPAddress& ip : m_allowedIPAddressRanges) {
|
||||||
|
ranges.append(ip.toString());
|
||||||
|
}
|
||||||
|
out << "AllowedIPs = " << ranges.join(", ") << "\n";
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
@ -10,22 +10,39 @@
|
||||||
|
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
||||||
struct InterfaceConfig {
|
class QJsonObject;
|
||||||
int m_hopindex = 0;
|
|
||||||
|
class InterfaceConfig {
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
public:
|
||||||
|
InterfaceConfig() {}
|
||||||
|
|
||||||
|
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
|
||||||
|
Q_ENUM(HopType)
|
||||||
|
|
||||||
|
HopType m_hopType;
|
||||||
QString m_privateKey;
|
QString m_privateKey;
|
||||||
QString m_deviceIpv4Address;
|
QString m_deviceIpv4Address;
|
||||||
QString m_deviceIpv6Address;
|
QString m_deviceIpv6Address;
|
||||||
QString m_serverIpv4Gateway;
|
QString m_serverIpv4Gateway;
|
||||||
QString m_serverIpv6Gateway;
|
QString m_serverIpv6Gateway;
|
||||||
QString m_serverPublicKey;
|
QString m_serverPublicKey;
|
||||||
QString m_serverPskKey;
|
|
||||||
QString m_serverIpv4AddrIn;
|
QString m_serverIpv4AddrIn;
|
||||||
|
QString m_serverPskKey;
|
||||||
QString m_serverIpv6AddrIn;
|
QString m_serverIpv6AddrIn;
|
||||||
QString m_dnsServer;
|
QString m_dnsServer;
|
||||||
int m_serverPort = 0;
|
int m_serverPort = 0;
|
||||||
QList<IPAddress> m_allowedIPAddressRanges;
|
QList<IPAddress> m_allowedIPAddressRanges;
|
||||||
QStringList m_excludedAddresses;
|
QStringList m_excludedAddresses;
|
||||||
QStringList m_vpnDisabledApps;
|
QStringList m_vpnDisabledApps;
|
||||||
|
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||||
|
QString m_installationId;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
QString toWgConf(
|
||||||
|
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INTERFACECONFIG_H
|
#endif // INTERFACECONFIG_H
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
#ifndef WIREGUARDUTILS_H
|
#ifndef WIREGUARDUTILS_H
|
||||||
#define WIREGUARDUTILS_H
|
#define WIREGUARDUTILS_H
|
||||||
|
|
||||||
|
#define _WINSOCKAPI_
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
@ -12,7 +14,7 @@
|
||||||
|
|
||||||
#include "interfaceconfig.h"
|
#include "interfaceconfig.h"
|
||||||
|
|
||||||
constexpr const char* WG_INTERFACE = "moz0";
|
constexpr const char* WG_INTERFACE = "amn0";
|
||||||
|
|
||||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||||
|
|
||||||
|
|
@ -41,11 +43,11 @@ class WireguardUtils : public QObject {
|
||||||
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
||||||
virtual QList<PeerStatus> getPeerStatus() = 0;
|
virtual QList<PeerStatus> getPeerStatus() = 0;
|
||||||
|
|
||||||
virtual bool updateRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
|
||||||
virtual bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
|
||||||
|
|
||||||
virtual bool addExclusionRoute(const QHostAddress& address) = 0;
|
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||||
virtual bool deleteExclusionRoute(const QHostAddress& address) = 0;
|
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIREGUARDUTILS_H
|
#endif // WIREGUARDUTILS_H
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
#ifndef CONTROLLERIMPL_H
|
#ifndef CONTROLLERIMPL_H
|
||||||
#define CONTROLLERIMPL_H
|
#define CONTROLLERIMPL_H
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
class Keys;
|
class Keys;
|
||||||
class Device;
|
class Device;
|
||||||
|
|
|
||||||
|
|
@ -52,18 +52,51 @@ DnsPingSender::DnsPingSender(const QHostAddress& source, QObject* parent)
|
||||||
: PingSender(parent) {
|
: PingSender(parent) {
|
||||||
MZ_COUNT_CTOR(DnsPingSender);
|
MZ_COUNT_CTOR(DnsPingSender);
|
||||||
|
|
||||||
if (source.isNull()) {
|
m_source = source;
|
||||||
m_socket.bind();
|
|
||||||
} else {
|
|
||||||
m_socket.bind(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(&m_socket, &QUdpSocket::readyRead, this, &DnsPingSender::readData);
|
connect(&m_socket, &QUdpSocket::readyRead, this, &DnsPingSender::readData);
|
||||||
}
|
}
|
||||||
|
|
||||||
DnsPingSender::~DnsPingSender() { MZ_COUNT_DTOR(DnsPingSender); }
|
DnsPingSender::~DnsPingSender() { MZ_COUNT_DTOR(DnsPingSender); }
|
||||||
|
|
||||||
|
void DnsPingSender::start() {
|
||||||
|
auto state = m_socket.state();
|
||||||
|
if (state != QAbstractSocket::UnconnectedState) {
|
||||||
|
logger.info()
|
||||||
|
<< "Attempted to start UDP socket, but it's in an invalid state:"
|
||||||
|
<< state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bindResult = false;
|
||||||
|
if (m_source.isNull()) {
|
||||||
|
bindResult = m_socket.bind();
|
||||||
|
} else {
|
||||||
|
bindResult = m_socket.bind(m_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bindResult) {
|
||||||
|
logger.error() << "Unable to bind UDP socket. Socket state:" << state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "UDP socket bound to:"
|
||||||
|
<< m_socket.localAddress().toString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||||
|
if (dest.isNull()) {
|
||||||
|
logger.error() << "Attempted to send DNS ping to invalid destination:"
|
||||||
|
<< dest.toString() << "Ignoring.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_socket.isValid()) {
|
||||||
|
logger.error() << "Attempted to send DNS ping, but socket is invalid.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray packet;
|
QByteArray packet;
|
||||||
|
|
||||||
// Assemble a DNS query header.
|
// Assemble a DNS query header.
|
||||||
|
|
@ -82,7 +115,14 @@ void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||||
packet.append(query, sizeof(query));
|
packet.append(query, sizeof(query));
|
||||||
|
|
||||||
// Send the datagram.
|
// Send the datagram.
|
||||||
m_socket.writeDatagram(packet, dest, DNS_PORT);
|
logger.debug() << "Sending" << packet.size() << "bytes to UDP socket.";
|
||||||
|
auto bytesWritten = m_socket.writeDatagram(packet, dest, DNS_PORT);
|
||||||
|
|
||||||
|
if (bytesWritten >= 0) {
|
||||||
|
logger.debug() << "Number of bytes written to UDP socket:" << bytesWritten;
|
||||||
|
} else {
|
||||||
|
logger.error() << "Error writing to UDP socket:" << m_socket.error();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DnsPingSender::readData() {
|
void DnsPingSender::readData() {
|
||||||
|
|
@ -112,6 +152,7 @@ void DnsPingSender::readData() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Received valid DNS reply";
|
||||||
emit recvPing(qFromBigEndian<quint16>(header.id));
|
emit recvPing(qFromBigEndian<quint16>(header.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,15 @@ class DnsPingSender final : public PingSender {
|
||||||
|
|
||||||
void sendPing(const QHostAddress& dest, quint16 sequence) override;
|
void sendPing(const QHostAddress& dest, quint16 sequence) override;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop() { m_socket.close(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void readData();
|
void readData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUdpSocket m_socket;
|
QUdpSocket m_socket;
|
||||||
|
QHostAddress m_source;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DNSPINGSENDER_H
|
#endif // DNSPINGSENDER_H
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "protocols/protocols_defs.h"
|
#include "protocols/protocols_defs.h"
|
||||||
#include "localsocketcontroller.h"
|
#include "localsocketcontroller.h"
|
||||||
|
|
||||||
|
|
@ -14,10 +13,14 @@
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
//#include "errorhandler.h"
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
//#include "models/device.h"
|
||||||
|
//#include "models/keys.h"
|
||||||
#include "models/server.h"
|
#include "models/server.h"
|
||||||
|
//#include "settingsholder.h"
|
||||||
|
|
||||||
// How many times do we try to reconnect.
|
// How many times do we try to reconnect.
|
||||||
constexpr int MAX_CONNECTION_RETRY = 10;
|
constexpr int MAX_CONNECTION_RETRY = 10;
|
||||||
|
|
@ -112,23 +115,22 @@ void LocalSocketController::daemonConnected() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
|
|
||||||
QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject();
|
QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject();
|
||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
json.insert("type", "activate");
|
json.insert("type", "activate");
|
||||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||||
json.insert("deviceIpv6Address", "dead::1");
|
json.insert("deviceIpv6Address", "dead::1");
|
||||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||||
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
||||||
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
|
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
|
||||||
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
|
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
|
||||||
|
|
||||||
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
|
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
|
||||||
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
|
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
|
||||||
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
|
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
|
||||||
|
|
||||||
QJsonArray jsAllowedIPAddesses;
|
QJsonArray jsAllowedIPAddesses;
|
||||||
|
|
@ -148,17 +150,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||||
|
|
||||||
|
|
||||||
QJsonArray jsExcludedAddresses;
|
QJsonArray jsExcludedAddresses;
|
||||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||||
|
|
||||||
|
|
||||||
// QJsonArray splitTunnelApps;
|
// QJsonArray splitTunnelApps;
|
||||||
// for (const auto& uri : hop.m_vpnDisabledApps) {
|
// for (const auto& uri : hop.m_vpnDisabledApps) {
|
||||||
// splitTunnelApps.append(QJsonValue(uri));
|
// splitTunnelApps.append(QJsonValue(uri));
|
||||||
// }
|
// }
|
||||||
// json.insert("vpnDisabledApps", splitTunnelApps);
|
// json.insert("vpnDisabledApps", splitTunnelApps);
|
||||||
|
|
||||||
write(json);
|
write(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,20 @@
|
||||||
|
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
|
||||||
|
//#include "controller.h"
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
//#include "mozillavpn.h"
|
||||||
#include "networkwatcherimpl.h"
|
#include "networkwatcherimpl.h"
|
||||||
#include "platforms/dummy/dummynetworkwatcher.h"
|
#include "platforms/dummy/dummynetworkwatcher.h"
|
||||||
|
//#include "settingsholder.h"
|
||||||
|
|
||||||
#ifdef MZ_WINDOWS
|
#ifdef MZ_WINDOWS
|
||||||
//# include "platforms/windows/windowsnetworkwatcher.h"
|
# include "platforms/windows/windowsnetworkwatcher.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MZ_LINUX
|
#ifdef MZ_LINUX
|
||||||
//# include "platforms/linux/linuxnetworkwatcher.h"
|
# include "platforms/linux/linuxnetworkwatcher.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MZ_MACOS
|
#ifdef MZ_MACOS
|
||||||
|
|
@ -51,9 +54,9 @@ void NetworkWatcher::initialize() {
|
||||||
logger.debug() << "Initialize";
|
logger.debug() << "Initialize";
|
||||||
|
|
||||||
#if defined(MZ_WINDOWS)
|
#if defined(MZ_WINDOWS)
|
||||||
//m_impl = new WindowsNetworkWatcher(this);
|
m_impl = new WindowsNetworkWatcher(this);
|
||||||
#elif defined(MZ_LINUX)
|
#elif defined(MZ_LINUX)
|
||||||
//m_impl = new LinuxNetworkWatcher(this);
|
m_impl = new LinuxNetworkWatcher(this);
|
||||||
#elif defined(MZ_MACOS)
|
#elif defined(MZ_MACOS)
|
||||||
m_impl = new MacOSNetworkWatcher(this);
|
m_impl = new MacOSNetworkWatcher(this);
|
||||||
#elif defined(MZ_WASM)
|
#elif defined(MZ_WASM)
|
||||||
|
|
@ -73,11 +76,34 @@ void NetworkWatcher::initialize() {
|
||||||
|
|
||||||
m_impl->initialize();
|
m_impl->initialize();
|
||||||
|
|
||||||
//TODO IMPL FOR AMNEZIA
|
|
||||||
|
// TODO: IMPL FOR AMNEZIA
|
||||||
|
#if 0
|
||||||
|
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||||
|
Q_ASSERT(settingsHolder);
|
||||||
|
|
||||||
|
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||||
|
settingsHolder->captivePortalAlert();
|
||||||
|
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||||
|
if (m_active) {
|
||||||
|
m_impl->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
|
||||||
|
&NetworkWatcher::settingsChanged);
|
||||||
|
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
|
||||||
|
&NetworkWatcher::settingsChanged);
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkWatcher::settingsChanged() {
|
void NetworkWatcher::settingsChanged() {
|
||||||
//TODO IMPL FOR AMNEZIA
|
// TODO: IMPL FOR AMNEZIA
|
||||||
|
#if 0
|
||||||
|
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||||
|
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||||
|
settingsHolder->captivePortalAlert();
|
||||||
|
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||||
|
|
||||||
if (m_active) {
|
if (m_active) {
|
||||||
logger.debug()
|
logger.debug()
|
||||||
|
|
@ -88,6 +114,7 @@ void NetworkWatcher::settingsChanged() {
|
||||||
logger.debug() << "Stopping Network Watcher";
|
logger.debug() << "Stopping Network Watcher";
|
||||||
m_impl->stop();
|
m_impl->stop();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||||
|
|
@ -95,9 +122,55 @@ void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||||
<< "id:" << logger.sensitive(networkId);
|
<< "id:" << logger.sensitive(networkId);
|
||||||
|
|
||||||
//TODO IMPL FOR AMNEZIA
|
#ifndef UNIT_TEST
|
||||||
|
if (!m_reportUnsecuredNetwork) {
|
||||||
|
logger.debug() << "Disabled. Ignoring unsecured network";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: IMPL FOR AMNEZIA
|
||||||
|
#if 0
|
||||||
|
MozillaVPN* vpn = MozillaVPN::instance();
|
||||||
|
|
||||||
|
if (vpn->state() != App::StateMain) {
|
||||||
|
logger.debug() << "VPN not ready. Ignoring unsecured network";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller::State state = vpn->controller()->state();
|
||||||
|
if (state == Controller::StateOn || state == Controller::StateConnecting ||
|
||||||
|
state == Controller::StateCheckSubscription ||
|
||||||
|
state == Controller::StateSwitching ||
|
||||||
|
state == Controller::StateSilentSwitching) {
|
||||||
|
logger.debug() << "VPN on. Ignoring unsecured network";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_networks.contains(networkId)) {
|
||||||
|
m_networks.insert(networkId, QElapsedTimer());
|
||||||
|
} else if (!m_networks[networkId].hasExpired(NETWORK_WATCHER_TIMER_MSEC)) {
|
||||||
|
logger.debug() << "Notification already shown. Ignoring unsecured network";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's activate the QElapsedTimer to avoid notification loops.
|
||||||
|
m_networks[networkId].start();
|
||||||
|
|
||||||
|
// We don't connect the system tray handler in the CTOR because it can be too
|
||||||
|
// early. Maybe the NotificationHandler has not been created yet. We do it at
|
||||||
|
// the first detection of an unsecured network.
|
||||||
|
if (m_firstNotification) {
|
||||||
|
connect(NotificationHandler::instance(),
|
||||||
|
&NotificationHandler::notificationClicked, this,
|
||||||
|
&NetworkWatcher::notificationClicked);
|
||||||
|
m_firstNotification = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationHandler::instance()->unsecuredNetworkNotification(networkName);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString NetworkWatcher::getCurrentTransport() {
|
QString NetworkWatcher::getCurrentTransport() {
|
||||||
auto type = m_impl->getTransportType();
|
auto type = m_impl->getTransportType();
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>();
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ class NetworkWatcher final : public QObject {
|
||||||
private:
|
private:
|
||||||
void settingsChanged();
|
void settingsChanged();
|
||||||
|
|
||||||
|
// void notificationClicked(NotificationHandler::Message message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_active = false;
|
bool m_active = false;
|
||||||
bool m_reportUnsecuredNetwork = false;
|
bool m_reportUnsecuredNetwork = false;
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ class PingHelper final : public QObject {
|
||||||
|
|
||||||
QTimer m_pingTimer;
|
QTimer m_pingTimer;
|
||||||
PingSender* m_pingSender = nullptr;
|
PingSender* m_pingSender = nullptr;
|
||||||
|
|
||||||
|
#ifdef UNIT_TEST
|
||||||
|
friend class TestConnectionHealth;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PINGHELPER_H
|
#endif // PINGHELPER_H
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||||
# include "platforms/macos/macospingsender.h"
|
# include "platforms/macos/macospingsender.h"
|
||||||
#elif defined(MZ_WINDOWS)
|
#elif defined(MZ_WINDOWS)
|
||||||
// #include "platforms/windows/windowspingsender.h"
|
# include "platforms/windows/windowspingsender.h"
|
||||||
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
|
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
|
||||||
# include "platforms/dummy/dummypingsender.h"
|
# include "platforms/dummy/dummypingsender.h"
|
||||||
#else
|
#else
|
||||||
|
|
@ -19,13 +19,12 @@
|
||||||
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
||||||
QObject* parent) {
|
QObject* parent) {
|
||||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
//return new LinuxPingSender(source, parent);
|
// return new LinuxPingSender(source, parent);
|
||||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||||
return new MacOSPingSender(source, parent);
|
return new MacOSPingSender(source, parent);
|
||||||
#elif defined(MZ_WINDOWS)
|
#elif defined(MZ_WINDOWS)
|
||||||
return nullptr;
|
return new WindowsPingSender(source, parent);
|
||||||
//return new WindowsPingSender(source, parent);
|
|
||||||
#else
|
#else
|
||||||
return new DummyPingSender(source, parent);
|
return new DummyPingSender(source, parent);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ IPAddress::IPAddress(const QString& ip) {
|
||||||
if (m_prefixLength >= 128) {
|
if (m_prefixLength >= 128) {
|
||||||
m_prefixLength = 128;
|
m_prefixLength = 128;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Q_ASSERT(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,4 +50,9 @@ class IPAddress final {
|
||||||
int m_prefixLength;
|
int m_prefixLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline size_t qHash(const IPAddress& key, size_t seed) {
|
||||||
|
const QHostAddress& address = key.address();
|
||||||
|
return qHash(address, seed) ^ key.prefixLength();
|
||||||
|
}
|
||||||
|
|
||||||
#endif // IPADDRESS_H
|
#endif // IPADDRESS_H
|
||||||
|
|
|
||||||
170
client/platforms/linux/daemon/dbustypeslinux.h
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef DBUSTYPESLINUX_H
|
||||||
|
#define DBUSTYPESLINUX_H
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QtDBus/QtDBus>
|
||||||
|
|
||||||
|
/* D-Bus metatype for marshalling arguments to the SetLinkDNS method */
|
||||||
|
class DnsResolver : public QHostAddress {
|
||||||
|
public:
|
||||||
|
DnsResolver(const QHostAddress& address = QHostAddress())
|
||||||
|
: QHostAddress(address) {}
|
||||||
|
|
||||||
|
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsResolver& ip) {
|
||||||
|
args.beginStructure();
|
||||||
|
if (ip.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
Q_IPV6ADDR addrv6 = ip.toIPv6Address();
|
||||||
|
args << AF_INET6;
|
||||||
|
args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6));
|
||||||
|
} else {
|
||||||
|
quint32 addrv4 = ip.toIPv4Address();
|
||||||
|
QByteArray data(4, 0);
|
||||||
|
data[0] = (addrv4 >> 24) & 0xff;
|
||||||
|
data[1] = (addrv4 >> 16) & 0xff;
|
||||||
|
data[2] = (addrv4 >> 8) & 0xff;
|
||||||
|
data[3] = (addrv4 >> 0) & 0xff;
|
||||||
|
args << AF_INET;
|
||||||
|
args << data;
|
||||||
|
}
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||||
|
DnsResolver& ip) {
|
||||||
|
int family;
|
||||||
|
QByteArray data;
|
||||||
|
args.beginStructure();
|
||||||
|
args >> family >> data;
|
||||||
|
args.endStructure();
|
||||||
|
if (family == AF_INET6) {
|
||||||
|
ip.setAddress(data.constData());
|
||||||
|
} else if (data.count() >= 4) {
|
||||||
|
quint32 addrv4 = 0;
|
||||||
|
addrv4 |= (data[0] << 24);
|
||||||
|
addrv4 |= (data[1] << 16);
|
||||||
|
addrv4 |= (data[2] << 8);
|
||||||
|
addrv4 |= (data[3] << 0);
|
||||||
|
ip.setAddress(addrv4);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
typedef QList<DnsResolver> DnsResolverList;
|
||||||
|
Q_DECLARE_METATYPE(DnsResolver);
|
||||||
|
Q_DECLARE_METATYPE(DnsResolverList);
|
||||||
|
|
||||||
|
/* D-Bus metatype for marshalling arguments to the SetLinkDomains method */
|
||||||
|
class DnsLinkDomain {
|
||||||
|
public:
|
||||||
|
DnsLinkDomain(const QString d = "", bool s = false) {
|
||||||
|
domain = d;
|
||||||
|
search = s;
|
||||||
|
};
|
||||||
|
QString domain;
|
||||||
|
bool search;
|
||||||
|
|
||||||
|
friend QDBusArgument& operator<<(QDBusArgument& args,
|
||||||
|
const DnsLinkDomain& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args << data.domain << data.search;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||||
|
DnsLinkDomain& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args >> data.domain >> data.search;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
bool operator==(const DnsLinkDomain& other) const {
|
||||||
|
return (domain == other.domain) && (search == other.search);
|
||||||
|
}
|
||||||
|
bool operator==(const QString& other) const { return (domain == other); }
|
||||||
|
};
|
||||||
|
typedef QList<DnsLinkDomain> DnsLinkDomainList;
|
||||||
|
Q_DECLARE_METATYPE(DnsLinkDomain);
|
||||||
|
Q_DECLARE_METATYPE(DnsLinkDomainList);
|
||||||
|
|
||||||
|
/* D-Bus metatype for marshalling the Domains property */
|
||||||
|
class DnsDomain {
|
||||||
|
public:
|
||||||
|
DnsDomain() {}
|
||||||
|
int ifindex = 0;
|
||||||
|
QString domain = "";
|
||||||
|
bool search = false;
|
||||||
|
|
||||||
|
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsDomain& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args << data.ifindex << data.domain << data.search;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||||
|
DnsDomain& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args >> data.ifindex >> data.domain >> data.search;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
typedef QList<DnsDomain> DnsDomainList;
|
||||||
|
Q_DECLARE_METATYPE(DnsDomain);
|
||||||
|
Q_DECLARE_METATYPE(DnsDomainList);
|
||||||
|
|
||||||
|
/* D-Bus metatype for marshalling the freedesktop login manager data. */
|
||||||
|
class UserData {
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
uint userid;
|
||||||
|
QDBusObjectPath path;
|
||||||
|
|
||||||
|
friend QDBusArgument& operator<<(QDBusArgument& args, const UserData& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args << data.userid << data.name << data.path;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||||
|
UserData& data) {
|
||||||
|
args.beginStructure();
|
||||||
|
args >> data.userid >> data.name >> data.path;
|
||||||
|
args.endStructure();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
typedef QList<UserData> UserDataList;
|
||||||
|
Q_DECLARE_METATYPE(UserData);
|
||||||
|
Q_DECLARE_METATYPE(UserDataList);
|
||||||
|
|
||||||
|
class DnsMetatypeRegistrationProxy {
|
||||||
|
public:
|
||||||
|
DnsMetatypeRegistrationProxy() {
|
||||||
|
qRegisterMetaType<DnsResolver>();
|
||||||
|
qDBusRegisterMetaType<DnsResolver>();
|
||||||
|
qRegisterMetaType<DnsResolverList>();
|
||||||
|
qDBusRegisterMetaType<DnsResolverList>();
|
||||||
|
qRegisterMetaType<DnsLinkDomain>();
|
||||||
|
qDBusRegisterMetaType<DnsLinkDomain>();
|
||||||
|
qRegisterMetaType<DnsLinkDomainList>();
|
||||||
|
qDBusRegisterMetaType<DnsLinkDomainList>();
|
||||||
|
qRegisterMetaType<DnsDomain>();
|
||||||
|
qDBusRegisterMetaType<DnsDomain>();
|
||||||
|
qRegisterMetaType<DnsDomainList>();
|
||||||
|
qDBusRegisterMetaType<DnsDomainList>();
|
||||||
|
qRegisterMetaType<UserData>();
|
||||||
|
qDBusRegisterMetaType<UserData>();
|
||||||
|
qRegisterMetaType<UserDataList>();
|
||||||
|
qDBusRegisterMetaType<UserDataList>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DBUSTYPESLINUX_H
|
||||||
212
client/platforms/linux/daemon/dnsutilslinux.cpp
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
/* 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 "dnsutilslinux.h"
|
||||||
|
|
||||||
|
#include <net/if.h>
|
||||||
|
|
||||||
|
#include <QDBusVariant>
|
||||||
|
#include <QtDBus/QtDBus>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
|
||||||
|
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
|
||||||
|
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
|
||||||
|
constexpr const char* DBUS_PROPERTY_INTERFACE =
|
||||||
|
"org.freedesktop.DBus.Properties";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("DnsUtilsLinux");
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
|
||||||
|
MZ_COUNT_CTOR(DnsUtilsLinux);
|
||||||
|
logger.debug() << "DnsUtilsLinux created.";
|
||||||
|
|
||||||
|
QDBusConnection conn = QDBusConnection::systemBus();
|
||||||
|
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||||
|
DBUS_RESOLVE_MANAGER, conn, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsUtilsLinux::~DnsUtilsLinux() {
|
||||||
|
MZ_COUNT_DTOR(DnsUtilsLinux);
|
||||||
|
|
||||||
|
for (auto iterator = m_linkDomains.constBegin();
|
||||||
|
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||||
|
QList<QVariant> argumentList;
|
||||||
|
argumentList << QVariant::fromValue(iterator.key());
|
||||||
|
argumentList << QVariant::fromValue(iterator.value());
|
||||||
|
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||||
|
argumentList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ifindex > 0) {
|
||||||
|
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "DnsUtilsLinux destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
|
||||||
|
const QList<QHostAddress>& resolvers) {
|
||||||
|
m_ifindex = if_nametoindex(qPrintable(ifname));
|
||||||
|
if (m_ifindex <= 0) {
|
||||||
|
logger.error() << "Unable to resolve ifindex for" << ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLinkDNS(m_ifindex, resolvers);
|
||||||
|
setLinkDefaultRoute(m_ifindex, true);
|
||||||
|
updateLinkDomains();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DnsUtilsLinux::restoreResolvers() {
|
||||||
|
for (auto iterator = m_linkDomains.constBegin();
|
||||||
|
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||||
|
setLinkDomains(iterator.key(), iterator.value());
|
||||||
|
}
|
||||||
|
m_linkDomains.clear();
|
||||||
|
|
||||||
|
/* Revert the VPN interface's DNS configuration */
|
||||||
|
if (m_ifindex > 0) {
|
||||||
|
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
|
||||||
|
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||||
|
QStringLiteral("RevertLink"), argumentList);
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
|
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||||
|
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||||
|
|
||||||
|
m_ifindex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
|
||||||
|
QDBusPendingReply<> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
logger.error() << "Error received from the DBus service";
|
||||||
|
}
|
||||||
|
delete call;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||||
|
const QList<QHostAddress>& resolvers) {
|
||||||
|
QList<DnsResolver> resolverList;
|
||||||
|
char ifnamebuf[IF_NAMESIZE];
|
||||||
|
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||||
|
for (const auto& ip : resolvers) {
|
||||||
|
resolverList.append(ip);
|
||||||
|
if (ifname) {
|
||||||
|
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
|
||||||
|
<< ifname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> argumentList;
|
||||||
|
argumentList << QVariant::fromValue(ifindex);
|
||||||
|
argumentList << QVariant::fromValue(resolverList);
|
||||||
|
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||||
|
QStringLiteral("SetLinkDNS"), argumentList);
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
|
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||||
|
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||||
|
const QList<DnsLinkDomain>& domains) {
|
||||||
|
char ifnamebuf[IF_NAMESIZE];
|
||||||
|
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||||
|
if (ifname) {
|
||||||
|
for (const auto& d : domains) {
|
||||||
|
// The DNS search domains often winds up revealing user's ISP which
|
||||||
|
// can correlate back to their location.
|
||||||
|
logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain)
|
||||||
|
<< "via" << ifname << (d.search ? "search" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> argumentList;
|
||||||
|
argumentList << QVariant::fromValue(ifindex);
|
||||||
|
argumentList << QVariant::fromValue(domains);
|
||||||
|
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||||
|
QStringLiteral("SetLinkDomains"), argumentList);
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
|
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||||
|
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||||
|
QList<QVariant> argumentList;
|
||||||
|
argumentList << QVariant::fromValue(ifindex);
|
||||||
|
argumentList << QVariant::fromValue(enable);
|
||||||
|
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||||
|
QStringLiteral("SetLinkDefaultRoute"), argumentList);
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
|
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||||
|
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::updateLinkDomains() {
|
||||||
|
/* Get the list of search domains, and remove any others that might conspire
|
||||||
|
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
|
||||||
|
* seem to be able to demarshall complex property types.
|
||||||
|
*/
|
||||||
|
QDBusMessage message = QDBusMessage::createMethodCall(
|
||||||
|
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
|
||||||
|
message << QString(DBUS_RESOLVE_MANAGER);
|
||||||
|
message << QString("Domains");
|
||||||
|
QDBusPendingReply<QVariant> reply =
|
||||||
|
m_resolver->connection().asyncCall(message);
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
|
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||||
|
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||||
|
QDBusPendingReply<QVariant> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
logger.error() << "Error retrieving the DNS domains from the DBus service";
|
||||||
|
delete call;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the state of the DNS domains */
|
||||||
|
m_linkDomains.clear();
|
||||||
|
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
|
||||||
|
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
|
||||||
|
for (const auto& d : list) {
|
||||||
|
if (d.ifindex == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop any competing root search domains. */
|
||||||
|
DnsLinkDomain root = DnsLinkDomain(".", true);
|
||||||
|
for (auto iterator = m_linkDomains.constBegin();
|
||||||
|
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||||
|
if (!iterator.value().contains(root)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QList<DnsLinkDomain> newlist = iterator.value();
|
||||||
|
newlist.removeAll(root);
|
||||||
|
setLinkDomains(iterator.key(), newlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a root search domain for the new interface. */
|
||||||
|
QList<DnsLinkDomain> newlist = {root};
|
||||||
|
setLinkDomains(m_ifindex, newlist);
|
||||||
|
delete call;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
|
||||||
41
client/platforms/linux/daemon/dnsutilslinux.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef DNSUTILSLINUX_H
|
||||||
|
#define DNSUTILSLINUX_H
|
||||||
|
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include "daemon/dnsutils.h"
|
||||||
|
#include "dbustypeslinux.h"
|
||||||
|
|
||||||
|
class DnsUtilsLinux final : public DnsUtils {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(DnsUtilsLinux)
|
||||||
|
|
||||||
|
public:
|
||||||
|
DnsUtilsLinux(QObject* parent);
|
||||||
|
~DnsUtilsLinux();
|
||||||
|
bool updateResolvers(const QString& ifname,
|
||||||
|
const QList<QHostAddress>& resolvers) override;
|
||||||
|
bool restoreResolvers() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers);
|
||||||
|
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains);
|
||||||
|
void setLinkDefaultRoute(int ifindex, bool enable);
|
||||||
|
void updateLinkDomains();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void dnsCallCompleted(QDBusPendingCallWatcher*);
|
||||||
|
void dnsDomainsReceived(QDBusPendingCallWatcher*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_ifindex = 0;
|
||||||
|
QMap<int, DnsLinkDomainList> m_linkDomains;
|
||||||
|
QDBusInterface* m_resolver = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DNSUTILSLINUX_H
|
||||||
150
client/platforms/linux/daemon/iputilslinux.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
/* 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 "iputilslinux.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
|
#include "daemon/wireguardutils.h"
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
constexpr uint32_t ETH_MTU = 1500;
|
||||||
|
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("IPUtilsLinux");
|
||||||
|
}
|
||||||
|
|
||||||
|
IPUtilsLinux::IPUtilsLinux(QObject* parent) : IPUtils(parent) {
|
||||||
|
MZ_COUNT_CTOR(IPUtilsLinux);
|
||||||
|
logger.debug() << "IPUtilsLinux created.";
|
||||||
|
}
|
||||||
|
|
||||||
|
IPUtilsLinux::~IPUtilsLinux() {
|
||||||
|
MZ_COUNT_DTOR(IPUtilsLinux);
|
||||||
|
logger.debug() << "IPUtilsLinux destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
||||||
|
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||||
|
Q_UNUSED(config);
|
||||||
|
|
||||||
|
// Create socket file descriptor to perform the ioctl operations on
|
||||||
|
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
logger.error() << "Failed to create ioctl socket.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||||
|
|
||||||
|
// Setup the interface to interact with
|
||||||
|
struct ifreq ifr;
|
||||||
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
|
|
||||||
|
// MTU
|
||||||
|
// FIXME: We need to know how many layers deep this particular
|
||||||
|
// interface is into a tunnel to work effectively. Otherwise
|
||||||
|
// we will run into fragmentation issues.
|
||||||
|
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
|
||||||
|
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
|
||||||
|
if (ret) {
|
||||||
|
logger.error() << "Failed to set MTU -- Return code: " << ret;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up
|
||||||
|
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
|
||||||
|
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
|
||||||
|
if (ret) {
|
||||||
|
logger.error() << "Failed to set device up -- Return code: " << ret;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||||
|
struct ifreq ifr;
|
||||||
|
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
||||||
|
|
||||||
|
// Name the interface and set family
|
||||||
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
|
ifr.ifr_addr.sa_family = AF_INET;
|
||||||
|
|
||||||
|
// Get the device address to add to interface
|
||||||
|
QPair<QHostAddress, int> parsedAddr =
|
||||||
|
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||||
|
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||||
|
char* deviceAddr = _deviceAddr.data();
|
||||||
|
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||||
|
|
||||||
|
// Create IPv4 socket to perform the ioctl operations on
|
||||||
|
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
logger.error() << "Failed to create ioctl socket.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||||
|
|
||||||
|
// Set ifr to interface
|
||||||
|
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
|
||||||
|
if (ret) {
|
||||||
|
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
|
||||||
|
<< "error:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||||
|
// Set up the ifr and the companion ifr6
|
||||||
|
struct in6_ifreq ifr6;
|
||||||
|
ifr6.prefixlen = 64;
|
||||||
|
|
||||||
|
// Get the device address to add to ifr6 interface
|
||||||
|
QPair<QHostAddress, int> parsedAddr =
|
||||||
|
QHostAddress::parseSubnet(config.m_deviceIpv6Address);
|
||||||
|
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||||
|
char* deviceAddr = _deviceAddr.data();
|
||||||
|
inet_pton(AF_INET6, deviceAddr, &ifr6.addr);
|
||||||
|
|
||||||
|
// Create IPv6 socket to perform the ioctl operations on
|
||||||
|
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
logger.error() << "Failed to create ioctl socket.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||||
|
|
||||||
|
// Get the index of named ifr and link with ifr6
|
||||||
|
struct ifreq ifr;
|
||||||
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
|
ifr.ifr_addr.sa_family = AF_INET6;
|
||||||
|
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
|
||||||
|
if (ret) {
|
||||||
|
logger.error() << "Failed to get ifindex. Return code: " << ret;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ifr6.ifindex = ifr.ifr_ifindex;
|
||||||
|
|
||||||
|
// Set ifr6 to the interface
|
||||||
|
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
|
||||||
|
if (ret && (errno != EEXIST)) {
|
||||||
|
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
|
||||||
|
<< "error:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
31
client/platforms/linux/daemon/iputilslinux.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef IPUTILSLINUX_H
|
||||||
|
#define IPUTILSLINUX_H
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "daemon/iputils.h"
|
||||||
|
|
||||||
|
class IPUtilsLinux final : public IPUtils {
|
||||||
|
public:
|
||||||
|
IPUtilsLinux(QObject* parent);
|
||||||
|
~IPUtilsLinux();
|
||||||
|
bool addInterfaceIPs(const InterfaceConfig& config) override;
|
||||||
|
bool setMTUAndUp(const InterfaceConfig& config) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool addIP4AddressToDevice(const InterfaceConfig& config);
|
||||||
|
bool addIP6AddressToDevice(const InterfaceConfig& config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct in6_ifreq {
|
||||||
|
struct in6_addr addr;
|
||||||
|
uint32_t prefixlen;
|
||||||
|
unsigned int ifindex;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IPUTILSLINUX_H
|
||||||
52
client/platforms/linux/daemon/linuxdaemon.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* 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 "linuxdaemon.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("LinuxDaemon");
|
||||||
|
LinuxDaemon* s_daemon = nullptr;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
|
||||||
|
MZ_COUNT_CTOR(LinuxDaemon);
|
||||||
|
|
||||||
|
logger.debug() << "Daemon created";
|
||||||
|
|
||||||
|
m_wgutils = new WireguardUtilsLinux(this);
|
||||||
|
m_dnsutils = new DnsUtilsLinux(this);
|
||||||
|
m_iputils = new IPUtilsLinux(this);
|
||||||
|
|
||||||
|
Q_ASSERT(s_daemon == nullptr);
|
||||||
|
s_daemon = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxDaemon::~LinuxDaemon() {
|
||||||
|
MZ_COUNT_DTOR(LinuxDaemon);
|
||||||
|
|
||||||
|
logger.debug() << "Daemon released";
|
||||||
|
|
||||||
|
Q_ASSERT(s_daemon == this);
|
||||||
|
s_daemon = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
LinuxDaemon* LinuxDaemon::instance() {
|
||||||
|
Q_ASSERT(s_daemon);
|
||||||
|
return s_daemon;
|
||||||
|
}
|
||||||
36
client/platforms/linux/daemon/linuxdaemon.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef LINUXDAEMON_H
|
||||||
|
#define LINUXDAEMON_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "daemon/daemon.h"
|
||||||
|
#include "dnsutilslinux.h"
|
||||||
|
#include "iputilslinux.h"
|
||||||
|
#include "wireguardutilslinux.h"
|
||||||
|
|
||||||
|
class LinuxDaemon final : public Daemon {
|
||||||
|
friend class IPUtilsMacos;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinuxDaemon();
|
||||||
|
~LinuxDaemon();
|
||||||
|
|
||||||
|
static LinuxDaemon* instance();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
|
bool supportDnsUtils() const override { return true; }
|
||||||
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
|
bool supportIPUtils() const override { return true; }
|
||||||
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
WireguardUtilsLinux* m_wgutils = nullptr;
|
||||||
|
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||||
|
IPUtilsLinux* m_iputils = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINUXDAEMON_H
|
||||||
354
client/platforms/linux/daemon/linuxroutemonitor.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
/* 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 "linuxroutemonitor.h"
|
||||||
|
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("LinuxRouteMonitor");
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct wg_allowedip {
|
||||||
|
uint16_t family;
|
||||||
|
union {
|
||||||
|
struct in_addr ip4;
|
||||||
|
struct in6_addr ip6;
|
||||||
|
};
|
||||||
|
uint8_t cidr;
|
||||||
|
struct wg_allowedip *next_allowedip;
|
||||||
|
} wg_allowedip;
|
||||||
|
|
||||||
|
constexpr const char* WG_INTERFACE = "amn0";
|
||||||
|
|
||||||
|
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
|
int attrtype, const void* attrdata,
|
||||||
|
size_t attrlen);
|
||||||
|
static void nlmsg_append_attr32(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
|
int attrtype, uint32_t value);
|
||||||
|
|
||||||
|
static bool buildAllowedIp(wg_allowedip* ip, const IPAddress& prefix);
|
||||||
|
|
||||||
|
|
||||||
|
LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
|
||||||
|
: QObject(parent), m_ifname(ifname) {
|
||||||
|
MZ_COUNT_CTOR(LinuxRouteMonitor);
|
||||||
|
logger.debug() << "LinuxRouteMonitor created.";
|
||||||
|
|
||||||
|
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||||
|
if (m_nlsock < 0) {
|
||||||
|
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_nl nladdr;
|
||||||
|
memset(&nladdr, 0, sizeof(nladdr));
|
||||||
|
nladdr.nl_family = AF_NETLINK;
|
||||||
|
nladdr.nl_pid = getpid();
|
||||||
|
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
||||||
|
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_notifier = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
|
||||||
|
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||||
|
&LinuxRouteMonitor::nlsockReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxRouteMonitor::~LinuxRouteMonitor() {
|
||||||
|
MZ_COUNT_DTOR(LinuxRouteMonitor);
|
||||||
|
if (m_nlsock >= 0) {
|
||||||
|
close(m_nlsock);
|
||||||
|
}
|
||||||
|
logger.debug() << "WireguardUtilsLinux destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare memory against zero.
|
||||||
|
static int memcmpzero(const void* data, size_t len) {
|
||||||
|
const quint8* ptr = static_cast<const quint8*>(data);
|
||||||
|
while (len--) {
|
||||||
|
if (*ptr++) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxRouteMonitor::insertRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Adding route to" << prefix.toString();
|
||||||
|
|
||||||
|
const int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
||||||
|
return rtmSendRoute(RTM_NEWROUTE, flags, RTN_UNICAST, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Removing route to" << prefix.toString();
|
||||||
|
|
||||||
|
const int flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||||
|
return rtmSendRoute(RTM_DELROUTE, flags, RTN_UNICAST, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Adding exclusion route for"
|
||||||
|
<< prefix.toString();
|
||||||
|
const int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
||||||
|
return rtmSendRoute(RTM_NEWROUTE, flags, RTN_THROW, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Removing exclusion route for"
|
||||||
|
<< prefix.toString();
|
||||||
|
const int flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||||
|
return rtmSendRoute(RTM_DELROUTE, flags, RTN_THROW, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||||
|
const IPAddress& prefix) {
|
||||||
|
constexpr size_t rtm_max_size = sizeof(struct rtmsg) +
|
||||||
|
2 * RTA_SPACE(sizeof(uint32_t)) +
|
||||||
|
RTA_SPACE(sizeof(struct in6_addr));
|
||||||
|
wg_allowedip ip;
|
||||||
|
if (!buildAllowedIp(&ip, prefix)) {
|
||||||
|
logger.warning() << "Invalid destination prefix";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[NLMSG_SPACE(rtm_max_size)];
|
||||||
|
struct nlmsghdr* nlmsg = reinterpret_cast<struct nlmsghdr*>(buf);
|
||||||
|
struct rtmsg* rtm = static_cast<struct rtmsg*>(NLMSG_DATA(nlmsg));
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||||
|
nlmsg->nlmsg_type = action;
|
||||||
|
nlmsg->nlmsg_flags = flags;
|
||||||
|
nlmsg->nlmsg_pid = getpid();
|
||||||
|
nlmsg->nlmsg_seq = m_nlseq++;
|
||||||
|
rtm->rtm_dst_len = ip.cidr;
|
||||||
|
rtm->rtm_family = ip.family;
|
||||||
|
rtm->rtm_type = type;
|
||||||
|
rtm->rtm_table = RT_TABLE_UNSPEC;
|
||||||
|
rtm->rtm_protocol = RTPROT_BOOT;
|
||||||
|
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
|
||||||
|
|
||||||
|
if (rtm->rtm_family == AF_INET6) {
|
||||||
|
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_DST, &ip.ip6, sizeof(ip.ip6));
|
||||||
|
} else {
|
||||||
|
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_DST, &ip.ip4, sizeof(ip.ip4));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rtm->rtm_type == RTN_UNICAST) {
|
||||||
|
int index = if_nametoindex(WG_INTERFACE);
|
||||||
|
|
||||||
|
if (index <= 0) {
|
||||||
|
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rtm->rtm_type == RTN_THROW) {
|
||||||
|
int index = if_nametoindex(getgatewayandiface().toUtf8());
|
||||||
|
if (index <= 0) {
|
||||||
|
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_nl nladdr;
|
||||||
|
memset(&nladdr, 0, sizeof(nladdr));
|
||||||
|
nladdr.nl_family = AF_NETLINK;
|
||||||
|
size_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||||
|
(struct sockaddr*)&nladdr, sizeof(nladdr));
|
||||||
|
|
||||||
|
return (result == nlmsg->nlmsg_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
|
int attrtype, const void* attrdata,
|
||||||
|
size_t attrlen) {
|
||||||
|
size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(attrlen);
|
||||||
|
if (newlen <= maxlen) {
|
||||||
|
char* buf = reinterpret_cast<char*>(nlmsg) + NLMSG_ALIGN(nlmsg->nlmsg_len);
|
||||||
|
struct rtattr* attr = reinterpret_cast<struct rtattr*>(buf);
|
||||||
|
attr->rta_type = attrtype;
|
||||||
|
attr->rta_len = RTA_LENGTH(attrlen);
|
||||||
|
memcpy(RTA_DATA(attr), attrdata, attrlen);
|
||||||
|
nlmsg->nlmsg_len = newlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nlmsg_append_attr32(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
|
int attrtype, uint32_t value) {
|
||||||
|
nlmsg_append_attr(nlmsg, maxlen, attrtype, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxRouteMonitor::nlsockReady() {
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t len = recv(m_nlsock, buf, sizeof(buf), MSG_DONTWAIT);
|
||||||
|
if (len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||||
|
while (NLMSG_OK(nlmsg, len)) {
|
||||||
|
if (nlmsg->nlmsg_type == NLMSG_DONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nlmsg->nlmsg_type != NLMSG_ERROR) {
|
||||||
|
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
struct nlmsgerr* err = static_cast<struct nlmsgerr*>(NLMSG_DATA(nlmsg));
|
||||||
|
if (err->error != 0) {
|
||||||
|
logger.debug() << "Netlink request failed:" << strerror(-err->error);
|
||||||
|
}
|
||||||
|
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 4096
|
||||||
|
|
||||||
|
QString LinuxRouteMonitor::getgatewayandiface()
|
||||||
|
{
|
||||||
|
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||||
|
int sock = -1, msgseq = 0;
|
||||||
|
struct nlmsghdr *nlh, *nlmsg;
|
||||||
|
struct rtmsg *route_entry;
|
||||||
|
// This struct contain route attributes (route type)
|
||||||
|
struct rtattr *route_attribute;
|
||||||
|
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||||
|
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||||
|
char *ptr = buffer;
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||||
|
perror("socket failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(msgbuf, 0, sizeof(msgbuf));
|
||||||
|
memset(gateway_address, 0, sizeof(gateway_address));
|
||||||
|
memset(interface, 0, sizeof(interface));
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
|
||||||
|
/* point the header and the msg structure pointers into the buffer */
|
||||||
|
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||||
|
|
||||||
|
/* Fill in the nlmsg header*/
|
||||||
|
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||||
|
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||||
|
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||||
|
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||||
|
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||||
|
|
||||||
|
/* 1 Sec Timeout to avoid stall */
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||||
|
/* send msg */
|
||||||
|
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||||
|
perror("send failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* receive response */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||||
|
if (received_bytes < 0) {
|
||||||
|
perror("Error in recv");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
|
|
||||||
|
/* Check if the header is valid */
|
||||||
|
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||||
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
|
{
|
||||||
|
perror("Error in received packet");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we received all data break */
|
||||||
|
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||||
|
break;
|
||||||
|
else {
|
||||||
|
ptr += received_bytes;
|
||||||
|
msg_len += received_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Break if its not a multi part message */
|
||||||
|
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||||
|
|
||||||
|
/* parse response */
|
||||||
|
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||||
|
{
|
||||||
|
/* Get the route data */
|
||||||
|
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||||
|
|
||||||
|
/* We are just interested in main routing table */
|
||||||
|
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||||
|
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||||
|
|
||||||
|
/* Loop through all attributes */
|
||||||
|
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||||
|
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||||
|
{
|
||||||
|
switch(route_attribute->rta_type) {
|
||||||
|
case RTA_OIF:
|
||||||
|
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||||
|
break;
|
||||||
|
case RTA_GATEWAY:
|
||||||
|
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||||
|
gateway_address, sizeof(gateway_address));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*gateway_address) && (*interface)) {
|
||||||
|
logger.debug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(sock);
|
||||||
|
return interface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool buildAllowedIp(wg_allowedip* ip,
|
||||||
|
const IPAddress& prefix) {
|
||||||
|
const char* addrString = qPrintable(prefix.address().toString());
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
ip->family = AF_INET;
|
||||||
|
ip->cidr = prefix.prefixLength();
|
||||||
|
return inet_pton(AF_INET, addrString, &ip->ip4) == 1;
|
||||||
|
}
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
ip->family = AF_INET6;
|
||||||
|
ip->cidr = prefix.prefixLength();
|
||||||
|
return inet_pton(AF_INET6, addrString, &ip->ip6) == 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
46
client/platforms/linux/daemon/linuxroutemonitor.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef LINUXROUTEMONITOR_H
|
||||||
|
#define LINUXROUTEMONITOR_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QList>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
|
#include "ipaddress.h"
|
||||||
|
|
||||||
|
|
||||||
|
class LinuxRouteMonitor final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinuxRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
||||||
|
~LinuxRouteMonitor();
|
||||||
|
|
||||||
|
bool insertRoute(const IPAddress& prefix);
|
||||||
|
bool deleteRoute(const IPAddress& prefix);
|
||||||
|
|
||||||
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
|
private:
|
||||||
|
static QString addrToString(const struct sockaddr* sa);
|
||||||
|
static QString addrToString(const QByteArray& data);
|
||||||
|
bool rtmSendRoute(int action, int flags, int type,
|
||||||
|
const IPAddress& prefix);
|
||||||
|
QString getgatewayandiface();
|
||||||
|
QString m_ifname;
|
||||||
|
unsigned int m_ifindex = 0;
|
||||||
|
int m_nlsock = -1;
|
||||||
|
int m_nlseq = 0;
|
||||||
|
QSocketNotifier* m_notifier = nullptr;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void nlsockReady();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINUXROUTEMONITOR_H
|
||||||
369
client/platforms/linux/daemon/wireguardutilslinux.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
/* 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 "wireguardutilslinux.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||||
|
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WireguardUtilsLinux");
|
||||||
|
Logger logwireguard("WireguardGo");
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent)
|
||||||
|
: WireguardUtils(parent), m_tunnel(this) {
|
||||||
|
MZ_COUNT_CTOR(WireguardUtilsLinux);
|
||||||
|
logger.debug() << "WireguardUtilsLinux created.";
|
||||||
|
|
||||||
|
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
|
||||||
|
SLOT(tunnelStdoutReady()));
|
||||||
|
connect(&m_tunnel, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
|
||||||
|
SLOT(tunnelErrorOccurred(QProcess::ProcessError)));
|
||||||
|
}
|
||||||
|
|
||||||
|
WireguardUtilsLinux::~WireguardUtilsLinux() {
|
||||||
|
MZ_COUNT_DTOR(WireguardUtilsLinux);
|
||||||
|
logger.debug() << "WireguardUtilsLinux destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void WireguardUtilsLinux::tunnelStdoutReady() {
|
||||||
|
for (;;) {
|
||||||
|
QByteArray line = m_tunnel.readLine();
|
||||||
|
if (line.length() <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logwireguard.debug() << QString::fromUtf8(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
|
||||||
|
logger.warning() << "Tunnel process encountered an error:" << error;
|
||||||
|
emit backendFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||||
|
Q_UNUSED(config);
|
||||||
|
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||||
|
logger.warning() << "Unable to start: tunnel process already running";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
if (!wgRuntimeDir.exists()) {
|
||||||
|
wgRuntimeDir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||||
|
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".sock");
|
||||||
|
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||||
|
#ifdef MZ_DEBUG
|
||||||
|
pe.insert("LOG_LEVEL", "debug");
|
||||||
|
#endif
|
||||||
|
m_tunnel.setProcessEnvironment(pe);
|
||||||
|
|
||||||
|
QDir appPath(QCoreApplication::applicationDirPath());
|
||||||
|
QStringList wgArgs = {"-f", "amn0"};
|
||||||
|
m_tunnel.start(appPath.filePath("../../client/bin/wireguard-go"), wgArgs);
|
||||||
|
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||||
|
logger.error() << "Unable to start tunnel process due to timeout";
|
||||||
|
m_tunnel.kill();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ifname = waitForTunnelName(wgNameFile);
|
||||||
|
if (m_ifname.isNull()) {
|
||||||
|
logger.error() << "Unable to read tunnel interface name";
|
||||||
|
m_tunnel.kill();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.debug() << "Created wireguard interface" << m_ifname;
|
||||||
|
|
||||||
|
// Start the routing table monitor.
|
||||||
|
m_rtmonitor = new LinuxRouteMonitor(m_ifname, this);
|
||||||
|
|
||||||
|
// Send a UAPI command to configure the interface
|
||||||
|
QString message("set=1\n");
|
||||||
|
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
|
||||||
|
QTextStream out(&message);
|
||||||
|
out << "private_key=" << QString(privateKey.toHex()) << "\n";
|
||||||
|
out << "replace_peers=true\n";
|
||||||
|
int err = uapiErrno(uapiCommand(message));
|
||||||
|
if (err != 0) {
|
||||||
|
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||||
|
}
|
||||||
|
return (err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::deleteInterface() {
|
||||||
|
if (m_rtmonitor) {
|
||||||
|
delete m_rtmonitor;
|
||||||
|
m_rtmonitor = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_tunnel.state() == QProcess::NotRunning) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to terminate gracefully.
|
||||||
|
m_tunnel.terminate();
|
||||||
|
if (!m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT)) {
|
||||||
|
m_tunnel.kill();
|
||||||
|
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Garbage collect.
|
||||||
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummy implementations for now
|
||||||
|
bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||||
|
QByteArray publicKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||||
|
|
||||||
|
logger.debug() << "Configuring peer" << config.m_serverPublicKey << "via" << config.m_serverIpv4AddrIn;
|
||||||
|
|
||||||
|
// Update/create the peer config
|
||||||
|
QString message;
|
||||||
|
QTextStream out(&message);
|
||||||
|
out << "set=1\n";
|
||||||
|
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||||
|
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||||
|
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||||
|
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||||
|
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||||
|
out << "endpoint=[" << config.m_serverIpv6AddrIn << "]:";
|
||||||
|
} else {
|
||||||
|
logger.warning() << "Failed to create peer with no endpoints";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out << config.m_serverPort << "\n";
|
||||||
|
|
||||||
|
out << "replace_allowed_ips=true\n";
|
||||||
|
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = uapiErrno(uapiCommand(message));
|
||||||
|
if (err != 0) {
|
||||||
|
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||||
|
}
|
||||||
|
return (err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
|
||||||
|
QByteArray publicKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString message;
|
||||||
|
QTextStream out(&message);
|
||||||
|
out << "set=1\n";
|
||||||
|
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||||
|
out << "remove=true\n";
|
||||||
|
|
||||||
|
int err = uapiErrno(uapiCommand(message));
|
||||||
|
if (err != 0) {
|
||||||
|
logger.error() << "Peer deletion failed:" << strerror(err);
|
||||||
|
}
|
||||||
|
return (err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
|
||||||
|
QString reply = uapiCommand("get=1");
|
||||||
|
PeerStatus status;
|
||||||
|
QList<PeerStatus> peerList;
|
||||||
|
for (const QString& line : reply.split('\n')) {
|
||||||
|
int eq = line.indexOf('=');
|
||||||
|
if (eq <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString name = line.left(eq);
|
||||||
|
QString value = line.mid(eq + 1);
|
||||||
|
|
||||||
|
if (name == "public_key") {
|
||||||
|
if (!status.m_pubkey.isEmpty()) {
|
||||||
|
peerList.append(status);
|
||||||
|
}
|
||||||
|
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
|
||||||
|
status = PeerStatus(pubkey.toBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "tx_bytes") {
|
||||||
|
status.m_txBytes = value.toDouble();
|
||||||
|
}
|
||||||
|
if (name == "rx_bytes") {
|
||||||
|
status.m_rxBytes = value.toDouble();
|
||||||
|
}
|
||||||
|
if (name == "last_handshake_time_sec") {
|
||||||
|
status.m_handshake += value.toLongLong() * 1000;
|
||||||
|
}
|
||||||
|
if (name == "last_handshake_time_nsec") {
|
||||||
|
status.m_handshake += value.toLongLong() / 1000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!status.m_pubkey.isEmpty()) {
|
||||||
|
peerList.append(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (prefix.prefixLength() > 0) {
|
||||||
|
return m_rtmonitor->insertRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we do not replace the default route.
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
return m_rtmonitor->insertRoute(IPAddress("0.0.0.0/1")) &&
|
||||||
|
m_rtmonitor->insertRoute(IPAddress("128.0.0.0/1"));
|
||||||
|
}
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
return m_rtmonitor->insertRoute(IPAddress("::/1")) &&
|
||||||
|
m_rtmonitor->insertRoute(IPAddress("8000::/1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (prefix.prefixLength() > 0) {
|
||||||
|
return m_rtmonitor->insertRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we do not replace the default route.
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
|
||||||
|
m_rtmonitor->deleteRoute(IPAddress("128.0.0.0/1"));
|
||||||
|
} else if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
return m_rtmonitor->deleteRoute(IPAddress("::/1")) &&
|
||||||
|
m_rtmonitor->deleteRoute(IPAddress("8000::/1"));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_rtmonitor->addExclusionRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||||
|
QLocalSocket socket;
|
||||||
|
QTimer uapiTimeout;
|
||||||
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||||
|
|
||||||
|
uapiTimeout.setSingleShot(true);
|
||||||
|
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||||
|
|
||||||
|
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||||
|
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||||
|
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||||
|
<< socket.errorString();
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message to the UAPI socket.
|
||||||
|
QByteArray message = command.toLocal8Bit();
|
||||||
|
while (!message.endsWith("\n\n")) {
|
||||||
|
message.append('\n');
|
||||||
|
}
|
||||||
|
socket.write(message);
|
||||||
|
|
||||||
|
QByteArray reply;
|
||||||
|
while (!reply.contains("\n\n")) {
|
||||||
|
if (!uapiTimeout.isActive()) {
|
||||||
|
logger.error() << "UAPI command timed out";
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
|
reply.append(socket.readAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString::fromUtf8(reply).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
int WireguardUtilsLinux::uapiErrno(const QString& reply) {
|
||||||
|
for (const QString& line : reply.split("\n")) {
|
||||||
|
int eq = line.indexOf('=');
|
||||||
|
if (eq <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.left(eq) == "errno") {
|
||||||
|
return line.mid(eq + 1).toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
||||||
|
QTimer timeout;
|
||||||
|
timeout.setSingleShot(true);
|
||||||
|
timeout.start(WG_TUN_PROC_TIMEOUT);
|
||||||
|
|
||||||
|
QFile file(filename);
|
||||||
|
|
||||||
|
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
|
QString ifname = "amn0";
|
||||||
|
|
||||||
|
// Test-connect to the UAPI socket.
|
||||||
|
QLocalSocket sock;
|
||||||
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
|
||||||
|
sock.connectToServer(sockName, QIODevice::ReadWrite);
|
||||||
|
if (sock.waitForConnected(100)) {
|
||||||
|
return ifname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
55
client/platforms/linux/daemon/wireguardutilslinux.h
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WIREGUARDUTILSLINUX_H
|
||||||
|
#define WIREGUARDUTILSLINUX_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#include "daemon/wireguardutils.h"
|
||||||
|
#include "linuxroutemonitor.h"
|
||||||
|
|
||||||
|
class WireguardUtilsLinux final : public WireguardUtils {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WireguardUtilsLinux(QObject* parent);
|
||||||
|
~WireguardUtilsLinux();
|
||||||
|
|
||||||
|
bool interfaceExists() override {
|
||||||
|
return m_tunnel.state() == QProcess::Running;
|
||||||
|
}
|
||||||
|
QString interfaceName() override { return m_ifname; }
|
||||||
|
bool addInterface(const InterfaceConfig& config) override;
|
||||||
|
bool deleteInterface() override;
|
||||||
|
|
||||||
|
bool updatePeer(const InterfaceConfig& config) override;
|
||||||
|
bool deletePeer(const InterfaceConfig& config) override;
|
||||||
|
QList<PeerStatus> getPeerStatus() override;
|
||||||
|
|
||||||
|
bool updateRoutePrefix(const IPAddress& prefix) override;
|
||||||
|
bool deleteRoutePrefix(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backendFailure();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void tunnelStdoutReady();
|
||||||
|
void tunnelErrorOccurred(QProcess::ProcessError error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString uapiCommand(const QString& command);
|
||||||
|
static int uapiErrno(const QString& command);
|
||||||
|
QString waitForTunnelName(const QString& filename);
|
||||||
|
|
||||||
|
QString m_ifname;
|
||||||
|
QProcess m_tunnel;
|
||||||
|
LinuxRouteMonitor* m_rtmonitor = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WIREGUARDUTILSLINUX_H
|
||||||
47
client/platforms/linux/interfaceconfig.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef INTERFACECONFIG_H
|
||||||
|
#define INTERFACECONFIG_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "ipaddress.h"
|
||||||
|
|
||||||
|
class QJsonObject;
|
||||||
|
|
||||||
|
class InterfaceConfig {
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
public:
|
||||||
|
InterfaceConfig() {}
|
||||||
|
|
||||||
|
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
|
||||||
|
Q_ENUM(HopType)
|
||||||
|
|
||||||
|
HopType m_hopType;
|
||||||
|
QString m_privateKey;
|
||||||
|
QString m_deviceIpv4Address;
|
||||||
|
QString m_deviceIpv6Address;
|
||||||
|
QString m_serverIpv4Gateway;
|
||||||
|
QString m_serverIpv6Gateway;
|
||||||
|
QString m_serverPublicKey;
|
||||||
|
QString m_serverIpv4AddrIn;
|
||||||
|
QString m_serverIpv6AddrIn;
|
||||||
|
QString m_dnsServer;
|
||||||
|
int m_serverPort = 0;
|
||||||
|
QList<IPAddress> m_allowedIPAddressRanges;
|
||||||
|
QStringList m_excludedAddresses;
|
||||||
|
QStringList m_vpnDisabledApps;
|
||||||
|
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||||
|
QString m_installationId;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
QString toWgConf(
|
||||||
|
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INTERFACECONFIG_H
|
||||||
139
client/platforms/linux/linuxdependencies.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/* 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 "linuxdependencies.h"
|
||||||
|
|
||||||
|
#include <mntent.h>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
//#include "dbusclient.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Logger logger("LinuxDependencies");
|
||||||
|
|
||||||
|
void showAlert(const QString& message) {
|
||||||
|
logger.debug() << "Show alert:" << message;
|
||||||
|
|
||||||
|
QMessageBox alert;
|
||||||
|
alert.setText(message);
|
||||||
|
alert.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkDaemonVersion() {
|
||||||
|
logger.debug() << "Check Daemon Version";
|
||||||
|
|
||||||
|
bool completed = false;
|
||||||
|
bool value = false;
|
||||||
|
|
||||||
|
while (!completed) {
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool LinuxDependencies::checkDependencies() {
|
||||||
|
char* path = getenv("PATH");
|
||||||
|
if (!path) {
|
||||||
|
showAlert("No PATH env found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkDaemonVersion()) {
|
||||||
|
showAlert("mozillavpn linuxdaemon needs to be updated or restarted.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
QString LinuxDependencies::findCgroupPath(const QString& type) {
|
||||||
|
struct mntent entry;
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
|
||||||
|
FILE* fp = fopen("/etc/mtab", "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
|
||||||
|
if (strcmp(entry.mnt_type, "cgroup") != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (hasmntopt(&entry, type.toLocal8Bit().constData()) != NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
return QString(entry.mnt_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
QString LinuxDependencies::findCgroup2Path() {
|
||||||
|
struct mntent entry;
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
|
||||||
|
FILE* fp = fopen("/etc/mtab", "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
|
||||||
|
if (strcmp(entry.mnt_type, "cgroup2") != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return QString(entry.mnt_dir);
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
QString LinuxDependencies::gnomeShellVersion() {
|
||||||
|
QDBusInterface iface("org.gnome.Shell", "/org/gnome/Shell",
|
||||||
|
"org.gnome.Shell");
|
||||||
|
if (!iface.isValid()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant shellVersion = iface.property("ShellVersion");
|
||||||
|
if (!shellVersion.isValid()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
return shellVersion.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
QString LinuxDependencies::kdeFrameworkVersion() {
|
||||||
|
QProcess proc;
|
||||||
|
proc.start("kf5-config", QStringList{"--version"}, QIODeviceBase::ReadOnly);
|
||||||
|
if (!proc.waitForFinished()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray result = proc.readAllStandardOutput();
|
||||||
|
for (const QByteArray& line : result.split('\n')) {
|
||||||
|
if (line.startsWith("KDE Frameworks: ")) {
|
||||||
|
return QString::fromUtf8(line.last(line.size() - 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
25
client/platforms/linux/linuxdependencies.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef LINUXDEPENDENCIES_H
|
||||||
|
#define LINUXDEPENDENCIES_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class LinuxDependencies final {
|
||||||
|
public:
|
||||||
|
static bool checkDependencies();
|
||||||
|
static QString findCgroupPath(const QString& type);
|
||||||
|
static QString findCgroup2Path();
|
||||||
|
static QString gnomeShellVersion();
|
||||||
|
static QString kdeFrameworkVersion();
|
||||||
|
|
||||||
|
private:
|
||||||
|
LinuxDependencies() = default;
|
||||||
|
~LinuxDependencies() = default;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(LinuxDependencies)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINUXDEPENDENCIES_H
|
||||||
57
client/platforms/linux/linuxnetworkwatcher.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* 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 "linuxnetworkwatcher.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "linuxnetworkwatcherworker.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("LinuxNetworkWatcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent)
|
||||||
|
: NetworkWatcherImpl(parent) {
|
||||||
|
MZ_COUNT_CTOR(LinuxNetworkWatcher);
|
||||||
|
|
||||||
|
m_thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxNetworkWatcher::~LinuxNetworkWatcher() {
|
||||||
|
MZ_COUNT_DTOR(LinuxNetworkWatcher);
|
||||||
|
|
||||||
|
delete m_worker;
|
||||||
|
|
||||||
|
m_thread.quit();
|
||||||
|
m_thread.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxNetworkWatcher::initialize() {
|
||||||
|
logger.debug() << "initialize";
|
||||||
|
|
||||||
|
m_worker = new LinuxNetworkWatcherWorker(&m_thread);
|
||||||
|
|
||||||
|
connect(this, &LinuxNetworkWatcher::checkDevicesInThread, m_worker,
|
||||||
|
&LinuxNetworkWatcherWorker::checkDevices);
|
||||||
|
|
||||||
|
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||||
|
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||||
|
|
||||||
|
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||||
|
// This is not strictly needed, but it's better for user experience because
|
||||||
|
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
||||||
|
// UI to appear and the first notification.
|
||||||
|
QTimer::singleShot(2000, this, [this]() {
|
||||||
|
QMetaObject::invokeMethod(m_worker, "initialize", Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxNetworkWatcher::start() {
|
||||||
|
logger.debug() << "actived";
|
||||||
|
NetworkWatcherImpl::start();
|
||||||
|
emit checkDevicesInThread();
|
||||||
|
}
|
||||||
38
client/platforms/linux/linuxnetworkwatcher.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef LINUXNETWORKWATCHER_H
|
||||||
|
#define LINUXNETWORKWATCHER_H
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "networkwatcherimpl.h"
|
||||||
|
|
||||||
|
class LinuxNetworkWatcherWorker;
|
||||||
|
|
||||||
|
class LinuxNetworkWatcher final : public NetworkWatcherImpl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LinuxNetworkWatcher(QObject* parent);
|
||||||
|
~LinuxNetworkWatcher();
|
||||||
|
|
||||||
|
void initialize() override;
|
||||||
|
|
||||||
|
void start() override;
|
||||||
|
|
||||||
|
NetworkWatcherImpl::TransportType getTransportType() {
|
||||||
|
// TODO: Find out how to do that on linux generally. (VPN-2382)
|
||||||
|
return NetworkWatcherImpl::TransportType_Unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void checkDevicesInThread();
|
||||||
|
|
||||||
|
private:
|
||||||
|
LinuxNetworkWatcherWorker* m_worker = nullptr;
|
||||||
|
QThread m_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINUXNETWORKWATCHER_H
|
||||||
177
client/platforms/linux/linuxnetworkwatcherworker.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/* 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 "linuxnetworkwatcherworker.h"
|
||||||
|
|
||||||
|
#include <QtDBus/QtDBus>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType
|
||||||
|
#ifndef NM_DEVICE_TYPE_WIFI
|
||||||
|
# define NM_DEVICE_TYPE_WIFI 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NM80211ApFlags
|
||||||
|
// Wifi network has no security
|
||||||
|
#ifndef NM_802_11_AP_SEC_NONE
|
||||||
|
# define NM_802_11_AP_SEC_NONE 0x00000000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Wifi network has WEP (40 bits)
|
||||||
|
#ifndef NM_802_11_AP_SEC_PAIR_WEP40
|
||||||
|
# define NM_802_11_AP_SEC_PAIR_WEP40 0x00000001
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Wifi network has WEP (104 bits)
|
||||||
|
#ifndef NM_802_11_AP_SEC_PAIR_WEP104
|
||||||
|
# define NM_802_11_AP_SEC_PAIR_WEP104 0x00000002
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
||||||
|
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
||||||
|
|
||||||
|
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("LinuxNetworkWatcherWorker");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool checkUnsecureFlags(int rsnFlags, int wpaFlags) {
|
||||||
|
// If neither WPA nor WPA2/RSN are supported, then the network is unencrypted
|
||||||
|
if (rsnFlags == NM_802_11_AP_SEC_NONE && wpaFlags == NM_802_11_AP_SEC_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider the user of weak cryptography to be unsecure
|
||||||
|
if ((rsnFlags & NM_802_11_AP_SEC_WEAK_CRYPTO) ||
|
||||||
|
(wpaFlags & NM_802_11_AP_SEC_WEAK_CRYPTO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Otherwise, the network is secured with reasonable cryptography
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxNetworkWatcherWorker::LinuxNetworkWatcherWorker(QThread* thread) {
|
||||||
|
MZ_COUNT_CTOR(LinuxNetworkWatcherWorker);
|
||||||
|
moveToThread(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxNetworkWatcherWorker::~LinuxNetworkWatcherWorker() {
|
||||||
|
MZ_COUNT_DTOR(LinuxNetworkWatcherWorker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxNetworkWatcherWorker::initialize() {
|
||||||
|
logger.debug() << "initialize";
|
||||||
|
|
||||||
|
logger.debug()
|
||||||
|
<< "Retrieving the list of wifi network devices from NetworkManager";
|
||||||
|
|
||||||
|
// To know the NeworkManager DBus methods and properties, read the official
|
||||||
|
// documentation:
|
||||||
|
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
||||||
|
|
||||||
|
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
|
||||||
|
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
||||||
|
if (!nm.isValid()) {
|
||||||
|
logger.error()
|
||||||
|
<< "Failed to connect to the network manager via system dbus";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage msg = nm.call("GetDevices");
|
||||||
|
QDBusArgument arg = msg.arguments().at(0).value<QDBusArgument>();
|
||||||
|
if (arg.currentType() != QDBusArgument::ArrayType) {
|
||||||
|
logger.error() << "Expected an array of devices";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QDBusObjectPath> paths = qdbus_cast<QList<QDBusObjectPath> >(arg);
|
||||||
|
for (const QDBusObjectPath& path : paths) {
|
||||||
|
QString devicePath = path.path();
|
||||||
|
QDBusInterface device(DBUS_NETWORKMANAGER, devicePath,
|
||||||
|
"org.freedesktop.NetworkManager.Device",
|
||||||
|
QDBusConnection::systemBus());
|
||||||
|
if (device.property("DeviceType").toInt() != NM_DEVICE_TYPE_WIFI) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Found a wifi device:" << devicePath;
|
||||||
|
m_devicePaths.append(devicePath);
|
||||||
|
|
||||||
|
// Here we monitor the changes.
|
||||||
|
QDBusConnection::systemBus().connect(
|
||||||
|
DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.DBus.Properties",
|
||||||
|
"PropertiesChanged", this,
|
||||||
|
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_devicePaths.isEmpty()) {
|
||||||
|
logger.warning() << "No wifi devices found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could be already be activated.
|
||||||
|
checkDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxNetworkWatcherWorker::propertyChanged(QString interface,
|
||||||
|
QVariantMap properties,
|
||||||
|
QStringList list) {
|
||||||
|
Q_UNUSED(list);
|
||||||
|
|
||||||
|
logger.debug() << "Properties changed for interface" << interface;
|
||||||
|
|
||||||
|
if (!properties.contains("ActiveAccessPoint")) {
|
||||||
|
logger.debug() << "Access point did not changed. Ignoring the changes";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinuxNetworkWatcherWorker::checkDevices() {
|
||||||
|
logger.debug() << "Checking devices";
|
||||||
|
|
||||||
|
for (const QString& devicePath : m_devicePaths) {
|
||||||
|
QDBusInterface wifiDevice(DBUS_NETWORKMANAGER, devicePath,
|
||||||
|
"org.freedesktop.NetworkManager.Device.Wireless",
|
||||||
|
QDBusConnection::systemBus());
|
||||||
|
|
||||||
|
// Check the access point path
|
||||||
|
QString accessPointPath = wifiDevice.property("ActiveAccessPoint")
|
||||||
|
.value<QDBusObjectPath>()
|
||||||
|
.path();
|
||||||
|
if (accessPointPath.isEmpty()) {
|
||||||
|
logger.warning() << "No access point found";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface ap(DBUS_NETWORKMANAGER, accessPointPath,
|
||||||
|
"org.freedesktop.NetworkManager.AccessPoint",
|
||||||
|
QDBusConnection::systemBus());
|
||||||
|
|
||||||
|
QVariant rsnFlags = ap.property("RsnFlags");
|
||||||
|
QVariant wpaFlags = ap.property("WpaFlags");
|
||||||
|
if (!rsnFlags.isValid() || !wpaFlags.isValid()) {
|
||||||
|
// We are probably not connected.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkUnsecureFlags(rsnFlags.toInt(), wpaFlags.toInt())) {
|
||||||
|
QString ssid = ap.property("Ssid").toString();
|
||||||
|
QString bssid = ap.property("HwAddress").toString();
|
||||||
|
|
||||||
|
// We have found 1 unsecured network. We don't need to check other wifi
|
||||||
|
// network devices.
|
||||||
|
logger.warning() << "Unsecured AP detected!"
|
||||||
|
<< "rsnFlags:" << rsnFlags.toInt()
|
||||||
|
<< "wpaFlags:" << wpaFlags.toInt()
|
||||||
|
<< "ssid:" << logger.sensitive(ssid);
|
||||||
|
emit unsecuredNetwork(ssid, bssid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
client/platforms/linux/linuxnetworkwatcherworker.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef LINUXNETWORKWATCHERWORKER_H
|
||||||
|
#define LINUXNETWORKWATCHERWORKER_H
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
class QThread;
|
||||||
|
|
||||||
|
class LinuxNetworkWatcherWorker final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(LinuxNetworkWatcherWorker)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LinuxNetworkWatcherWorker(QThread* thread);
|
||||||
|
~LinuxNetworkWatcherWorker();
|
||||||
|
|
||||||
|
void checkDevices();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void propertyChanged(QString interface, QVariantMap properties,
|
||||||
|
QStringList list);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// We collect the list of DBus wifi network device paths during the
|
||||||
|
// initialization. When a property of them changes, we check if the access
|
||||||
|
// point is active and unsecure.
|
||||||
|
QStringList m_devicePaths;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LINUXNETWORKWATCHERWORKER_H
|
||||||
|
|
@ -124,26 +124,23 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
|
||||||
const struct sockaddr* dst =
|
const struct sockaddr* dst =
|
||||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||||
unsigned int plen;
|
|
||||||
if (dst->sa_family == AF_INET) {
|
if (dst->sa_family == AF_INET) {
|
||||||
m_defaultGatewayIpv4.clear();
|
m_defaultGatewayIpv4.clear();
|
||||||
m_defaultIfindexIpv4 = 0;
|
m_defaultIfindexIpv4 = 0;
|
||||||
protocol = QAbstractSocket::IPv4Protocol;
|
protocol = QAbstractSocket::IPv4Protocol;
|
||||||
plen = 32;
|
|
||||||
} else if (dst->sa_family == AF_INET6) {
|
} else if (dst->sa_family == AF_INET6) {
|
||||||
m_defaultGatewayIpv6.clear();
|
m_defaultGatewayIpv6.clear();
|
||||||
m_defaultIfindexIpv6 = 0;
|
m_defaultIfindexIpv6 = 0;
|
||||||
protocol = QAbstractSocket::IPv6Protocol;
|
protocol = QAbstractSocket::IPv6Protocol;
|
||||||
plen = 128;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "Lost default route via" << ifname
|
logger.debug() << "Lost default route via" << ifname
|
||||||
<< logger.sensitive(addrToString(addrlist[1]));
|
<< logger.sensitive(addrToString(addrlist[1]));
|
||||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||||
if (addr.protocol() == protocol) {
|
if (prefix.address().protocol() == protocol) {
|
||||||
logger.debug() << "Removing exclusion route to"
|
logger.debug() << "Removing exclusion route to"
|
||||||
<< logger.sensitive(addr.toString());
|
<< logger.sensitive(prefix.toString());
|
||||||
rtmSendRoute(RTM_DELETE, addr, plen, rtm->rtm_index, nullptr);
|
rtmSendRoute(RTM_DELETE, prefix, rtm->rtm_index, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +224,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||||
const struct sockaddr* dst =
|
const struct sockaddr* dst =
|
||||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||||
unsigned int plen;
|
|
||||||
int rtm_type = RTM_ADD;
|
int rtm_type = RTM_ADD;
|
||||||
if (dst->sa_family == AF_INET) {
|
if (dst->sa_family == AF_INET) {
|
||||||
if (m_defaultIfindexIpv4 != 0) {
|
if (m_defaultIfindexIpv4 != 0) {
|
||||||
|
|
@ -236,7 +232,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||||
m_defaultGatewayIpv4 = addrlist[1];
|
m_defaultGatewayIpv4 = addrlist[1];
|
||||||
m_defaultIfindexIpv4 = ifindex;
|
m_defaultIfindexIpv4 = ifindex;
|
||||||
protocol = QAbstractSocket::IPv4Protocol;
|
protocol = QAbstractSocket::IPv4Protocol;
|
||||||
plen = 32;
|
|
||||||
} else if (dst->sa_family == AF_INET6) {
|
} else if (dst->sa_family == AF_INET6) {
|
||||||
if (m_defaultIfindexIpv6 != 0) {
|
if (m_defaultIfindexIpv6 != 0) {
|
||||||
rtm_type = RTM_CHANGE;
|
rtm_type = RTM_CHANGE;
|
||||||
|
|
@ -244,7 +239,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||||
m_defaultGatewayIpv6 = addrlist[1];
|
m_defaultGatewayIpv6 = addrlist[1];
|
||||||
m_defaultIfindexIpv6 = ifindex;
|
m_defaultIfindexIpv6 = ifindex;
|
||||||
protocol = QAbstractSocket::IPv6Protocol;
|
protocol = QAbstractSocket::IPv6Protocol;
|
||||||
plen = 128;
|
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -252,11 +246,11 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||||
// Update the exclusion routes with the new default route.
|
// Update the exclusion routes with the new default route.
|
||||||
logger.debug() << "Updating default route via" << ifname
|
logger.debug() << "Updating default route via" << ifname
|
||||||
<< addrToString(addrlist[1]);
|
<< addrToString(addrlist[1]);
|
||||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||||
if (addr.protocol() == protocol) {
|
if (prefix.address().protocol() == protocol) {
|
||||||
logger.debug() << "Updating exclusion route to"
|
logger.debug() << "Updating exclusion route to"
|
||||||
<< logger.sensitive(addr.toString());
|
<< logger.sensitive(prefix.toString());
|
||||||
rtmSendRoute(rtm_type, addr, plen, ifindex, addrlist[1].constData());
|
rtmSendRoute(rtm_type, prefix, ifindex, addrlist[1].constData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -353,8 +347,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||||
unsigned int plen, unsigned int ifindex,
|
unsigned int ifindex,
|
||||||
const void* gateway) {
|
const void* gateway) {
|
||||||
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
||||||
sizeof(struct sockaddr_in6) * 2 +
|
sizeof(struct sockaddr_in6) * 2 +
|
||||||
|
|
@ -375,9 +369,9 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
||||||
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
|
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
|
||||||
|
|
||||||
// Append RTA_DST
|
// Append RTA_DST
|
||||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
struct sockaddr_in6 sin6;
|
struct sockaddr_in6 sin6;
|
||||||
Q_IPV6ADDR dst = prefix.toIPv6Address();
|
Q_IPV6ADDR dst = prefix.address().toIPv6Address();
|
||||||
memset(&sin6, 0, sizeof(sin6));
|
memset(&sin6, 0, sizeof(sin6));
|
||||||
sin6.sin6_family = AF_INET6;
|
sin6.sin6_family = AF_INET6;
|
||||||
sin6.sin6_len = sizeof(sin6);
|
sin6.sin6_len = sizeof(sin6);
|
||||||
|
|
@ -385,7 +379,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
||||||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
|
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
|
||||||
} else {
|
} else {
|
||||||
struct sockaddr_in sin;
|
struct sockaddr_in sin;
|
||||||
quint32 dst = prefix.toIPv4Address();
|
quint32 dst = prefix.address().toIPv4Address();
|
||||||
memset(&sin, 0, sizeof(sin));
|
memset(&sin, 0, sizeof(sin));
|
||||||
sin.sin_family = AF_INET;
|
sin.sin_family = AF_INET;
|
||||||
sin.sin_len = sizeof(sin);
|
sin.sin_len = sizeof(sin);
|
||||||
|
|
@ -403,7 +397,8 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append RTA_NETMASK
|
// Append RTA_NETMASK
|
||||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
unsigned int plen = prefix.prefixLength();
|
||||||
|
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
struct sockaddr_in6 sin6;
|
struct sockaddr_in6 sin6;
|
||||||
memset(&sin6, 0, sizeof(sin6));
|
memset(&sin6, 0, sizeof(sin6));
|
||||||
sin6.sin6_family = AF_INET6;
|
sin6.sin6_family = AF_INET6;
|
||||||
|
|
@ -413,7 +408,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
||||||
sin6.sin6_addr.s6_addr[plen / 8] = 0xFF ^ (0xFF >> (plen % 8));
|
sin6.sin6_addr.s6_addr[plen / 8] = 0xFF ^ (0xFF >> (plen % 8));
|
||||||
}
|
}
|
||||||
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
|
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
|
||||||
} else if (prefix.protocol() == QAbstractSocket::IPv4Protocol) {
|
} else if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
struct sockaddr_in sin;
|
struct sockaddr_in sin;
|
||||||
memset(&sin, 0, sizeof(sin));
|
memset(&sin, 0, sizeof(sin));
|
||||||
sin.sin_family = AF_INET;
|
sin.sin_family = AF_INET;
|
||||||
|
|
@ -497,34 +492,32 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
||||||
datalink.sdl_slen = 0;
|
datalink.sdl_slen = 0;
|
||||||
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
||||||
|
|
||||||
return rtmSendRoute(RTM_ADD, prefix.address(), prefix.prefixLength(),
|
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
|
||||||
m_ifindex, &datalink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
||||||
return rtmSendRoute(RTM_DELETE, prefix.address(), prefix.prefixLength(),
|
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
|
||||||
m_ifindex, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
|
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
logger.debug() << "Adding exclusion route for"
|
logger.debug() << "Adding exclusion route for"
|
||||||
<< logger.sensitive(address.toString());
|
<< logger.sensitive(prefix.toString());
|
||||||
|
|
||||||
if (m_exclusionRoutes.contains(address)) {
|
if (m_exclusionRoutes.contains(prefix)) {
|
||||||
logger.warning() << "Exclusion route already exists";
|
logger.warning() << "Exclusion route already exists";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_exclusionRoutes.append(address);
|
m_exclusionRoutes.append(prefix);
|
||||||
|
|
||||||
// If the default route is known, then updte the routing table immediately.
|
// If the default route is known, then updte the routing table immediately.
|
||||||
if ((address.protocol() == QAbstractSocket::IPv4Protocol) &&
|
if ((prefix.address().protocol() == QAbstractSocket::IPv4Protocol) &&
|
||||||
(m_defaultIfindexIpv4 != 0) && !m_defaultGatewayIpv4.isEmpty()) {
|
(m_defaultIfindexIpv4 != 0) && !m_defaultGatewayIpv4.isEmpty()) {
|
||||||
return rtmSendRoute(RTM_ADD, address, 32, m_defaultIfindexIpv4,
|
return rtmSendRoute(RTM_ADD, prefix, m_defaultIfindexIpv4,
|
||||||
m_defaultGatewayIpv4.constData());
|
m_defaultGatewayIpv4.constData());
|
||||||
}
|
}
|
||||||
if ((address.protocol() == QAbstractSocket::IPv6Protocol) &&
|
if ((prefix.address().protocol() == QAbstractSocket::IPv6Protocol) &&
|
||||||
(m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) {
|
(m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) {
|
||||||
return rtmSendRoute(RTM_ADD, address, 128, m_defaultIfindexIpv6,
|
return rtmSendRoute(RTM_ADD, prefix, m_defaultIfindexIpv6,
|
||||||
m_defaultGatewayIpv6.constData());
|
m_defaultGatewayIpv6.constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,16 +525,15 @@ bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
|
bool MacosRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
logger.debug() << "Deleting exclusion route for"
|
logger.debug() << "Deleting exclusion route for"
|
||||||
<< logger.sensitive(address.toString());
|
<< logger.sensitive(prefix.toString());
|
||||||
|
|
||||||
m_exclusionRoutes.removeAll(address);
|
m_exclusionRoutes.removeAll(prefix);
|
||||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
return rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
return rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv4, nullptr);
|
||||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
} else if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
return rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6,
|
return rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv6, nullptr);
|
||||||
nullptr);
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -549,11 +541,11 @@ bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
|
||||||
|
|
||||||
void MacosRouteMonitor::flushExclusionRoutes() {
|
void MacosRouteMonitor::flushExclusionRoutes() {
|
||||||
while (!m_exclusionRoutes.isEmpty()) {
|
while (!m_exclusionRoutes.isEmpty()) {
|
||||||
QHostAddress address = m_exclusionRoutes.takeFirst();
|
IPAddress prefix = m_exclusionRoutes.takeFirst();
|
||||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv4, nullptr);
|
||||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
} else if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, nullptr);
|
rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv6, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,16 @@ class MacosRouteMonitor final : public QObject {
|
||||||
bool deleteRoute(const IPAddress& prefix);
|
bool deleteRoute(const IPAddress& prefix);
|
||||||
int interfaceFlags() { return m_ifflags; }
|
int interfaceFlags() { return m_ifflags; }
|
||||||
|
|
||||||
bool addExclusionRoute(const QHostAddress& address);
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
bool deleteExclusionRoute(const QHostAddress& address);
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
void flushExclusionRoutes();
|
void flushExclusionRoutes();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleRtmDelete(const struct rt_msghdr* msg, const QByteArray& payload);
|
void handleRtmDelete(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||||
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||||
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
||||||
bool rtmSendRoute(int action, const QHostAddress& prefix, unsigned int plen,
|
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
|
||||||
unsigned int ifindex, const void* gateway);
|
const void* gateway);
|
||||||
bool rtmFetchRoutes(int family);
|
bool rtmFetchRoutes(int family);
|
||||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||||
const void* sa);
|
const void* sa);
|
||||||
|
|
@ -50,7 +50,7 @@ class MacosRouteMonitor final : public QObject {
|
||||||
static QString addrToString(const struct sockaddr* sa);
|
static QString addrToString(const struct sockaddr* sa);
|
||||||
static QString addrToString(const QByteArray& data);
|
static QString addrToString(const QByteArray& data);
|
||||||
|
|
||||||
QList<QHostAddress> m_exclusionRoutes;
|
QList<IPAddress> m_exclusionRoutes;
|
||||||
QByteArray m_defaultGatewayIpv4;
|
QByteArray m_defaultGatewayIpv4;
|
||||||
QByteArray m_defaultGatewayIpv6;
|
QByteArray m_defaultGatewayIpv6;
|
||||||
unsigned int m_defaultIfindexIpv4 = 0;
|
unsigned int m_defaultIfindexIpv4 = 0;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||||
// Send a UAPI command to configure the interface
|
// Send a UAPI command to configure the interface
|
||||||
QString message("set=1\n");
|
QString message("set=1\n");
|
||||||
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
|
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
|
||||||
|
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
out << "private_key=" << QString(privateKey.toHex()) << "\n";
|
out << "private_key=" << QString(privateKey.toHex()) << "\n";
|
||||||
out << "replace_peers=true\n";
|
out << "replace_peers=true\n";
|
||||||
|
|
@ -133,9 +132,14 @@ bool WireguardUtilsMacos::deleteInterface() {
|
||||||
|
|
||||||
// dummy implementations for now
|
// dummy implementations for now
|
||||||
bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||||
QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray publicKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||||
|
|
||||||
|
logger.debug() << "Configuring peer" << config.m_serverPublicKey
|
||||||
|
<< "via" << config.m_serverIpv4AddrIn;
|
||||||
|
|
||||||
// Update/create the peer config
|
// Update/create the peer config
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
|
|
@ -150,6 +154,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||||
logger.warning() << "Failed to create peer with no endpoints";
|
logger.warning() << "Failed to create peer with no endpoints";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
out << config.m_serverPort << "\n";
|
out << config.m_serverPort << "\n";
|
||||||
|
|
||||||
out << "replace_allowed_ips=true\n";
|
out << "replace_allowed_ips=true\n";
|
||||||
|
|
@ -158,7 +163,13 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||||
out << "allowed_ip=" << ip.toString() << "\n";
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << message;
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||||
|
|
@ -170,6 +181,13 @@ bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
||||||
QByteArray publicKey =
|
QByteArray publicKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
out << "set=1\n";
|
out << "set=1\n";
|
||||||
|
|
@ -223,9 +241,7 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsMacos::getPeerStatus() {
|
||||||
return peerList;
|
return peerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
|
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
int hopindex) {
|
|
||||||
Q_UNUSED(hopindex);
|
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -246,9 +262,7 @@ bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix,
|
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
int hopindex) {
|
|
||||||
Q_UNUSED(hopindex);
|
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -268,18 +282,18 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsMacos::addExclusionRoute(const QHostAddress& address) {
|
bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return m_rtmonitor->addExclusionRoute(address);
|
return m_rtmonitor->addExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsMacos::deleteExclusionRoute(const QHostAddress& address) {
|
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return m_rtmonitor->deleteExclusionRoute(address);
|
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
||||||
bool deletePeer(const InterfaceConfig& config) override;
|
bool deletePeer(const InterfaceConfig& config) override;
|
||||||
QList<PeerStatus> getPeerStatus() override;
|
QList<PeerStatus> getPeerStatus() override;
|
||||||
|
|
||||||
bool updateRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
bool updateRoutePrefix(const IPAddress& prefix) override;
|
||||||
bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
bool deleteRoutePrefix(const IPAddress& prefix) override;
|
||||||
|
|
||||||
bool addExclusionRoute(const QHostAddress& address) override;
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
bool deleteExclusionRoute(const QHostAddress& address) override;
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ bool dockClickHandler(id self, SEL cmd, ...) {
|
||||||
Q_UNUSED(cmd);
|
Q_UNUSED(cmd);
|
||||||
|
|
||||||
logger.debug() << "Dock icon clicked.";
|
logger.debug() << "Dock icon clicked.";
|
||||||
|
|
||||||
//TODO IMPL FOR AMNEZIA
|
//TODO IMPL FOR AMNEZIA
|
||||||
//QmlEngineHolder::instance()->showWindow();
|
//QmlEngineHolder::instance()->showWindow();
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
|
||||||
177
client/platforms/windows/daemon/dnsutilswindows.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/* 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 "dnsutilswindows.h"
|
||||||
|
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
constexpr uint32_t WINDOWS_NETSH_TIMEOUT_MSEC = 2000;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("DnsUtilsWindows");
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsUtilsWindows::DnsUtilsWindows(QObject* parent) : DnsUtils(parent) {
|
||||||
|
MZ_COUNT_CTOR(DnsUtilsWindows);
|
||||||
|
logger.debug() << "DnsUtilsWindows created.";
|
||||||
|
|
||||||
|
typedef DWORD WindowsSetDnsCallType(GUID, const void*);
|
||||||
|
HMODULE library = LoadLibrary(TEXT("iphlpapi.dll"));
|
||||||
|
if (library) {
|
||||||
|
m_setInterfaceDnsSettingsProcAddr = (WindowsSetDnsCallType*)GetProcAddress(
|
||||||
|
library, "SetInterfaceDnsSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsUtilsWindows::~DnsUtilsWindows() {
|
||||||
|
MZ_COUNT_DTOR(DnsUtilsWindows);
|
||||||
|
restoreResolvers();
|
||||||
|
logger.debug() << "DnsUtilsWindows destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DnsUtilsWindows::updateResolvers(const QString& ifname,
|
||||||
|
const QList<QHostAddress>& resolvers) {
|
||||||
|
NET_LUID luid;
|
||||||
|
if (ConvertInterfaceAliasToLuid((wchar_t*)ifname.utf16(), &luid) != 0) {
|
||||||
|
logger.error() << "Failed to resolve LUID for" << ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_luid = luid.Value;
|
||||||
|
|
||||||
|
logger.debug() << "Configuring DNS for" << ifname;
|
||||||
|
if (m_setInterfaceDnsSettingsProcAddr == nullptr) {
|
||||||
|
return updateResolversNetsh(resolvers);
|
||||||
|
}
|
||||||
|
return updateResolversWin32(resolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DnsUtilsWindows::updateResolversWin32(
|
||||||
|
const QList<QHostAddress>& resolvers) {
|
||||||
|
GUID guid;
|
||||||
|
NET_LUID luid;
|
||||||
|
luid.Value = m_luid;
|
||||||
|
if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to resolve GUID";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList v4resolvers;
|
||||||
|
QStringList v6resolvers;
|
||||||
|
for (const QHostAddress& addr : resolvers) {
|
||||||
|
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
v4resolvers.append(addr.toString());
|
||||||
|
}
|
||||||
|
if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
v6resolvers.append(addr.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_INTERFACE_SETTINGS settings;
|
||||||
|
settings.Version = DNS_INTERFACE_SETTINGS_VERSION1;
|
||||||
|
settings.Flags = DNS_SETTING_NAMESERVER | DNS_SETTING_SEARCHLIST;
|
||||||
|
settings.Domain = nullptr;
|
||||||
|
settings.NameServer = nullptr;
|
||||||
|
settings.SearchList = (wchar_t*)L".";
|
||||||
|
settings.RegistrationEnabled = false;
|
||||||
|
settings.RegisterAdapterName = false;
|
||||||
|
settings.EnableLLMNR = false;
|
||||||
|
settings.QueryAdapterName = false;
|
||||||
|
settings.ProfileNameServer = nullptr;
|
||||||
|
|
||||||
|
// Configure nameservers for IPv4
|
||||||
|
QString v4resolverstring = v4resolvers.join(",");
|
||||||
|
settings.NameServer = (wchar_t*)v4resolverstring.utf16();
|
||||||
|
DWORD v4result = m_setInterfaceDnsSettingsProcAddr(guid, &settings);
|
||||||
|
if (v4result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to configure IPv4 resolvers:" << v4result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure nameservers for IPv6
|
||||||
|
QString v6resolverstring = v6resolvers.join(",");
|
||||||
|
settings.Flags |= DNS_SETTING_IPV6;
|
||||||
|
settings.NameServer = (wchar_t*)v6resolverstring.utf16();
|
||||||
|
DWORD v6result = m_setInterfaceDnsSettingsProcAddr(guid, &settings);
|
||||||
|
if (v6result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to configure IPv6 resolvers" << v6result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((v4result == NO_ERROR) && (v6result == NO_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char* netshFlushTemplate =
|
||||||
|
"interface %1 set dnsservers name=%2 address=none valdiate=no "
|
||||||
|
"register=both\r\n";
|
||||||
|
constexpr const char* netshAddTemplate =
|
||||||
|
"interface %1 add dnsservers name=%2 address=%3 validate=no\r\n";
|
||||||
|
|
||||||
|
bool DnsUtilsWindows::updateResolversNetsh(
|
||||||
|
const QList<QHostAddress>& resolvers) {
|
||||||
|
QProcess netsh;
|
||||||
|
NET_LUID luid;
|
||||||
|
NET_IFINDEX ifindex;
|
||||||
|
luid.Value = m_luid;
|
||||||
|
if (ConvertInterfaceLuidToIndex(&luid, &ifindex) != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to resolve GUID";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
netsh.setProgram("netsh");
|
||||||
|
netsh.start();
|
||||||
|
if (!netsh.waitForStarted(WINDOWS_NETSH_TIMEOUT_MSEC)) {
|
||||||
|
logger.error() << "Failed to start netsh";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream cmdstream(&netsh);
|
||||||
|
|
||||||
|
// Flush DNS servers
|
||||||
|
QString v4flush = QString(netshFlushTemplate).arg("ipv4").arg(ifindex);
|
||||||
|
QString v6flush = QString(netshFlushTemplate).arg("ipv6").arg(ifindex);
|
||||||
|
logger.debug() << "netsh write:" << v4flush.trimmed();
|
||||||
|
cmdstream << v4flush;
|
||||||
|
logger.debug() << "netsh write:" << v6flush.trimmed();
|
||||||
|
cmdstream << v6flush;
|
||||||
|
|
||||||
|
// Add new DNS servers
|
||||||
|
for (const QHostAddress& addr : resolvers) {
|
||||||
|
const char* family = "ipv4";
|
||||||
|
if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
family = "ipv6";
|
||||||
|
}
|
||||||
|
QString nsAddr = addr.toString();
|
||||||
|
QString nsCommand =
|
||||||
|
QString(netshAddTemplate).arg(family).arg(ifindex).arg(nsAddr);
|
||||||
|
logger.debug() << "netsh write:" << nsCommand.trimmed();
|
||||||
|
cmdstream << nsCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit and cleanup netsh
|
||||||
|
cmdstream << "exit\r\n";
|
||||||
|
cmdstream.flush();
|
||||||
|
if (!netsh.waitForFinished(WINDOWS_NETSH_TIMEOUT_MSEC)) {
|
||||||
|
logger.error() << "Failed to exit netsh";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return netsh.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DnsUtilsWindows::restoreResolvers() {
|
||||||
|
if (m_luid == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> empty;
|
||||||
|
if (m_setInterfaceDnsSettingsProcAddr == nullptr) {
|
||||||
|
return updateResolversNetsh(empty);
|
||||||
|
}
|
||||||
|
return updateResolversWin32(empty);
|
||||||
|
}
|
||||||
34
client/platforms/windows/daemon/dnsutilswindows.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef DNSUTILSWINDOWS_H
|
||||||
|
#define DNSUTILSWINDOWS_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "daemon/dnsutils.h"
|
||||||
|
|
||||||
|
class DnsUtilsWindows final : public DnsUtils {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(DnsUtilsWindows)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DnsUtilsWindows(QObject* parent);
|
||||||
|
virtual ~DnsUtilsWindows();
|
||||||
|
bool updateResolvers(const QString& ifname,
|
||||||
|
const QList<QHostAddress>& resolvers) override;
|
||||||
|
bool restoreResolvers() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
quint64 m_luid = 0;
|
||||||
|
DWORD (*m_setInterfaceDnsSettingsProcAddr)(GUID, const void*) = nullptr;
|
||||||
|
|
||||||
|
bool updateResolversWin32(const QList<QHostAddress>& resolvers);
|
||||||
|
bool updateResolversNetsh(const QList<QHostAddress>& resolvers);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DNSUTILSWINDOWS_H
|
||||||
81
client/platforms/windows/daemon/windowsdaemon.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* 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 "windowsdaemon.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include "dnsutilswindows.h"
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/windowscommons.h"
|
||||||
|
#include "platforms/windows/windowsservicemanager.h"
|
||||||
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsDaemon");
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) {
|
||||||
|
MZ_COUNT_CTOR(WindowsDaemon);
|
||||||
|
|
||||||
|
m_wgutils = new WireguardUtilsWindows(this);
|
||||||
|
m_dnsutils = new DnsUtilsWindows(this);
|
||||||
|
|
||||||
|
connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this,
|
||||||
|
&WindowsDaemon::monitorBackendFailure);
|
||||||
|
connect(this, &WindowsDaemon::activationFailure,
|
||||||
|
[]() { WindowsFirewall::instance()->disableKillSwitch(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsDaemon::~WindowsDaemon() {
|
||||||
|
MZ_COUNT_DTOR(WindowsDaemon);
|
||||||
|
logger.debug() << "Daemon released";
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsDaemon::prepareActivation(const InterfaceConfig& config) {
|
||||||
|
// Before creating the interface we need to check which adapter
|
||||||
|
// routes to the server endpoint
|
||||||
|
auto serveraddr = QHostAddress(config.m_serverIpv4AddrIn);
|
||||||
|
m_inetAdapterIndex = WindowsCommons::AdapterIndexTo(serveraddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
|
||||||
|
if (op == Down) {
|
||||||
|
m_splitTunnelManager.stop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op == Up) {
|
||||||
|
logger.debug() << "Tunnel UP, Starting SplitTunneling";
|
||||||
|
if (!WindowsSplitTunnel::isInstalled()) {
|
||||||
|
logger.warning() << "Split Tunnel Driver not Installed yet, fixing this.";
|
||||||
|
WindowsSplitTunnel::installDriver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.m_vpnDisabledApps.length() > 0) {
|
||||||
|
m_splitTunnelManager.start(m_inetAdapterIndex);
|
||||||
|
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
|
||||||
|
} else {
|
||||||
|
m_splitTunnelManager.stop();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsDaemon::monitorBackendFailure() {
|
||||||
|
logger.warning() << "Tunnel service is down";
|
||||||
|
|
||||||
|
emit backendFailure();
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
48
client/platforms/windows/daemon/windowsdaemon.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSDAEMON_H
|
||||||
|
#define WINDOWSDAEMON_H
|
||||||
|
|
||||||
|
#include "daemon/daemon.h"
|
||||||
|
#include "dnsutilswindows.h"
|
||||||
|
#include "windowssplittunnel.h"
|
||||||
|
#include "windowstunnelservice.h"
|
||||||
|
#include "wireguardutilswindows.h"
|
||||||
|
|
||||||
|
#define TUNNEL_SERVICE_NAME L"WireGuardTunnel$AmneziaVPN"
|
||||||
|
|
||||||
|
class WindowsDaemon final : public Daemon {
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsDaemon)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsDaemon();
|
||||||
|
~WindowsDaemon();
|
||||||
|
|
||||||
|
void prepareActivation(const InterfaceConfig& config) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool run(Op op, const InterfaceConfig& config) override;
|
||||||
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
|
bool supportDnsUtils() const override { return true; }
|
||||||
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void monitorBackendFailure();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum State {
|
||||||
|
Active,
|
||||||
|
Inactive,
|
||||||
|
};
|
||||||
|
|
||||||
|
State m_state = Inactive;
|
||||||
|
int m_inetAdapterIndex = -1;
|
||||||
|
|
||||||
|
WireguardUtilsWindows* m_wgutils = nullptr;
|
||||||
|
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||||
|
WindowsSplitTunnel m_splitTunnelManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSDAEMON_H
|
||||||
77
client/platforms/windows/daemon/windowsdaemontunnel.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* 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 "windowsdaemontunnel.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
//#include "commandlineparser.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/daemon/wireguardutilswindows.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsDaemonTunnel");
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WindowsDaemonTunnel::WindowsDaemonTunnel() {
|
||||||
|
MZ_COUNT_CTOR(WindowsDaemonTunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsDaemonTunnel::~WindowsDaemonTunnel() {
|
||||||
|
MZ_COUNT_DTOR(WindowsDaemonTunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowsDaemonTunnel::run(QStringList& tokens) {
|
||||||
|
Q_ASSERT(!tokens.isEmpty());
|
||||||
|
|
||||||
|
logger.debug() << "Tunnel daemon service is starting";
|
||||||
|
|
||||||
|
QCoreApplication app();
|
||||||
|
|
||||||
|
QCoreApplication::setApplicationName("Amnezia VPN Tunnel");
|
||||||
|
QCoreApplication::setApplicationVersion(Constants::versionString());
|
||||||
|
|
||||||
|
if (tokens.length() != 2) {
|
||||||
|
logger.error() << "Expected 1 parameter only: the config file.";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
QString maybeConfig = tokens.at(1);
|
||||||
|
|
||||||
|
if (!maybeConfig.startsWith("[Interface]")) {
|
||||||
|
logger.error() << "parameter Does not seem to be a config";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// This process will be used by the wireguard tunnel. No need to call
|
||||||
|
// FreeLibrary.
|
||||||
|
HMODULE tunnelLib = LoadLibrary(TEXT("tunnel.dll"));
|
||||||
|
if (!tunnelLib) {
|
||||||
|
WindowsUtils::windowsLog("Failed to load tunnel.dll");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef bool WireGuardTunnelService(const ushort* settings,
|
||||||
|
const ushort* name);
|
||||||
|
|
||||||
|
WireGuardTunnelService* tunnelProc = (WireGuardTunnelService*)GetProcAddress(
|
||||||
|
tunnelLib, "WireGuardTunnelService");
|
||||||
|
if (!tunnelProc) {
|
||||||
|
WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
auto name = WireguardUtilsWindows::s_interfaceName();
|
||||||
|
if (!tunnelProc(maybeConfig.utf16(), name.utf16())) {
|
||||||
|
logger.error() << "Failed to activate the tunnel service";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//static Command::RegistrationProxy<WindowsDaemonTunnel>
|
||||||
|
// s_commandWindowsDaemonTunnel;
|
||||||
18
client/platforms/windows/daemon/windowsdaemontunnel.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSDAEMONTUNNEL_H
|
||||||
|
#define WINDOWSDAEMONTUNNEL_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class WindowsDaemonTunnel {
|
||||||
|
public:
|
||||||
|
explicit WindowsDaemonTunnel();
|
||||||
|
~WindowsDaemonTunnel();
|
||||||
|
|
||||||
|
int run(QStringList& tokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSDAEMONTUNNEL_H
|
||||||
850
client/platforms/windows/daemon/windowsfirewall.cpp
Normal file
|
|
@ -0,0 +1,850 @@
|
||||||
|
/* 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 "windowsfirewall.h"
|
||||||
|
|
||||||
|
#include <comdef.h>
|
||||||
|
#include <fwpmu.h>
|
||||||
|
#include <guiddef.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <netfw.h>
|
||||||
|
//#include <qaccessible.h>
|
||||||
|
#include <Ws2tcpip.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
#include "ipaddress.h"
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
#include "winsock.h"
|
||||||
|
|
||||||
|
#define IPV6_ADDRESS_SIZE 16
|
||||||
|
|
||||||
|
// ID for the Firewall Sublayer
|
||||||
|
DEFINE_GUID(ST_FW_WINFW_BASELINE_SUBLAYER_KEY, 0xc78056ff, 0x2bc1, 0x4211, 0xaa,
|
||||||
|
0xdd, 0x7f, 0x35, 0x8d, 0xef, 0x20, 0x2d);
|
||||||
|
// ID for the Mullvad Split-Tunnel Sublayer Provider
|
||||||
|
DEFINE_GUID(ST_FW_PROVIDER_KEY, 0xe2c114ee, 0xf32a, 0x4264, 0xa6, 0xcb, 0x3f,
|
||||||
|
0xa7, 0x99, 0x63, 0x56, 0xd9);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsFirewall");
|
||||||
|
WindowsFirewall* s_instance = nullptr;
|
||||||
|
|
||||||
|
// Note Filter Weight may be between 0-15!
|
||||||
|
constexpr uint8_t LOW_WEIGHT = 0;
|
||||||
|
constexpr uint8_t MED_WEIGHT = 7;
|
||||||
|
constexpr uint8_t HIGH_WEIGHT = 13;
|
||||||
|
constexpr uint8_t MAX_WEIGHT = 15;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WindowsFirewall* WindowsFirewall::instance() {
|
||||||
|
if (s_instance == nullptr) {
|
||||||
|
s_instance = new WindowsFirewall(qApp);
|
||||||
|
}
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
|
||||||
|
MZ_COUNT_CTOR(WindowsFirewall);
|
||||||
|
Q_ASSERT(s_instance == nullptr);
|
||||||
|
|
||||||
|
HANDLE engineHandle = NULL;
|
||||||
|
DWORD result = ERROR_SUCCESS;
|
||||||
|
// Use dynamic sessions for efficiency and safety:
|
||||||
|
// -> Filtering policy objects are deleted even when the application crashes/
|
||||||
|
// deamon goes down
|
||||||
|
FWPM_SESSION0 session;
|
||||||
|
memset(&session, 0, sizeof(session));
|
||||||
|
session.flags = FWPM_SESSION_FLAG_DYNAMIC;
|
||||||
|
|
||||||
|
logger.debug() << "Opening the filter engine.";
|
||||||
|
|
||||||
|
result =
|
||||||
|
FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
|
||||||
|
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Filter engine opened successfully.";
|
||||||
|
m_sessionHandle = engineHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsFirewall::~WindowsFirewall() {
|
||||||
|
MZ_COUNT_DTOR(WindowsFirewall);
|
||||||
|
if (m_sessionHandle != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(m_sessionHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::init() {
|
||||||
|
if (m_init) {
|
||||||
|
logger.warning() << "Alread initialised FW_WFP layer";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m_sessionHandle == INVALID_HANDLE_VALUE) {
|
||||||
|
logger.error() << "Cant Init Sublayer with invalid wfp handle";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If we were not able to aquire a handle, this will fail anyway.
|
||||||
|
// We need to open up another handle because of wfp rules:
|
||||||
|
// If a wfp resource was created with SESSION_DYNAMIC,
|
||||||
|
// the session exlusively owns the resource, meaning the driver can't add
|
||||||
|
// filters to the sublayer. So let's have non dynamic session only for the
|
||||||
|
// sublayer creation. This means the Layer exists until the next Reboot.
|
||||||
|
DWORD result = ERROR_SUCCESS;
|
||||||
|
HANDLE wfp = INVALID_HANDLE_VALUE;
|
||||||
|
FWPM_SESSION0 session;
|
||||||
|
memset(&session, 0, sizeof(session));
|
||||||
|
|
||||||
|
logger.debug() << "Opening the filter engine";
|
||||||
|
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &wfp);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmEngineOpen0 failed. Return value:.\n" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
|
||||||
|
|
||||||
|
// Check if the Layer Already Exists
|
||||||
|
FWPM_SUBLAYER0* maybeLayer;
|
||||||
|
result = FwpmSubLayerGetByKey0(wfp, &ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
|
||||||
|
&maybeLayer);
|
||||||
|
if (result == ERROR_SUCCESS) {
|
||||||
|
logger.debug() << "The Sublayer Already Exists!";
|
||||||
|
FwpmFreeMemory0((void**)&maybeLayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Start Transaction
|
||||||
|
result = FwpmTransactionBegin(wfp, NULL);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Add Sublayer
|
||||||
|
FWPM_SUBLAYER0 subLayer;
|
||||||
|
memset(&subLayer, 0, sizeof(subLayer));
|
||||||
|
subLayer.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
subLayer.displayData.name = (PWSTR)L"Amnezia-SplitTunnel-Sublayer";
|
||||||
|
subLayer.displayData.description =
|
||||||
|
(PWSTR)L"Filters that enforce a good baseline";
|
||||||
|
subLayer.weight = 0xFFFF;
|
||||||
|
|
||||||
|
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Step 4: Commit!
|
||||||
|
result = FwpmTransactionCommit0(wfp);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.debug() << "Initialised Sublayer";
|
||||||
|
m_init = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
||||||
|
// Checks if the FW_Rule was enabled succesfully,
|
||||||
|
// disables the whole killswitch and returns false if not.
|
||||||
|
#define FW_OK(rule) \
|
||||||
|
{ \
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL); \
|
||||||
|
if (result != ERROR_SUCCESS) { \
|
||||||
|
disableKillSwitch(); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
if (!rule) { \
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle); \
|
||||||
|
disableKillSwitch(); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle); \
|
||||||
|
if (result != ERROR_SUCCESS) { \
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" \
|
||||||
|
<< result; \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||||
|
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||||
|
"Allow usage of VPN Adapter"));
|
||||||
|
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
|
||||||
|
FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic"));
|
||||||
|
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
||||||
|
"Allow all for AmneziaVPN.exe"));
|
||||||
|
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
|
||||||
|
FW_OK(
|
||||||
|
allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1"));
|
||||||
|
|
||||||
|
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
|
||||||
|
return true;
|
||||||
|
#undef FW_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||||
|
// Start the firewall transaction
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
disableKillSwitch();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto cleanup = qScopeGuard([&] {
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle);
|
||||||
|
disableKillSwitch();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the firewall rules for this peer.
|
||||||
|
logger.info() << "Enabling traffic for peer"
|
||||||
|
<< config.m_serverPublicKey;
|
||||||
|
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
|
||||||
|
"Block Internet", config.m_serverPublicKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!config.m_dnsServer.isEmpty()) {
|
||||||
|
if (!allowTrafficTo(QHostAddress(config.m_dnsServer), 53, HIGH_WEIGHT,
|
||||||
|
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||||
|
// this should probably be cleaned up by converting m_dnsServer into
|
||||||
|
// a QStringList instead.
|
||||||
|
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
|
||||||
|
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||||
|
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||||
|
config.m_serverPublicKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
auto cleanup = qScopeGuard([&] {
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info() << "Disabling traffic for peer" << pubkey;
|
||||||
|
for (const auto& filterID : m_peerRules.values(pubkey)) {
|
||||||
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
|
m_peerRules.remove(pubkey, filterID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit!
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::disableKillSwitch() {
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
auto cleanup = qScopeGuard([&] {
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& filterID : m_peerRules.values()) {
|
||||||
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||||
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit!
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_peerRules.clear();
|
||||||
|
m_activeRules.clear();
|
||||||
|
logger.debug() << "Firewall Disabled!";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||||
|
int weight,
|
||||||
|
const QString& title) {
|
||||||
|
DWORD result = ERROR_SUCCESS;
|
||||||
|
Q_ASSERT(weight <= 15);
|
||||||
|
|
||||||
|
// Get the AppID for the Executable;
|
||||||
|
QString appName = QFileInfo(exePath).baseName();
|
||||||
|
std::wstring wstr = exePath.toStdWString();
|
||||||
|
PCWSTR appPath = wstr.c_str();
|
||||||
|
FWP_BYTE_BLOB* appID = NULL;
|
||||||
|
result = FwpmGetAppIdFromFileName0(appPath, &appID);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
WindowsUtils::windowsLog("FwpmGetAppIdFromFileName0 failure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Condition: Request must come from the .exe
|
||||||
|
FWPM_FILTER_CONDITION0 conds;
|
||||||
|
conds.fieldKey = FWPM_CONDITION_ALE_APP_ID;
|
||||||
|
conds.matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds.conditionValue.type = FWP_BYTE_BLOB_TYPE;
|
||||||
|
conds.conditionValue.byteBlob = appID;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = &conds;
|
||||||
|
filter.numFilterConditions = 1;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; // Make this decision
|
||||||
|
// only blockable by veto
|
||||||
|
// Build and add the Filters
|
||||||
|
// #1 Permit outbound IPv4 traffic.
|
||||||
|
{
|
||||||
|
QString desc("Permit (out) IPv4 Traffic of: " + appName);
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
if (!enableFilter(&filter, title, desc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #2 Permit inbound IPv4 traffic.
|
||||||
|
{
|
||||||
|
QString desc("Permit (in) IPv4 Traffic of: " + appName);
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
if (!enableFilter(&filter, title, desc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
|
const QString& title) {
|
||||||
|
FWPM_FILTER_CONDITION0 conds;
|
||||||
|
// Condition: Request must be targeting the TUN interface
|
||||||
|
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
|
||||||
|
conds.matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds.conditionValue.type = FWP_UINT32;
|
||||||
|
conds.conditionValue.uint32 = networkAdapter;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = &conds;
|
||||||
|
filter.numFilterConditions = 1;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
|
QString description("Allow %0 traffic on Adapter %1");
|
||||||
|
// #1 Permit outbound IPv4 traffic.
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("out").arg(networkAdapter))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// #2 Permit inbound IPv4 traffic.
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("in").arg(networkAdapter))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// #3 Permit outbound IPv6 traffic.
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("out").arg(networkAdapter))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// #4 Permit inbound IPv6 traffic.
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("in").arg(networkAdapter))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||||
|
int weight, const QString& title,
|
||||||
|
const QString& peer) {
|
||||||
|
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
|
GUID layerOut =
|
||||||
|
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
GUID layerIn = isIPv4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||||
|
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
|
||||||
|
quint32_be ipBigEndian;
|
||||||
|
quint32 ip = targetIP.toIPv4Address();
|
||||||
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
|
|
||||||
|
// Allow Traffic to IP with PORT using any protocol
|
||||||
|
FWPM_FILTER_CONDITION0 conds[4];
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[1].conditionValue.uint16 = (IPPROTO_TCP);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = port;
|
||||||
|
|
||||||
|
conds[3].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||||
|
conds[3].matchType = FWP_MATCH_EQUAL;
|
||||||
|
QByteArray buffer;
|
||||||
|
// will hold v6 Addess bytes if present
|
||||||
|
importAddress(targetIP, conds[3].conditionValue, &buffer);
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 4;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; // Hard Permit!
|
||||||
|
|
||||||
|
QString description("Permit traffic %1 %2 on port %3");
|
||||||
|
filter.layerKey = layerOut;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("to").arg(targetIP.toString()).arg(port),
|
||||||
|
peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
filter.layerKey = layerIn;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("from").arg(targetIP.toString()).arg(port),
|
||||||
|
peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||||
|
// Allow outbound DHCPv4
|
||||||
|
{
|
||||||
|
FWPM_FILTER_CONDITION0 conds[4];
|
||||||
|
// Condition: Request must be targeting the TUN interface
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[1].conditionValue.uint16 = (68);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = 67;
|
||||||
|
|
||||||
|
conds[3].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||||
|
conds[3].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[3].conditionValue.type = FWP_UINT32;
|
||||||
|
conds[3].conditionValue.uint32 = (0xffffffff);
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 4;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
|
||||||
|
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow inbound DHCPv4
|
||||||
|
{
|
||||||
|
FWPM_FILTER_CONDITION0 conds[3];
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[1].conditionValue.uint16 = (68);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = 67;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 3;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
|
||||||
|
if (!enableFilter(&filter, title, "Allow inbound DHCP")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow outbound DHCPv6
|
||||||
|
{
|
||||||
|
FWPM_FILTER_CONDITION0 conds[3];
|
||||||
|
// Condition: Request must be targeting the TUN interface
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[1].conditionValue.uint16 = (68);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = 67;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 3;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
|
||||||
|
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow inbound DHCPv6
|
||||||
|
{
|
||||||
|
FWPM_FILTER_CONDITION0 conds[3];
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[1].conditionValue.uint16 = (68);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = 67;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 3;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
if (!enableFilter(&filter, title, "Allow inbound DHCPv6")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows the internal Hyper-V Switches to work.
|
||||||
|
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
|
||||||
|
FWPM_FILTER_CONDITION0 cond;
|
||||||
|
// Condition: Request must be targeting the TUN interface
|
||||||
|
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
|
||||||
|
cond.matchType = FWP_MATCH_EQUAL;
|
||||||
|
cond.conditionValue.type = FWP_UINT32;
|
||||||
|
cond.conditionValue.uint32 = FWP_CONDITION_L2_IS_VM2VM;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = &cond;
|
||||||
|
filter.numFilterConditions = 1;
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
|
// #1 Permit Hyper-V => Hyper-V outbound.
|
||||||
|
filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE;
|
||||||
|
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// #2 Permit Hyper-V => Hyper-V inbound.
|
||||||
|
filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE;
|
||||||
|
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||||
|
const QString& title,
|
||||||
|
const QString& peer) {
|
||||||
|
QString description("Block traffic %1 %2 ");
|
||||||
|
|
||||||
|
auto lower = addr.address();
|
||||||
|
auto upper = addr.broadcastAddress();
|
||||||
|
|
||||||
|
const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
|
||||||
|
const GUID layerKeyOut =
|
||||||
|
isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||||
|
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.action.type = FWP_ACTION_BLOCK;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
|
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
||||||
|
FWP_RANGE0 ipRange;
|
||||||
|
QByteArray lowIpV6Buffer;
|
||||||
|
QByteArray highIpV6Buffer;
|
||||||
|
|
||||||
|
importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
|
||||||
|
importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
|
||||||
|
|
||||||
|
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||||
|
cond[0].matchType = FWP_MATCH_RANGE;
|
||||||
|
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
||||||
|
cond[0].conditionValue.rangeValue = &ipRange;
|
||||||
|
|
||||||
|
filter.numFilterConditions = 1;
|
||||||
|
filter.filterCondition = cond;
|
||||||
|
|
||||||
|
filter.layerKey = layerKeyOut;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
||||||
|
peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
filter.layerKey = layerKeyIn;
|
||||||
|
if (!enableFilter(&filter, title,
|
||||||
|
description.arg("from").arg(addr.toString()), peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
|
||||||
|
uint8_t weight, const QString& title,
|
||||||
|
const QString& peer) {
|
||||||
|
for (auto range : rangeList) {
|
||||||
|
if (!blockTrafficTo(range, weight, title, peer)) {
|
||||||
|
logger.info() << "Setting Range of" << range.toString() << "failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Path of the Current Executable this runs in
|
||||||
|
QString WindowsFirewall::getCurrentPath() {
|
||||||
|
const unsigned char initValue = 0xff;
|
||||||
|
QByteArray buffer(2048, initValue);
|
||||||
|
auto ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
if (ok == ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
buffer.resize(buffer.size() * 2);
|
||||||
|
ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
if (ok == 0) {
|
||||||
|
WindowsUtils::windowsLog("Err fetching dos path");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString::fromLocal8Bit(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsFirewall::importAddress(const QHostAddress& addr,
|
||||||
|
OUT FWP_VALUE0_& value,
|
||||||
|
OUT QByteArray* v6DataBuffer) {
|
||||||
|
const bool isV4 = addr.protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
|
if (isV4) {
|
||||||
|
value.type = FWP_UINT32;
|
||||||
|
value.uint32 = addr.toIPv4Address();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto v6bytes = addr.toIPv6Address();
|
||||||
|
v6DataBuffer->append((const char*)v6bytes.c, IPV6_ADDRESS_SIZE);
|
||||||
|
value.type = FWP_BYTE_ARRAY16_TYPE;
|
||||||
|
value.byteArray16 = (FWP_BYTE_ARRAY16*)v6DataBuffer->data();
|
||||||
|
}
|
||||||
|
void WindowsFirewall::importAddress(const QHostAddress& addr,
|
||||||
|
OUT FWP_CONDITION_VALUE0_& value,
|
||||||
|
OUT QByteArray* v6DataBuffer) {
|
||||||
|
const bool isV4 = addr.protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
|
if (isV4) {
|
||||||
|
value.type = FWP_UINT32;
|
||||||
|
value.uint32 = addr.toIPv4Address();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto v6bytes = addr.toIPv6Address();
|
||||||
|
v6DataBuffer->append((const char*)v6bytes.c, IPV6_ADDRESS_SIZE);
|
||||||
|
value.type = FWP_BYTE_ARRAY16_TYPE;
|
||||||
|
value.byteArray16 = (FWP_BYTE_ARRAY16*)v6DataBuffer->data();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
||||||
|
const QString& title) {
|
||||||
|
// Allow Traffic to IP with PORT using any protocol
|
||||||
|
FWPM_FILTER_CONDITION0 conds[3];
|
||||||
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[0].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[0].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[0].conditionValue.uint8 = (IPPROTO_UDP);
|
||||||
|
|
||||||
|
conds[1].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
|
conds[1].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[1].conditionValue.type = FWP_UINT8;
|
||||||
|
conds[1].conditionValue.uint8 = (IPPROTO_TCP);
|
||||||
|
|
||||||
|
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
|
||||||
|
conds[2].matchType = FWP_MATCH_EQUAL;
|
||||||
|
conds[2].conditionValue.type = FWP_UINT16;
|
||||||
|
conds[2].conditionValue.uint16 = port;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.filterCondition = conds;
|
||||||
|
filter.numFilterConditions = 3;
|
||||||
|
filter.action.type = FWP_ACTION_BLOCK;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
|
QString description("Block %1 on Port %2");
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||||
|
const QString& description,
|
||||||
|
const QString& peer) {
|
||||||
|
uint64_t filterID = 0;
|
||||||
|
auto name = title.toStdWString();
|
||||||
|
auto desc = description.toStdWString();
|
||||||
|
filter->displayData.name = (PWSTR)name.c_str();
|
||||||
|
filter->displayData.description = (PWSTR)desc.c_str();
|
||||||
|
auto result = FwpmFilterAdd0(m_sessionHandle, filter, NULL, &filterID);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "Failed to enable filter: " << title << " "
|
||||||
|
<< description;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.info() << "Filter added: " << title << ":" << description;
|
||||||
|
if (peer.isEmpty()) {
|
||||||
|
m_activeRules.append(filterID);
|
||||||
|
} else {
|
||||||
|
m_peerRules.insert(peer, filterID);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
|
||||||
|
const QString& title) {
|
||||||
|
QList<QNetworkInterface> networkInterfaces =
|
||||||
|
QNetworkInterface::allInterfaces();
|
||||||
|
for (const auto& iface : networkInterfaces) {
|
||||||
|
if (iface.type() != QNetworkInterface::Loopback) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!allowTrafficOfAdapter(iface.index(), weight,
|
||||||
|
title.arg(iface.name()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
72
client/platforms/windows/daemon/windowsfirewall.h
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSFIREWALL_H
|
||||||
|
#define WINDOWSFIREWALL_H
|
||||||
|
|
||||||
|
#pragma comment(lib, "Fwpuclnt")
|
||||||
|
|
||||||
|
// Note: The windows.h import needs to come before the fwpmu.h import.
|
||||||
|
// clang-format off
|
||||||
|
#include <windows.h>
|
||||||
|
#include <fwpmu.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "../client/daemon/interfaceconfig.h"
|
||||||
|
|
||||||
|
class IpAdressRange;
|
||||||
|
struct FWP_VALUE0_;
|
||||||
|
struct FWP_CONDITION_VALUE0_;
|
||||||
|
|
||||||
|
class WindowsFirewall final : public QObject {
|
||||||
|
public:
|
||||||
|
~WindowsFirewall();
|
||||||
|
|
||||||
|
static WindowsFirewall* instance();
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
bool enableKillSwitch(int vpnAdapterIndex);
|
||||||
|
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||||
|
bool disablePeerTraffic(const QString& pubkey);
|
||||||
|
bool disableKillSwitch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowsFirewall(QObject* parent);
|
||||||
|
HANDLE m_sessionHandle;
|
||||||
|
bool m_init = false;
|
||||||
|
QList<uint64_t> m_activeRules;
|
||||||
|
QMultiMap<QString, uint64_t> m_peerRules;
|
||||||
|
|
||||||
|
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
|
||||||
|
const QString& title);
|
||||||
|
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
|
||||||
|
const QString& title, const QString& peer = QString());
|
||||||
|
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||||
|
const QString& title, const QString& peer = QString());
|
||||||
|
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||||
|
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||||
|
const QString& title, const QString& peer = QString());
|
||||||
|
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
|
const QString& title);
|
||||||
|
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||||
|
bool allowHyperVTraffic(uint8_t weight, const QString& title);
|
||||||
|
bool allowLoopbackTraffic(uint8_t weight, const QString& title);
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
QString getCurrentPath();
|
||||||
|
void importAddress(const QHostAddress& addr, OUT FWP_VALUE0_& value,
|
||||||
|
OUT QByteArray* v6DataBuffer);
|
||||||
|
void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value,
|
||||||
|
OUT QByteArray* v6DataBuffer);
|
||||||
|
bool enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||||
|
const QString& description,
|
||||||
|
const QString& peer = QString());
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSFIREWALL_H
|
||||||
317
client/platforms/windows/daemon/windowsroutemonitor.cpp
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
/* 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 "windowsroutemonitor.h"
|
||||||
|
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsRouteMonitor");
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
// Called by the kernel on route changes - perform some basic filtering and
|
||||||
|
// invoke the routeChanged slot to do the real work.
|
||||||
|
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
||||||
|
MIB_NOTIFICATION_TYPE type) {
|
||||||
|
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
|
||||||
|
Q_UNUSED(type);
|
||||||
|
|
||||||
|
// Ignore host route changes, and unsupported protocols.
|
||||||
|
if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||||
|
if (row->DestinationPrefix.PrefixLength >= 128) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||||
|
if (row->DestinationPrefix.PrefixLength >= 32) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor->getLuid() != row->InterfaceLuid.Value) {
|
||||||
|
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform prefix matching comparison on IP addresses in host order.
|
||||||
|
static int prefixcmp(const void* a, const void* b, size_t bits) {
|
||||||
|
size_t bytes = bits / 8;
|
||||||
|
if (bytes > 0) {
|
||||||
|
int diff = memcmp(a, b, bytes);
|
||||||
|
if (diff != 0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bits % 8) {
|
||||||
|
quint8 avalue = *((const quint8*)a + bytes) >> (8 - bits % 8);
|
||||||
|
quint8 bvalue = *((const quint8*)b + bytes) >> (8 - bits % 8);
|
||||||
|
return avalue - bvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
|
||||||
|
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||||
|
logger.debug() << "WindowsRouteMonitor created.";
|
||||||
|
|
||||||
|
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||||
|
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||||
|
CancelMibChangeNotify2(m_routeHandle);
|
||||||
|
flushExclusionRoutes();
|
||||||
|
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
||||||
|
PMIB_IPINTERFACE_TABLE table;
|
||||||
|
DWORD result = GetIpInterfaceTable(family, &table);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.warning() << "Failed to retrive interface table." << result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto guard = qScopeGuard([&] { FreeMibTable(table); });
|
||||||
|
|
||||||
|
// Flush the list of interfaces that are valid for routing.
|
||||||
|
if ((family == AF_INET) || (family == AF_UNSPEC)) {
|
||||||
|
m_validInterfacesIpv4.clear();
|
||||||
|
}
|
||||||
|
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
|
||||||
|
m_validInterfacesIpv6.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the list of interfaces that are valid for routing.
|
||||||
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
||||||
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!row->Connected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row->Family == AF_INET) {
|
||||||
|
logger.debug() << "Interface" << row->InterfaceIndex
|
||||||
|
<< "is valid for IPv4 routing";
|
||||||
|
m_validInterfacesIpv4.append(row->InterfaceLuid.Value);
|
||||||
|
}
|
||||||
|
if (row->Family == AF_INET6) {
|
||||||
|
logger.debug() << "Interface" << row->InterfaceIndex
|
||||||
|
<< "is valid for IPv6 routing";
|
||||||
|
m_validInterfacesIpv6.append(row->InterfaceLuid.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
||||||
|
void* ptable) {
|
||||||
|
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||||
|
SOCKADDR_INET nexthop = {0};
|
||||||
|
quint64 bestLuid = 0;
|
||||||
|
int bestMatch = -1;
|
||||||
|
ULONG bestMetric = ULONG_MAX;
|
||||||
|
|
||||||
|
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||||
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
|
// Ignore routes into the VPN interface.
|
||||||
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ignore host routes, and shorter potential matches.
|
||||||
|
if (row->DestinationPrefix.PrefixLength >=
|
||||||
|
data->DestinationPrefix.PrefixLength) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the routing table entry matches the destination.
|
||||||
|
if (data->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||||
|
if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!m_validInterfacesIpv6.contains(row->InterfaceLuid.Value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
||||||
|
&row->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
||||||
|
row->DestinationPrefix.PrefixLength) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||||
|
if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!m_validInterfacesIpv4.contains(row->InterfaceLuid.Value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
||||||
|
&row->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
||||||
|
row->DestinationPrefix.PrefixLength) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unsupported destination address family.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer routes with lower metric if we find multiple matches
|
||||||
|
// with the same prefix length.
|
||||||
|
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
|
||||||
|
(row->Metric >= bestMetric)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, then this is the longest prefix match so far.
|
||||||
|
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
|
||||||
|
bestLuid = row->InterfaceLuid.Value;
|
||||||
|
bestMatch = row->DestinationPrefix.PrefixLength;
|
||||||
|
bestMetric = row->Metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither the interface nor next-hop have changed, then do nothing.
|
||||||
|
if ((data->InterfaceLuid.Value) == bestLuid &&
|
||||||
|
memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the routing table entry.
|
||||||
|
if (data->InterfaceLuid.Value != 0) {
|
||||||
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
|
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
||||||
|
logger.error() << "Failed to delete route:" << result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->InterfaceLuid.Value = bestLuid;
|
||||||
|
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
|
||||||
|
if (data->InterfaceLuid.Value != 0) {
|
||||||
|
DWORD result = CreateIpForwardEntry2(data);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to update route:" << result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Adding exclusion route for"
|
||||||
|
<< logger.sensitive(prefix.toString());
|
||||||
|
|
||||||
|
if (m_exclusionRoutes.contains(prefix)) {
|
||||||
|
logger.warning() << "Exclusion route already exists";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and initialize the MIB routing table row.
|
||||||
|
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
||||||
|
InitializeIpForwardEntry(data);
|
||||||
|
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
Q_IPV6ADDR buf = prefix.address().toIPv6Address();
|
||||||
|
|
||||||
|
memcpy(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr, &buf, sizeof(buf));
|
||||||
|
data->DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||||
|
data->DestinationPrefix.PrefixLength = prefix.prefixLength();
|
||||||
|
} else {
|
||||||
|
quint32 buf = prefix.address().toIPv4Address();
|
||||||
|
|
||||||
|
data->DestinationPrefix.Prefix.Ipv4.sin_addr.s_addr = htonl(buf);
|
||||||
|
data->DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||||
|
data->DestinationPrefix.PrefixLength = prefix.prefixLength();
|
||||||
|
}
|
||||||
|
data->NextHop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||||
|
|
||||||
|
// Set the rest of the flags for a static route.
|
||||||
|
data->ValidLifetime = 0xffffffff;
|
||||||
|
data->PreferredLifetime = 0xffffffff;
|
||||||
|
data->Metric = 0;
|
||||||
|
data->Protocol = MIB_IPPROTO_NETMGMT;
|
||||||
|
data->Loopback = false;
|
||||||
|
data->AutoconfigureAddress = false;
|
||||||
|
data->Publish = false;
|
||||||
|
data->Immortal = false;
|
||||||
|
data->Age = 0;
|
||||||
|
|
||||||
|
PMIB_IPFORWARD_TABLE2 table;
|
||||||
|
int family;
|
||||||
|
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
family = AF_INET6;
|
||||||
|
} else {
|
||||||
|
family = AF_INET;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD result = GetIpForwardTable2(family, &table);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to fetch routing table:" << result;
|
||||||
|
delete data;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
updateValidInterfaces(family);
|
||||||
|
updateExclusionRoute(data, table);
|
||||||
|
FreeMibTable(table);
|
||||||
|
|
||||||
|
m_exclusionRoutes[prefix] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
|
logger.debug() << "Deleting exclusion route for"
|
||||||
|
<< logger.sensitive(prefix.address().toString());
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||||
|
if (data == nullptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
|
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||||
|
logger.error() << "Failed to delete route to"
|
||||||
|
<< logger.sensitive(prefix.toString())
|
||||||
|
<< "result:" << result;
|
||||||
|
}
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::flushExclusionRoutes() {
|
||||||
|
for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) {
|
||||||
|
MIB_IPFORWARD_ROW2* data = i.value();
|
||||||
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
|
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||||
|
logger.error() << "Failed to delete route to"
|
||||||
|
<< logger.sensitive(i.key().toString())
|
||||||
|
<< "result:" << result;
|
||||||
|
}
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
m_exclusionRoutes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::routeChanged() {
|
||||||
|
logger.debug() << "Routes changed";
|
||||||
|
|
||||||
|
PMIB_IPFORWARD_TABLE2 table;
|
||||||
|
DWORD result = GetIpForwardTable2(AF_UNSPEC, &table);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to fetch routing table:" << result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateValidInterfaces(AF_UNSPEC);
|
||||||
|
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
||||||
|
updateExclusionRoute(data, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeMibTable(table);
|
||||||
|
}
|
||||||
47
client/platforms/windows/daemon/windowsroutemonitor.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSROUTEMONITOR_H
|
||||||
|
#define WINDOWSROUTEMONITOR_H
|
||||||
|
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2ipdef.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "ipaddress.h"
|
||||||
|
|
||||||
|
class WindowsRouteMonitor final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsRouteMonitor(QObject* parent);
|
||||||
|
~WindowsRouteMonitor();
|
||||||
|
|
||||||
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
|
void flushExclusionRoutes();
|
||||||
|
|
||||||
|
void setLuid(quint64 luid) { m_luid = luid; }
|
||||||
|
quint64 getLuid() { return m_luid; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void routeChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table);
|
||||||
|
void updateValidInterfaces(int family);
|
||||||
|
|
||||||
|
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
||||||
|
QList<quint64> m_validInterfacesIpv4;
|
||||||
|
QList<quint64> m_validInterfacesIpv6;
|
||||||
|
|
||||||
|
quint64 m_luid = 0;
|
||||||
|
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* WINDOWSROUTEMONITOR_H */
|
||||||
547
client/platforms/windows/daemon/windowssplittunnel.cpp
Normal file
|
|
@ -0,0 +1,547 @@
|
||||||
|
/* 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 "windowssplittunnel.h"
|
||||||
|
|
||||||
|
#include "../windowscommons.h"
|
||||||
|
#include "../windowsservicemanager.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
|
#define PSAPI_VERSION 2
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsSplitTunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
|
||||||
|
if (detectConflict()) {
|
||||||
|
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
||||||
|
uninstallDriver();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isInstalled()) {
|
||||||
|
logger.debug() << "Driver is not Installed, doing so";
|
||||||
|
auto handle = installDriver();
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
WindowsUtils::windowsLog("Failed to install Driver");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Driver installed";
|
||||||
|
CloseServiceHandle(handle);
|
||||||
|
} else {
|
||||||
|
logger.debug() << "Driver is installed";
|
||||||
|
}
|
||||||
|
initDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsSplitTunnel::~WindowsSplitTunnel() {
|
||||||
|
CloseHandle(m_driver);
|
||||||
|
uninstallDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::initDriver() {
|
||||||
|
if (detectConflict()) {
|
||||||
|
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Try to open Split Tunnel Driver";
|
||||||
|
// Open the Driver Symlink
|
||||||
|
m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||||
|
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
|
;
|
||||||
|
|
||||||
|
if (m_driver == INVALID_HANDLE_VALUE) {
|
||||||
|
WindowsUtils::windowsLog("Failed to open Driver: ");
|
||||||
|
|
||||||
|
// If the handle is not present, try again after the serivce has started;
|
||||||
|
auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME);
|
||||||
|
QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted,
|
||||||
|
this, &WindowsSplitTunnel::initDriver);
|
||||||
|
driver_manager.startService();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Connected to the Driver";
|
||||||
|
// Reset Driver as it has wfp handles probably >:(
|
||||||
|
|
||||||
|
if (!WindowsFirewall::instance()->init()) {
|
||||||
|
logger.error() << "Init WFP-Sublayer failed, driver won't be functional";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to now check the state and init it, if required
|
||||||
|
|
||||||
|
auto state = getState();
|
||||||
|
if (state == STATE_UNKNOWN) {
|
||||||
|
logger.debug() << "Cannot check if driver is initialized";
|
||||||
|
}
|
||||||
|
if (state >= STATE_INITIALIZED) {
|
||||||
|
logger.debug() << "Driver already initialized: " << state;
|
||||||
|
reset();
|
||||||
|
|
||||||
|
auto newState = getState();
|
||||||
|
logger.debug() << "New state after reset:" << newState;
|
||||||
|
if (newState >= STATE_INITIALIZED) {
|
||||||
|
logger.debug() << "Reset unsuccesfull";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
|
||||||
|
&bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
auto err = GetLastError();
|
||||||
|
logger.error() << "Driver init failed err -" << err;
|
||||||
|
logger.error() << "State:" << getState();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Driver initialized" << getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
|
||||||
|
auto state = getState();
|
||||||
|
if (state != STATE_READY && state != STATE_RUNNING) {
|
||||||
|
logger.warning() << "Driver is not in the right State to set Rules"
|
||||||
|
<< state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
|
||||||
|
auto config = generateAppConfiguration(appPaths);
|
||||||
|
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0],
|
||||||
|
(DWORD)config.size(), nullptr, 0, &bytesReturned,
|
||||||
|
nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
auto err = GetLastError();
|
||||||
|
WindowsUtils::windowsLog("Set Config Failed:");
|
||||||
|
logger.error() << "Failed to set Config err code " << err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "New Configuration applied: " << getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::start(int inetAdapterIndex) {
|
||||||
|
// To Start we need to send 2 things:
|
||||||
|
// Network info (what is vpn what is network)
|
||||||
|
logger.debug() << "Starting SplitTunnel";
|
||||||
|
DWORD bytesReturned;
|
||||||
|
|
||||||
|
if (getState() == STATE_STARTED) {
|
||||||
|
logger.debug() << "Driver needs Init Call";
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr,
|
||||||
|
0, &bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
logger.error() << "Driver init failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Info (what is running already)
|
||||||
|
if (getState() == STATE_INITIALIZED) {
|
||||||
|
logger.debug() << "State is Init, requires process config";
|
||||||
|
auto config = generateProcessBlob();
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_PROCESSES, &config[0],
|
||||||
|
(DWORD)config.size(), nullptr, 0, &bytesReturned,
|
||||||
|
nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
logger.error() << "Failed to set Process Config";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Set Process Config ok || new State:" << getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getState() == STATE_INITIALIZED) {
|
||||||
|
logger.warning() << "Driver is still not ready after process list send";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Driver is ready || new State:" << getState();
|
||||||
|
|
||||||
|
auto config = generateIPConfiguration(inetAdapterIndex);
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
|
||||||
|
(DWORD)config.size(), nullptr, 0, &bytesReturned,
|
||||||
|
nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
logger.error() << "Failed to set Network Config";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "New Network Config Applied || new State:" << getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::stop() {
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_CLEAR_CONFIGURATION, nullptr, 0,
|
||||||
|
nullptr, 0, &bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
logger.error() << "Stopping Split tunnel not successfull";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Stopping Split tunnel successfull";
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::reset() {
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
|
||||||
|
&bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
logger.error() << "Reset Split tunnel not successfull";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "Reset Split tunnel successfull";
|
||||||
|
}
|
||||||
|
|
||||||
|
DRIVER_STATE WindowsSplitTunnel::getState() {
|
||||||
|
if (m_driver == INVALID_HANDLE_VALUE) {
|
||||||
|
logger.debug() << "Can't query State from non Opened Driver";
|
||||||
|
return STATE_UNKNOWN;
|
||||||
|
}
|
||||||
|
DWORD bytesReturned;
|
||||||
|
SIZE_T outBuffer;
|
||||||
|
bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
|
||||||
|
sizeof(outBuffer), &bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
WindowsUtils::windowsLog("getState response failure");
|
||||||
|
return STATE_UNKNOWN;
|
||||||
|
}
|
||||||
|
if (bytesReturned == 0) {
|
||||||
|
WindowsUtils::windowsLog("getState response is empty");
|
||||||
|
return STATE_UNKNOWN;
|
||||||
|
}
|
||||||
|
return static_cast<DRIVER_STATE>(outBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||||
|
const QStringList& appPaths) {
|
||||||
|
// Step 1: Calculate how much size the buffer will need
|
||||||
|
size_t cummulated_string_size = 0;
|
||||||
|
QStringList dosPaths;
|
||||||
|
for (auto const& path : appPaths) {
|
||||||
|
auto dosPath = convertPath(path);
|
||||||
|
dosPaths.append(dosPath);
|
||||||
|
cummulated_string_size += dosPath.toStdWString().size() * sizeof(wchar_t);
|
||||||
|
logger.debug() << dosPath;
|
||||||
|
}
|
||||||
|
size_t bufferSize = sizeof(CONFIGURATION_HEADER) +
|
||||||
|
(sizeof(CONFIGURATION_ENTRY) * appPaths.size()) +
|
||||||
|
cummulated_string_size;
|
||||||
|
std::vector<uint8_t> outBuffer(bufferSize);
|
||||||
|
|
||||||
|
auto header = (CONFIGURATION_HEADER*)&outBuffer[0];
|
||||||
|
auto entry = (CONFIGURATION_ENTRY*)(header + 1);
|
||||||
|
|
||||||
|
auto stringDest = &outBuffer[0] + sizeof(CONFIGURATION_HEADER) +
|
||||||
|
(sizeof(CONFIGURATION_ENTRY) * appPaths.size());
|
||||||
|
|
||||||
|
SIZE_T stringOffset = 0;
|
||||||
|
|
||||||
|
for (const QString& path : dosPaths) {
|
||||||
|
auto wstr = path.toStdWString();
|
||||||
|
auto cstr = wstr.c_str();
|
||||||
|
auto stringLength = wstr.size() * sizeof(wchar_t);
|
||||||
|
|
||||||
|
entry->ImageNameLength = (USHORT)stringLength;
|
||||||
|
entry->ImageNameOffset = stringOffset;
|
||||||
|
|
||||||
|
memcpy(stringDest, cstr, stringLength);
|
||||||
|
|
||||||
|
++entry;
|
||||||
|
stringDest += stringLength;
|
||||||
|
stringOffset += stringLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
header->NumEntries = appPaths.length();
|
||||||
|
header->TotalLength = bufferSize;
|
||||||
|
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
|
||||||
|
int inetAdapterIndex) {
|
||||||
|
std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG));
|
||||||
|
|
||||||
|
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
|
||||||
|
|
||||||
|
auto ifaces = QNetworkInterface::allInterfaces();
|
||||||
|
// Always the VPN
|
||||||
|
getAddress(WindowsCommons::VPNAdapterIndex(), &config->TunnelIpv4,
|
||||||
|
&config->TunnelIpv6);
|
||||||
|
// 2nd best route
|
||||||
|
getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||||
|
IN6_ADDR* out_ipv6) {
|
||||||
|
QNetworkInterface target =
|
||||||
|
QNetworkInterface::interfaceFromIndex(adapterIndex);
|
||||||
|
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
|
||||||
|
|
||||||
|
// take the first v4/v6 Adress and convert to in_addr
|
||||||
|
for (auto address : target.addressEntries()) {
|
||||||
|
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
auto adrr = address.ip().toString();
|
||||||
|
std::wstring wstr = adrr.toStdWString();
|
||||||
|
logger.debug() << "IpV4" << logger.sensitive(adrr);
|
||||||
|
PCWSTR w_str_ip = wstr.c_str();
|
||||||
|
auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4);
|
||||||
|
if (ok != 1) {
|
||||||
|
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto address : target.addressEntries()) {
|
||||||
|
if (address.ip().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
auto adrr = address.ip().toString();
|
||||||
|
std::wstring wstr = adrr.toStdWString();
|
||||||
|
logger.debug() << "IpV6" << logger.sensitive(adrr);
|
||||||
|
PCWSTR w_str_ip = wstr.c_str();
|
||||||
|
auto ok = InetPtonW(AF_INET6, w_str_ip, out_ipv6);
|
||||||
|
if (ok != 1) {
|
||||||
|
logger.error() << "Ipv6 Conversation error" << WSAGetLastError();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
||||||
|
// Get a Snapshot of all processes that are running:
|
||||||
|
HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snapshot_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
WindowsUtils::windowsLog("Creating Process snapshot failed");
|
||||||
|
return std::vector<uint8_t>(0);
|
||||||
|
}
|
||||||
|
auto cleanup = qScopeGuard([&] { CloseHandle(snapshot_handle); });
|
||||||
|
// Load the First Entry, later iterate over all
|
||||||
|
PROCESSENTRY32W currentProcess;
|
||||||
|
currentProcess.dwSize = sizeof(PROCESSENTRY32W);
|
||||||
|
|
||||||
|
if (FALSE == (Process32First(snapshot_handle, ¤tProcess))) {
|
||||||
|
WindowsUtils::windowsLog("Cant read first entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<DWORD, ProcessInfo> processes;
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
|
||||||
|
currentProcess.th32ProcessID);
|
||||||
|
|
||||||
|
if (process_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ProcessInfo info = getProcessInfo(process_handle, currentProcess);
|
||||||
|
processes.insert(info.ProcessId, info);
|
||||||
|
CloseHandle(process_handle);
|
||||||
|
|
||||||
|
} while (FALSE != (Process32NextW(snapshot_handle, ¤tProcess)));
|
||||||
|
|
||||||
|
auto process_list = processes.values();
|
||||||
|
if (process_list.isEmpty()) {
|
||||||
|
logger.debug() << "Process Snapshot list was empty";
|
||||||
|
return std::vector<uint8_t>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Reading Processes NUM: " << process_list.size();
|
||||||
|
// Determine the Size of the outBuffer:
|
||||||
|
size_t totalStringSize = 0;
|
||||||
|
|
||||||
|
for (const auto& process : process_list) {
|
||||||
|
totalStringSize += (process.DevicePath.size() * sizeof(wchar_t));
|
||||||
|
}
|
||||||
|
auto bufferSize = sizeof(PROCESS_DISCOVERY_HEADER) +
|
||||||
|
(sizeof(PROCESS_DISCOVERY_ENTRY) * processes.size()) +
|
||||||
|
totalStringSize;
|
||||||
|
|
||||||
|
std::vector<uint8_t> out(bufferSize);
|
||||||
|
|
||||||
|
auto header = reinterpret_cast<PROCESS_DISCOVERY_HEADER*>(&out[0]);
|
||||||
|
auto entry = reinterpret_cast<PROCESS_DISCOVERY_ENTRY*>(header + 1);
|
||||||
|
auto stringBuffer = reinterpret_cast<uint8_t*>(entry + processes.size());
|
||||||
|
|
||||||
|
SIZE_T currentStringOffset = 0;
|
||||||
|
|
||||||
|
for (const auto& process : process_list) {
|
||||||
|
// Wierd DWORD -> Handle Pointer magic.
|
||||||
|
entry->ProcessId = (HANDLE)((size_t)process.ProcessId);
|
||||||
|
entry->ParentProcessId = (HANDLE)((size_t)process.ParentProcessId);
|
||||||
|
|
||||||
|
if (process.DevicePath.empty()) {
|
||||||
|
entry->ImageNameOffset = 0;
|
||||||
|
entry->ImageNameLength = 0;
|
||||||
|
} else {
|
||||||
|
const auto imageNameLength = process.DevicePath.size() * sizeof(wchar_t);
|
||||||
|
|
||||||
|
entry->ImageNameOffset = currentStringOffset;
|
||||||
|
entry->ImageNameLength = static_cast<USHORT>(imageNameLength);
|
||||||
|
|
||||||
|
RtlCopyMemory(stringBuffer + currentStringOffset, &process.DevicePath[0],
|
||||||
|
imageNameLength);
|
||||||
|
|
||||||
|
currentStringOffset += imageNameLength;
|
||||||
|
}
|
||||||
|
++entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
header->NumEntries = processes.size();
|
||||||
|
header->TotalLength = bufferSize;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsSplitTunnel::close() {
|
||||||
|
CloseHandle(m_driver);
|
||||||
|
m_driver = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessInfo WindowsSplitTunnel::getProcessInfo(
|
||||||
|
HANDLE process, const PROCESSENTRY32W& processMeta) {
|
||||||
|
ProcessInfo pi;
|
||||||
|
pi.ParentProcessId = processMeta.th32ParentProcessID;
|
||||||
|
pi.ProcessId = processMeta.th32ProcessID;
|
||||||
|
pi.CreationTime = {0, 0};
|
||||||
|
pi.DevicePath = L"";
|
||||||
|
|
||||||
|
FILETIME creationTime, null_time;
|
||||||
|
auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
|
||||||
|
&null_time);
|
||||||
|
if (ok) {
|
||||||
|
pi.CreationTime = creationTime;
|
||||||
|
}
|
||||||
|
wchar_t imagepath[MAX_PATH + 1];
|
||||||
|
if (K32GetProcessImageFileNameW(
|
||||||
|
process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
|
||||||
|
pi.DevicePath = imagepath;
|
||||||
|
}
|
||||||
|
return pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
SC_HANDLE WindowsSplitTunnel::installDriver() {
|
||||||
|
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
|
||||||
|
QFileInfo driver(qApp->applicationDirPath() + "/" + DRIVER_FILENAME);
|
||||||
|
if (!driver.exists()) {
|
||||||
|
logger.error() << "Split Tunnel Driver File not found "
|
||||||
|
<< driver.absoluteFilePath();
|
||||||
|
return (SC_HANDLE)INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
|
||||||
|
LPCWSTR binPath = (const wchar_t*)path.utf16();
|
||||||
|
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
||||||
|
auto serviceManager = OpenSCManager(NULL, // local computer
|
||||||
|
NULL, // servicesActive database
|
||||||
|
scm_rights);
|
||||||
|
auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName,
|
||||||
|
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
|
||||||
|
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||||
|
binPath, nullptr, 0, nullptr, nullptr, nullptr);
|
||||||
|
CloseServiceHandle(serviceManager);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
// static
|
||||||
|
bool WindowsSplitTunnel::uninstallDriver() {
|
||||||
|
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
||||||
|
auto serviceManager = OpenSCManager(NULL, // local computer
|
||||||
|
NULL, // servicesActive database
|
||||||
|
scm_rights);
|
||||||
|
|
||||||
|
auto servicehandle =
|
||||||
|
OpenService(serviceManager, DRIVER_SERVICE_NAME, GENERIC_READ);
|
||||||
|
auto result = DeleteService(servicehandle);
|
||||||
|
if (result) {
|
||||||
|
logger.debug() << "Split Tunnel Driver Removed";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// static
|
||||||
|
bool WindowsSplitTunnel::isInstalled() {
|
||||||
|
// Check if the Drivers I/O File is present
|
||||||
|
auto symlink = QFileInfo(QString::fromWCharArray(DRIVER_SYMLINK));
|
||||||
|
if (symlink.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If not check with SCM, if the kernel service exists
|
||||||
|
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
||||||
|
auto serviceManager = OpenSCManager(NULL, // local computer
|
||||||
|
NULL, // servicesActive database
|
||||||
|
scm_rights);
|
||||||
|
auto servicehandle =
|
||||||
|
OpenService(serviceManager, DRIVER_SERVICE_NAME, GENERIC_READ);
|
||||||
|
auto err = GetLastError();
|
||||||
|
CloseServiceHandle(serviceManager);
|
||||||
|
CloseServiceHandle(servicehandle);
|
||||||
|
return err != ERROR_SERVICE_DOES_NOT_EXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WindowsSplitTunnel::convertPath(const QString& path) {
|
||||||
|
auto parts = path.split("/");
|
||||||
|
QString driveLetter = parts.takeFirst();
|
||||||
|
if (!driveLetter.contains(":") || parts.size() == 0) {
|
||||||
|
// device should contain : for e.g C:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
QByteArray buffer(2048, 0xFF);
|
||||||
|
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
|
||||||
|
(wchar_t*)buffer.data(), buffer.size() / 2);
|
||||||
|
|
||||||
|
if (ok == ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
buffer.resize(buffer.size() * 2);
|
||||||
|
ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(),
|
||||||
|
buffer.size() / 2);
|
||||||
|
}
|
||||||
|
if (ok == 0) {
|
||||||
|
WindowsUtils::windowsLog("Err fetching dos path");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
QString deviceName;
|
||||||
|
deviceName = QString::fromWCharArray((wchar_t*)buffer.data());
|
||||||
|
parts.prepend(deviceName);
|
||||||
|
|
||||||
|
return parts.join("\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool WindowsSplitTunnel::detectConflict() {
|
||||||
|
auto scm_rights = SC_MANAGER_ENUMERATE_SERVICE;
|
||||||
|
auto serviceManager = OpenSCManager(NULL, // local computer
|
||||||
|
NULL, // servicesActive database
|
||||||
|
scm_rights);
|
||||||
|
auto cleanup = qScopeGuard([&] { CloseServiceHandle(serviceManager); });
|
||||||
|
// Query for Mullvad Service.
|
||||||
|
auto servicehandle =
|
||||||
|
OpenService(serviceManager, MV_SERVICE_NAME, GENERIC_READ);
|
||||||
|
auto err = GetLastError();
|
||||||
|
CloseServiceHandle(servicehandle);
|
||||||
|
if (err != ERROR_SERVICE_DOES_NOT_EXIST) {
|
||||||
|
WindowsUtils::windowsLog("Mullvad Detected - Disabling SplitTunnel: ");
|
||||||
|
// Mullvad is installed, so we would certainly break things.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto symlink = QFileInfo(QString::fromWCharArray(DRIVER_SYMLINK));
|
||||||
|
if (!symlink.exists()) {
|
||||||
|
// The driver is not loaded / installed.. MV is not installed, all good!
|
||||||
|
logger.info() << "No Split-Tunnel Conflict detected, continue.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The driver exists, so let's check if it has been created by us.
|
||||||
|
// If our service is not present, it's has been created by
|
||||||
|
// someone else so we should not use that :)
|
||||||
|
servicehandle =
|
||||||
|
OpenService(serviceManager, DRIVER_SERVICE_NAME, GENERIC_READ);
|
||||||
|
err = GetLastError();
|
||||||
|
CloseServiceHandle(servicehandle);
|
||||||
|
return err == ERROR_SERVICE_DOES_NOT_EXIST;
|
||||||
|
}
|
||||||
180
client/platforms/windows/daemon/windowssplittunnel.h
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSSPLITTUNNEL_H
|
||||||
|
#define WINDOWSSPLITTUNNEL_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
// Note: the ws2tcpip.h import must come before the others.
|
||||||
|
// clang-format off
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
// clang-format on
|
||||||
|
#include <Ws2ipdef.h>
|
||||||
|
#include <ioapiset.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// States for GetState
|
||||||
|
enum DRIVER_STATE {
|
||||||
|
STATE_UNKNOWN = -1,
|
||||||
|
STATE_NONE = 0,
|
||||||
|
STATE_STARTED = 1,
|
||||||
|
STATE_INITIALIZED = 2,
|
||||||
|
STATE_READY = 3,
|
||||||
|
STATE_RUNNING = 4,
|
||||||
|
STATE_ZOMBIE = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef CTL_CODE
|
||||||
|
|
||||||
|
# define FILE_ANY_ACCESS 0x0000
|
||||||
|
|
||||||
|
# define METHOD_BUFFERED 0
|
||||||
|
# define METHOD_IN_DIRECT 1
|
||||||
|
# define METHOD_NEITHER 3
|
||||||
|
|
||||||
|
# define CTL_CODE(DeviceType, Function, Method, Access) \
|
||||||
|
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Known ControlCodes
|
||||||
|
#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_DEQUEUE_EVENT \
|
||||||
|
CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_REGISTER_PROCESSES \
|
||||||
|
CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_REGISTER_IP_ADDRESSES \
|
||||||
|
CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_IP_ADDRESSES \
|
||||||
|
CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_SET_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_CLEAR_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_QUERY_PROCESS \
|
||||||
|
CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
// Driver Configuration structures
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Offset into buffer region that follows all entries.
|
||||||
|
// The image name uses the device path.
|
||||||
|
SIZE_T ImageNameOffset;
|
||||||
|
// Length of the String
|
||||||
|
USHORT ImageNameLength;
|
||||||
|
} CONFIGURATION_ENTRY;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Number of entries immediately following the header.
|
||||||
|
SIZE_T NumEntries;
|
||||||
|
|
||||||
|
// Total byte length: header + entries + string buffer.
|
||||||
|
SIZE_T TotalLength;
|
||||||
|
} CONFIGURATION_HEADER;
|
||||||
|
|
||||||
|
// Used to Configure Which IP is network/vpn
|
||||||
|
typedef struct {
|
||||||
|
IN_ADDR TunnelIpv4;
|
||||||
|
IN_ADDR InternetIpv4;
|
||||||
|
|
||||||
|
IN6_ADDR TunnelIpv6;
|
||||||
|
IN6_ADDR InternetIpv6;
|
||||||
|
} IP_ADDRESSES_CONFIG;
|
||||||
|
|
||||||
|
// Used to Define Which Processes are alive on activation
|
||||||
|
typedef struct {
|
||||||
|
SIZE_T NumEntries;
|
||||||
|
SIZE_T TotalLength;
|
||||||
|
} PROCESS_DISCOVERY_HEADER;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HANDLE ProcessId;
|
||||||
|
HANDLE ParentProcessId;
|
||||||
|
|
||||||
|
SIZE_T ImageNameOffset;
|
||||||
|
USHORT ImageNameLength;
|
||||||
|
} PROCESS_DISCOVERY_ENTRY;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD ProcessId;
|
||||||
|
DWORD ParentProcessId;
|
||||||
|
FILETIME CreationTime;
|
||||||
|
std::wstring DevicePath;
|
||||||
|
} ProcessInfo;
|
||||||
|
|
||||||
|
class WindowsSplitTunnel final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsSplitTunnel)
|
||||||
|
public:
|
||||||
|
explicit WindowsSplitTunnel(QObject* parent);
|
||||||
|
~WindowsSplitTunnel();
|
||||||
|
|
||||||
|
// void excludeApps(const QStringList& paths);
|
||||||
|
// Excludes an Application from the VPN
|
||||||
|
void setRules(const QStringList& appPaths);
|
||||||
|
|
||||||
|
// Fetches and Pushed needed info to move to engaged mode
|
||||||
|
void start(int inetAdapterIndex);
|
||||||
|
// Deletes Rules and puts the driver into passive mode
|
||||||
|
void stop();
|
||||||
|
// Resets the Whole Driver
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
// Just close connection, leave state as is
|
||||||
|
void close();
|
||||||
|
|
||||||
|
// Installes the Kernel Driver as Driver Service
|
||||||
|
static SC_HANDLE installDriver();
|
||||||
|
static bool uninstallDriver();
|
||||||
|
static bool isInstalled();
|
||||||
|
static bool detectConflict();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initDriver();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE m_driver = INVALID_HANDLE_VALUE;
|
||||||
|
constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
|
||||||
|
constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
|
||||||
|
constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
|
||||||
|
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
||||||
|
DRIVER_STATE getState();
|
||||||
|
|
||||||
|
// Initializes the WFP Sublayer
|
||||||
|
bool initSublayer();
|
||||||
|
|
||||||
|
// Generates a Configuration for Each APP
|
||||||
|
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
|
||||||
|
// Generates a Configuration which IP's are VPN and which network
|
||||||
|
std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex);
|
||||||
|
std::vector<uint8_t> generateProcessBlob();
|
||||||
|
|
||||||
|
void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
|
||||||
|
// Collects info about an Opened Process
|
||||||
|
ProcessInfo getProcessInfo(HANDLE process,
|
||||||
|
const PROCESSENTRY32W& processMeta);
|
||||||
|
|
||||||
|
// Converts a path to a Dos Path:
|
||||||
|
// e.g C:/a.exe -> /harddisk0/a.exe
|
||||||
|
QString convertPath(const QString& path);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSSPLITTUNNEL_H
|
||||||
135
client/platforms/windows/daemon/windowstunnellogger.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
/* 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 "windowstunnellogger.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
/* The ring logger format used by the Wireguard DLL is as follows, assuming
|
||||||
|
* no padding:
|
||||||
|
*
|
||||||
|
* struct {
|
||||||
|
* uint32_t magic;
|
||||||
|
* uint32_t index;
|
||||||
|
* struct {
|
||||||
|
* uint64_t timestamp;
|
||||||
|
* char message[512];
|
||||||
|
* } ring[2048];
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
|
||||||
|
constexpr uint32_t RINGLOG_POLL_MSEC = 250;
|
||||||
|
constexpr uint32_t RINGLOG_MAGIC_HEADER = 0xbadbabe;
|
||||||
|
constexpr uint32_t RINGLOG_INDEX_OFFSET = 4;
|
||||||
|
constexpr uint32_t RINGLOG_HEADER_SIZE = 8;
|
||||||
|
constexpr uint32_t RINGLOG_MAX_ENTRIES = 2048;
|
||||||
|
constexpr uint32_t RINGLOG_MESSAGE_SIZE = 512;
|
||||||
|
constexpr uint32_t RINGLOG_TIMESTAMP_SIZE = 8;
|
||||||
|
constexpr uint32_t RINGLOG_FILE_SIZE =
|
||||||
|
RINGLOG_HEADER_SIZE +
|
||||||
|
((RINGLOG_MESSAGE_SIZE + RINGLOG_TIMESTAMP_SIZE) * RINGLOG_MAX_ENTRIES);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("tunnel.dll");
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WindowsTunnelLogger::WindowsTunnelLogger(const QString& filename,
|
||||||
|
QObject* parent)
|
||||||
|
: QObject(parent), m_logfile(filename, this), m_timer(this) {
|
||||||
|
MZ_COUNT_CTOR(WindowsTunnelLogger);
|
||||||
|
|
||||||
|
m_startTime = QDateTime::currentMSecsSinceEpoch() * 1000000;
|
||||||
|
m_logindex = -1;
|
||||||
|
|
||||||
|
connect(&m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
|
||||||
|
m_timer.start(RINGLOG_POLL_MSEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsTunnelLogger::~WindowsTunnelLogger() {
|
||||||
|
MZ_COUNT_DTOR(WindowsTunnelLogger);
|
||||||
|
if (m_logdata) {
|
||||||
|
timeout();
|
||||||
|
|
||||||
|
m_logfile.unmap(m_logdata);
|
||||||
|
m_logdata = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsTunnelLogger::openLogData() {
|
||||||
|
if (m_logdata) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!m_logfile.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logdata = m_logfile.map(0, RINGLOG_FILE_SIZE);
|
||||||
|
if (!m_logdata) {
|
||||||
|
m_logfile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a valid magic header
|
||||||
|
uint32_t magic;
|
||||||
|
memcpy(&magic, m_logdata, 4);
|
||||||
|
logger.debug() << "Opening tunnel log file" << m_logfile.fileName();
|
||||||
|
if (magic != RINGLOG_MAGIC_HEADER) {
|
||||||
|
logger.error() << "Unexpected magic header:" << QString::number(magic, 16);
|
||||||
|
m_logfile.unmap(m_logdata);
|
||||||
|
m_logfile.close();
|
||||||
|
m_logdata = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowsTunnelLogger::nextIndex() {
|
||||||
|
qint32 value;
|
||||||
|
memcpy(&value, m_logdata + RINGLOG_INDEX_OFFSET, 4);
|
||||||
|
return value % RINGLOG_MAX_ENTRIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsTunnelLogger::process(int index) {
|
||||||
|
Q_ASSERT(index >= 0);
|
||||||
|
Q_ASSERT(index < RINGLOG_MAX_ENTRIES);
|
||||||
|
size_t offset = static_cast<size_t>(index) *
|
||||||
|
(RINGLOG_TIMESTAMP_SIZE + RINGLOG_MESSAGE_SIZE);
|
||||||
|
uchar* data = m_logdata + RINGLOG_HEADER_SIZE + offset;
|
||||||
|
|
||||||
|
quint64 timestamp;
|
||||||
|
memcpy(×tamp, data, RINGLOG_TIMESTAMP_SIZE);
|
||||||
|
if (timestamp <= m_startTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray message((const char*)data + RINGLOG_TIMESTAMP_SIZE,
|
||||||
|
RINGLOG_MESSAGE_SIZE);
|
||||||
|
int nullIndex = message.indexOf(0x0);
|
||||||
|
if (nullIndex >= 0) {
|
||||||
|
message.truncate(nullIndex);
|
||||||
|
}
|
||||||
|
logger.info() << QString::fromUtf8(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsTunnelLogger::timeout() {
|
||||||
|
if (!openLogData()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On the first pass, scan all log messages. */
|
||||||
|
if (m_logindex < 0) {
|
||||||
|
m_logindex = nextIndex();
|
||||||
|
process(m_logindex);
|
||||||
|
m_logindex = (m_logindex + 1) % RINGLOG_MAX_ENTRIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report new messages. */
|
||||||
|
while (m_logindex != nextIndex()) {
|
||||||
|
process(m_logindex);
|
||||||
|
m_logindex = (m_logindex + 1) % RINGLOG_MAX_ENTRIES;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
client/platforms/windows/daemon/windowstunnellogger.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSTUNNELLOGGER_H
|
||||||
|
#define WINDOWSTUNNELLOGGER_H
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
class WindowsTunnelLogger final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsTunnelLogger)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsTunnelLogger(const QString& filename, QObject* parent = nullptr);
|
||||||
|
~WindowsTunnelLogger();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void timeout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool openLogData();
|
||||||
|
void process(int index);
|
||||||
|
int nextIndex();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer m_timer;
|
||||||
|
QFile m_logfile;
|
||||||
|
uchar* m_logdata = nullptr;
|
||||||
|
int m_logindex = -1;
|
||||||
|
quint64 m_startTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSTUNNELLOGGER_H
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
client/platforms/windows/daemon/windowstunnelservice.h
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSTUNNELSERVICE_H
|
||||||
|
#define WINDOWSTUNNELSERVICE_H
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "windowstunnellogger.h"
|
||||||
|
|
||||||
|
class WindowsTunnelService final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsTunnelService)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsTunnelService(QObject* parent = nullptr);
|
||||||
|
~WindowsTunnelService();
|
||||||
|
|
||||||
|
bool start(const QString& configData);
|
||||||
|
void stop();
|
||||||
|
bool isRunning();
|
||||||
|
QString uapiCommand(const QString& command);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backendFailure();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void timeout();
|
||||||
|
static QString exitCodeToFailure(const void* status);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer m_timer;
|
||||||
|
QThread m_logthread;
|
||||||
|
WindowsTunnelLogger* m_logworker = nullptr;
|
||||||
|
|
||||||
|
// These are really SC_HANDLEs in disguise.
|
||||||
|
void* m_scm = nullptr;
|
||||||
|
void* m_service = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSTUNNELSERVICE_H
|
||||||
275
client/platforms/windows/daemon/wireguardutilswindows.cpp
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
/* 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 "wireguardutilswindows.h"
|
||||||
|
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2ipdef.h>
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/windowscommons.h"
|
||||||
|
#include "windowsdaemon.h"
|
||||||
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WireguardUtilsWindows");
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent)
|
||||||
|
: WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) {
|
||||||
|
MZ_COUNT_CTOR(WireguardUtilsWindows);
|
||||||
|
logger.debug() << "WireguardUtilsWindows created.";
|
||||||
|
|
||||||
|
connect(&m_tunnel, &WindowsTunnelService::backendFailure, this,
|
||||||
|
[&] { emit backendFailure(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
WireguardUtilsWindows::~WireguardUtilsWindows() {
|
||||||
|
MZ_COUNT_DTOR(WireguardUtilsWindows);
|
||||||
|
logger.debug() << "WireguardUtilsWindows destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<WireguardUtils::PeerStatus> WireguardUtilsWindows::getPeerStatus() {
|
||||||
|
QString reply = m_tunnel.uapiCommand("get=1");
|
||||||
|
PeerStatus status;
|
||||||
|
QList<PeerStatus> peerList;
|
||||||
|
for (const QString& line : reply.split('\n')) {
|
||||||
|
int eq = line.indexOf('=');
|
||||||
|
if (eq <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString name = line.left(eq);
|
||||||
|
QString value = line.mid(eq + 1);
|
||||||
|
|
||||||
|
if (name == "public_key") {
|
||||||
|
if (!status.m_pubkey.isEmpty()) {
|
||||||
|
peerList.append(status);
|
||||||
|
}
|
||||||
|
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
|
||||||
|
status = PeerStatus(pubkey.toBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "tx_bytes") {
|
||||||
|
status.m_txBytes = value.toDouble();
|
||||||
|
}
|
||||||
|
if (name == "rx_bytes") {
|
||||||
|
status.m_rxBytes = value.toDouble();
|
||||||
|
}
|
||||||
|
if (name == "last_handshake_time_sec") {
|
||||||
|
status.m_handshake += value.toLongLong() * 1000;
|
||||||
|
}
|
||||||
|
if (name == "last_handshake_time_nsec") {
|
||||||
|
status.m_handshake += value.toLongLong() / 1000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!status.m_pubkey.isEmpty()) {
|
||||||
|
peerList.append(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||||
|
QStringList addresses;
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
addresses.append(ip.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, QString> extraConfig;
|
||||||
|
extraConfig["Table"] = "off";
|
||||||
|
QString configString = config.toWgConf(extraConfig);
|
||||||
|
if (configString.isEmpty()) {
|
||||||
|
logger.error() << "Failed to create a config file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to pass a peer just yet, that will happen later with
|
||||||
|
// a UAPI command in WireguardUtilsWindows::updatePeer(), so truncate
|
||||||
|
// the config file to remove the [Peer] section.
|
||||||
|
qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive);
|
||||||
|
if (peerStart >= 0) {
|
||||||
|
configString.truncate(peerStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_tunnel.start(configString)) {
|
||||||
|
logger.error() << "Failed to activate the tunnel service";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the interface LUID
|
||||||
|
NET_LUID luid;
|
||||||
|
QString ifAlias = interfaceName();
|
||||||
|
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)ifAlias.utf16(), &luid);
|
||||||
|
if (result != 0) {
|
||||||
|
logger.error() << "Failed to lookup LUID:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_luid = luid.Value;
|
||||||
|
m_routeMonitor.setLuid(luid.Value);
|
||||||
|
|
||||||
|
// Enable the windows firewall
|
||||||
|
NET_IFINDEX ifindex;
|
||||||
|
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||||
|
WindowsFirewall::instance()->enableKillSwitch(ifindex);
|
||||||
|
|
||||||
|
logger.debug() << "Registration completed";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::deleteInterface() {
|
||||||
|
WindowsFirewall::instance()->disableKillSwitch();
|
||||||
|
m_tunnel.stop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||||
|
QByteArray publicKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
QByteArray pskKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||||
|
|
||||||
|
// Enable the windows firewall for this peer.
|
||||||
|
WindowsFirewall::instance()->enablePeerTraffic(config);
|
||||||
|
|
||||||
|
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||||
|
<< "via" << config.m_serverIpv4AddrIn;
|
||||||
|
|
||||||
|
// Update/create the peer config
|
||||||
|
QString message;
|
||||||
|
QTextStream out(&message);
|
||||||
|
out << "set=1\n";
|
||||||
|
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||||
|
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||||
|
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||||
|
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||||
|
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||||
|
out << "endpoint=[" << config.m_serverIpv6AddrIn << "]:";
|
||||||
|
} else {
|
||||||
|
logger.warning() << "Failed to create peer with no endpoints";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out << config.m_serverPort << "\n";
|
||||||
|
|
||||||
|
out << "replace_allowed_ips=true\n";
|
||||||
|
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
|
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString reply = m_tunnel.uapiCommand(message);
|
||||||
|
logger.debug() << "DATA:" << reply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
|
||||||
|
QByteArray publicKey =
|
||||||
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
|
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the windows firewall for this peer.
|
||||||
|
WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey);
|
||||||
|
|
||||||
|
QString message;
|
||||||
|
QTextStream out(&message);
|
||||||
|
out << "set=1\n";
|
||||||
|
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||||
|
out << "remove=true\n";
|
||||||
|
|
||||||
|
QString reply = m_tunnel.uapiCommand(message);
|
||||||
|
logger.debug() << "DATA:" << reply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WireguardUtilsWindows::buildMibForwardRow(const IPAddress& prefix,
|
||||||
|
void* row) {
|
||||||
|
MIB_IPFORWARD_ROW2* entry = (MIB_IPFORWARD_ROW2*)row;
|
||||||
|
InitializeIpForwardEntry(entry);
|
||||||
|
|
||||||
|
// Populate the next hop
|
||||||
|
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
InetPtonA(AF_INET6, qPrintable(prefix.address().toString()),
|
||||||
|
&entry->DestinationPrefix.Prefix.Ipv6.sin6_addr);
|
||||||
|
entry->DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||||
|
entry->DestinationPrefix.PrefixLength = prefix.prefixLength();
|
||||||
|
} else {
|
||||||
|
InetPtonA(AF_INET, qPrintable(prefix.address().toString()),
|
||||||
|
&entry->DestinationPrefix.Prefix.Ipv4.sin_addr);
|
||||||
|
entry->DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||||
|
entry->DestinationPrefix.PrefixLength = prefix.prefixLength();
|
||||||
|
}
|
||||||
|
entry->InterfaceLuid.Value = m_luid;
|
||||||
|
entry->NextHop.si_family = entry->DestinationPrefix.Prefix.si_family;
|
||||||
|
|
||||||
|
// Set the rest of the flags for a static route.
|
||||||
|
entry->ValidLifetime = 0xffffffff;
|
||||||
|
entry->PreferredLifetime = 0xffffffff;
|
||||||
|
entry->Metric = 0;
|
||||||
|
entry->Protocol = MIB_IPPROTO_NETMGMT;
|
||||||
|
entry->Loopback = false;
|
||||||
|
entry->AutoconfigureAddress = false;
|
||||||
|
entry->Publish = false;
|
||||||
|
entry->Immortal = false;
|
||||||
|
entry->Age = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
|
MIB_IPFORWARD_ROW2 entry;
|
||||||
|
buildMibForwardRow(prefix, &entry);
|
||||||
|
|
||||||
|
// Install the route
|
||||||
|
DWORD result = CreateIpForwardEntry2(&entry);
|
||||||
|
if (result == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to create route to"
|
||||||
|
<< logger.sensitive(prefix.toString())
|
||||||
|
<< "result:" << result;
|
||||||
|
}
|
||||||
|
return result == NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
|
MIB_IPFORWARD_ROW2 entry;
|
||||||
|
buildMibForwardRow(prefix, &entry);
|
||||||
|
|
||||||
|
// Install the route
|
||||||
|
DWORD result = DeleteIpForwardEntry2(&entry);
|
||||||
|
if (result == ERROR_NOT_FOUND) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to delete route to"
|
||||||
|
<< logger.sensitive(prefix.toString())
|
||||||
|
<< "result:" << result;
|
||||||
|
}
|
||||||
|
return result == NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
return m_routeMonitor.addExclusionRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
|
return m_routeMonitor.deleteExclusionRoute(prefix);
|
||||||
|
}
|
||||||
53
client/platforms/windows/daemon/wireguardutilswindows.h
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WIREGUARDUTILSWINDOWS_H
|
||||||
|
#define WIREGUARDUTILSWINDOWS_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "daemon/wireguardutils.h"
|
||||||
|
#include "windowsroutemonitor.h"
|
||||||
|
#include "windowstunnelservice.h"
|
||||||
|
|
||||||
|
class WireguardUtilsWindows final : public WireguardUtils {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WireguardUtilsWindows(QObject* parent);
|
||||||
|
~WireguardUtilsWindows();
|
||||||
|
|
||||||
|
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
||||||
|
QString interfaceName() override {
|
||||||
|
return WireguardUtilsWindows::s_interfaceName();
|
||||||
|
}
|
||||||
|
static const QString s_interfaceName() { return "AmneziaVPN"; }
|
||||||
|
bool addInterface(const InterfaceConfig& config) override;
|
||||||
|
bool deleteInterface() override;
|
||||||
|
|
||||||
|
bool updatePeer(const InterfaceConfig& config) override;
|
||||||
|
bool deletePeer(const InterfaceConfig& config) override;
|
||||||
|
QList<PeerStatus> getPeerStatus() override;
|
||||||
|
|
||||||
|
bool updateRoutePrefix(const IPAddress& prefix) override;
|
||||||
|
bool deleteRoutePrefix(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void backendFailure();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
||||||
|
|
||||||
|
quint64 m_luid = 0;
|
||||||
|
WindowsTunnelService m_tunnel;
|
||||||
|
WindowsRouteMonitor m_routeMonitor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WIREGUARDUTILSWINDOWS_H
|
||||||
186
client/platforms/windows/windowscommons.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
/* 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 "windowscommons.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
|
#define TUNNEL_SERVICE_NAME L"WireGuardTunnel$amnvpn"
|
||||||
|
|
||||||
|
constexpr const char* VPN_NAME = "AmneziaVPN";
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsCommons");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WindowsCommons::tunnelConfigFile() {
|
||||||
|
QStringList paths =
|
||||||
|
QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
|
||||||
|
for (const QString& path : paths) {
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir vpnDir(dir.filePath(VPN_NAME));
|
||||||
|
if (!vpnDir.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString wireguardFile(vpnDir.filePath(QString("%1.conf").arg(VPN_NAME)));
|
||||||
|
if (!QFileInfo::exists(wireguardFile)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Found the current wireguard configuration:"
|
||||||
|
<< wireguardFile;
|
||||||
|
return wireguardFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString& path : paths) {
|
||||||
|
QDir dir(path);
|
||||||
|
|
||||||
|
QDir vpnDir(dir.filePath(VPN_NAME));
|
||||||
|
if (!vpnDir.exists() && !dir.mkdir(VPN_NAME)) {
|
||||||
|
logger.debug() << "Failed to create path Amnezia under" << path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vpnDir.filePath(QString("%1.conf").arg(VPN_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error() << "Failed to create the right paths";
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WindowsCommons::tunnelLogFile() {
|
||||||
|
QStringList paths =
|
||||||
|
QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
|
||||||
|
|
||||||
|
for (const QString& path : paths) {
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir vpnDir(dir.filePath(VPN_NAME));
|
||||||
|
if (!vpnDir.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vpnDir.filePath("log.bin");
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
int WindowsCommons::AdapterIndexTo(const QHostAddress& dst) {
|
||||||
|
logger.debug() << "Getting Current Internet Adapter that routes to"
|
||||||
|
<< logger.sensitive(dst.toString());
|
||||||
|
quint32_be ipBigEndian;
|
||||||
|
quint32 ip = dst.toIPv4Address();
|
||||||
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
|
_MIB_IPFORWARDROW routeInfo;
|
||||||
|
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
auto adapter =
|
||||||
|
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
|
||||||
|
logger.debug() << "Internet Adapter:" << adapter.name();
|
||||||
|
return routeInfo.dwForwardIfIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
int WindowsCommons::VPNAdapterIndex() {
|
||||||
|
// For someReason QNetworkInterface::fromName(MozillaVPN) does not work >:(
|
||||||
|
auto adapterList = QNetworkInterface::allInterfaces();
|
||||||
|
for (const auto& adapter : adapterList) {
|
||||||
|
if (adapter.humanReadableName().contains("AmneziaVPN")) {
|
||||||
|
return adapter.index();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static
|
||||||
|
QString WindowsCommons::getCurrentPath() {
|
||||||
|
QByteArray buffer(2048, 0xFF);
|
||||||
|
auto ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
if (ok == ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
buffer.resize(buffer.size() * 2);
|
||||||
|
ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
if (ok == 0) {
|
||||||
|
WindowsUtils::windowsLog("Err fetching dos path");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return QString::fromLocal8Bit(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static
|
||||||
|
bool WindowsCommons::requireSoftwareRendering() {
|
||||||
|
/* Qt6 appears to require Direct3D shader level 5, and can result in rendering
|
||||||
|
* failures on some platforms. To workaround the issue, try to identify if
|
||||||
|
* this device can reliably run the shaders, and request fallback to software
|
||||||
|
* rendering if not.
|
||||||
|
*
|
||||||
|
* See: https://bugreports.qt.io/browse/QTBUG-100689
|
||||||
|
*/
|
||||||
|
IDXGIFactory1* factory;
|
||||||
|
HRESULT result;
|
||||||
|
|
||||||
|
result = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&factory));
|
||||||
|
if (FAILED(result)) {
|
||||||
|
logger.error() << "Failed to create DXGI Factory:" << result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto guard = qScopeGuard([&] { factory->Release(); });
|
||||||
|
|
||||||
|
// Enumerate the graphics adapters to find the minimum D3D shader version
|
||||||
|
// that we can guarantee will render successfully.
|
||||||
|
UINT i = 0;
|
||||||
|
IDXGIAdapter1* adapter;
|
||||||
|
D3D_FEATURE_LEVEL minFeatureLevel = D3D_FEATURE_LEVEL_11_1;
|
||||||
|
while (factory->EnumAdapters1(i++, &adapter) != DXGI_ERROR_NOT_FOUND) {
|
||||||
|
auto adapterGuard = qScopeGuard([adapter] { adapter->Release(); });
|
||||||
|
DXGI_ADAPTER_DESC1 desc;
|
||||||
|
adapter->GetDesc1(&desc);
|
||||||
|
QString gpuName = QString::fromWCharArray(desc.Description);
|
||||||
|
|
||||||
|
// Try creating the driver to see what D3D feature level it supports.
|
||||||
|
D3D_FEATURE_LEVEL gpuFeatureLevel = D3D_FEATURE_LEVEL_9_1;
|
||||||
|
result = D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0,
|
||||||
|
nullptr, 0, D3D11_SDK_VERSION, nullptr,
|
||||||
|
&gpuFeatureLevel, nullptr);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
logger.error() << "D3D Device" << gpuName
|
||||||
|
<< "failed:" << QString::number((quint32)result, 16);
|
||||||
|
} else {
|
||||||
|
if (gpuFeatureLevel < minFeatureLevel) {
|
||||||
|
minFeatureLevel = gpuFeatureLevel;
|
||||||
|
}
|
||||||
|
logger.debug() << "D3D Device" << gpuName
|
||||||
|
<< "supports D3D:" << QString::number(gpuFeatureLevel, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D3D version 11.0 shader level 5, is required for GPU rendering.
|
||||||
|
return (minFeatureLevel < D3D_FEATURE_LEVEL_11_0);
|
||||||
|
}
|
||||||
28
client/platforms/windows/windowscommons.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSCOMMONS_H
|
||||||
|
#define WINDOWSCOMMONS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class QHostAddress;
|
||||||
|
|
||||||
|
class WindowsCommons final {
|
||||||
|
public:
|
||||||
|
static QString tunnelConfigFile();
|
||||||
|
static QString tunnelLogFile();
|
||||||
|
|
||||||
|
// Returns whether we need to fallback to software rendering.
|
||||||
|
static bool requireSoftwareRendering();
|
||||||
|
|
||||||
|
// Returns the Interface Index of the VPN Adapter
|
||||||
|
static int VPNAdapterIndex();
|
||||||
|
// Returns the Interface Index that could Route to dst
|
||||||
|
static int AdapterIndexTo(const QHostAddress& dst);
|
||||||
|
// Returns the Path of the Current process
|
||||||
|
static QString getCurrentPath();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSCOMMONS_H
|
||||||
144
client/platforms/windows/windowsnetworkwatcher.cpp
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
/* 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 "windowsnetworkwatcher.h"
|
||||||
|
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "networkwatcherimpl.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "Wlanapi.lib")
|
||||||
|
#pragma comment(lib, "windowsapp.lib")
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsNetworkWatcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsNetworkWatcher::WindowsNetworkWatcher(QObject* parent)
|
||||||
|
: NetworkWatcherImpl(parent) {
|
||||||
|
MZ_COUNT_CTOR(WindowsNetworkWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsNetworkWatcher::~WindowsNetworkWatcher() {
|
||||||
|
MZ_COUNT_DTOR(WindowsNetworkWatcher);
|
||||||
|
|
||||||
|
if (m_wlanHandle) {
|
||||||
|
WlanCloseHandle(m_wlanHandle, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsNetworkWatcher::initialize() {
|
||||||
|
logger.debug() << "initialize";
|
||||||
|
|
||||||
|
DWORD negotiatedVersion;
|
||||||
|
if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) !=
|
||||||
|
ERROR_SUCCESS) {
|
||||||
|
WindowsUtils::windowsLog("Failed to open the WLAN handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD prefNotifSource;
|
||||||
|
if (WlanRegisterNotification(m_wlanHandle, WLAN_NOTIFICATION_SOURCE_MSM,
|
||||||
|
true /* ignore duplicates */,
|
||||||
|
(WLAN_NOTIFICATION_CALLBACK)wlanCallback, this,
|
||||||
|
nullptr, &prefNotifSource) != ERROR_SUCCESS) {
|
||||||
|
WindowsUtils::windowsLog("Failed to register a wlan callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "callback registered";
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void WindowsNetworkWatcher::wlanCallback(PWLAN_NOTIFICATION_DATA data,
|
||||||
|
PVOID context) {
|
||||||
|
logger.debug() << "Callback";
|
||||||
|
|
||||||
|
WindowsNetworkWatcher* that = static_cast<WindowsNetworkWatcher*>(context);
|
||||||
|
Q_ASSERT(that);
|
||||||
|
|
||||||
|
that->processWlan(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) {
|
||||||
|
logger.debug() << "Processing wlan data";
|
||||||
|
|
||||||
|
if (!isActive()) {
|
||||||
|
logger.debug() << "The watcher is off";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->NotificationSource != WLAN_NOTIFICATION_SOURCE_MSM) {
|
||||||
|
logger.debug() << "The wlan source is not MSM";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->NotificationCode != wlan_notification_msm_connected) {
|
||||||
|
logger.debug() << "Wlan unprocessed code: " << data->NotificationCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PWLAN_CONNECTION_ATTRIBUTES connectionInfo = nullptr;
|
||||||
|
DWORD connectionInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES);
|
||||||
|
WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_invalid;
|
||||||
|
|
||||||
|
DWORD result = WlanQueryInterface(
|
||||||
|
m_wlanHandle, &data->InterfaceGuid, wlan_intf_opcode_current_connection,
|
||||||
|
nullptr, &connectionInfoSize, (PVOID*)&connectionInfo, &opCode);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
WindowsUtils::windowsLog("Failed to query the interface");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto guard = qScopeGuard([&] { WlanFreeMemory(connectionInfo); });
|
||||||
|
|
||||||
|
QString bssid;
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < sizeof(connectionInfo->wlanAssociationAttributes.dot11Bssid); ++i) {
|
||||||
|
if (i == 5) {
|
||||||
|
bssid.append(QString::asprintf(
|
||||||
|
"%.2X\n", connectionInfo->wlanAssociationAttributes.dot11Bssid[i]));
|
||||||
|
} else {
|
||||||
|
bssid.append(QString::asprintf(
|
||||||
|
"%.2X-", connectionInfo->wlanAssociationAttributes.dot11Bssid[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bssid != m_lastBSSID) {
|
||||||
|
emit networkChanged(bssid);
|
||||||
|
m_lastBSSID = bssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionInfo->wlanSecurityAttributes.dot11AuthAlgorithm !=
|
||||||
|
DOT11_AUTH_ALGO_80211_OPEN &&
|
||||||
|
connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm !=
|
||||||
|
DOT11_CIPHER_ALGO_WEP &&
|
||||||
|
connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm !=
|
||||||
|
DOT11_CIPHER_ALGO_WEP40 &&
|
||||||
|
connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm !=
|
||||||
|
DOT11_CIPHER_ALGO_WEP104) {
|
||||||
|
logger.debug() << "The network is secure enough";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ssid;
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < connectionInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength;
|
||||||
|
++i) {
|
||||||
|
ssid.append(QString::asprintf(
|
||||||
|
"%c",
|
||||||
|
(char)connectionInfo->wlanAssociationAttributes.dot11Ssid.ucSSID[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Unsecure network:" << logger.sensitive(ssid)
|
||||||
|
<< "id:" << logger.sensitive(bssid);
|
||||||
|
emit unsecuredNetwork(ssid, bssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkWatcherImpl::TransportType WindowsNetworkWatcher::getTransportType() {
|
||||||
|
// TODO: Implement this once we update to Qt6.3 (VPN-3511)
|
||||||
|
return TransportType_Other;
|
||||||
|
}
|
||||||
34
client/platforms/windows/windowsnetworkwatcher.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSNETWORKWATCHER_H
|
||||||
|
#define WINDOWSNETWORKWATCHER_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wlanapi.h>
|
||||||
|
|
||||||
|
#include "networkwatcherimpl.h"
|
||||||
|
|
||||||
|
class WindowsNetworkWatcher final : public NetworkWatcherImpl {
|
||||||
|
public:
|
||||||
|
WindowsNetworkWatcher(QObject* parent);
|
||||||
|
~WindowsNetworkWatcher();
|
||||||
|
|
||||||
|
void initialize() override;
|
||||||
|
|
||||||
|
NetworkWatcherImpl::TransportType getTransportType() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context);
|
||||||
|
|
||||||
|
void processWlan(PWLAN_NOTIFICATION_DATA data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The handle is set during the initialization. Windows calls processWlan()
|
||||||
|
// to inform about network changes.
|
||||||
|
HANDLE m_wlanHandle = nullptr;
|
||||||
|
QString m_lastBSSID;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSNETWORKWATCHER_H
|
||||||
133
client/platforms/windows/windowspingsender.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
/* 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 "windowspingsender.h"
|
||||||
|
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
// Note: This important must come after the previous three.
|
||||||
|
// clang-format off
|
||||||
|
#include <IcmpAPI.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
#include "leakdetector.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "windowscommons.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "Ws2_32")
|
||||||
|
|
||||||
|
constexpr WORD WindowsPingPayloadSize = sizeof(quint16);
|
||||||
|
|
||||||
|
struct WindowsPingSenderPrivate {
|
||||||
|
HANDLE m_handle;
|
||||||
|
HANDLE m_event;
|
||||||
|
unsigned char m_buffer[sizeof(ICMP_ECHO_REPLY) + WindowsPingPayloadSize + 8];
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsPingSender");
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD icmpCleanupHelper(LPVOID data) {
|
||||||
|
struct WindowsPingSenderPrivate* p = (struct WindowsPingSenderPrivate*)data;
|
||||||
|
if (p->m_event != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(p->m_event);
|
||||||
|
}
|
||||||
|
if (p->m_handle != INVALID_HANDLE_VALUE) {
|
||||||
|
IcmpCloseHandle(p->m_handle);
|
||||||
|
}
|
||||||
|
delete p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsPingSender::WindowsPingSender(const QHostAddress& source,
|
||||||
|
QObject* parent)
|
||||||
|
: PingSender(parent) {
|
||||||
|
MZ_COUNT_CTOR(WindowsPingSender);
|
||||||
|
m_source = source;
|
||||||
|
m_private = new struct WindowsPingSenderPrivate;
|
||||||
|
m_private->m_handle = IcmpCreateFile();
|
||||||
|
m_private->m_event = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
m_notifier = new QWinEventNotifier(m_private->m_event, this);
|
||||||
|
QObject::connect(m_notifier, &QWinEventNotifier::activated, this,
|
||||||
|
&WindowsPingSender::pingEventReady);
|
||||||
|
|
||||||
|
memset(m_private->m_buffer, 0, sizeof(m_private->m_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsPingSender::~WindowsPingSender() {
|
||||||
|
MZ_COUNT_DTOR(WindowsPingSender);
|
||||||
|
if (m_notifier) {
|
||||||
|
delete m_notifier;
|
||||||
|
}
|
||||||
|
// Closing the ICMP handle can hang if there are lost ping replies. Moving
|
||||||
|
// the cleanup into a separate thread avoids deadlocking the application.
|
||||||
|
HANDLE h = CreateThread(NULL, 0, icmpCleanupHelper, m_private, 0, NULL);
|
||||||
|
if (h == NULL) {
|
||||||
|
icmpCleanupHelper(m_private);
|
||||||
|
} else {
|
||||||
|
CloseHandle(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||||
|
if (m_private->m_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_private->m_event == INVALID_HANDLE_VALUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 v4dst = dest.toIPv4Address();
|
||||||
|
if (m_source.isNull()) {
|
||||||
|
IcmpSendEcho2(m_private->m_handle, m_private->m_event, nullptr, nullptr,
|
||||||
|
qToBigEndian<quint32>(v4dst), &sequence, sizeof(sequence),
|
||||||
|
nullptr, m_private->m_buffer, sizeof(m_private->m_buffer),
|
||||||
|
10000);
|
||||||
|
} else {
|
||||||
|
quint32 v4src = m_source.toIPv4Address();
|
||||||
|
IcmpSendEcho2Ex(m_private->m_handle, m_private->m_event, nullptr, nullptr,
|
||||||
|
qToBigEndian<quint32>(v4src), qToBigEndian<quint32>(v4dst),
|
||||||
|
&sequence, sizeof(sequence), nullptr, m_private->m_buffer,
|
||||||
|
sizeof(m_private->m_buffer), 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD status = GetLastError();
|
||||||
|
if (status != ERROR_IO_PENDING) {
|
||||||
|
QString errmsg = WindowsUtils::getErrorMessage();
|
||||||
|
logger.error() << "failed to start Code: " << status
|
||||||
|
<< " Message: " << errmsg
|
||||||
|
<< " dest:" << logger.sensitive(dest.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsPingSender::pingEventReady() {
|
||||||
|
DWORD replyCount =
|
||||||
|
IcmpParseReplies(m_private->m_buffer, sizeof(m_private->m_buffer));
|
||||||
|
if (replyCount == 0) {
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
if (error == IP_REQ_TIMED_OUT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString errmsg = WindowsUtils::getErrorMessage();
|
||||||
|
logger.error() << "No ping reply. Code: " << error
|
||||||
|
<< " Message: " << errmsg;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ICMP_ECHO_REPLY* replies = (const ICMP_ECHO_REPLY*)m_private->m_buffer;
|
||||||
|
for (DWORD i = 0; i < replyCount; i++) {
|
||||||
|
if (replies[i].DataSize < sizeof(quint16)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
quint16 sequence;
|
||||||
|
memcpy(&sequence, replies[i].Data, sizeof(quint16));
|
||||||
|
emit recvPing(sequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
client/platforms/windows/windowspingsender.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSPINGSENDER_H
|
||||||
|
#define WINDOWSPINGSENDER_H
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QWinEventNotifier>
|
||||||
|
|
||||||
|
#include "pingsender.h"
|
||||||
|
|
||||||
|
struct WindowsPingSenderPrivate;
|
||||||
|
|
||||||
|
class WindowsPingSender final : public PingSender {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsPingSender)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsPingSender(const QHostAddress& source, QObject* parent = nullptr);
|
||||||
|
~WindowsPingSender();
|
||||||
|
|
||||||
|
void sendPing(const QHostAddress& destination, quint16 sequence) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void pingEventReady();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHostAddress m_source;
|
||||||
|
QWinEventNotifier* m_notifier = nullptr;
|
||||||
|
struct WindowsPingSenderPrivate* m_private = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSPINGSENDER_H
|
||||||
139
client/platforms/windows/windowsservicemanager.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/* 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 "windowsservicemanager.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "Windows.h"
|
||||||
|
#include "Winsvc.h"
|
||||||
|
#include "logger.h"
|
||||||
|
//#include "mozillavpn.h"
|
||||||
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsServiceManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) {
|
||||||
|
DWORD err = NULL;
|
||||||
|
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
|
||||||
|
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
|
||||||
|
m_serviceManager = OpenSCManager(NULL, // local computer
|
||||||
|
NULL, // servicesActive database
|
||||||
|
scm_rights);
|
||||||
|
err = GetLastError();
|
||||||
|
if (err != NULL) {
|
||||||
|
logger.error() << " OpenSCManager failed code: " << err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug() << "OpenSCManager access given - " << err;
|
||||||
|
|
||||||
|
logger.debug() << "Opening Service - "
|
||||||
|
<< QString::fromWCharArray(serviceName);
|
||||||
|
// Try to get an elevated handle
|
||||||
|
m_service = OpenService(m_serviceManager, // SCM database
|
||||||
|
serviceName, // name of service
|
||||||
|
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
|
||||||
|
err = GetLastError();
|
||||||
|
if (err != NULL) {
|
||||||
|
WindowsUtils::windowsLog("OpenService failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_has_access = true;
|
||||||
|
m_timer.setSingleShot(false);
|
||||||
|
|
||||||
|
logger.debug() << "Service manager execute access granted";
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsServiceManager::~WindowsServiceManager() {
|
||||||
|
if (m_service != NULL) {
|
||||||
|
CloseServiceHandle(m_service);
|
||||||
|
}
|
||||||
|
if (m_serviceManager != NULL) {
|
||||||
|
CloseServiceHandle(m_serviceManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
|
||||||
|
int tries = 0;
|
||||||
|
while (tries < max_wait_sec) {
|
||||||
|
SERVICE_STATUS status;
|
||||||
|
if (!QueryServiceStatus(m_service, &status)) {
|
||||||
|
WindowsUtils::windowsLog("Failed to retrieve the service status");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.dwCurrentState == goal_state) {
|
||||||
|
if (status.dwCurrentState == SERVICE_RUNNING) {
|
||||||
|
emit serviceStarted();
|
||||||
|
}
|
||||||
|
if (status.dwCurrentState == SERVICE_STOPPED) {
|
||||||
|
emit serviceStopped();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Polling Status" << m_state_target
|
||||||
|
<< "wanted, has: " << status.dwCurrentState;
|
||||||
|
Sleep(1000);
|
||||||
|
++tries;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
|
||||||
|
SERVICE_STATUS_PROCESS serviceStatus;
|
||||||
|
if (!m_has_access) {
|
||||||
|
logger.debug() << "Need read access to get service state";
|
||||||
|
return serviceStatus;
|
||||||
|
}
|
||||||
|
DWORD dwBytesNeeded; // Contains missing bytes if struct is too small?
|
||||||
|
QueryServiceStatusEx(m_service, // handle to service
|
||||||
|
SC_STATUS_PROCESS_INFO, // information level
|
||||||
|
(LPBYTE)&serviceStatus, // address of structure
|
||||||
|
sizeof(SERVICE_STATUS_PROCESS), // size of structure
|
||||||
|
&dwBytesNeeded);
|
||||||
|
return serviceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsServiceManager::startService() {
|
||||||
|
auto state = getStatus().dwCurrentState;
|
||||||
|
if (state != SERVICE_STOPPED && state != SERVICE_STOP_PENDING) {
|
||||||
|
logger.warning() << ("Service start not possible, as its running");
|
||||||
|
emit serviceStarted();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = StartService(m_service, // handle to service
|
||||||
|
0, // number of arguments
|
||||||
|
NULL); // no arguments
|
||||||
|
if (ok) {
|
||||||
|
logger.debug() << ("Service start requested");
|
||||||
|
startPolling(SERVICE_RUNNING, 30);
|
||||||
|
} else {
|
||||||
|
WindowsUtils::windowsLog("StartService failed");
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsServiceManager::stopService() {
|
||||||
|
if (!m_has_access) {
|
||||||
|
logger.error() << "Need execute access to stop services";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto state = getStatus().dwCurrentState;
|
||||||
|
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
|
||||||
|
logger.warning() << ("Service stop not possible, as its not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = ControlService(m_service, SERVICE_CONTROL_STOP, NULL);
|
||||||
|
if (ok) {
|
||||||
|
logger.debug() << ("Service stop requested");
|
||||||
|
startPolling(SERVICE_STOPPED, 10);
|
||||||
|
} else {
|
||||||
|
WindowsUtils::windowsLog("StopService failed");
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
60
client/platforms/windows/windowsservicemanager.h
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSSERVICEMANAGER
|
||||||
|
#define WINDOWSSERVICEMANAGER
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "Windows.h"
|
||||||
|
#include "Winsvc.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The WindowsServiceManager provides controll over the MozillaVPNBroker
|
||||||
|
* service via SCM
|
||||||
|
*/
|
||||||
|
class WindowsServiceManager : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindowsServiceManager(LPCWSTR serviceName);
|
||||||
|
~WindowsServiceManager();
|
||||||
|
|
||||||
|
// true if the Service is running
|
||||||
|
bool isRunning() { return getStatus().dwCurrentState == SERVICE_RUNNING; };
|
||||||
|
|
||||||
|
// Starts the service if execute rights are present
|
||||||
|
// Starts to poll for serviceStarted
|
||||||
|
bool startService();
|
||||||
|
|
||||||
|
// Stops the service if execute rights are present.
|
||||||
|
// Starts to poll for serviceStopped
|
||||||
|
bool stopService();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Gets Emitted after the Service moved From SERVICE_START_PENDING to
|
||||||
|
// SERVICE_RUNNING
|
||||||
|
void serviceStarted();
|
||||||
|
void serviceStopped();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Returns the State of the Process:
|
||||||
|
// See
|
||||||
|
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
|
||||||
|
SERVICE_STATUS_PROCESS getStatus();
|
||||||
|
bool m_has_access = false;
|
||||||
|
LPWSTR m_serviceName;
|
||||||
|
SC_HANDLE m_serviceManager;
|
||||||
|
SC_HANDLE m_service; // Service handle with r/w priv.
|
||||||
|
DWORD m_state_target;
|
||||||
|
int m_currentWaitTime;
|
||||||
|
int m_maxWaitTime;
|
||||||
|
QTimer m_timer;
|
||||||
|
|
||||||
|
bool startPolling(DWORD goal_state, int maxS);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSSERVICEMANAGER
|
||||||
62
client/platforms/windows/windowsutils.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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 "windowsutils.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <errhandlingapi.h>
|
||||||
|
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QSysInfo>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("WindowsUtils");
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
constexpr const int WINDOWS_11_BUILD =
|
||||||
|
22000; // Build Number of the first release win 11 iso
|
||||||
|
|
||||||
|
QString WindowsUtils::getErrorMessage(quint32 code) {
|
||||||
|
LPSTR messageBuffer = nullptr;
|
||||||
|
size_t size = FormatMessageA(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(LPSTR)&messageBuffer, 0, nullptr);
|
||||||
|
|
||||||
|
std::string message(messageBuffer, size);
|
||||||
|
QString result(message.c_str());
|
||||||
|
LocalFree(messageBuffer);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WindowsUtils::getErrorMessage() {
|
||||||
|
return getErrorMessage(GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple function to log windows error messages.
|
||||||
|
void WindowsUtils::windowsLog(const QString& msg) {
|
||||||
|
QString errmsg = getErrorMessage();
|
||||||
|
logger.error() << msg << "-" << errmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static
|
||||||
|
QString WindowsUtils::windowsVersion() {
|
||||||
|
QSettings regCurrentVersion(
|
||||||
|
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||||
|
QSettings::NativeFormat);
|
||||||
|
|
||||||
|
int buildNr = regCurrentVersion.value("CurrentBuild").toInt();
|
||||||
|
if (buildNr >= WINDOWS_11_BUILD) {
|
||||||
|
return "11";
|
||||||
|
}
|
||||||
|
return QSysInfo::productVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void WindowsUtils::forceCrash() {
|
||||||
|
RaiseException(0x0000DEAD, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||||
|
}
|
||||||
23
client/platforms/windows/windowsutils.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#ifndef WINDOWSUTILS_H
|
||||||
|
#define WINDOWSUTILS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class WindowsUtils final {
|
||||||
|
public:
|
||||||
|
static QString getErrorMessage();
|
||||||
|
static QString getErrorMessage(quint32 code);
|
||||||
|
static void windowsLog(const QString& msg);
|
||||||
|
|
||||||
|
// Returns the major version of Windows
|
||||||
|
static QString windowsVersion();
|
||||||
|
|
||||||
|
// Force an application crash for testing
|
||||||
|
static void forceCrash();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSUTILS_H
|
||||||
|
|
@ -16,9 +16,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject*
|
||||||
writeWireguardConfiguration(configuration);
|
writeWireguardConfiguration(configuration);
|
||||||
|
|
||||||
// MZ
|
// MZ
|
||||||
#if defined(MZ_LINUX)
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
//m_impl.reset(new LinuxController());
|
|
||||||
#elif defined(MZ_MACOS) // || defined(MZ_WINDOWS)
|
|
||||||
m_impl.reset(new LocalSocketController());
|
m_impl.reset(new LocalSocketController());
|
||||||
connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) {
|
connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) {
|
||||||
emit connectionStateChanged(VpnProtocol::Connected);
|
emit connectionStateChanged(VpnProtocol::Connected);
|
||||||
|
|
@ -38,7 +36,7 @@ WireguardProtocol::~WireguardProtocol()
|
||||||
|
|
||||||
void WireguardProtocol::stop()
|
void WireguardProtocol::stop()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
stopMzImpl();
|
stopMzImpl();
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -98,9 +96,11 @@ void WireguardProtocol::stop()
|
||||||
setConnectionState(VpnProtocol::Disconnected);
|
setConnectionState(VpnProtocol::Disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
ErrorCode WireguardProtocol::startMzImpl()
|
ErrorCode WireguardProtocol::startMzImpl()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig;
|
||||||
m_impl->activate(m_rawConfig);
|
m_impl->activate(m_rawConfig);
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura
|
||||||
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
||||||
m_configFile.close();
|
m_configFile.close();
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#if 0
|
||||||
if (IpcClient::Interface()) {
|
if (IpcClient::Interface()) {
|
||||||
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
|
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
|
||||||
if (result.returnValue()) {
|
if (result.returnValue()) {
|
||||||
|
|
@ -169,7 +169,7 @@ ErrorCode WireguardProtocol::start()
|
||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
return startMzImpl();
|
return startMzImpl();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public:
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
ErrorCode startMzImpl();
|
ErrorCode startMzImpl();
|
||||||
ErrorCode stopMzImpl();
|
ErrorCode stopMzImpl();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -47,7 +47,7 @@ private:
|
||||||
|
|
||||||
bool m_isConfigLoaded = false;
|
bool m_isConfigLoaded = false;
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||||
QScopedPointer<ControllerImpl> m_impl;
|
QScopedPointer<ControllerImpl> m_impl;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,73 @@
|
||||||
#include "StartPageLogic.h"
|
#include "StartPageLogic.h"
|
||||||
#include "ViewConfigLogic.h"
|
#include "ViewConfigLogic.h"
|
||||||
|
|
||||||
#include "core/errorstrings.h"
|
#include "../uilogic.h"
|
||||||
#include "configurators/ssh_configurator.h"
|
#include "configurators/ssh_configurator.h"
|
||||||
#include "configurators/vpn_configurator.h"
|
#include "configurators/vpn_configurator.h"
|
||||||
#include "../uilogic.h"
|
#include "core/errorstrings.h"
|
||||||
#include "utilities.h"
|
|
||||||
#include "core/servercontroller.h"
|
#include "core/servercontroller.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QEventLoop>
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include <QJniObject>
|
#include "../../platforms/android/android_controller.h"
|
||||||
#include "../../platforms/android/androidutils.h"
|
#include "../../platforms/android/androidutils.h"
|
||||||
#include "../../platforms/android/android_controller.h"
|
#include <QJniObject>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace
|
||||||
enum class ConfigTypes {
|
|
||||||
Amnezia,
|
|
||||||
OpenVpn,
|
|
||||||
WireGuard
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigTypes checkConfigFormat(const QString &config)
|
|
||||||
{
|
{
|
||||||
const QString openVpnConfigPatternCli = "client";
|
enum class ConfigTypes {
|
||||||
const QString openVpnConfigPatternProto1 = "proto tcp";
|
Amnezia,
|
||||||
const QString openVpnConfigPatternProto2 = "proto udp";
|
OpenVpn,
|
||||||
const QString openVpnConfigPatternDriver1 = "dev tun";
|
WireGuard
|
||||||
const QString openVpnConfigPatternDriver2 = "dev tap";
|
};
|
||||||
|
|
||||||
const QString wireguardConfigPatternSectionInterface = "[Interface]";
|
ConfigTypes checkConfigFormat(const QString &config)
|
||||||
const QString wireguardConfigPatternSectionPeer = "[Peer]";
|
{
|
||||||
|
const QString openVpnConfigPatternCli = "client";
|
||||||
|
const QString openVpnConfigPatternProto1 = "proto tcp";
|
||||||
|
const QString openVpnConfigPatternProto2 = "proto udp";
|
||||||
|
const QString openVpnConfigPatternDriver1 = "dev tun";
|
||||||
|
const QString openVpnConfigPatternDriver2 = "dev tap";
|
||||||
|
|
||||||
if (config.contains(openVpnConfigPatternCli) &&
|
const QString wireguardConfigPatternSectionInterface = "[Interface]";
|
||||||
(config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) &&
|
const QString wireguardConfigPatternSectionPeer = "[Peer]";
|
||||||
(config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
|
||||||
return ConfigTypes::OpenVpn;
|
if (config.contains(openVpnConfigPatternCli)
|
||||||
} else if (config.contains(wireguardConfigPatternSectionInterface) &&
|
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
|
||||||
config.contains(wireguardConfigPatternSectionPeer))
|
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
||||||
return ConfigTypes::WireGuard;
|
return ConfigTypes::OpenVpn;
|
||||||
return ConfigTypes::Amnezia;
|
} else if (config.contains(wireguardConfigPatternSectionInterface)
|
||||||
}
|
&& config.contains(wireguardConfigPatternSectionPeer))
|
||||||
|
return ConfigTypes::WireGuard;
|
||||||
|
return ConfigTypes::Amnezia;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent):
|
StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent)
|
||||||
PageLogicBase(logic, parent),
|
: PageLogicBase(logic, parent),
|
||||||
m_pushButtonConnectEnabled{true},
|
m_pushButtonConnectEnabled { true },
|
||||||
m_pushButtonConnectText{tr("Connect")},
|
m_pushButtonConnectText { tr("Connect") },
|
||||||
m_pushButtonConnectKeyChecked{false},
|
m_pushButtonConnectKeyChecked { false },
|
||||||
m_labelWaitInfoVisible{true},
|
m_labelWaitInfoVisible { true },
|
||||||
m_pushButtonBackFromStartVisible{true},
|
m_pushButtonBackFromStartVisible { true },
|
||||||
m_ipAddressPortRegex{Utils::ipAddressPortRegExp()}
|
m_ipAddressPortRegex { Utils::ipAddressPortRegExp() }
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
// Set security screen for Android app
|
// Set security screen for Android app
|
||||||
AndroidUtils::runOnAndroidThreadSync([]() {
|
AndroidUtils::runOnAndroidThreadSync([]() {
|
||||||
QJniObject activity = AndroidUtils::getActivity();
|
QJniObject activity = AndroidUtils::getActivity();
|
||||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||||
if (window.isValid()){
|
if (window.isValid()) {
|
||||||
const int FLAG_SECURE = 8192;
|
const int FLAG_SECURE = 8192;
|
||||||
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
|
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
|
||||||
}
|
}
|
||||||
|
|
@ -93,17 +94,13 @@ void StartPageLogic::onUpdatePage()
|
||||||
|
|
||||||
void StartPageLogic::onPushButtonConnect()
|
void StartPageLogic::onPushButtonConnect()
|
||||||
{
|
{
|
||||||
if (pushButtonConnectKeyChecked()){
|
if (pushButtonConnectKeyChecked()) {
|
||||||
if (lineEditIpText().isEmpty() ||
|
if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) {
|
||||||
lineEditLoginText().isEmpty() ||
|
|
||||||
textEditSshKeyText().isEmpty() ) {
|
|
||||||
set_labelWaitInfoText(tr("Please fill in all fields"));
|
set_labelWaitInfoText(tr("Please fill in all fields"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (lineEditIpText().isEmpty() ||
|
if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) {
|
||||||
lineEditLoginText().isEmpty() ||
|
|
||||||
lineEditPasswordText().isEmpty() ) {
|
|
||||||
set_labelWaitInfoText(tr("Please fill in all fields"));
|
set_labelWaitInfoText(tr("Please fill in all fields"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +175,8 @@ void StartPageLogic::onPushButtonConnect()
|
||||||
set_pushButtonConnectText(tr("Connect"));
|
set_pushButtonConnectText(tr("Connect"));
|
||||||
|
|
||||||
uiLogic()->m_installCredentials = serverCredentials;
|
uiLogic()->m_installCredentials = serverCredentials;
|
||||||
if (ok) emit uiLogic()->goToPage(Page::NewServer);
|
if (ok)
|
||||||
|
emit uiLogic()->goToPage(Page::NewServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartPageLogic::onPushButtonImport()
|
void StartPageLogic::onPushButtonImport()
|
||||||
|
|
@ -189,16 +187,18 @@ void StartPageLogic::onPushButtonImport()
|
||||||
void StartPageLogic::onPushButtonImportOpenFile()
|
void StartPageLogic::onPushButtonImportOpenFile()
|
||||||
{
|
{
|
||||||
QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"),
|
QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"),
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf");
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||||||
if (fileName.isEmpty()) return;
|
"*.vpn *.ovpn *.conf");
|
||||||
|
if (fileName.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
QFile file(fileName);
|
QFile file(fileName);
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
CFURLRef url = CFURLCreateWithFileSystemPath(
|
CFURLRef url = CFURLCreateWithFileSystemPath(
|
||||||
kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()),
|
kCFAllocatorDefault,
|
||||||
fileName.length()),
|
CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()), fileName.length()),
|
||||||
kCFURLPOSIXPathStyle, 0);
|
kCFURLPOSIXPathStyle, 0);
|
||||||
|
|
||||||
if (!CFURLStartAccessingSecurityScopedResource(url)) {
|
if (!CFURLStartAccessingSecurityScopedResource(url)) {
|
||||||
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
|
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
|
||||||
|
|
@ -242,8 +242,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile)
|
||||||
// check config
|
// check config
|
||||||
uiLogic()->pageLogic<ViewConfigLogic>()->set_configJson(profile);
|
uiLogic()->pageLogic<ViewConfigLogic>()->set_configJson(profile);
|
||||||
emit uiLogic()->goToPage(Page::ViewConfig);
|
emit uiLogic()->goToPage(Page::ViewConfig);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
qDebug() << "Failed to import profile";
|
qDebug() << "Failed to import profile";
|
||||||
qDebug().noquote() << QJsonDocument(profile).toJson();
|
qDebug().noquote() << QJsonDocument(profile).toJson();
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -314,7 +313,6 @@ bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config)
|
||||||
o[config_key::defaultContainer] = "amnezia-openvpn";
|
o[config_key::defaultContainer] = "amnezia-openvpn";
|
||||||
o[config_key::description] = m_settings->nextAvailableServerName();
|
o[config_key::description] = m_settings->nextAvailableServerName();
|
||||||
|
|
||||||
|
|
||||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config);
|
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config);
|
||||||
if (dnsMatch.hasNext()) {
|
if (dnsMatch.hasNext()) {
|
||||||
|
|
@ -338,9 +336,51 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config)
|
||||||
QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config);
|
QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config);
|
||||||
QString hostName;
|
QString hostName;
|
||||||
QString port;
|
QString port;
|
||||||
if (hostNameAndPortMatch.hasMatch()) {
|
if (hostNameAndPortMatch.hasCaptured(1)) {
|
||||||
hostName = hostNameAndPortMatch.captured(1);
|
hostName = hostNameAndPortMatch.captured(1);
|
||||||
|
} else {
|
||||||
|
return importConnection(QJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostNameAndPortMatch.hasCaptured(2)) {
|
||||||
port = hostNameAndPortMatch.captured(2);
|
port = hostNameAndPortMatch.captured(2);
|
||||||
|
} else {
|
||||||
|
port = protocols::wireguard::defaultPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastConfig[config_key::hostName] = hostName;
|
||||||
|
lastConfig[config_key::port] = port.toInt();
|
||||||
|
|
||||||
|
const static QRegularExpression clientPrivKeyRegExp("PrivateKey = (.*)");
|
||||||
|
QRegularExpressionMatch clientPrivKeyMatch = clientPrivKeyRegExp.match(config);
|
||||||
|
if (clientPrivKeyMatch.hasMatch()) {
|
||||||
|
lastConfig[config_key::client_priv_key] = clientPrivKeyMatch.captured(1);
|
||||||
|
} else {
|
||||||
|
return importConnection(QJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
const static QRegularExpression clientIpRegExp("Address = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
||||||
|
QRegularExpressionMatch clientIpMatch = clientIpRegExp.match(config);
|
||||||
|
if (clientIpMatch.hasMatch()) {
|
||||||
|
lastConfig[config_key::client_ip] = clientIpMatch.captured(1);
|
||||||
|
} else {
|
||||||
|
return importConnection(QJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
const static QRegularExpression pskKeyRegExp("PresharedKey = (.*)");
|
||||||
|
QRegularExpressionMatch pskKeyMatch = pskKeyRegExp.match(config);
|
||||||
|
if (pskKeyMatch.hasMatch()) {
|
||||||
|
lastConfig[config_key::psk_key] = pskKeyMatch.captured(1);
|
||||||
|
} else {
|
||||||
|
return importConnection(QJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
const static QRegularExpression serverPubKeyRegExp("PublicKey = (.*)");
|
||||||
|
QRegularExpressionMatch serverPubKeyMatch = serverPubKeyRegExp.match(config);
|
||||||
|
if (serverPubKeyMatch.hasMatch()) {
|
||||||
|
lastConfig[config_key::server_pub_key] = serverPubKeyMatch.captured(1);
|
||||||
|
} else {
|
||||||
|
return importConnection(QJsonObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject wireguardConfig;
|
QJsonObject wireguardConfig;
|
||||||
|
|
@ -361,7 +401,9 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config)
|
||||||
o[config_key::defaultContainer] = "amnezia-wireguard";
|
o[config_key::defaultContainer] = "amnezia-wireguard";
|
||||||
o[config_key::description] = m_settings->nextAvailableServerName();
|
o[config_key::description] = m_settings->nextAvailableServerName();
|
||||||
|
|
||||||
const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
const static QRegularExpression dnsRegExp(
|
||||||
|
"DNS = "
|
||||||
|
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||||
QRegularExpressionMatch dnsMatch = dnsRegExp.match(config);
|
QRegularExpressionMatch dnsMatch = dnsRegExp.match(config);
|
||||||
if (dnsMatch.hasMatch()) {
|
if (dnsMatch.hasMatch()) {
|
||||||
o[config_key::dns1] = dnsMatch.captured(1);
|
o[config_key::dns1] = dnsMatch.captured(1);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ clang -v
|
||||||
# Generate XCodeProj
|
# Generate XCodeProj
|
||||||
$QT_BIN_DIR/qt-cmake . -B $BUILD_DIR -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
$QT_BIN_DIR/qt-cmake . -B $BUILD_DIR -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||||
|
|
||||||
KEYCHAIN=amnezia.build.keychain
|
KEYCHAIN=amnezia.build.ios.keychain
|
||||||
KEYCHAIN_FILE=$HOME/Library/Keychains/${KEYCHAIN}-db
|
KEYCHAIN_FILE=$HOME/Library/Keychains/${KEYCHAIN}-db
|
||||||
|
|
||||||
# Setup keychain
|
# Setup keychain
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
|
||||||
|
|
||||||
CERTIFICATE_P12=$DEPLOY_DIR/PrivacyTechAppleCertDeveloperId.p12
|
CERTIFICATE_P12=$DEPLOY_DIR/PrivacyTechAppleCertDeveloperId.p12
|
||||||
WWDRCA=$DEPLOY_DIR/WWDRCA.cer
|
WWDRCA=$DEPLOY_DIR/WWDRCA.cer
|
||||||
KEYCHAIN=amnezia.build.keychain
|
KEYCHAIN=amnezia.build.macos.keychain
|
||||||
TEMP_PASS=tmp_pass
|
TEMP_PASS=tmp_pass
|
||||||
|
|
||||||
security create-keychain -p $TEMP_PASS $KEYCHAIN || true
|
security create-keychain -p $TEMP_PASS $KEYCHAIN || true
|
||||||
|
|
|
||||||
|
|
@ -14,41 +14,31 @@ set PROJECT_DIR=%cd%
|
||||||
set SCRIPT_DIR=%PROJECT_DIR:"=%\deploy
|
set SCRIPT_DIR=%PROJECT_DIR:"=%\deploy
|
||||||
|
|
||||||
set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=%
|
set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=%
|
||||||
rmdir /Q /S %WORK_DIR%
|
|
||||||
mkdir %WORK_DIR%
|
|
||||||
|
|
||||||
|
|
||||||
set APP_NAME=AmneziaVPN
|
set APP_NAME=AmneziaVPN
|
||||||
set APP_FILENAME=%APP_NAME:"=%.exe
|
set APP_FILENAME=%APP_NAME:"=%.exe
|
||||||
set APP_DOMAIN=org.amneziavpn.package
|
set APP_DOMAIN=org.amneziavpn.package
|
||||||
set RELEASE_DIR=%WORK_DIR:"=%
|
set OUT_APP_DIR=%WORK_DIR:"=%\client\release
|
||||||
set OUT_APP_DIR=%RELEASE_DIR:"=%\client\release
|
set PREBILT_DEPLOY_DATA_DIR=%PROJECT_DIR:"=%\client\3rd-prebuilt\deploy-prebuilt\windows\x%BUILD_ARCH:"=%
|
||||||
set PREBILT_DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\deploy-prebuilt\windows\x%BUILD_ARCH:"=%
|
|
||||||
set DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\windows\x%BUILD_ARCH:"=%
|
set DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\windows\x%BUILD_ARCH:"=%
|
||||||
set INSTALLER_DATA_DIR=%RELEASE_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data
|
set INSTALLER_DATA_DIR=%WORK_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data
|
||||||
set TARGET_FILENAME=%PROJECT_DIR:"=%\%APP_NAME:"=%_x%BUILD_ARCH:"=%.exe
|
set TARGET_FILENAME=%PROJECT_DIR:"=%\%APP_NAME:"=%_x%BUILD_ARCH:"=%.exe
|
||||||
|
|
||||||
echo "Environment:"
|
echo "Environment:"
|
||||||
|
echo "WORK_DIR: %WORK_DIR%"
|
||||||
echo "APP_FILENAME: %APP_FILENAME%"
|
echo "APP_FILENAME: %APP_FILENAME%"
|
||||||
echo "PROJECT_DIR: %PROJECT_DIR%"
|
echo "PROJECT_DIR: %PROJECT_DIR%"
|
||||||
echo "SCRIPT_DIR: %SCRIPT_DIR%"
|
echo "SCRIPT_DIR: %SCRIPT_DIR%"
|
||||||
echo "RELEASE_DIR: %RELEASE_DIR%"
|
|
||||||
echo "OUT_APP_DIR: %OUT_APP_DIR%"
|
echo "OUT_APP_DIR: %OUT_APP_DIR%"
|
||||||
echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%"
|
echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%"
|
||||||
echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%"
|
echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%"
|
||||||
echo "QMAKE_STASH_FILE: %QMAKE_STASH_FILE%"
|
|
||||||
echo "TARGET_FILENAME: %TARGET_FILENAME%"
|
echo "TARGET_FILENAME: %TARGET_FILENAME%"
|
||||||
|
|
||||||
rem Signing staff
|
|
||||||
powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine
|
|
||||||
powershell Get-ExecutionPolicy -List
|
|
||||||
|
|
||||||
powershell Import-PfxCertificate -FilePath %SCRIPT_DIR:"=%\PrivacyTechWindowsCert.pfx -CertStoreLocation Cert:\LocalMachine\My -Password $(ConvertTo-SecureString -String $Env:WIN_CERT_PW -AsPlainText -Force)
|
|
||||||
|
|
||||||
echo "Cleanup..."
|
echo "Cleanup..."
|
||||||
Rmdir /Q /S %RELEASE_DIR%
|
rmdir /Q /S %WORK_DIR%
|
||||||
Del %TARGET_FILENAME%
|
Del %TARGET_FILENAME%
|
||||||
|
|
||||||
|
mkdir %WORK_DIR%
|
||||||
|
|
||||||
call "%QT_BIN_DIR:"=%\qt-cmake" --version
|
call "%QT_BIN_DIR:"=%\qt-cmake" --version
|
||||||
"%QT_BIN_DIR:"=%\windeployqt" -v
|
"%QT_BIN_DIR:"=%\windeployqt" -v
|
||||||
cmake --version
|
cmake --version
|
||||||
|
|
@ -69,11 +59,11 @@ copy "%WORK_DIR:"=%\service\server\release\%APP_NAME:"=%-service.exe" %OUT_APP_D
|
||||||
|
|
||||||
echo "Signing exe"
|
echo "Signing exe"
|
||||||
cd %OUT_APP_DIR%
|
cd %OUT_APP_DIR%
|
||||||
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe
|
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe
|
||||||
|
|
||||||
"%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%"
|
"%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%"
|
||||||
|
|
||||||
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll
|
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll
|
||||||
|
|
||||||
echo "Copying deploy data..."
|
echo "Copying deploy data..."
|
||||||
xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f
|
xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f
|
||||||
|
|
@ -81,7 +71,7 @@ xcopy %PREBILT_DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f
|
||||||
copy "%WORK_DIR:"=%\service\wireguard-service\release\wireguard-service.exe" %OUT_APP_DIR%\wireguard\
|
copy "%WORK_DIR:"=%\service\wireguard-service\release\wireguard-service.exe" %OUT_APP_DIR%\wireguard\
|
||||||
|
|
||||||
cd %SCRIPT_DIR%
|
cd %SCRIPT_DIR%
|
||||||
xcopy %SCRIPT_DIR:"=%\installer %RELEASE_DIR:"=%\installer /s /e /y /i /f
|
xcopy %SCRIPT_DIR:"=%\installer %WORK_DIR:"=%\installer /s /e /y /i /f
|
||||||
mkdir %INSTALLER_DATA_DIR%
|
mkdir %INSTALLER_DATA_DIR%
|
||||||
|
|
||||||
echo "Deploy finished, content:"
|
echo "Deploy finished, content:"
|
||||||
|
|
@ -91,14 +81,14 @@ cd %OUT_APP_DIR%
|
||||||
echo "Compressing data..."
|
echo "Compressing data..."
|
||||||
"%QIF_BIN_DIR:"=%\archivegen" -c 9 %INSTALLER_DATA_DIR:"=%\%APP_NAME:"=%.7z .
|
"%QIF_BIN_DIR:"=%\archivegen" -c 9 %INSTALLER_DATA_DIR:"=%\%APP_NAME:"=%.7z .
|
||||||
|
|
||||||
cd "%RELEASE_DIR:"=%\installer"
|
cd "%WORK_DIR:"=%\installer"
|
||||||
echo "Creating installer..."
|
echo "Creating installer..."
|
||||||
"%QIF_BIN_DIR:"=%\binarycreator" --offline-only -v -c config\windows.xml -p packages -f %TARGET_FILENAME%
|
"%QIF_BIN_DIR:"=%\binarycreator" --offline-only -v -c config\windows.xml -p packages -f %TARGET_FILENAME%
|
||||||
|
|
||||||
timeout 5
|
timeout 5
|
||||||
|
|
||||||
cd %PROJECT_DIR%
|
cd %PROJECT_DIR%
|
||||||
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 "%TARGET_FILENAME%"
|
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 "%TARGET_FILENAME%"
|
||||||
|
|
||||||
echo "Finished, see %TARGET_FILENAME%"
|
echo "Finished, see %TARGET_FILENAME%"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,3 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
if(NOT IOS AND NOT ANDROID)
|
if(NOT IOS AND NOT ANDROID)
|
||||||
add_subdirectory(server)
|
add_subdirectory(server)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
add_subdirectory(wireguard-service)
|
|
||||||
endif()
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ project(${PROJECT})
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat)
|
find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat)
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
|
@ -50,7 +51,7 @@ set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.h
|
||||||
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/controllerimpl.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/controllerimpl.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/dnspingsender.cpp
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/dnspingsender.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/localsocketcontroller.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/localsocketcontroller.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcher.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcher.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcherimpl.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcherimpl.h
|
||||||
|
|
@ -70,6 +71,7 @@ set(SOURCES ${SOURCES}
|
||||||
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/platforms/dummy/dummynetworkwatcher.cpp
|
${CMAKE_CURRENT_LIST_DIR}/../../client/platforms/dummy/dummynetworkwatcher.cpp
|
||||||
|
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/interfaceconfig.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/ipaddress.cpp
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/ipaddress.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/leakdetector.cpp
|
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/leakdetector.cpp
|
||||||
|
|
||||||
|
|
@ -90,7 +92,7 @@ if(UNIX)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32 OR APPLE)
|
if (WIN32 OR APPLE OR LINUX)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.h
|
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.h
|
||||||
|
|
@ -107,11 +109,39 @@ if(WIN32)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.h
|
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/router_win.h
|
${CMAKE_CURRENT_LIST_DIR}/router_win.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemon.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemontunnel.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsfirewall.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowscommons.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsservicemanager.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/dnsutilswindows.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowssplittunnel.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowstunnelservice.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/wireguardutilswindows.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowstunnellogger.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsroutemonitor.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsutils.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowspingsender.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsnetworkwatcher.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.cpp
|
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/router_win.cpp
|
${CMAKE_CURRENT_LIST_DIR}/router_win.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemon.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemontunnel.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsfirewall.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowscommons.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsservicemanager.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/dnsutilswindows.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowssplittunnel.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowstunnelservice.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/wireguardutilswindows.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowstunnellogger.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsroutemonitor.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowspingsender.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsnetworkwatcher.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsutils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBS
|
set(LIBS
|
||||||
|
|
@ -169,12 +199,30 @@ if(APPLE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/router_linux.h
|
${CMAKE_CURRENT_LIST_DIR}/router_linux.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dbustypeslinux.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/router_linux.cpp
|
${CMAKE_CURRENT_LIST_DIR}/router_linux.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -188,7 +236,7 @@ include_directories(
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT} ${SOURCES} ${HEADERS})
|
add_executable(${PROJECT} ${SOURCES} ${HEADERS})
|
||||||
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS})
|
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS})
|
||||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("MacOSDaemonServer");
|
Logger logger("WgDaemonServer");
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
||||||
|
|
@ -41,16 +41,28 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init Mozilla Wireguard Daemon
|
// Init Mozilla Wireguard Daemon
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
if (!server.initialize()) {
|
if (!server.initialize()) {
|
||||||
logger.error() << "Failed to initialize the server";
|
logger.error() << "Failed to initialize the server";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
// Signal handling for a proper shutdown.
|
||||||
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
|
[]() { LinuxDaemon::instance()->deactivate(); });
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
// Signal handling for a proper shutdown.
|
// Signal handling for a proper shutdown.
|
||||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
[]() { MacOSDaemon::instance()->deactivate(); });
|
[]() { MacOSDaemon::instance()->deactivate(); });
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// Signal handling for a proper shutdown.
|
||||||
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
|
[]() { WindowsDaemon::instance()->deactivate(); });
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalServer::~LocalServer()
|
LocalServer::~LocalServer()
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,19 @@
|
||||||
|
|
||||||
#include "ipcserver.h"
|
#include "ipcserver.h"
|
||||||
|
|
||||||
|
#include "../../client/daemon/daemonlocalserver.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "windows/daemon/windowsdaemon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include "linux/daemon/linuxdaemon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
#include "macos/daemon/macosdaemon.h"
|
#include "macos/daemon/macosdaemon.h"
|
||||||
#include "../../client/daemon/daemonlocalserver.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class QLocalServer;
|
class QLocalServer;
|
||||||
|
|
@ -26,16 +36,21 @@ class LocalServer : public QObject
|
||||||
public:
|
public:
|
||||||
explicit LocalServer(QObject* parent = nullptr);
|
explicit LocalServer(QObject* parent = nullptr);
|
||||||
~LocalServer();
|
~LocalServer();
|
||||||
|
|
||||||
QSharedPointer<QLocalServer> m_server;
|
QSharedPointer<QLocalServer> m_server;
|
||||||
|
|
||||||
IpcServer m_ipcServer;
|
IpcServer m_ipcServer;
|
||||||
QRemoteObjectHost m_serverNode;
|
QRemoteObjectHost m_serverNode;
|
||||||
bool m_isRemotingEnabled = false;
|
bool m_isRemotingEnabled = false;
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
MacOSDaemon daemon;
|
|
||||||
DaemonLocalServer server{qApp};
|
DaemonLocalServer server{qApp};
|
||||||
|
LinuxDaemon daemon;
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
DaemonLocalServer server{qApp};
|
||||||
|
WindowsDaemon daemon;
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
DaemonLocalServer server{qApp};
|
||||||
|
MacOSDaemon daemon;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,39 @@
|
||||||
#include "systemservice.h"
|
#include "systemservice.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int s_argc = 0;
|
||||||
|
char** s_argv = nullptr;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
int runApplication(int argc, char** argv)
|
int runApplication(int argc, char** argv)
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc,argv);
|
QCoreApplication app(argc,argv);
|
||||||
LocalServer localServer;
|
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
if(argc > 2){
|
||||||
|
s_argc = argc;
|
||||||
|
s_argv = argv;
|
||||||
|
QStringList tokens;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
tokens.append(QString(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokens.empty() && tokens[0] == "tunneldaemon") {
|
||||||
|
WindowsDaemonTunnel *daemon = new WindowsDaemonTunnel();
|
||||||
|
daemon->run(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LocalServer localServer;
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,7 +48,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
Logger::init();
|
Logger::init();
|
||||||
|
|
||||||
if (argc == 2) {
|
if (argc >= 2) {
|
||||||
qInfo() << "Started as console application";
|
qInfo() << "Started as console application";
|
||||||
return runApplication(argc, argv);
|
return runApplication(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,9 @@ bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const
|
||||||
if (int err = ioctl(sock, SIOCADDRT, &route) < 0)
|
if (int err = ioctl(sock, SIOCADDRT, &route) < 0)
|
||||||
{
|
{
|
||||||
qDebug().noquote() << "route add error: gw "
|
qDebug().noquote() << "route add error: gw "
|
||||||
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
|
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
|
||||||
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
|
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
|
||||||
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
|
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public:
|
||||||
bool clearSavedRoutes();
|
bool clearSavedRoutes();
|
||||||
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
|
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
|
||||||
bool routeDeleteList(const QString &gw, const QStringList &ips);
|
bool routeDeleteList(const QString &gw, const QStringList &ips);
|
||||||
|
QString getgatewayandiface();
|
||||||
void flushDns();
|
void flushDns();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,39 @@
|
||||||
#include "localserver.h"
|
#include "localserver.h"
|
||||||
#include "systemservice.h"
|
#include "systemservice.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int s_argc = 0;
|
||||||
|
char** s_argv = nullptr;
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
SystemService::SystemService(int argc, char **argv)
|
SystemService::SystemService(int argc, char **argv)
|
||||||
: QtService<QCoreApplication>(argc, argv, SERVICE_NAME)
|
: QtService<QCoreApplication>(argc, argv, SERVICE_NAME)
|
||||||
{
|
{
|
||||||
setServiceDescription("Service for AmneziaVPN");
|
setServiceDescription("Service for AmneziaVPN");
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
if(argc > 2){
|
||||||
|
s_argc = argc;
|
||||||
|
s_argv = argv;
|
||||||
|
QStringList tokens;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
tokens.append(QString(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokens.empty() && tokens[0] == "tunneldaemon") {
|
||||||
|
WindowsDaemonTunnel *daemon = new WindowsDaemonTunnel();
|
||||||
|
daemon->run(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemService::start()
|
void SystemService::start()
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|
||||||
|
|
||||||
set(PROJECT wireguard-service)
|
|
||||||
project(${PROJECT} LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
|
||||||
qt_standard_project_setup()
|
|
||||||
|
|
||||||
set(SOURCES
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/main.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/wireguardtunnelservice.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set(HEADERS
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/wireguardtunnelservice.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(LIBS
|
|
||||||
user32
|
|
||||||
rasapi32
|
|
||||||
shlwapi
|
|
||||||
iphlpapi
|
|
||||||
ws2_32
|
|
||||||
iphlpapi
|
|
||||||
gdi32
|
|
||||||
Advapi32
|
|
||||||
Kernel32
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(${PROJECT} ${SOURCES} ${HEADERS})
|
|
||||||
target_link_libraries(${PROJECT} PRIVATE Qt6::Core ${LIBS})
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#include "wireguardtunnelservice.h"
|
|
||||||
#include <strsafe.h>
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
int wmain(int argc, wchar_t** argv)
|
|
||||||
{
|
|
||||||
if (argc != 3) {
|
|
||||||
debug_log(L"Wrong argument provided");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
TCHAR option[20];
|
|
||||||
TCHAR configFile[5000];
|
|
||||||
|
|
||||||
StringCchCopy(option, 20, argv[1]);
|
|
||||||
StringCchCopy(configFile, 5000, argv[2]);
|
|
||||||
|
|
||||||
WireguardTunnelService tunnel(configFile);
|
|
||||||
|
|
||||||
if (lstrcmpi(option, TEXT("--run")) == 0) {
|
|
||||||
debug_log(L"start tunnel");
|
|
||||||
tunnel.startTunnel();
|
|
||||||
} else if (lstrcmpi(option, TEXT("--add")) == 0) {
|
|
||||||
tunnel.addService();
|
|
||||||
} else if (lstrcmpi(option, TEXT("--remove")) == 0) {
|
|
||||||
tunnel.removeService();
|
|
||||||
} else {
|
|
||||||
debug_log(L"Wrong argument provided");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
#include "wireguardtunnelservice.h"
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
#include <strsafe.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
|
|
||||||
void debug_log(const std::wstring& msg)
|
|
||||||
{
|
|
||||||
std::wcerr << msg << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
WireguardTunnelService::WireguardTunnelService(const std::wstring& configFile):
|
|
||||||
m_configFile{configFile}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void WireguardTunnelService::addService()
|
|
||||||
{
|
|
||||||
SC_HANDLE scm;
|
|
||||||
SC_HANDLE service;
|
|
||||||
scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|
||||||
if (NULL == scm) {
|
|
||||||
debug_log(L"OpenSCManager failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WCHAR szFileName[MAX_PATH];
|
|
||||||
|
|
||||||
GetModuleFileNameW(NULL, szFileName, MAX_PATH);
|
|
||||||
std::wstring runCommand = szFileName;
|
|
||||||
runCommand += TEXT(" --run ");
|
|
||||||
runCommand += m_configFile;
|
|
||||||
|
|
||||||
debug_log(runCommand);
|
|
||||||
// check if service is already running
|
|
||||||
service = OpenServiceW(
|
|
||||||
scm,
|
|
||||||
SVCNAME,
|
|
||||||
SERVICE_ALL_ACCESS
|
|
||||||
);
|
|
||||||
if (NULL != service) {
|
|
||||||
//service is already running, remove it before add new service
|
|
||||||
debug_log(L"service is already running, remove it before add new service");
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
removeService();
|
|
||||||
}
|
|
||||||
service = CreateServiceW(
|
|
||||||
scm,
|
|
||||||
SVCNAME,
|
|
||||||
SVCNAME,
|
|
||||||
SERVICE_ALL_ACCESS,
|
|
||||||
SERVICE_WIN32_OWN_PROCESS,
|
|
||||||
SERVICE_DEMAND_START,
|
|
||||||
SERVICE_ERROR_NORMAL,
|
|
||||||
runCommand.c_str(),
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
TEXT("Nsi\0TcpIp"),
|
|
||||||
NULL,
|
|
||||||
NULL);
|
|
||||||
if (NULL == service) {
|
|
||||||
debug_log(L"CreateServiceW failed");
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SERVICE_SID_INFO info;
|
|
||||||
info.dwServiceSidType = SERVICE_SID_TYPE_UNRESTRICTED;
|
|
||||||
if (ChangeServiceConfig2W(service,
|
|
||||||
SERVICE_CONFIG_SERVICE_SID_INFO,
|
|
||||||
&info) == 0) {
|
|
||||||
debug_log(L"ChangeServiceConfig2 failed");
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (StartServiceW(service, 0, NULL) == 0) {
|
|
||||||
debug_log(L"StartServiceW failed");
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (DeleteService(service) == 0) {
|
|
||||||
debug_log(L"DeleteService failed");
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WireguardTunnelService::removeService()
|
|
||||||
{
|
|
||||||
SC_HANDLE scm;
|
|
||||||
SC_HANDLE service;
|
|
||||||
scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|
||||||
if (NULL == scm) {
|
|
||||||
debug_log(L"OpenSCManager failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
service = OpenServiceW(
|
|
||||||
scm,
|
|
||||||
SVCNAME,
|
|
||||||
SERVICE_ALL_ACCESS
|
|
||||||
);
|
|
||||||
if (NULL == service) {
|
|
||||||
debug_log(L"OpenServiceW failed");
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SERVICE_STATUS stt;
|
|
||||||
if (ControlService(service, SERVICE_CONTROL_STOP, &stt) == 0) {
|
|
||||||
debug_log(L"ControlService failed");
|
|
||||||
DeleteService(service);
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0;
|
|
||||||
i < 180 && QueryServiceStatus(scm, &stt) && stt.dwCurrentState != SERVICE_STOPPED;
|
|
||||||
++i) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
|
||||||
}
|
|
||||||
DeleteService(service);
|
|
||||||
CloseServiceHandle(service);
|
|
||||||
CloseServiceHandle(scm);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int WireguardTunnelService::startTunnel()
|
|
||||||
{
|
|
||||||
debug_log(TEXT(__FUNCTION__));
|
|
||||||
|
|
||||||
HMODULE tunnelLib = LoadLibrary(TEXT("tunnel.dll"));
|
|
||||||
if (!tunnelLib) {
|
|
||||||
debug_log(L"Failed to load tunnel.dll");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef bool WireGuardTunnelService(const LPCWSTR settings);
|
|
||||||
|
|
||||||
WireGuardTunnelService* tunnelProc = (WireGuardTunnelService*)GetProcAddress(
|
|
||||||
tunnelLib, "WireGuardTunnelService");
|
|
||||||
if (!tunnelProc) {
|
|
||||||
debug_log(L"Failed to get WireGuardTunnelService function");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_log(m_configFile.c_str());
|
|
||||||
|
|
||||||
if (!tunnelProc(m_configFile.c_str())) {
|
|
||||||
debug_log(L"Failed to activate the tunnel service");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#ifndef WIREGUARDTUNNELSERVICE_H
|
|
||||||
#define WIREGUARDTUNNELSERVICE_H
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define SVCNAME TEXT("AmneziaVPNWireGuardService")
|
|
||||||
|
|
||||||
class WireguardTunnelService
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WireguardTunnelService(const std::wstring& configFile);
|
|
||||||
void addService();
|
|
||||||
void removeService();
|
|
||||||
int startTunnel();
|
|
||||||
private:
|
|
||||||
std::wstring m_configFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
void debug_log(const std::wstring& msg);
|
|
||||||
|
|
||||||
#endif // WIREGUARDTUNNELSERVICE_H
|
|
||||||