WireGuard rework for MacOS and Windows (#314)
WireGuard rework for MacOS and Windows
This commit is contained in:
parent
421a27ceae
commit
07c38e9b6c
60 changed files with 4779 additions and 434 deletions
|
@ -1 +1 @@
|
|||
Subproject commit 75ab7e2418b83af7f8ed0a448ec5081b37b54442
|
||||
Subproject commit 66d39ba0e294f65ed4933f01f9eedd56032610d3
|
|
@ -9,6 +9,7 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
|
@ -64,9 +65,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
// method calls switchServer().
|
||||
//
|
||||
// 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";
|
||||
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)) {
|
||||
logger.debug() << "Already connected. Server switching supported.";
|
||||
|
||||
|
@ -85,10 +89,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
bool status = run(Switch, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return status;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Already connected. Server switching not supported.";
|
||||
|
@ -96,8 +102,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopindex));
|
||||
return activate(config);
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||
if (activate(config)) {
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareActivation(config);
|
||||
|
@ -112,13 +122,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
|
||||
// Configure routing for excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
if (m_excludedAddrSet.contains(address)) {
|
||||
m_excludedAddrSet[address]++;
|
||||
continue;
|
||||
}
|
||||
wgutils()->addExclusionRoute(address);
|
||||
m_excludedAddrSet[address] = 1;
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Add the peer to this interface.
|
||||
|
@ -142,7 +146,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
|
||||
// set routing
|
||||
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.sensitive(ip.toString());
|
||||
return false;
|
||||
|
@ -152,15 +156,21 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
bool status = run(Up, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
return status;
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
||||
|
||||
|
@ -199,6 +209,28 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
|||
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
|
||||
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
#define GETVALUE(name, where, jsontype) \
|
||||
|
@ -216,8 +248,8 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||
|
||||
GETVALUE("privateKey", config.m_privateKey, String);
|
||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
|
||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").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();
|
||||
}
|
||||
|
||||
if (!obj.contains("hopindex")) {
|
||||
config.m_hopindex = 0;
|
||||
if (!obj.contains("hopType")) {
|
||||
config.m_hopType = InterfaceConfig::SingleHop;
|
||||
} else {
|
||||
QJsonValue value = obj.value("hopindex");
|
||||
if (!value.isDouble()) {
|
||||
logger.error() << "hopindex is not a number";
|
||||
QJsonValue value = obj.value("hopType");
|
||||
if (!value.isString()) {
|
||||
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;
|
||||
}
|
||||
config.m_hopindex = value.toInt();
|
||||
}
|
||||
|
||||
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
||||
|
@ -325,8 +366,8 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
// Deactivate the main interface.
|
||||
if (m_connections.contains(0)) {
|
||||
const ConnectionState& state = m_connections.value(0);
|
||||
if (!m_connections.isEmpty()) {
|
||||
const ConnectionState& state = m_connections.first();
|
||||
if (!run(Down, state.m_config)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -349,9 +390,9 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||
// Cleanup peers and routing
|
||||
for (const ConnectionState& state : m_connections) {
|
||||
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) {
|
||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
wgutils()->deletePeer(config);
|
||||
}
|
||||
|
@ -376,14 +417,14 @@ QString Daemon::logs() {
|
|||
return {};
|
||||
}
|
||||
|
||||
void Daemon::cleanLogs() { }
|
||||
void Daemon::cleanLogs() { }
|
||||
|
||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
if (!m_connections.contains(config.m_hopindex)) {
|
||||
if (!m_connections.contains(config.m_hopType)) {
|
||||
return false;
|
||||
}
|
||||
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 &&
|
||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||
|
@ -395,21 +436,15 @@ bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
|||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
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 =
|
||||
m_connections.value(config.m_hopindex).m_config;
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
// Configure routing for new excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
if (m_excludedAddrSet.contains(address)) {
|
||||
m_excludedAddrSet[address]++;
|
||||
continue;
|
||||
}
|
||||
wgutils()->addExclusionRoute(address);
|
||||
m_excludedAddrSet[address] = 1;
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Activate the new peer and its routes.
|
||||
|
@ -418,7 +453,7 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
|||
return false;
|
||||
}
|
||||
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";
|
||||
break;
|
||||
}
|
||||
|
@ -426,18 +461,11 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
|||
|
||||
// Remove routing entries for the old peer.
|
||||
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
Q_ASSERT(m_excludedAddrSet.contains(address));
|
||||
if (m_excludedAddrSet[address] > 1) {
|
||||
m_excludedAddrSet[address]--;
|
||||
continue;
|
||||
}
|
||||
wgutils()->deleteExclusionRoute(address);
|
||||
m_excludedAddrSet.remove(address);
|
||||
delExclusionRoute(QHostAddress(i));
|
||||
}
|
||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -457,12 +485,12 @@ QJsonObject Daemon::getStatus() {
|
|||
QJsonObject json;
|
||||
logger.debug() << "Status request";
|
||||
|
||||
if (!m_connections.contains(0) || !wgutils()->interfaceExists()) {
|
||||
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
const ConnectionState& connection = m_connections.value(0);
|
||||
const ConnectionState& connection = m_connections.first();
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||
|
@ -495,6 +523,7 @@ void Daemon::checkHandshake() {
|
|||
if (connection.m_date.isValid()) {
|
||||
continue;
|
||||
}
|
||||
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||
|
||||
// Check if the handshake has completed.
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
|
|
|
@ -43,11 +43,18 @@ class Daemon : public QObject {
|
|||
|
||||
signals:
|
||||
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 backendFailure();
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
bool addExclusionRoute(const IPAddress& address);
|
||||
bool delExclusionRoute(const IPAddress& address);
|
||||
|
||||
protected:
|
||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||
|
@ -75,8 +82,8 @@ class Daemon : public QObject {
|
|||
QDateTime m_date;
|
||||
InterfaceConfig m_config;
|
||||
};
|
||||
QMap<int, ConnectionState> m_connections;
|
||||
QHash<QHostAddress, int> m_excludedAddrSet;
|
||||
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||
QHash<IPAddress, int> m_excludedAddrSet;
|
||||
QTimer m_handshakeTimer;
|
||||
};
|
||||
|
||||
|
|
122
client/daemon/interfaceconfig.cpp
Normal file
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"
|
||||
|
||||
struct InterfaceConfig {
|
||||
int m_hopindex = 0;
|
||||
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_serverPskKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverPskKey;
|
||||
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
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef WIREGUARDUTILS_H
|
||||
#define WIREGUARDUTILS_H
|
||||
|
||||
#define _WINSOCKAPI_
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
|
@ -12,7 +14,7 @@
|
|||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
constexpr const char* WG_INTERFACE = "moz0";
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||
|
||||
|
@ -41,11 +43,11 @@ class WireguardUtils : public QObject {
|
|||
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
||||
virtual QList<PeerStatus> getPeerStatus() = 0;
|
||||
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
|
||||
|
||||
virtual bool addExclusionRoute(const QHostAddress& address) = 0;
|
||||
virtual bool deleteExclusionRoute(const QHostAddress& address) = 0;
|
||||
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILS_H
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
#ifndef CONTROLLERIMPL_H
|
||||
#define CONTROLLERIMPL_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
#include <QDateTime>
|
||||
|
||||
class Keys;
|
||||
class Device;
|
||||
|
|
|
@ -52,18 +52,51 @@ DnsPingSender::DnsPingSender(const QHostAddress& source, QObject* parent)
|
|||
: PingSender(parent) {
|
||||
MZ_COUNT_CTOR(DnsPingSender);
|
||||
|
||||
if (source.isNull()) {
|
||||
m_socket.bind();
|
||||
} else {
|
||||
m_socket.bind(source);
|
||||
}
|
||||
m_source = source;
|
||||
|
||||
connect(&m_socket, &QUdpSocket::readyRead, this, &DnsPingSender::readData);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
// Assemble a DNS query header.
|
||||
|
@ -82,7 +115,14 @@ void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
|||
packet.append(query, sizeof(query));
|
||||
|
||||
// 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() {
|
||||
|
@ -112,6 +152,7 @@ void DnsPingSender::readData() {
|
|||
continue;
|
||||
}
|
||||
|
||||
logger.debug() << "Received valid DNS reply";
|
||||
emit recvPing(qFromBigEndian<quint16>(header.id));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,15 @@ class DnsPingSender final : public PingSender {
|
|||
|
||||
void sendPing(const QHostAddress& dest, quint16 sequence) override;
|
||||
|
||||
void start();
|
||||
void stop() { m_socket.close(); }
|
||||
|
||||
private:
|
||||
void readData();
|
||||
|
||||
private:
|
||||
QUdpSocket m_socket;
|
||||
QHostAddress m_source;
|
||||
};
|
||||
|
||||
#endif // DNSPINGSENDER_H
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* 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 "protocols/protocols_defs.h"
|
||||
#include "localsocketcontroller.h"
|
||||
|
||||
|
@ -14,10 +13,14 @@
|
|||
#include <QJsonValue>
|
||||
#include <QStandardPaths>
|
||||
|
||||
//#include "errorhandler.h"
|
||||
#include "ipaddress.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
//#include "models/device.h"
|
||||
//#include "models/keys.h"
|
||||
#include "models/server.h"
|
||||
//#include "settingsholder.h"
|
||||
|
||||
// How many times do we try to reconnect.
|
||||
constexpr int MAX_CONNECTION_RETRY = 10;
|
||||
|
@ -112,23 +115,22 @@ void LocalSocketController::daemonConnected() {
|
|||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject();
|
||||
|
||||
QJsonObject json;
|
||||
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("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||
json.insert("deviceIpv6Address", "dead::1");
|
||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||
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("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));
|
||||
|
||||
QJsonArray jsAllowedIPAddesses;
|
||||
|
@ -148,17 +150,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
// QJsonArray splitTunnelApps;
|
||||
// for (const auto& uri : hop.m_vpnDisabledApps) {
|
||||
// splitTunnelApps.append(QJsonValue(uri));
|
||||
// }
|
||||
// json.insert("vpnDisabledApps", splitTunnelApps);
|
||||
|
||||
// QJsonArray splitTunnelApps;
|
||||
// for (const auto& uri : hop.m_vpnDisabledApps) {
|
||||
// splitTunnelApps.append(QJsonValue(uri));
|
||||
// }
|
||||
// json.insert("vpnDisabledApps", splitTunnelApps);
|
||||
write(json);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,16 @@
|
|||
|
||||
#include <QMetaEnum>
|
||||
|
||||
//#include "controller.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
//#include "mozillavpn.h"
|
||||
#include "networkwatcherimpl.h"
|
||||
#include "platforms/dummy/dummynetworkwatcher.h"
|
||||
//#include "settingsholder.h"
|
||||
|
||||
#ifdef MZ_WINDOWS
|
||||
//# include "platforms/windows/windowsnetworkwatcher.h"
|
||||
# include "platforms/windows/windowsnetworkwatcher.h"
|
||||
#endif
|
||||
|
||||
#ifdef MZ_LINUX
|
||||
|
@ -51,9 +54,9 @@ void NetworkWatcher::initialize() {
|
|||
logger.debug() << "Initialize";
|
||||
|
||||
#if defined(MZ_WINDOWS)
|
||||
//m_impl = new WindowsNetworkWatcher(this);
|
||||
m_impl = new WindowsNetworkWatcher(this);
|
||||
#elif defined(MZ_LINUX)
|
||||
//m_impl = new LinuxNetworkWatcher(this);
|
||||
// m_impl = new LinuxNetworkWatcher(this);
|
||||
#elif defined(MZ_MACOS)
|
||||
m_impl = new MacOSNetworkWatcher(this);
|
||||
#elif defined(MZ_WASM)
|
||||
|
@ -73,11 +76,34 @@ void NetworkWatcher::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() {
|
||||
//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) {
|
||||
logger.debug()
|
||||
|
@ -88,6 +114,7 @@ void NetworkWatcher::settingsChanged() {
|
|||
logger.debug() << "Stopping Network Watcher";
|
||||
m_impl->stop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||
|
@ -95,9 +122,55 @@ void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
|||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||
<< "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() {
|
||||
auto type = m_impl->getTransportType();
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>();
|
||||
|
|
|
@ -33,6 +33,8 @@ class NetworkWatcher final : public QObject {
|
|||
private:
|
||||
void settingsChanged();
|
||||
|
||||
// void notificationClicked(NotificationHandler::Message message);
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
bool m_reportUnsecuredNetwork = false;
|
||||
|
|
|
@ -59,6 +59,10 @@ class PingHelper final : public QObject {
|
|||
|
||||
QTimer m_pingTimer;
|
||||
PingSender* m_pingSender = nullptr;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
friend class TestConnectionHealth;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // PINGHELPER_H
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||
# include "platforms/macos/macospingsender.h"
|
||||
#elif defined(MZ_WINDOWS)
|
||||
// #include "platforms/windows/windowspingsender.h"
|
||||
# include "platforms/windows/windowspingsender.h"
|
||||
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
|
||||
# include "platforms/dummy/dummypingsender.h"
|
||||
#else
|
||||
|
@ -19,13 +19,12 @@
|
|||
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
||||
QObject* parent) {
|
||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||
return nullptr;
|
||||
//return new LinuxPingSender(source, parent);
|
||||
return nullptr;
|
||||
// return new LinuxPingSender(source, parent);
|
||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||
return new MacOSPingSender(source, parent);
|
||||
#elif defined(MZ_WINDOWS)
|
||||
return nullptr;
|
||||
//return new WindowsPingSender(source, parent);
|
||||
return new WindowsPingSender(source, parent);
|
||||
#else
|
||||
return new DummyPingSender(source, parent);
|
||||
#endif
|
||||
|
|
|
@ -50,4 +50,9 @@ class IPAddress final {
|
|||
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
|
||||
|
|
|
@ -124,26 +124,23 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
|
|||
const struct sockaddr* dst =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||
unsigned int plen;
|
||||
if (dst->sa_family == AF_INET) {
|
||||
m_defaultGatewayIpv4.clear();
|
||||
m_defaultIfindexIpv4 = 0;
|
||||
protocol = QAbstractSocket::IPv4Protocol;
|
||||
plen = 32;
|
||||
} else if (dst->sa_family == AF_INET6) {
|
||||
m_defaultGatewayIpv6.clear();
|
||||
m_defaultIfindexIpv6 = 0;
|
||||
protocol = QAbstractSocket::IPv6Protocol;
|
||||
plen = 128;
|
||||
}
|
||||
|
||||
logger.debug() << "Lost default route via" << ifname
|
||||
<< logger.sensitive(addrToString(addrlist[1]));
|
||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
||||
if (addr.protocol() == protocol) {
|
||||
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||
if (prefix.address().protocol() == protocol) {
|
||||
logger.debug() << "Removing exclusion route to"
|
||||
<< logger.sensitive(addr.toString());
|
||||
rtmSendRoute(RTM_DELETE, addr, plen, rtm->rtm_index, nullptr);
|
||||
<< logger.sensitive(prefix.toString());
|
||||
rtmSendRoute(RTM_DELETE, prefix, rtm->rtm_index, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +224,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
|||
const struct sockaddr* dst =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||
unsigned int plen;
|
||||
int rtm_type = RTM_ADD;
|
||||
if (dst->sa_family == AF_INET) {
|
||||
if (m_defaultIfindexIpv4 != 0) {
|
||||
|
@ -236,7 +232,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
|||
m_defaultGatewayIpv4 = addrlist[1];
|
||||
m_defaultIfindexIpv4 = ifindex;
|
||||
protocol = QAbstractSocket::IPv4Protocol;
|
||||
plen = 32;
|
||||
} else if (dst->sa_family == AF_INET6) {
|
||||
if (m_defaultIfindexIpv6 != 0) {
|
||||
rtm_type = RTM_CHANGE;
|
||||
|
@ -244,7 +239,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
|||
m_defaultGatewayIpv6 = addrlist[1];
|
||||
m_defaultIfindexIpv6 = ifindex;
|
||||
protocol = QAbstractSocket::IPv6Protocol;
|
||||
plen = 128;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -252,11 +246,11 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
|||
// Update the exclusion routes with the new default route.
|
||||
logger.debug() << "Updating default route via" << ifname
|
||||
<< addrToString(addrlist[1]);
|
||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
||||
if (addr.protocol() == protocol) {
|
||||
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||
if (prefix.address().protocol() == protocol) {
|
||||
logger.debug() << "Updating exclusion route to"
|
||||
<< logger.sensitive(addr.toString());
|
||||
rtmSendRoute(rtm_type, addr, plen, ifindex, addrlist[1].constData());
|
||||
<< logger.sensitive(prefix.toString());
|
||||
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,
|
||||
unsigned int plen, unsigned int ifindex,
|
||||
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||
unsigned int ifindex,
|
||||
const void* gateway) {
|
||||
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
||||
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));
|
||||
|
||||
// Append RTA_DST
|
||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
struct sockaddr_in6 sin6;
|
||||
Q_IPV6ADDR dst = prefix.toIPv6Address();
|
||||
Q_IPV6ADDR dst = prefix.address().toIPv6Address();
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
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);
|
||||
} else {
|
||||
struct sockaddr_in sin;
|
||||
quint32 dst = prefix.toIPv4Address();
|
||||
quint32 dst = prefix.address().toIPv4Address();
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_len = sizeof(sin);
|
||||
|
@ -403,7 +397,8 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
|||
}
|
||||
|
||||
// Append RTA_NETMASK
|
||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
unsigned int plen = prefix.prefixLength();
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
struct sockaddr_in6 sin6;
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
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));
|
||||
}
|
||||
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;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
|
@ -497,34 +492,32 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
|||
datalink.sdl_slen = 0;
|
||||
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
||||
|
||||
return rtmSendRoute(RTM_ADD, prefix.address(), prefix.prefixLength(),
|
||||
m_ifindex, &datalink);
|
||||
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix.address(), prefix.prefixLength(),
|
||||
m_ifindex, nullptr);
|
||||
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
|
||||
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
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";
|
||||
return false;
|
||||
}
|
||||
m_exclusionRoutes.append(address);
|
||||
m_exclusionRoutes.append(prefix);
|
||||
|
||||
// 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()) {
|
||||
return rtmSendRoute(RTM_ADD, address, 32, m_defaultIfindexIpv4,
|
||||
return rtmSendRoute(RTM_ADD, prefix, m_defaultIfindexIpv4,
|
||||
m_defaultGatewayIpv4.constData());
|
||||
}
|
||||
if ((address.protocol() == QAbstractSocket::IPv6Protocol) &&
|
||||
if ((prefix.address().protocol() == QAbstractSocket::IPv6Protocol) &&
|
||||
(m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) {
|
||||
return rtmSendRoute(RTM_ADD, address, 128, m_defaultIfindexIpv6,
|
||||
return rtmSendRoute(RTM_ADD, prefix, m_defaultIfindexIpv6,
|
||||
m_defaultGatewayIpv6.constData());
|
||||
}
|
||||
|
||||
|
@ -532,16 +525,15 @@ bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
|
||||
bool MacosRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< logger.sensitive(address.toString());
|
||||
<< logger.sensitive(prefix.toString());
|
||||
|
||||
m_exclusionRoutes.removeAll(address);
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6,
|
||||
nullptr);
|
||||
m_exclusionRoutes.removeAll(prefix);
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv6, nullptr);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -549,11 +541,11 @@ bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
|
|||
|
||||
void MacosRouteMonitor::flushExclusionRoutes() {
|
||||
while (!m_exclusionRoutes.isEmpty()) {
|
||||
QHostAddress address = m_exclusionRoutes.takeFirst();
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, nullptr);
|
||||
IPAddress prefix = m_exclusionRoutes.takeFirst();
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, prefix, m_defaultIfindexIpv6, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,16 +28,16 @@ class MacosRouteMonitor final : public QObject {
|
|||
bool deleteRoute(const IPAddress& prefix);
|
||||
int interfaceFlags() { return m_ifflags; }
|
||||
|
||||
bool addExclusionRoute(const QHostAddress& address);
|
||||
bool deleteExclusionRoute(const QHostAddress& address);
|
||||
bool addExclusionRoute(const IPAddress& prefix);
|
||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||
void flushExclusionRoutes();
|
||||
|
||||
private:
|
||||
void handleRtmDelete(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);
|
||||
bool rtmSendRoute(int action, const QHostAddress& prefix, unsigned int plen,
|
||||
unsigned int ifindex, const void* gateway);
|
||||
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
|
||||
const void* gateway);
|
||||
bool rtmFetchRoutes(int family);
|
||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||
const void* sa);
|
||||
|
@ -50,7 +50,7 @@ class MacosRouteMonitor final : public QObject {
|
|||
static QString addrToString(const struct sockaddr* sa);
|
||||
static QString addrToString(const QByteArray& data);
|
||||
|
||||
QList<QHostAddress> m_exclusionRoutes;
|
||||
QList<IPAddress> m_exclusionRoutes;
|
||||
QByteArray m_defaultGatewayIpv4;
|
||||
QByteArray m_defaultGatewayIpv6;
|
||||
unsigned int m_defaultIfindexIpv4 = 0;
|
||||
|
|
|
@ -97,7 +97,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||
// 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";
|
||||
|
@ -133,9 +132,14 @@ bool WireguardUtilsMacos::deleteInterface() {
|
|||
|
||||
// dummy implementations for now
|
||||
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));
|
||||
|
||||
logger.debug() << "Configuring peer" << config.m_serverPublicKey
|
||||
<< "via" << config.m_serverIpv4AddrIn;
|
||||
|
||||
// Update/create the peer config
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
|
@ -150,6 +154,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||
logger.warning() << "Failed to create peer with no endpoints";
|
||||
return false;
|
||||
}
|
||||
|
||||
out << config.m_serverPort << "\n";
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
|
@ -158,7 +163,13 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||
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));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
|
@ -170,6 +181,13 @@ bool WireguardUtilsMacos::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";
|
||||
|
@ -223,9 +241,7 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsMacos::getPeerStatus() {
|
|||
return peerList;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
|
@ -246,9 +262,7 @@ bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
|
||||
if (!m_rtmonitor) {
|
||||
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) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->deleteExclusionRoute(address);
|
||||
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
|
|
|
@ -29,11 +29,11 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
|||
bool deletePeer(const InterfaceConfig& config) override;
|
||||
QList<PeerStatus> getPeerStatus() override;
|
||||
|
||||
bool updateRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
||||
bool updateRoutePrefix(const IPAddress& prefix) override;
|
||||
bool deleteRoutePrefix(const IPAddress& prefix) override;
|
||||
|
||||
bool addExclusionRoute(const QHostAddress& address) override;
|
||||
bool deleteExclusionRoute(const QHostAddress& address) override;
|
||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
|
|
@ -55,6 +55,7 @@ bool dockClickHandler(id self, SEL cmd, ...) {
|
|||
Q_UNUSED(cmd);
|
||||
|
||||
logger.debug() << "Dock icon clicked.";
|
||||
|
||||
//TODO IMPL FOR AMNEZIA
|
||||
//QmlEngineHolder::instance()->showWindow();
|
||||
return FALSE;
|
||||
|
|
177
client/platforms/windows/daemon/dnsutilswindows.cpp
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
|
|
@ -18,7 +18,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject*
|
|||
// MZ
|
||||
#if defined(MZ_LINUX)
|
||||
//m_impl.reset(new LinuxController());
|
||||
#elif defined(MZ_MACOS) // || defined(MZ_WINDOWS)
|
||||
#elif defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
m_impl.reset(new LocalSocketController());
|
||||
connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) {
|
||||
emit connectionStateChanged(VpnProtocol::Connected);
|
||||
|
@ -38,7 +38,7 @@ WireguardProtocol::~WireguardProtocol()
|
|||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
stopMzImpl();
|
||||
return;
|
||||
#endif
|
||||
|
@ -98,9 +98,11 @@ void WireguardProtocol::stop()
|
|||
setConnectionState(VpnProtocol::Disconnected);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
{
|
||||
|
||||
qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig;
|
||||
m_impl->activate(m_rawConfig);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
@ -169,7 +171,7 @@ ErrorCode WireguardProtocol::start()
|
|||
return lastError();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
return startMzImpl();
|
||||
#endif
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
ErrorCode startMzImpl();
|
||||
ErrorCode stopMzImpl();
|
||||
#endif
|
||||
|
@ -47,7 +47,7 @@ private:
|
|||
|
||||
bool m_isConfigLoaded = false;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -9,7 +9,3 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||
if(NOT IOS AND NOT ANDROID)
|
||||
add_subdirectory(server)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_subdirectory(wireguard-service)
|
||||
endif()
|
||||
|
|
|
@ -6,7 +6,7 @@ project(${PROJECT})
|
|||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Network Widgets RemoteObjects Core5Compat)
|
||||
qt_standard_project_setup()
|
||||
|
||||
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
@ -50,7 +50,7 @@ set(HEADERS ${HEADERS}
|
|||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.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/networkwatcher.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcherimpl.h
|
||||
|
@ -69,7 +69,8 @@ set(SOURCES ${SOURCES}
|
|||
${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.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/leakdetector.cpp
|
||||
|
||||
|
@ -107,11 +108,39 @@ if(WIN32)
|
|||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_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}
|
||||
${CMAKE_CURRENT_LIST_DIR}/tapcontroller_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
|
||||
|
@ -188,7 +217,7 @@ include_directories(
|
|||
)
|
||||
|
||||
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 ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#endif
|
||||
|
||||
namespace {
|
||||
Logger logger("MacOSDaemonServer");
|
||||
Logger logger("WgDaemonServer");
|
||||
}
|
||||
|
||||
LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
||||
|
@ -40,17 +40,25 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
|||
}
|
||||
});
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
// Init Mozilla Wireguard Daemon
|
||||
#ifdef Q_OS_MAC
|
||||
if (!server.initialize()) {
|
||||
logger.error() << "Failed to initialize the server";
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Signal handling for a proper shutdown.
|
||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||
[]() { MacOSDaemon::instance()->deactivate(); });
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Signal handling for a proper shutdown.
|
||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||
[]() { WindowsDaemon::instance()->deactivate(); });
|
||||
#endif
|
||||
}
|
||||
|
||||
LocalServer::~LocalServer()
|
||||
|
|
|
@ -10,9 +10,14 @@
|
|||
|
||||
#include "ipcserver.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "macos/daemon/macosdaemon.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include "../../client/daemon/daemonlocalserver.h"
|
||||
#include "windows/daemon/windowsdaemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "../../client/daemon/daemonlocalserver.h"
|
||||
#include "macos/daemon/macosdaemon.h"
|
||||
#endif
|
||||
|
||||
class QLocalServer;
|
||||
|
@ -33,9 +38,13 @@ public:
|
|||
QRemoteObjectHost m_serverNode;
|
||||
bool m_isRemotingEnabled = false;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
MacOSDaemon daemon;
|
||||
#ifdef Q_OS_WIN
|
||||
DaemonLocalServer server{qApp};
|
||||
WindowsDaemon daemon;
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
DaemonLocalServer server{qApp};
|
||||
MacOSDaemon daemon;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -6,13 +6,39 @@
|
|||
#include "systemservice.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)
|
||||
{
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,7 +48,7 @@ int main(int argc, char **argv)
|
|||
|
||||
Logger::init();
|
||||
|
||||
if (argc == 2) {
|
||||
if (argc >= 2) {
|
||||
qInfo() << "Started as console application";
|
||||
return runApplication(argc, argv);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,39 @@
|
|||
#include "localserver.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)
|
||||
: QtService<QCoreApplication>(argc, argv, SERVICE_NAME)
|
||||
{
|
||||
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()
|
||||
|
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue