Merge pull request #339 from amnezia-vpn/dev

Release 3.1.0.1
This commit is contained in:
pokamest 2023-09-21 07:28:08 -07:00 committed by GitHub
commit 9090ec54e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 6981 additions and 539 deletions

View file

@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 3.1.0.0
project(${PROJECT} VERSION 3.1.0.1
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
set(RELEASE_DATE "2023-08-28")
set(RELEASE_DATE "2023-09-21")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")

@ -1 +1 @@
Subproject commit 75ab7e2418b83af7f8ed0a448ec5081b37b54442
Subproject commit e8795854a5cf27004fe78caecc90a961688d1d41

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

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

@ -12,7 +12,7 @@
#include "leakdetector.h"
#include "logger.h"
#ifdef MZ_MACOS
#if defined(MZ_MACOS) || defined(MZ_LINUX)
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
@ -68,7 +68,8 @@ bool DaemonLocalServer::initialize() {
QString DaemonLocalServer::daemonPath() const {
#if defined(MZ_WINDOWS)
return "\\\\.\\pipe\\amneziavpn";
#elif defined(MZ_MACOS)
#endif
#if defined(MZ_MACOS) || defined(MZ_LINUX)
QDir dir("/var/run");
if (!dir.exists()) {
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
@ -92,7 +93,5 @@ QString DaemonLocalServer::daemonPath() const {
}
return VAR_PATH;
#else
# error Unsupported platform
#endif
}

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,17 +6,20 @@
#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
//# include "platforms/linux/linuxnetworkwatcher.h"
# include "platforms/linux/linuxnetworkwatcher.h"
#endif
#ifdef MZ_MACOS
@ -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

@ -29,8 +29,6 @@ IPAddress::IPAddress(const QString& ip) {
if (m_prefixLength >= 128) {
m_prefixLength = 128;
}
} else {
Q_ASSERT(false);
}
}

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

@ -0,0 +1,170 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DBUSTYPESLINUX_H
#define DBUSTYPESLINUX_H
#include <sys/socket.h>
#include <QByteArray>
#include <QDBusArgument>
#include <QHostAddress>
#include <QtDBus/QtDBus>
/* D-Bus metatype for marshalling arguments to the SetLinkDNS method */
class DnsResolver : public QHostAddress {
public:
DnsResolver(const QHostAddress& address = QHostAddress())
: QHostAddress(address) {}
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsResolver& ip) {
args.beginStructure();
if (ip.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR addrv6 = ip.toIPv6Address();
args << AF_INET6;
args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6));
} else {
quint32 addrv4 = ip.toIPv4Address();
QByteArray data(4, 0);
data[0] = (addrv4 >> 24) & 0xff;
data[1] = (addrv4 >> 16) & 0xff;
data[2] = (addrv4 >> 8) & 0xff;
data[3] = (addrv4 >> 0) & 0xff;
args << AF_INET;
args << data;
}
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsResolver& ip) {
int family;
QByteArray data;
args.beginStructure();
args >> family >> data;
args.endStructure();
if (family == AF_INET6) {
ip.setAddress(data.constData());
} else if (data.count() >= 4) {
quint32 addrv4 = 0;
addrv4 |= (data[0] << 24);
addrv4 |= (data[1] << 16);
addrv4 |= (data[2] << 8);
addrv4 |= (data[3] << 0);
ip.setAddress(addrv4);
}
return args;
}
};
typedef QList<DnsResolver> DnsResolverList;
Q_DECLARE_METATYPE(DnsResolver);
Q_DECLARE_METATYPE(DnsResolverList);
/* D-Bus metatype for marshalling arguments to the SetLinkDomains method */
class DnsLinkDomain {
public:
DnsLinkDomain(const QString d = "", bool s = false) {
domain = d;
search = s;
};
QString domain;
bool search;
friend QDBusArgument& operator<<(QDBusArgument& args,
const DnsLinkDomain& data) {
args.beginStructure();
args << data.domain << data.search;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsLinkDomain& data) {
args.beginStructure();
args >> data.domain >> data.search;
args.endStructure();
return args;
}
bool operator==(const DnsLinkDomain& other) const {
return (domain == other.domain) && (search == other.search);
}
bool operator==(const QString& other) const { return (domain == other); }
};
typedef QList<DnsLinkDomain> DnsLinkDomainList;
Q_DECLARE_METATYPE(DnsLinkDomain);
Q_DECLARE_METATYPE(DnsLinkDomainList);
/* D-Bus metatype for marshalling the Domains property */
class DnsDomain {
public:
DnsDomain() {}
int ifindex = 0;
QString domain = "";
bool search = false;
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsDomain& data) {
args.beginStructure();
args << data.ifindex << data.domain << data.search;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsDomain& data) {
args.beginStructure();
args >> data.ifindex >> data.domain >> data.search;
args.endStructure();
return args;
}
};
typedef QList<DnsDomain> DnsDomainList;
Q_DECLARE_METATYPE(DnsDomain);
Q_DECLARE_METATYPE(DnsDomainList);
/* D-Bus metatype for marshalling the freedesktop login manager data. */
class UserData {
public:
QString name;
uint userid;
QDBusObjectPath path;
friend QDBusArgument& operator<<(QDBusArgument& args, const UserData& data) {
args.beginStructure();
args << data.userid << data.name << data.path;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
UserData& data) {
args.beginStructure();
args >> data.userid >> data.name >> data.path;
args.endStructure();
return args;
}
};
typedef QList<UserData> UserDataList;
Q_DECLARE_METATYPE(UserData);
Q_DECLARE_METATYPE(UserDataList);
class DnsMetatypeRegistrationProxy {
public:
DnsMetatypeRegistrationProxy() {
qRegisterMetaType<DnsResolver>();
qDBusRegisterMetaType<DnsResolver>();
qRegisterMetaType<DnsResolverList>();
qDBusRegisterMetaType<DnsResolverList>();
qRegisterMetaType<DnsLinkDomain>();
qDBusRegisterMetaType<DnsLinkDomain>();
qRegisterMetaType<DnsLinkDomainList>();
qDBusRegisterMetaType<DnsLinkDomainList>();
qRegisterMetaType<DnsDomain>();
qDBusRegisterMetaType<DnsDomain>();
qRegisterMetaType<DnsDomainList>();
qDBusRegisterMetaType<DnsDomainList>();
qRegisterMetaType<UserData>();
qDBusRegisterMetaType<UserData>();
qRegisterMetaType<UserDataList>();
qDBusRegisterMetaType<UserDataList>();
}
};
#endif // DBUSTYPESLINUX_H

View file

@ -0,0 +1,212 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "dnsutilslinux.h"
#include <net/if.h>
#include <QDBusVariant>
#include <QtDBus/QtDBus>
#include "leakdetector.h"
#include "logger.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
constexpr const char* DBUS_PROPERTY_INTERFACE =
"org.freedesktop.DBus.Properties";
namespace {
Logger logger("DnsUtilsLinux");
}
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
MZ_COUNT_CTOR(DnsUtilsLinux);
logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
}
DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
logger.debug() << "DnsUtilsLinux destroyed.";
}
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) {
m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
return true;
}
bool DnsUtilsLinux::restoreResolvers() {
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value());
}
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_ifindex = 0;
}
return true;
}
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
for (const auto& ip : resolvers) {
resolverList.append(ip);
if (ifname) {
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
<< ifname;
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDNS"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
for (const auto& d : domains) {
// The DNS search domains often winds up revealing user's ISP which
// can correlate back to their location.
logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain)
<< "via" << ifname << (d.search ? "search" : "");
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDomains"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDefaultRoute"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::updateLinkDomains() {
/* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types.
*/
QDBusMessage message = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains");
QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
return;
}
/* Update the state of the DNS domains */
m_linkDomains.clear();
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
for (const auto& d : list) {
if (d.ifindex == 0) {
continue;
}
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
}
/* Drop any competing root search domains. */
DnsLinkDomain root = DnsLinkDomain(".", true);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
if (!iterator.value().contains(root)) {
continue;
}
QList<DnsLinkDomain> newlist = iterator.value();
newlist.removeAll(root);
setLinkDomains(iterator.key(), newlist);
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DNSUTILSLINUX_H
#define DNSUTILSLINUX_H
#include <QDBusInterface>
#include <QDBusPendingCallWatcher>
#include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
class DnsUtilsLinux final : public DnsUtils {
Q_OBJECT
Q_DISABLE_COPY_MOVE(DnsUtilsLinux)
public:
DnsUtilsLinux(QObject* parent);
~DnsUtilsLinux();
bool updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) override;
bool restoreResolvers() override;
private:
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers);
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains);
void setLinkDefaultRoute(int ifindex, bool enable);
void updateLinkDomains();
private slots:
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
private:
int m_ifindex = 0;
QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr;
};
#endif // DNSUTILSLINUX_H

View file

@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iputilslinux.h"
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <QHostAddress>
#include <QScopeGuard>
#include "daemon/wireguardutils.h"
#include "leakdetector.h"
#include "logger.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsLinux");
}
IPUtilsLinux::IPUtilsLinux(QObject* parent) : IPUtils(parent) {
MZ_COUNT_CTOR(IPUtilsLinux);
logger.debug() << "IPUtilsLinux created.";
}
IPUtilsLinux::~IPUtilsLinux() {
MZ_COUNT_DTOR(IPUtilsLinux);
logger.debug() << "IPUtilsLinux destroyed.";
}
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
}
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config);
// Create socket file descriptor to perform the ioctl operations on
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) {
logger.error() << "Failed to create ioctl socket.";
return false;
}
auto guard = qScopeGuard([&] { close(sockfd); });
// Setup the interface to interact with
struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
// MTU
// FIXME: We need to know how many layers deep this particular
// interface is into a tunnel to work effectively. Otherwise
// we will run into fragmentation issues.
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU -- Return code: " << ret;
return false;
}
// Up
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
if (ret) {
logger.error() << "Failed to set device up -- Return code: " << ret;
return false;
}
return true;
}
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
struct ifreq ifr;
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
// Name the interface and set family
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET;
// Get the device address to add to interface
QPair<QHostAddress, int> parsedAddr =
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
char* deviceAddr = _deviceAddr.data();
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
// Create IPv4 socket to perform the ioctl operations on
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) {
logger.error() << "Failed to create ioctl socket.";
return false;
}
auto guard = qScopeGuard([&] { close(sockfd); });
// Set ifr to interface
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
if (ret) {
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}
return true;
}
bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
// Set up the ifr and the companion ifr6
struct in6_ifreq ifr6;
ifr6.prefixlen = 64;
// Get the device address to add to ifr6 interface
QPair<QHostAddress, int> parsedAddr =
QHostAddress::parseSubnet(config.m_deviceIpv6Address);
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
char* deviceAddr = _deviceAddr.data();
inet_pton(AF_INET6, deviceAddr, &ifr6.addr);
// Create IPv6 socket to perform the ioctl operations on
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) {
logger.error() << "Failed to create ioctl socket.";
return false;
}
auto guard = qScopeGuard([&] { close(sockfd); });
// Get the index of named ifr and link with ifr6
struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET6;
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
if (ret) {
logger.error() << "Failed to get ifindex. Return code: " << ret;
return false;
}
ifr6.ifindex = ifr.ifr_ifindex;
// Set ifr6 to the interface
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
if (ret && (errno != EEXIST)) {
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}
return true;
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IPUTILSLINUX_H
#define IPUTILSLINUX_H
#include <arpa/inet.h>
#include "daemon/iputils.h"
class IPUtilsLinux final : public IPUtils {
public:
IPUtilsLinux(QObject* parent);
~IPUtilsLinux();
bool addInterfaceIPs(const InterfaceConfig& config) override;
bool setMTUAndUp(const InterfaceConfig& config) override;
private:
bool addIP4AddressToDevice(const InterfaceConfig& config);
bool addIP6AddressToDevice(const InterfaceConfig& config);
private:
struct in6_ifreq {
struct in6_addr addr;
uint32_t prefixlen;
unsigned int ifindex;
};
};
#endif // IPUTILSLINUX_H

View file

@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "linuxdaemon.h"
#include <QCoreApplication>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QLocalSocket>
#include <QProcess>
#include <QSettings>
#include <QTextStream>
#include <QtGlobal>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger("LinuxDaemon");
LinuxDaemon* s_daemon = nullptr;
} // namespace
LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
MZ_COUNT_CTOR(LinuxDaemon);
logger.debug() << "Daemon created";
m_wgutils = new WireguardUtilsLinux(this);
m_dnsutils = new DnsUtilsLinux(this);
m_iputils = new IPUtilsLinux(this);
Q_ASSERT(s_daemon == nullptr);
s_daemon = this;
}
LinuxDaemon::~LinuxDaemon() {
MZ_COUNT_DTOR(LinuxDaemon);
logger.debug() << "Daemon released";
Q_ASSERT(s_daemon == this);
s_daemon = nullptr;
}
// static
LinuxDaemon* LinuxDaemon::instance() {
Q_ASSERT(s_daemon);
return s_daemon;
}

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 LINUXDAEMON_H
#define LINUXDAEMON_H
#include "daemon/daemon.h"
#include "dnsutilslinux.h"
#include "iputilslinux.h"
#include "wireguardutilslinux.h"
class LinuxDaemon final : public Daemon {
friend class IPUtilsMacos;
public:
LinuxDaemon();
~LinuxDaemon();
static LinuxDaemon* instance();
protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; }
private:
WireguardUtilsLinux* m_wgutils = nullptr;
DnsUtilsLinux* m_dnsutils = nullptr;
IPUtilsLinux* m_iputils = nullptr;
};
#endif // LINUXDAEMON_H

View file

@ -0,0 +1,354 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "linuxroutemonitor.h"
#include <QNetworkInterface>
#include <QCoreApplication>
#include <QProcess>
#include <QScopeGuard>
#include <QTimer>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger("LinuxRouteMonitor");
} // namespace
typedef struct wg_allowedip {
uint16_t family;
union {
struct in_addr ip4;
struct in6_addr ip6;
};
uint8_t cidr;
struct wg_allowedip *next_allowedip;
} wg_allowedip;
constexpr const char* WG_INTERFACE = "amn0";
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
int attrtype, const void* attrdata,
size_t attrlen);
static void nlmsg_append_attr32(struct nlmsghdr* nlmsg, size_t maxlen,
int attrtype, uint32_t value);
static bool buildAllowedIp(wg_allowedip* ip, const IPAddress& prefix);
LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
: QObject(parent), m_ifname(ifname) {
MZ_COUNT_CTOR(LinuxRouteMonitor);
logger.debug() << "LinuxRouteMonitor created.";
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (m_nlsock < 0) {
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
}
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
}
m_notifier = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxRouteMonitor::nlsockReady);
}
LinuxRouteMonitor::~LinuxRouteMonitor() {
MZ_COUNT_DTOR(LinuxRouteMonitor);
if (m_nlsock >= 0) {
close(m_nlsock);
}
logger.debug() << "WireguardUtilsLinux destroyed.";
}
// Compare memory against zero.
static int memcmpzero(const void* data, size_t len) {
const quint8* ptr = static_cast<const quint8*>(data);
while (len--) {
if (*ptr++) return 1;
}
return 0;
}
bool LinuxRouteMonitor::insertRoute(const IPAddress& prefix) {
logger.debug() << "Adding route to" << prefix.toString();
const int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
return rtmSendRoute(RTM_NEWROUTE, flags, RTN_UNICAST, prefix);
}
bool LinuxRouteMonitor::deleteRoute(const IPAddress& prefix) {
logger.debug() << "Removing route to" << prefix.toString();
const int flags = NLM_F_REQUEST | NLM_F_ACK;
return rtmSendRoute(RTM_DELROUTE, flags, RTN_UNICAST, prefix);
}
bool LinuxRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Adding exclusion route for"
<< prefix.toString();
const int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
return rtmSendRoute(RTM_NEWROUTE, flags, RTN_THROW, prefix);
}
bool LinuxRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Removing exclusion route for"
<< prefix.toString();
const int flags = NLM_F_REQUEST | NLM_F_ACK;
return rtmSendRoute(RTM_DELROUTE, flags, RTN_THROW, prefix);
}
bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
const IPAddress& prefix) {
constexpr size_t rtm_max_size = sizeof(struct rtmsg) +
2 * RTA_SPACE(sizeof(uint32_t)) +
RTA_SPACE(sizeof(struct in6_addr));
wg_allowedip ip;
if (!buildAllowedIp(&ip, prefix)) {
logger.warning() << "Invalid destination prefix";
return false;
}
char buf[NLMSG_SPACE(rtm_max_size)];
struct nlmsghdr* nlmsg = reinterpret_cast<struct nlmsghdr*>(buf);
struct rtmsg* rtm = static_cast<struct rtmsg*>(NLMSG_DATA(nlmsg));
memset(buf, 0, sizeof(buf));
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = action;
nlmsg->nlmsg_flags = flags;
nlmsg->nlmsg_pid = getpid();
nlmsg->nlmsg_seq = m_nlseq++;
rtm->rtm_dst_len = ip.cidr;
rtm->rtm_family = ip.family;
rtm->rtm_type = type;
rtm->rtm_table = RT_TABLE_UNSPEC;
rtm->rtm_protocol = RTPROT_BOOT;
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
if (rtm->rtm_family == AF_INET6) {
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_DST, &ip.ip6, sizeof(ip.ip6));
} else {
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_DST, &ip.ip4, sizeof(ip.ip4));
}
if (rtm->rtm_type == RTN_UNICAST) {
int index = if_nametoindex(WG_INTERFACE);
if (index <= 0) {
logger.error() << "if_nametoindex() failed:" << strerror(errno);
return false;
}
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
}
if (rtm->rtm_type == RTN_THROW) {
int index = if_nametoindex(getgatewayandiface().toUtf8());
if (index <= 0) {
logger.error() << "if_nametoindex() failed:" << strerror(errno);
return false;
}
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
}
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
size_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
(struct sockaddr*)&nladdr, sizeof(nladdr));
return (result == nlmsg->nlmsg_len);
}
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
int attrtype, const void* attrdata,
size_t attrlen) {
size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(attrlen);
if (newlen <= maxlen) {
char* buf = reinterpret_cast<char*>(nlmsg) + NLMSG_ALIGN(nlmsg->nlmsg_len);
struct rtattr* attr = reinterpret_cast<struct rtattr*>(buf);
attr->rta_type = attrtype;
attr->rta_len = RTA_LENGTH(attrlen);
memcpy(RTA_DATA(attr), attrdata, attrlen);
nlmsg->nlmsg_len = newlen;
}
}
static void nlmsg_append_attr32(struct nlmsghdr* nlmsg, size_t maxlen,
int attrtype, uint32_t value) {
nlmsg_append_attr(nlmsg, maxlen, attrtype, &value, sizeof(value));
}
void LinuxRouteMonitor::nlsockReady() {
char buf[1024];
ssize_t len = recv(m_nlsock, buf, sizeof(buf), MSG_DONTWAIT);
if (len <= 0) {
return;
}
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
while (NLMSG_OK(nlmsg, len)) {
if (nlmsg->nlmsg_type == NLMSG_DONE) {
return;
}
if (nlmsg->nlmsg_type != NLMSG_ERROR) {
nlmsg = NLMSG_NEXT(nlmsg, len);
continue;
}
struct nlmsgerr* err = static_cast<struct nlmsgerr*>(NLMSG_DATA(nlmsg));
if (err->error != 0) {
logger.debug() << "Netlink request failed:" << strerror(-err->error);
}
nlmsg = NLMSG_NEXT(nlmsg, len);
}
}
#define BUFFER_SIZE 4096
QString LinuxRouteMonitor::getgatewayandiface()
{
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *)msgbuf;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
}
/* receive response */
do
{
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
}
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
{
switch(route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
break;
}
}
if ((*gateway_address) && (*interface)) {
logger.debug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
}
close(sock);
return interface;
}
static bool buildAllowedIp(wg_allowedip* ip,
const IPAddress& prefix) {
const char* addrString = qPrintable(prefix.address().toString());
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
ip->family = AF_INET;
ip->cidr = prefix.prefixLength();
return inet_pton(AF_INET, addrString, &ip->ip4) == 1;
}
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
ip->family = AF_INET6;
ip->cidr = prefix.prefixLength();
return inet_pton(AF_INET6, addrString, &ip->ip6) == 1;
}
return false;
}

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LINUXROUTEMONITOR_H
#define LINUXROUTEMONITOR_H
#include <QByteArray>
#include <QHostAddress>
#include <QList>
#include <QObject>
#include <QSocketNotifier>
#include "ipaddress.h"
class LinuxRouteMonitor final : public QObject {
Q_OBJECT
public:
LinuxRouteMonitor(const QString& ifname, QObject* parent = nullptr);
~LinuxRouteMonitor();
bool insertRoute(const IPAddress& prefix);
bool deleteRoute(const IPAddress& prefix);
bool addExclusionRoute(const IPAddress& prefix);
bool deleteExclusionRoute(const IPAddress& prefix);
private:
static QString addrToString(const struct sockaddr* sa);
static QString addrToString(const QByteArray& data);
bool rtmSendRoute(int action, int flags, int type,
const IPAddress& prefix);
QString getgatewayandiface();
QString m_ifname;
unsigned int m_ifindex = 0;
int m_nlsock = -1;
int m_nlseq = 0;
QSocketNotifier* m_notifier = nullptr;
private slots:
void nlsockReady();
};
#endif // LINUXROUTEMONITOR_H

View file

@ -0,0 +1,369 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "wireguardutilslinux.h"
#include <errno.h>
#include <QByteArray>
#include <QDir>
#include <QFile>
#include <QLocalSocket>
#include <QTimer>
#include "leakdetector.h"
#include "logger.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
namespace {
Logger logger("WireguardUtilsLinux");
Logger logwireguard("WireguardGo");
}; // namespace
WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent)
: WireguardUtils(parent), m_tunnel(this) {
MZ_COUNT_CTOR(WireguardUtilsLinux);
logger.debug() << "WireguardUtilsLinux created.";
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
SLOT(tunnelStdoutReady()));
connect(&m_tunnel, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(tunnelErrorOccurred(QProcess::ProcessError)));
}
WireguardUtilsLinux::~WireguardUtilsLinux() {
MZ_COUNT_DTOR(WireguardUtilsLinux);
logger.debug() << "WireguardUtilsLinux destroyed.";
}
void WireguardUtilsLinux::tunnelStdoutReady() {
for (;;) {
QByteArray line = m_tunnel.readLine();
if (line.length() <= 0) {
break;
}
logwireguard.debug() << QString::fromUtf8(line);
}
}
void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
logger.warning() << "Tunnel process encountered an error:" << error;
emit backendFailure();
}
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
Q_UNUSED(config);
if (m_tunnel.state() != QProcess::NotRunning) {
logger.warning() << "Unable to start: tunnel process already running";
return false;
}
QDir wgRuntimeDir(WG_RUNTIME_DIR);
if (!wgRuntimeDir.exists()) {
wgRuntimeDir.mkpath(".");
}
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".sock");
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
#ifdef MZ_DEBUG
pe.insert("LOG_LEVEL", "debug");
#endif
m_tunnel.setProcessEnvironment(pe);
QDir appPath(QCoreApplication::applicationDirPath());
QStringList wgArgs = {"-f", "amn0"};
m_tunnel.start(appPath.filePath("../../client/bin/wireguard-go"), wgArgs);
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "Unable to start tunnel process due to timeout";
m_tunnel.kill();
return false;
}
m_ifname = waitForTunnelName(wgNameFile);
if (m_ifname.isNull()) {
logger.error() << "Unable to read tunnel interface name";
m_tunnel.kill();
return false;
}
logger.debug() << "Created wireguard interface" << m_ifname;
// Start the routing table monitor.
m_rtmonitor = new LinuxRouteMonitor(m_ifname, this);
// Send a UAPI command to configure the interface
QString message("set=1\n");
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
QTextStream out(&message);
out << "private_key=" << QString(privateKey.toHex()) << "\n";
out << "replace_peers=true\n";
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
}
return (err == 0);
}
bool WireguardUtilsLinux::deleteInterface() {
if (m_rtmonitor) {
delete m_rtmonitor;
m_rtmonitor = nullptr;
}
if (m_tunnel.state() == QProcess::NotRunning) {
return false;
}
// Attempt to terminate gracefully.
m_tunnel.terminate();
if (!m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT)) {
m_tunnel.kill();
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
}
// Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
return true;
}
// dummy implementations for now
bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
logger.debug() << "Configuring peer" << config.m_serverPublicKey << "via" << config.m_serverIpv4AddrIn;
// Update/create the peer config
QString message;
QTextStream out(&message);
out << "set=1\n";
out << "public_key=" << QString(publicKey.toHex()) << "\n";
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
if (!config.m_serverIpv4AddrIn.isNull()) {
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
} else if (!config.m_serverIpv6AddrIn.isNull()) {
out << "endpoint=[" << config.m_serverIpv6AddrIn << "]:";
} else {
logger.warning() << "Failed to create peer with no endpoints";
return false;
}
out << config.m_serverPort << "\n";
out << "replace_allowed_ips=true\n";
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
out << "allowed_ip=" << ip.toString() << "\n";
}
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Peer configuration failed:" << strerror(err);
}
return (err == 0);
}
bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString message;
QTextStream out(&message);
out << "set=1\n";
out << "public_key=" << QString(publicKey.toHex()) << "\n";
out << "remove=true\n";
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Peer deletion failed:" << strerror(err);
}
return (err == 0);
}
QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
QString reply = uapiCommand("get=1");
PeerStatus status;
QList<PeerStatus> peerList;
for (const QString& line : reply.split('\n')) {
int eq = line.indexOf('=');
if (eq <= 0) {
continue;
}
QString name = line.left(eq);
QString value = line.mid(eq + 1);
if (name == "public_key") {
if (!status.m_pubkey.isEmpty()) {
peerList.append(status);
}
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
status = PeerStatus(pubkey.toBase64());
}
if (name == "tx_bytes") {
status.m_txBytes = value.toDouble();
}
if (name == "rx_bytes") {
status.m_rxBytes = value.toDouble();
}
if (name == "last_handshake_time_sec") {
status.m_handshake += value.toLongLong() * 1000;
}
if (name == "last_handshake_time_nsec") {
status.m_handshake += value.toLongLong() / 1000000;
}
}
if (!status.m_pubkey.isEmpty()) {
peerList.append(status);
}
return peerList;
}
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
if (prefix.prefixLength() > 0) {
return m_rtmonitor->insertRoute(prefix);
}
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->insertRoute(IPAddress("0.0.0.0/1")) &&
m_rtmonitor->insertRoute(IPAddress("128.0.0.0/1"));
}
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
return m_rtmonitor->insertRoute(IPAddress("::/1")) &&
m_rtmonitor->insertRoute(IPAddress("8000::/1"));
}
return false;
}
bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
if (prefix.prefixLength() > 0) {
return m_rtmonitor->insertRoute(prefix);
}
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
m_rtmonitor->deleteRoute(IPAddress("128.0.0.0/1"));
} else if (prefix.type() == QAbstractSocket::IPv6Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("::/1")) &&
m_rtmonitor->deleteRoute(IPAddress("8000::/1"));
} else {
return false;
}
}
bool WireguardUtilsLinux::addExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->addExclusionRoute(prefix);
}
bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->deleteExclusionRoute(prefix);
}
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
QLocalSocket socket;
QTimer uapiTimeout;
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
uapiTimeout.setSingleShot(true);
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "QLocalSocket::waitForConnected() failed:"
<< socket.errorString();
return QString();
}
// Send the message to the UAPI socket.
QByteArray message = command.toLocal8Bit();
while (!message.endsWith("\n\n")) {
message.append('\n');
}
socket.write(message);
QByteArray reply;
while (!reply.contains("\n\n")) {
if (!uapiTimeout.isActive()) {
logger.error() << "UAPI command timed out";
return QString();
}
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
reply.append(socket.readAll());
}
return QString::fromUtf8(reply).trimmed();
}
// static
int WireguardUtilsLinux::uapiErrno(const QString& reply) {
for (const QString& line : reply.split("\n")) {
int eq = line.indexOf('=');
if (eq <= 0) {
continue;
}
if (line.left(eq) == "errno") {
return line.mid(eq + 1).toInt();
}
}
return EINVAL;
}
QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
QTimer timeout;
timeout.setSingleShot(true);
timeout.start(WG_TUN_PROC_TIMEOUT);
QFile file(filename);
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
QString ifname = "amn0";
// Test-connect to the UAPI socket.
QLocalSocket sock;
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
sock.connectToServer(sockName, QIODevice::ReadWrite);
if (sock.waitForConnected(100)) {
return ifname;
}
}
return QString();
}

View file

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WIREGUARDUTILSLINUX_H
#define WIREGUARDUTILSLINUX_H
#include <QObject>
#include <QProcess>
#include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h"
class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT
public:
WireguardUtilsLinux(QObject* parent);
~WireguardUtilsLinux();
bool interfaceExists() override {
return m_tunnel.state() == QProcess::Running;
}
QString interfaceName() override { return m_ifname; }
bool addInterface(const InterfaceConfig& config) override;
bool deleteInterface() override;
bool updatePeer(const InterfaceConfig& config) override;
bool deletePeer(const InterfaceConfig& config) override;
QList<PeerStatus> getPeerStatus() override;
bool updateRoutePrefix(const IPAddress& prefix) override;
bool deleteRoutePrefix(const IPAddress& prefix) override;
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
signals:
void backendFailure();
private slots:
void tunnelStdoutReady();
void tunnelErrorOccurred(QProcess::ProcessError error);
private:
QString uapiCommand(const QString& command);
static int uapiErrno(const QString& command);
QString waitForTunnelName(const QString& filename);
QString m_ifname;
QProcess m_tunnel;
LinuxRouteMonitor* m_rtmonitor = nullptr;
};
#endif // WIREGUARDUTILSLINUX_H

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 INTERFACECONFIG_H
#define INTERFACECONFIG_H
#include <QList>
#include <QString>
#include "ipaddress.h"
class QJsonObject;
class InterfaceConfig {
Q_GADGET
public:
InterfaceConfig() {}
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
Q_ENUM(HopType)
HopType m_hopType;
QString m_privateKey;
QString m_deviceIpv4Address;
QString m_deviceIpv6Address;
QString m_serverIpv4Gateway;
QString m_serverIpv6Gateway;
QString m_serverPublicKey;
QString m_serverIpv4AddrIn;
QString m_serverIpv6AddrIn;
QString m_dnsServer;
int m_serverPort = 0;
QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps;
#if defined(MZ_ANDROID) || defined(MZ_IOS)
QString m_installationId;
#endif
QJsonObject toJson() const;
QString toWgConf(
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
};
#endif // INTERFACECONFIG_H

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 "linuxdependencies.h"
#include <mntent.h>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QDBusInterface>
#include <QCoreApplication>
#include <QProcess>
//#include "dbusclient.h"
#include "logger.h"
namespace {
Logger logger("LinuxDependencies");
void showAlert(const QString& message) {
logger.debug() << "Show alert:" << message;
QMessageBox alert;
alert.setText(message);
alert.exec();
}
bool checkDaemonVersion() {
logger.debug() << "Check Daemon Version";
bool completed = false;
bool value = false;
while (!completed) {
QCoreApplication::processEvents();
}
return value;
}
} // namespace
// static
bool LinuxDependencies::checkDependencies() {
char* path = getenv("PATH");
if (!path) {
showAlert("No PATH env found.");
return false;
}
if (!checkDaemonVersion()) {
showAlert("mozillavpn linuxdaemon needs to be updated or restarted.");
return false;
}
return true;
}
// static
QString LinuxDependencies::findCgroupPath(const QString& type) {
struct mntent entry;
char buf[PATH_MAX];
FILE* fp = fopen("/etc/mtab", "r");
if (fp == NULL) {
return QString();
}
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
if (strcmp(entry.mnt_type, "cgroup") != 0) {
continue;
}
if (hasmntopt(&entry, type.toLocal8Bit().constData()) != NULL) {
fclose(fp);
return QString(entry.mnt_dir);
}
}
fclose(fp);
return QString();
}
// static
QString LinuxDependencies::findCgroup2Path() {
struct mntent entry;
char buf[PATH_MAX];
FILE* fp = fopen("/etc/mtab", "r");
if (fp == NULL) {
return QString();
}
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
if (strcmp(entry.mnt_type, "cgroup2") != 0) {
continue;
}
return QString(entry.mnt_dir);
}
fclose(fp);
return QString();
}
// static
QString LinuxDependencies::gnomeShellVersion() {
QDBusInterface iface("org.gnome.Shell", "/org/gnome/Shell",
"org.gnome.Shell");
if (!iface.isValid()) {
return QString();
}
QVariant shellVersion = iface.property("ShellVersion");
if (!shellVersion.isValid()) {
return QString();
}
return shellVersion.toString();
}
// static
QString LinuxDependencies::kdeFrameworkVersion() {
QProcess proc;
proc.start("kf5-config", QStringList{"--version"}, QIODeviceBase::ReadOnly);
if (!proc.waitForFinished()) {
return QString();
}
QByteArray result = proc.readAllStandardOutput();
for (const QByteArray& line : result.split('\n')) {
if (line.startsWith("KDE Frameworks: ")) {
return QString::fromUtf8(line.last(line.size() - 16));
}
}
return QString();
}

View file

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LINUXDEPENDENCIES_H
#define LINUXDEPENDENCIES_H
#include <QObject>
class LinuxDependencies final {
public:
static bool checkDependencies();
static QString findCgroupPath(const QString& type);
static QString findCgroup2Path();
static QString gnomeShellVersion();
static QString kdeFrameworkVersion();
private:
LinuxDependencies() = default;
~LinuxDependencies() = default;
Q_DISABLE_COPY(LinuxDependencies)
};
#endif // LINUXDEPENDENCIES_H

View file

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "linuxnetworkwatcher.h"
#include <QTimer>
#include "leakdetector.h"
#include "linuxnetworkwatcherworker.h"
#include "logger.h"
namespace {
Logger logger("LinuxNetworkWatcher");
}
LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent)
: NetworkWatcherImpl(parent) {
MZ_COUNT_CTOR(LinuxNetworkWatcher);
m_thread.start();
}
LinuxNetworkWatcher::~LinuxNetworkWatcher() {
MZ_COUNT_DTOR(LinuxNetworkWatcher);
delete m_worker;
m_thread.quit();
m_thread.wait();
}
void LinuxNetworkWatcher::initialize() {
logger.debug() << "initialize";
m_worker = new LinuxNetworkWatcherWorker(&m_thread);
connect(this, &LinuxNetworkWatcher::checkDevicesInThread, m_worker,
&LinuxNetworkWatcherWorker::checkDevices);
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork);
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because
// it makes the UI faster to appear, plus it gives a bit of delay between the
// UI to appear and the first notification.
QTimer::singleShot(2000, this, [this]() {
QMetaObject::invokeMethod(m_worker, "initialize", Qt::QueuedConnection);
});
}
void LinuxNetworkWatcher::start() {
logger.debug() << "actived";
NetworkWatcherImpl::start();
emit checkDevicesInThread();
}

View file

@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LINUXNETWORKWATCHER_H
#define LINUXNETWORKWATCHER_H
#include <QThread>
#include "networkwatcherimpl.h"
class LinuxNetworkWatcherWorker;
class LinuxNetworkWatcher final : public NetworkWatcherImpl {
Q_OBJECT
public:
explicit LinuxNetworkWatcher(QObject* parent);
~LinuxNetworkWatcher();
void initialize() override;
void start() override;
NetworkWatcherImpl::TransportType getTransportType() {
// TODO: Find out how to do that on linux generally. (VPN-2382)
return NetworkWatcherImpl::TransportType_Unknown;
};
signals:
void checkDevicesInThread();
private:
LinuxNetworkWatcherWorker* m_worker = nullptr;
QThread m_thread;
};
#endif // LINUXNETWORKWATCHER_H

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 "linuxnetworkwatcherworker.h"
#include <QtDBus/QtDBus>
#include "leakdetector.h"
#include "logger.h"
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType
#ifndef NM_DEVICE_TYPE_WIFI
# define NM_DEVICE_TYPE_WIFI 2
#endif
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NM80211ApFlags
// Wifi network has no security
#ifndef NM_802_11_AP_SEC_NONE
# define NM_802_11_AP_SEC_NONE 0x00000000
#endif
// Wifi network has WEP (40 bits)
#ifndef NM_802_11_AP_SEC_PAIR_WEP40
# define NM_802_11_AP_SEC_PAIR_WEP40 0x00000001
#endif
// Wifi network has WEP (104 bits)
#ifndef NM_802_11_AP_SEC_PAIR_WEP104
# define NM_802_11_AP_SEC_PAIR_WEP104 0x00000002
#endif
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
namespace {
Logger logger("LinuxNetworkWatcherWorker");
}
static inline bool checkUnsecureFlags(int rsnFlags, int wpaFlags) {
// If neither WPA nor WPA2/RSN are supported, then the network is unencrypted
if (rsnFlags == NM_802_11_AP_SEC_NONE && wpaFlags == NM_802_11_AP_SEC_NONE) {
return false;
}
// Consider the user of weak cryptography to be unsecure
if ((rsnFlags & NM_802_11_AP_SEC_WEAK_CRYPTO) ||
(wpaFlags & NM_802_11_AP_SEC_WEAK_CRYPTO)) {
return false;
}
// Otherwise, the network is secured with reasonable cryptography
return true;
}
LinuxNetworkWatcherWorker::LinuxNetworkWatcherWorker(QThread* thread) {
MZ_COUNT_CTOR(LinuxNetworkWatcherWorker);
moveToThread(thread);
}
LinuxNetworkWatcherWorker::~LinuxNetworkWatcherWorker() {
MZ_COUNT_DTOR(LinuxNetworkWatcherWorker);
}
void LinuxNetworkWatcherWorker::initialize() {
logger.debug() << "initialize";
logger.debug()
<< "Retrieving the list of wifi network devices from NetworkManager";
// To know the NeworkManager DBus methods and properties, read the official
// documentation:
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
if (!nm.isValid()) {
logger.error()
<< "Failed to connect to the network manager via system dbus";
return;
}
QDBusMessage msg = nm.call("GetDevices");
QDBusArgument arg = msg.arguments().at(0).value<QDBusArgument>();
if (arg.currentType() != QDBusArgument::ArrayType) {
logger.error() << "Expected an array of devices";
return;
}
QList<QDBusObjectPath> paths = qdbus_cast<QList<QDBusObjectPath> >(arg);
for (const QDBusObjectPath& path : paths) {
QString devicePath = path.path();
QDBusInterface device(DBUS_NETWORKMANAGER, devicePath,
"org.freedesktop.NetworkManager.Device",
QDBusConnection::systemBus());
if (device.property("DeviceType").toInt() != NM_DEVICE_TYPE_WIFI) {
continue;
}
logger.debug() << "Found a wifi device:" << devicePath;
m_devicePaths.append(devicePath);
// Here we monitor the changes.
QDBusConnection::systemBus().connect(
DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.DBus.Properties",
"PropertiesChanged", this,
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
}
if (m_devicePaths.isEmpty()) {
logger.warning() << "No wifi devices found";
return;
}
// We could be already be activated.
checkDevices();
}
void LinuxNetworkWatcherWorker::propertyChanged(QString interface,
QVariantMap properties,
QStringList list) {
Q_UNUSED(list);
logger.debug() << "Properties changed for interface" << interface;
if (!properties.contains("ActiveAccessPoint")) {
logger.debug() << "Access point did not changed. Ignoring the changes";
return;
}
checkDevices();
}
void LinuxNetworkWatcherWorker::checkDevices() {
logger.debug() << "Checking devices";
for (const QString& devicePath : m_devicePaths) {
QDBusInterface wifiDevice(DBUS_NETWORKMANAGER, devicePath,
"org.freedesktop.NetworkManager.Device.Wireless",
QDBusConnection::systemBus());
// Check the access point path
QString accessPointPath = wifiDevice.property("ActiveAccessPoint")
.value<QDBusObjectPath>()
.path();
if (accessPointPath.isEmpty()) {
logger.warning() << "No access point found";
continue;
}
QDBusInterface ap(DBUS_NETWORKMANAGER, accessPointPath,
"org.freedesktop.NetworkManager.AccessPoint",
QDBusConnection::systemBus());
QVariant rsnFlags = ap.property("RsnFlags");
QVariant wpaFlags = ap.property("WpaFlags");
if (!rsnFlags.isValid() || !wpaFlags.isValid()) {
// We are probably not connected.
continue;
}
if (!checkUnsecureFlags(rsnFlags.toInt(), wpaFlags.toInt())) {
QString ssid = ap.property("Ssid").toString();
QString bssid = ap.property("HwAddress").toString();
// We have found 1 unsecured network. We don't need to check other wifi
// network devices.
logger.warning() << "Unsecured AP detected!"
<< "rsnFlags:" << rsnFlags.toInt()
<< "wpaFlags:" << wpaFlags.toInt()
<< "ssid:" << logger.sensitive(ssid);
emit unsecuredNetwork(ssid, bssid);
break;
}
}
}

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LINUXNETWORKWATCHERWORKER_H
#define LINUXNETWORKWATCHERWORKER_H
#include <QMap>
#include <QObject>
#include <QVariant>
class QThread;
class LinuxNetworkWatcherWorker final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(LinuxNetworkWatcherWorker)
public:
explicit LinuxNetworkWatcherWorker(QThread* thread);
~LinuxNetworkWatcherWorker();
void checkDevices();
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
public slots:
void initialize();
private slots:
void propertyChanged(QString interface, QVariantMap properties,
QStringList list);
private:
// We collect the list of DBus wifi network device paths during the
// initialization. When a property of them changes, we check if the access
// point is active and unsecure.
QStringList m_devicePaths;
};
#endif // LINUXNETWORKWATCHERWORKER_H

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

@ -16,9 +16,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject*
writeWireguardConfiguration(configuration);
// MZ
#if defined(MZ_LINUX)
//m_impl.reset(new LinuxController());
#elif defined(MZ_MACOS) // || defined(MZ_WINDOWS)
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
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 +36,7 @@ WireguardProtocol::~WireguardProtocol()
void WireguardProtocol::stop()
{
#ifdef Q_OS_MAC
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
stopMzImpl();
return;
#endif
@ -98,9 +96,11 @@ void WireguardProtocol::stop()
setConnectionState(VpnProtocol::Disconnected);
}
#ifdef Q_OS_MAC
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
ErrorCode WireguardProtocol::startMzImpl()
{
qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig;
m_impl->activate(m_rawConfig);
return ErrorCode::NoError;
}
@ -124,7 +124,7 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
#ifdef Q_OS_LINUX
#if 0
if (IpcClient::Interface()) {
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
if (result.returnValue()) {
@ -169,7 +169,7 @@ ErrorCode WireguardProtocol::start()
return lastError();
}
#ifdef Q_OS_MAC
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
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) || defined(Q_OS_LINUX)
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) || defined(Q_OS_LINUX)
QScopedPointer<ControllerImpl> m_impl;
#endif
};

View file

@ -1,72 +1,73 @@
#include "StartPageLogic.h"
#include "ViewConfigLogic.h"
#include "core/errorstrings.h"
#include "../uilogic.h"
#include "configurators/ssh_configurator.h"
#include "configurators/vpn_configurator.h"
#include "../uilogic.h"
#include "utilities.h"
#include "core/errorstrings.h"
#include "core/servercontroller.h"
#include "utilities.h"
#include <QEventLoop>
#include <QFileDialog>
#include <QStandardPaths>
#include <QEventLoop>
#ifdef Q_OS_ANDROID
#include <QJniObject>
#include "../../platforms/android/androidutils.h"
#include "../../platforms/android/android_controller.h"
#include "../../platforms/android/android_controller.h"
#include "../../platforms/android/androidutils.h"
#include <QJniObject>
#endif
#ifdef Q_OS_IOS
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
namespace {
enum class ConfigTypes {
Amnezia,
OpenVpn,
WireGuard
};
ConfigTypes checkConfigFormat(const QString &config)
namespace
{
const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap";
enum class ConfigTypes {
Amnezia,
OpenVpn,
WireGuard
};
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
ConfigTypes checkConfigFormat(const QString &config)
{
const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap";
if (config.contains(openVpnConfigPatternCli) &&
(config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) &&
(config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface) &&
config.contains(wireguardConfigPatternSectionPeer))
return ConfigTypes::WireGuard;
return ConfigTypes::Amnezia;
}
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface)
&& config.contains(wireguardConfigPatternSectionPeer))
return ConfigTypes::WireGuard;
return ConfigTypes::Amnezia;
}
}
StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent):
PageLogicBase(logic, parent),
m_pushButtonConnectEnabled{true},
m_pushButtonConnectText{tr("Connect")},
m_pushButtonConnectKeyChecked{false},
m_labelWaitInfoVisible{true},
m_pushButtonBackFromStartVisible{true},
m_ipAddressPortRegex{Utils::ipAddressPortRegExp()}
StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent)
: PageLogicBase(logic, parent),
m_pushButtonConnectEnabled { true },
m_pushButtonConnectText { tr("Connect") },
m_pushButtonConnectKeyChecked { false },
m_labelWaitInfoVisible { true },
m_pushButtonBackFromStartVisible { true },
m_ipAddressPortRegex { Utils::ipAddressPortRegExp() }
{
#ifdef Q_OS_ANDROID
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()){
if (window.isValid()) {
const int FLAG_SECURE = 8192;
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
}
@ -93,17 +94,13 @@ void StartPageLogic::onUpdatePage()
void StartPageLogic::onPushButtonConnect()
{
if (pushButtonConnectKeyChecked()){
if (lineEditIpText().isEmpty() ||
lineEditLoginText().isEmpty() ||
textEditSshKeyText().isEmpty() ) {
if (pushButtonConnectKeyChecked()) {
if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) {
set_labelWaitInfoText(tr("Please fill in all fields"));
return;
}
} else {
if (lineEditIpText().isEmpty() ||
lineEditLoginText().isEmpty() ||
lineEditPasswordText().isEmpty() ) {
if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) {
set_labelWaitInfoText(tr("Please fill in all fields"));
return;
}
@ -178,7 +175,8 @@ void StartPageLogic::onPushButtonConnect()
set_pushButtonConnectText(tr("Connect"));
uiLogic()->m_installCredentials = serverCredentials;
if (ok) emit uiLogic()->goToPage(Page::NewServer);
if (ok)
emit uiLogic()->goToPage(Page::NewServer);
}
void StartPageLogic::onPushButtonImport()
@ -189,16 +187,18 @@ void StartPageLogic::onPushButtonImport()
void StartPageLogic::onPushButtonImportOpenFile()
{
QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"),
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf");
if (fileName.isEmpty()) return;
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
"*.vpn *.ovpn *.conf");
if (fileName.isEmpty())
return;
QFile file(fileName);
#ifdef Q_OS_IOS
CFURLRef url = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()),
fileName.length()),
kCFURLPOSIXPathStyle, 0);
kCFAllocatorDefault,
CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()), fileName.length()),
kCFURLPOSIXPathStyle, 0);
if (!CFURLStartAccessingSecurityScopedResource(url)) {
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
@ -242,8 +242,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile)
// check config
uiLogic()->pageLogic<ViewConfigLogic>()->set_configJson(profile);
emit uiLogic()->goToPage(Page::ViewConfig);
}
else {
} else {
qDebug() << "Failed to import profile";
qDebug().noquote() << QJsonDocument(profile).toJson();
return false;
@ -314,7 +313,6 @@ bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config)
o[config_key::defaultContainer] = "amnezia-openvpn";
o[config_key::description] = m_settings->nextAvailableServerName();
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config);
if (dnsMatch.hasNext()) {
@ -338,9 +336,51 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config)
QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config);
QString hostName;
QString port;
if (hostNameAndPortMatch.hasMatch()) {
if (hostNameAndPortMatch.hasCaptured(1)) {
hostName = hostNameAndPortMatch.captured(1);
} else {
return importConnection(QJsonObject());
}
if (hostNameAndPortMatch.hasCaptured(2)) {
port = hostNameAndPortMatch.captured(2);
} else {
port = protocols::wireguard::defaultPort;
}
lastConfig[config_key::hostName] = hostName;
lastConfig[config_key::port] = port.toInt();
const static QRegularExpression clientPrivKeyRegExp("PrivateKey = (.*)");
QRegularExpressionMatch clientPrivKeyMatch = clientPrivKeyRegExp.match(config);
if (clientPrivKeyMatch.hasMatch()) {
lastConfig[config_key::client_priv_key] = clientPrivKeyMatch.captured(1);
} else {
return importConnection(QJsonObject());
}
const static QRegularExpression clientIpRegExp("Address = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
QRegularExpressionMatch clientIpMatch = clientIpRegExp.match(config);
if (clientIpMatch.hasMatch()) {
lastConfig[config_key::client_ip] = clientIpMatch.captured(1);
} else {
return importConnection(QJsonObject());
}
const static QRegularExpression pskKeyRegExp("PresharedKey = (.*)");
QRegularExpressionMatch pskKeyMatch = pskKeyRegExp.match(config);
if (pskKeyMatch.hasMatch()) {
lastConfig[config_key::psk_key] = pskKeyMatch.captured(1);
} else {
return importConnection(QJsonObject());
}
const static QRegularExpression serverPubKeyRegExp("PublicKey = (.*)");
QRegularExpressionMatch serverPubKeyMatch = serverPubKeyRegExp.match(config);
if (serverPubKeyMatch.hasMatch()) {
lastConfig[config_key::server_pub_key] = serverPubKeyMatch.captured(1);
} else {
return importConnection(QJsonObject());
}
QJsonObject wireguardConfig;
@ -361,7 +401,9 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config)
o[config_key::defaultContainer] = "amnezia-wireguard";
o[config_key::description] = m_settings->nextAvailableServerName();
const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
const static QRegularExpression dnsRegExp(
"DNS = "
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatch dnsMatch = dnsRegExp.match(config);
if (dnsMatch.hasMatch()) {
o[config_key::dns1] = dnsMatch.captured(1);

Binary file not shown.

Binary file not shown.

View file

@ -34,7 +34,7 @@ clang -v
# Generate XCodeProj
$QT_BIN_DIR/qt-cmake . -B $BUILD_DIR -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
KEYCHAIN=amnezia.build.keychain
KEYCHAIN=amnezia.build.ios.keychain
KEYCHAIN_FILE=$HOME/Library/Keychains/${KEYCHAIN}-db
# Setup keychain

View file

@ -79,7 +79,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
CERTIFICATE_P12=$DEPLOY_DIR/PrivacyTechAppleCertDeveloperId.p12
WWDRCA=$DEPLOY_DIR/WWDRCA.cer
KEYCHAIN=amnezia.build.keychain
KEYCHAIN=amnezia.build.macos.keychain
TEMP_PASS=tmp_pass
security create-keychain -p $TEMP_PASS $KEYCHAIN || true

View file

@ -14,41 +14,31 @@ set PROJECT_DIR=%cd%
set SCRIPT_DIR=%PROJECT_DIR:"=%\deploy
set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=%
rmdir /Q /S %WORK_DIR%
mkdir %WORK_DIR%
set APP_NAME=AmneziaVPN
set APP_FILENAME=%APP_NAME:"=%.exe
set APP_DOMAIN=org.amneziavpn.package
set RELEASE_DIR=%WORK_DIR:"=%
set OUT_APP_DIR=%RELEASE_DIR:"=%\client\release
set PREBILT_DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\deploy-prebuilt\windows\x%BUILD_ARCH:"=%
set OUT_APP_DIR=%WORK_DIR:"=%\client\release
set PREBILT_DEPLOY_DATA_DIR=%PROJECT_DIR:"=%\client\3rd-prebuilt\deploy-prebuilt\windows\x%BUILD_ARCH:"=%
set DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\windows\x%BUILD_ARCH:"=%
set INSTALLER_DATA_DIR=%RELEASE_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data
set INSTALLER_DATA_DIR=%WORK_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data
set TARGET_FILENAME=%PROJECT_DIR:"=%\%APP_NAME:"=%_x%BUILD_ARCH:"=%.exe
echo "Environment:"
echo "WORK_DIR: %WORK_DIR%"
echo "APP_FILENAME: %APP_FILENAME%"
echo "PROJECT_DIR: %PROJECT_DIR%"
echo "SCRIPT_DIR: %SCRIPT_DIR%"
echo "RELEASE_DIR: %RELEASE_DIR%"
echo "OUT_APP_DIR: %OUT_APP_DIR%"
echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%"
echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%"
echo "QMAKE_STASH_FILE: %QMAKE_STASH_FILE%"
echo "TARGET_FILENAME: %TARGET_FILENAME%"
rem Signing staff
powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine
powershell Get-ExecutionPolicy -List
powershell Import-PfxCertificate -FilePath %SCRIPT_DIR:"=%\PrivacyTechWindowsCert.pfx -CertStoreLocation Cert:\LocalMachine\My -Password $(ConvertTo-SecureString -String $Env:WIN_CERT_PW -AsPlainText -Force)
echo "Cleanup..."
Rmdir /Q /S %RELEASE_DIR%
rmdir /Q /S %WORK_DIR%
Del %TARGET_FILENAME%
mkdir %WORK_DIR%
call "%QT_BIN_DIR:"=%\qt-cmake" --version
"%QT_BIN_DIR:"=%\windeployqt" -v
cmake --version
@ -69,11 +59,11 @@ copy "%WORK_DIR:"=%\service\server\release\%APP_NAME:"=%-service.exe" %OUT_APP_D
echo "Signing exe"
cd %OUT_APP_DIR%
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe
"%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%"
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll
echo "Copying deploy data..."
xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f
@ -81,7 +71,7 @@ xcopy %PREBILT_DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f
copy "%WORK_DIR:"=%\service\wireguard-service\release\wireguard-service.exe" %OUT_APP_DIR%\wireguard\
cd %SCRIPT_DIR%
xcopy %SCRIPT_DIR:"=%\installer %RELEASE_DIR:"=%\installer /s /e /y /i /f
xcopy %SCRIPT_DIR:"=%\installer %WORK_DIR:"=%\installer /s /e /y /i /f
mkdir %INSTALLER_DATA_DIR%
echo "Deploy finished, content:"
@ -91,14 +81,14 @@ cd %OUT_APP_DIR%
echo "Compressing data..."
"%QIF_BIN_DIR:"=%\archivegen" -c 9 %INSTALLER_DATA_DIR:"=%\%APP_NAME:"=%.7z .
cd "%RELEASE_DIR:"=%\installer"
cd "%WORK_DIR:"=%\installer"
echo "Creating installer..."
"%QIF_BIN_DIR:"=%\binarycreator" --offline-only -v -c config\windows.xml -p packages -f %TARGET_FILENAME%
timeout 5
cd %PROJECT_DIR%
signtool sign /v /sm /s My /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 "%TARGET_FILENAME%"
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 "%TARGET_FILENAME%"
echo "Finished, see %TARGET_FILENAME%"
exit 0

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,9 +6,10 @@ 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 DBus Core Network Widgets RemoteObjects Core5Compat)
qt_standard_project_setup()
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
set(HEADERS
@ -50,7 +51,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
@ -70,6 +71,7 @@ set(SOURCES ${SOURCES}
${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
@ -90,7 +92,7 @@ if(UNIX)
)
endif()
if (WIN32 OR APPLE)
if (WIN32 OR APPLE OR LINUX)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.h
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.h
@ -107,11 +109,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
@ -169,12 +199,30 @@ if(APPLE)
endif()
if(LINUX)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/router_linux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dbustypeslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/router_linux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp
)
endif()
@ -188,7 +236,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 Qt6::DBus ${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),
@ -41,16 +41,28 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
});
// Init Mozilla Wireguard Daemon
#ifdef Q_OS_MAC
if (!server.initialize()) {
logger.error() << "Failed to initialize the server";
return;
}
#ifdef Q_OS_LINUX
// Signal handling for a proper shutdown.
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
[]() { LinuxDaemon::instance()->deactivate(); });
#endif
#ifdef Q_OS_MAC
// Signal handling for a proper shutdown.
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,19 @@
#include "ipcserver.h"
#include "../../client/daemon/daemonlocalserver.h"
#ifdef Q_OS_WIN
#include "windows/daemon/windowsdaemon.h"
#endif
#ifdef Q_OS_LINUX
#include "linux/daemon/linuxdaemon.h"
#endif
#ifdef Q_OS_MAC
#include "macos/daemon/macosdaemon.h"
#include "../../client/daemon/daemonlocalserver.h"
#endif
class QLocalServer;
@ -26,16 +36,21 @@ class LocalServer : public QObject
public:
explicit LocalServer(QObject* parent = nullptr);
~LocalServer();
QSharedPointer<QLocalServer> m_server;
IpcServer m_ipcServer;
QRemoteObjectHost m_serverNode;
bool m_isRemotingEnabled = false;
#ifdef Q_OS_MAC
MacOSDaemon daemon;
#ifdef Q_OS_LINUX
DaemonLocalServer server{qApp};
LinuxDaemon daemon;
#endif
#ifdef Q_OS_WIN
DaemonLocalServer server{qApp};
WindowsDaemon daemon;
#endif
#ifdef Q_OS_MAC
DaemonLocalServer server{qApp};
MacOSDaemon daemon;
#endif
};

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

@ -54,9 +54,9 @@ bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const
if (int err = ioctl(sock, SIOCADDRT, &route) < 0)
{
qDebug().noquote() << "route add error: gw "
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
return false;
}

View file

@ -27,6 +27,7 @@ public:
bool clearSavedRoutes();
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
bool routeDeleteList(const QString &gw, const QStringList &ips);
QString getgatewayandiface();
void flushDns();
public slots:

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