WireGuard rework for MacOS and Windows (#314)

WireGuard rework for MacOS and Windows
This commit is contained in:
Mykola Baibuz 2023-09-14 19:44:17 +03:00 committed by GitHub
parent 421a27ceae
commit 07c38e9b6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 4779 additions and 434 deletions

@ -1 +1 @@
Subproject commit 75ab7e2418b83af7f8ed0a448ec5081b37b54442
Subproject commit 66d39ba0e294f65ed4933f01f9eedd56032610d3

View file

@ -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) {

View file

@ -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;
};

View 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;
}

View file

@ -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

View file

@ -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

View file

@ -5,9 +5,9 @@
#ifndef CONTROLLERIMPL_H
#define CONTROLLERIMPL_H
#include <QDateTime>
#include <QObject>
#include <functional>
#include <QDateTime>
class Keys;
class Device;

View file

@ -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));
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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>();

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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;

View 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);
}

View 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

View 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();
}

View 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

View 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;

View 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

View 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;
}

View 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

View 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);
}

View 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 */

View 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, &currentProcess))) {
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, &currentProcess)));
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;
}

View 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

View 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(&timestamp, 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;
}
}

View 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

View 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);
}
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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;
}

View 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

View 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);
}
}

View 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

View 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;
}

View 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

View 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);
}

View 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

View file

@ -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

View file

@ -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
};

View file

@ -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()

View file

@ -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")

View file

@ -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()

View file

@ -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
};

View file

@ -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);
}

View file

@ -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()

View file

@ -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})

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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