WireGuard for MacOS (#248)
* WireGuard for MacOS * Fix openvpn block-outside-dns
This commit is contained in:
parent
ed5dc7cdfd
commit
35ecb8499d
118 changed files with 5150 additions and 3486 deletions
519
client/daemon/daemon.cpp
Normal file
519
client/daemon/daemon.cpp
Normal file
|
@ -0,0 +1,519 @@
|
|||
/* 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 "daemon.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
constexpr const char* JSON_ALLOWEDIPADDRESSRANGES = "allowedIPAddressRanges";
|
||||
constexpr int HANDSHAKE_POLL_MSEC = 250;
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger("Daemon");
|
||||
|
||||
Daemon* s_daemon = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
Daemon::Daemon(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(Daemon);
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
Q_ASSERT(s_daemon == nullptr);
|
||||
s_daemon = this;
|
||||
|
||||
m_handshakeTimer.setSingleShot(true);
|
||||
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake);
|
||||
}
|
||||
|
||||
Daemon::~Daemon() {
|
||||
MZ_COUNT_DTOR(Daemon);
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
Q_ASSERT(s_daemon == this);
|
||||
s_daemon = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
Daemon* Daemon::instance() {
|
||||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
bool Daemon::activate(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
// There are 3 possible scenarios in which this method is called:
|
||||
//
|
||||
// 1. the VPN is off: the method tries to enable the VPN.
|
||||
// 2. the VPN is on and the platform doesn't support the server-switching:
|
||||
// this method calls deactivate() and then it continues as 1.
|
||||
// 3. the VPN is on and the platform supports the server-switching: this
|
||||
// method calls switchServer().
|
||||
//
|
||||
// At the end, if the activation succeds, the `connected` signal is emitted.
|
||||
logger.debug() << "Activating interface";
|
||||
|
||||
if (m_connections.contains(config.m_hopindex)) {
|
||||
if (supportServerSwitching(config)) {
|
||||
logger.debug() << "Already connected. Server switching supported.";
|
||||
|
||||
if (!switchServer(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = run(Switch, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
logger.warning() << "Already connected. Server switching not supported.";
|
||||
if (!deactivate(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopindex));
|
||||
return activate(config);
|
||||
}
|
||||
|
||||
prepareActivation(config);
|
||||
|
||||
// Bring up the wireguard interface if not already done.
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
if (!wgutils()->addInterface(config)) {
|
||||
logger.error() << "Interface creation failed.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Add the peer to this interface.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
logger.error() << "Peer creation failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportIPUtils()) {
|
||||
if (!iputils()->addInterfaceIPs(config)) {
|
||||
return false;
|
||||
}
|
||||
if (!iputils()->setMTUAndUp(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
||||
logger.debug() << "Routing configuration failed for"
|
||||
<< logger.sensitive(ip.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool status = run(Up, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if ((config.m_hopindex == 0) && supportDnsUtils()) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
||||
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
|
||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||
}
|
||||
|
||||
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
||||
QStringList& list) {
|
||||
if (obj.contains(name)) {
|
||||
QJsonValue value = obj.value(name);
|
||||
if (!value.isArray()) {
|
||||
logger.error() << name << "is not an array";
|
||||
return false;
|
||||
}
|
||||
QJsonArray array = value.toArray();
|
||||
for (const QJsonValue& i : array) {
|
||||
if (!i.isString()) {
|
||||
logger.error() << name << "must contain only strings";
|
||||
return false;
|
||||
}
|
||||
list.append(i.toString());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
#define GETVALUE(name, where, jsontype) \
|
||||
if (!obj.contains(name)) { \
|
||||
logger.debug() << name << " missing in the jsonConfig input"; \
|
||||
return false; \
|
||||
} else { \
|
||||
QJsonValue value = obj.value(name); \
|
||||
if (value.type() != QJsonValue::jsontype) { \
|
||||
logger.error() << name << " is not a " #jsontype; \
|
||||
return false; \
|
||||
} \
|
||||
where = value.to##jsontype(); \
|
||||
}
|
||||
|
||||
GETVALUE("privateKey", config.m_privateKey, String);
|
||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
||||
|
||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
||||
if (config.m_deviceIpv4Address.isNull() &&
|
||||
config.m_deviceIpv6Address.isNull()) {
|
||||
logger.warning() << "no device addresses found in jsonConfig input";
|
||||
return false;
|
||||
}
|
||||
config.m_serverIpv4AddrIn = obj.value("serverIpv4AddrIn").toString();
|
||||
config.m_serverIpv6AddrIn = obj.value("serverIpv6AddrIn").toString();
|
||||
if (config.m_serverIpv4AddrIn.isNull() &&
|
||||
config.m_serverIpv6AddrIn.isNull()) {
|
||||
logger.error() << "no server addresses found in jsonConfig input";
|
||||
return false;
|
||||
}
|
||||
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
||||
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
||||
|
||||
if (!obj.contains("dnsServer")) {
|
||||
config.m_dnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("dnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
return false;
|
||||
}
|
||||
config.m_dnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("hopindex")) {
|
||||
config.m_hopindex = 0;
|
||||
} else {
|
||||
QJsonValue value = obj.value("hopindex");
|
||||
if (!value.isDouble()) {
|
||||
logger.error() << "hopindex is not a number";
|
||||
return false;
|
||||
}
|
||||
config.m_hopindex = value.toInt();
|
||||
}
|
||||
|
||||
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "missing in the jsonconfig input";
|
||||
return false;
|
||||
} else {
|
||||
QJsonValue value = obj.value(JSON_ALLOWEDIPADDRESSRANGES);
|
||||
if (!value.isArray()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES << "is not an array";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray array = value.toArray();
|
||||
for (const QJsonValue& i : array) {
|
||||
if (!i.isObject()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "must contain only objects";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject ipObj = i.toObject();
|
||||
|
||||
QJsonValue address = ipObj.value("address");
|
||||
if (!address.isString()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "objects must have a string address";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonValue range = ipObj.value("range");
|
||||
if (!range.isDouble()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "object must have a numberic range";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonValue isIpv6 = ipObj.value("isIpv6");
|
||||
if (!isIpv6.isBool()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "object must have a boolean isIpv6";
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_allowedIPAddressRanges.append(
|
||||
IPAddress(QHostAddress(address.toString()), range.toInt()));
|
||||
}
|
||||
|
||||
// Sort allowed IPs by decreasing prefix length.
|
||||
std::sort(config.m_allowedIPAddressRanges.begin(),
|
||||
config.m_allowedIPAddressRanges.end(),
|
||||
[&](const IPAddress& a, const IPAddress& b) -> bool {
|
||||
return a.prefixLength() > b.prefixLength();
|
||||
});
|
||||
}
|
||||
|
||||
if (!parseStringList(obj, "excludedAddresses", config.m_excludedAddresses)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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 (!run(Down, state.m_config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (emitSignals) {
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
// Cleanup DNS
|
||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
logger.warning() << "Wireguard interface does not exist.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
||||
}
|
||||
wgutils()->deletePeer(config);
|
||||
}
|
||||
|
||||
// Cleanup routing for excluded addresses.
|
||||
for (auto iterator = m_excludedAddrSet.constBegin();
|
||||
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
|
||||
wgutils()->deleteExclusionRoute(iterator.key());
|
||||
}
|
||||
m_excludedAddrSet.clear();
|
||||
|
||||
// Delete the interface
|
||||
if (!wgutils()->deleteInterface()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connections.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Daemon::cleanLogs() { }
|
||||
|
||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
if (!m_connections.contains(config.m_hopindex)) {
|
||||
return false;
|
||||
}
|
||||
const InterfaceConfig& current =
|
||||
m_connections.value(config.m_hopindex).m_config;
|
||||
|
||||
return current.m_privateKey == config.m_privateKey &&
|
||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||
current.m_deviceIpv6Address == config.m_deviceIpv6Address &&
|
||||
current.m_serverIpv4Gateway == config.m_serverIpv4Gateway &&
|
||||
current.m_serverIpv6Gateway == config.m_serverIpv6Gateway;
|
||||
}
|
||||
|
||||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Switching server for hop" << config.m_hopindex;
|
||||
|
||||
Q_ASSERT(m_connections.contains(config.m_hopindex));
|
||||
const InterfaceConfig& lastConfig =
|
||||
m_connections.value(config.m_hopindex).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;
|
||||
}
|
||||
|
||||
// Activate the new peer and its routes.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
logger.error() << "Server switch failed to update the wireguard interface";
|
||||
return false;
|
||||
}
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
||||
logger.error() << "Server switch failed to update the routing table";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old peer if it is no longer necessary.
|
||||
if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) {
|
||||
if (!wgutils()->deletePeer(lastConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject Daemon::getStatus() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
QJsonObject json;
|
||||
logger.debug() << "Status request";
|
||||
|
||||
if (!m_connections.contains(0) || !wgutils()->interfaceExists()) {
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
const ConnectionState& connection = m_connections.value(0);
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||
continue;
|
||||
}
|
||||
json.insert("connected", QJsonValue(true));
|
||||
json.insert("serverIpv4Gateway",
|
||||
QJsonValue(connection.m_config.m_serverIpv4Gateway));
|
||||
json.insert("deviceIpv4Address",
|
||||
QJsonValue(connection.m_config.m_deviceIpv4Address));
|
||||
json.insert("date", connection.m_date.toString());
|
||||
json.insert("txBytes", QJsonValue(status.m_txBytes));
|
||||
json.insert("rxBytes", QJsonValue(status.m_rxBytes));
|
||||
return json;
|
||||
}
|
||||
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
void Daemon::checkHandshake() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Checking for handshake...";
|
||||
|
||||
int pendingHandshakes = 0;
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (ConnectionState& connection : m_connections) {
|
||||
const InterfaceConfig& config = connection.m_config;
|
||||
if (connection.m_date.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the handshake has completed.
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (config.m_serverPublicKey != status.m_pubkey) {
|
||||
continue;
|
||||
}
|
||||
if (status.m_handshake != 0) {
|
||||
connection.m_date.setMSecsSinceEpoch(status.m_handshake);
|
||||
emit connected(status.m_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!connection.m_date.isValid()) {
|
||||
pendingHandshakes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check again if there were connections that haven't completed a handshake.
|
||||
if (pendingHandshakes > 0) {
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
}
|
||||
}
|
83
client/daemon/daemon.h
Normal file
83
client/daemon/daemon.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* 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 DAEMON_H
|
||||
#define DAEMON_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "dnsutils.h"
|
||||
#include "interfaceconfig.h"
|
||||
#include "iputils.h"
|
||||
#include "wireguardutils.h"
|
||||
|
||||
class Daemon : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Op {
|
||||
Up,
|
||||
Down,
|
||||
Switch,
|
||||
};
|
||||
|
||||
explicit Daemon(QObject* parent);
|
||||
~Daemon();
|
||||
|
||||
static Daemon* instance();
|
||||
|
||||
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
||||
|
||||
virtual bool activate(const InterfaceConfig& config);
|
||||
virtual bool deactivate(bool emitSignals = true);
|
||||
virtual QJsonObject getStatus();
|
||||
|
||||
// Callback before any Activating measure is done
|
||||
virtual void prepareActivation(const InterfaceConfig& config){
|
||||
Q_UNUSED(config)};
|
||||
|
||||
QString logs();
|
||||
void cleanLogs();
|
||||
|
||||
signals:
|
||||
void connected(const QString& pubkey);
|
||||
void disconnected();
|
||||
void backendFailure();
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
|
||||
protected:
|
||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||
Q_UNUSED(op);
|
||||
Q_UNUSED(config);
|
||||
return true;
|
||||
}
|
||||
virtual bool supportServerSwitching(const InterfaceConfig& config) const;
|
||||
virtual bool switchServer(const InterfaceConfig& config);
|
||||
virtual WireguardUtils* wgutils() const = 0;
|
||||
virtual bool supportIPUtils() const { return false; }
|
||||
virtual IPUtils* iputils() { return nullptr; }
|
||||
virtual bool supportDnsUtils() const { return false; }
|
||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||
|
||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||
QStringList& list);
|
||||
|
||||
void checkHandshake();
|
||||
|
||||
class ConnectionState {
|
||||
public:
|
||||
ConnectionState(){};
|
||||
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
||||
QDateTime m_date;
|
||||
InterfaceConfig m_config;
|
||||
};
|
||||
QMap<int, ConnectionState> m_connections;
|
||||
QHash<QHostAddress, int> m_excludedAddrSet;
|
||||
QTimer m_handshakeTimer;
|
||||
};
|
||||
|
||||
#endif // DAEMON_H
|
98
client/daemon/daemonlocalserver.cpp
Normal file
98
client/daemon/daemonlocalserver.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* 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 "daemonlocalserver.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "daemonlocalserverconnection.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#ifdef MZ_MACOS
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
|
||||
constexpr const char* TMP_PATH = "/tmp/amneziavpn.socket";
|
||||
constexpr const char* VAR_PATH = "/var/run/amneziavpn/daemon.socket";
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
Logger logger("DaemonLocalServer");
|
||||
} // namespace
|
||||
|
||||
DaemonLocalServer::DaemonLocalServer(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(DaemonLocalServer);
|
||||
}
|
||||
|
||||
DaemonLocalServer::~DaemonLocalServer() { MZ_COUNT_DTOR(DaemonLocalServer); }
|
||||
|
||||
bool DaemonLocalServer::initialize() {
|
||||
m_server.setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
|
||||
QString path = daemonPath();
|
||||
logger.debug() << "Server path:" << path;
|
||||
|
||||
if (QFileInfo::exists(path)) {
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
if (!m_server.listen(path)) {
|
||||
logger.error() << "Failed to listen the daemon path";
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(&m_server, &QLocalServer::newConnection, [&] {
|
||||
logger.debug() << "New connection received";
|
||||
|
||||
if (!m_server.hasPendingConnections()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLocalSocket* socket = m_server.nextPendingConnection();
|
||||
Q_ASSERT(socket);
|
||||
|
||||
DaemonLocalServerConnection* connection =
|
||||
new DaemonLocalServerConnection(&m_server, socket);
|
||||
connect(socket, &QLocalSocket::disconnected, connection,
|
||||
&DaemonLocalServerConnection::deleteLater);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DaemonLocalServer::daemonPath() const {
|
||||
#if defined(MZ_WINDOWS)
|
||||
return "\\\\.\\pipe\\amneziavpn";
|
||||
#elif defined(MZ_MACOS)
|
||||
QDir dir("/var/run");
|
||||
if (!dir.exists()) {
|
||||
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
if (dir.exists("amneziavpn")) {
|
||||
logger.debug() << "/var/run/amneziavpn seems to be usable";
|
||||
return VAR_PATH;
|
||||
}
|
||||
|
||||
if (!dir.mkdir("amneziavpn")) {
|
||||
logger.warning() << "Failed to create /var/run/amneziavpn";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
if (chmod("/var/run/amneziavpn", S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
|
||||
logger.warning()
|
||||
<< "Failed to set the right permissions to /var/run/amneziavpn";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
return VAR_PATH;
|
||||
#else
|
||||
# error Unsupported platform
|
||||
#endif
|
||||
}
|
26
client/daemon/daemonlocalserver.h
Normal file
26
client/daemon/daemonlocalserver.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* 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 DAEMONLOCALSERVER_H
|
||||
#define DAEMONLOCALSERVER_H
|
||||
|
||||
#include <QLocalServer>
|
||||
|
||||
class DaemonLocalServer final : public QObject {
|
||||
Q_DISABLE_COPY_MOVE(DaemonLocalServer)
|
||||
|
||||
public:
|
||||
explicit DaemonLocalServer(QObject* parent);
|
||||
~DaemonLocalServer();
|
||||
|
||||
bool initialize();
|
||||
|
||||
private:
|
||||
QString daemonPath() const;
|
||||
|
||||
private:
|
||||
QLocalServer m_server;
|
||||
};
|
||||
|
||||
#endif // DAEMONLOCALSERVER_H
|
162
client/daemon/daemonlocalserverconnection.cpp
Normal file
162
client/daemon/daemonlocalserverconnection.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
/* 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 "daemonlocalserverconnection.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "daemon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("DaemonLocalServerConnection");
|
||||
}
|
||||
|
||||
DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
|
||||
QLocalSocket* socket)
|
||||
: QObject(parent) {
|
||||
MZ_COUNT_CTOR(DaemonLocalServerConnection);
|
||||
|
||||
logger.debug() << "Connection created";
|
||||
|
||||
Q_ASSERT(socket);
|
||||
m_socket = socket;
|
||||
|
||||
connect(m_socket, &QLocalSocket::readyRead, this,
|
||||
&DaemonLocalServerConnection::readData);
|
||||
|
||||
Daemon* daemon = Daemon::instance();
|
||||
connect(daemon, &Daemon::connected, this,
|
||||
&DaemonLocalServerConnection::connected);
|
||||
connect(daemon, &Daemon::disconnected, this,
|
||||
&DaemonLocalServerConnection::disconnected);
|
||||
connect(daemon, &Daemon::backendFailure, this,
|
||||
&DaemonLocalServerConnection::backendFailure);
|
||||
}
|
||||
|
||||
DaemonLocalServerConnection::~DaemonLocalServerConnection() {
|
||||
MZ_COUNT_DTOR(DaemonLocalServerConnection);
|
||||
|
||||
logger.debug() << "Connection released";
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::readData() {
|
||||
logger.debug() << "Read Data";
|
||||
|
||||
Q_ASSERT(m_socket);
|
||||
|
||||
while (true) {
|
||||
int pos = m_buffer.indexOf("\n");
|
||||
if (pos == -1) {
|
||||
QByteArray input = m_socket->readAll();
|
||||
if (input.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
m_buffer.append(input);
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray line = m_buffer.left(pos);
|
||||
m_buffer.remove(0, pos + 1);
|
||||
|
||||
QByteArray command(line);
|
||||
command = command.trimmed();
|
||||
|
||||
if (command.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parseCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
QJsonDocument json = QJsonDocument::fromJson(data);
|
||||
if (!json.isObject()) {
|
||||
logger.error() << "Invalid input";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
QJsonValue typeValue = obj.value("type");
|
||||
if (!typeValue.isString()) {
|
||||
logger.warning() << "No type command. Ignoring request.";
|
||||
return;
|
||||
}
|
||||
QString type = typeValue.toString();
|
||||
|
||||
logger.debug() << "Command received:" << type;
|
||||
|
||||
if (type == "activate") {
|
||||
InterfaceConfig config;
|
||||
if (!Daemon::parseConfig(obj, config)) {
|
||||
logger.error() << "Invalid configuration";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Daemon::instance()->activate(config)) {
|
||||
logger.error() << "Failed to activate the interface";
|
||||
emit disconnected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "deactivate") {
|
||||
Daemon::instance()->deactivate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "status") {
|
||||
QJsonObject obj = Daemon::instance()->getStatus();
|
||||
obj.insert("type", "status");
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "logs") {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "logs");
|
||||
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "cleanlogs") {
|
||||
Daemon::instance()->cleanLogs();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warning() << "Invalid command:" << type;
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::connected(const QString& pubkey) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "connected");
|
||||
obj.insert("pubkey", QJsonValue(pubkey));
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::disconnected() {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "disconnected");
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::backendFailure() {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "backendFailure");
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::write(const QJsonObject& obj) {
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
}
|
36
client/daemon/daemonlocalserverconnection.h
Normal file
36
client/daemon/daemonlocalserverconnection.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DAEMONLOCALSERVERCONNECTION_H
|
||||
#define DAEMONLOCALSERVERCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QLocalSocket;
|
||||
|
||||
class DaemonLocalServerConnection final : public QObject {
|
||||
Q_DISABLE_COPY_MOVE(DaemonLocalServerConnection)
|
||||
|
||||
public:
|
||||
DaemonLocalServerConnection(QObject* parent, QLocalSocket* socket);
|
||||
~DaemonLocalServerConnection();
|
||||
|
||||
private:
|
||||
void readData();
|
||||
|
||||
void parseCommand(const QByteArray& json);
|
||||
|
||||
void connected(const QString& pubkey);
|
||||
void disconnected();
|
||||
void backendFailure();
|
||||
|
||||
void write(const QJsonObject& obj);
|
||||
|
||||
private:
|
||||
QLocalSocket* m_socket = nullptr;
|
||||
|
||||
QByteArray m_buffer;
|
||||
};
|
||||
|
||||
#endif // DAEMONLOCALSERVERCONNECTION_H
|
34
client/daemon/dnsutils.h
Normal file
34
client/daemon/dnsutils.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DNSUTILS_H
|
||||
#define DNSUTILS_H
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QString>
|
||||
|
||||
#include "dnsutils.h"
|
||||
|
||||
class DnsUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DnsUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~DnsUtils() = default;
|
||||
|
||||
virtual bool updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
Q_UNUSED(ifname);
|
||||
Q_UNUSED(resolvers);
|
||||
qFatal("Have you forgotten to implement DnsUtils::updateResolvers?");
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual bool restoreResolvers() {
|
||||
qFatal("Have you forgotten to implement DnsUtils::restoreResolvers?");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DNSUTILS_H
|
31
client/daemon/interfaceconfig.h
Normal file
31
client/daemon/interfaceconfig.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef INTERFACECONFIG_H
|
||||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
||||
struct InterfaceConfig {
|
||||
int m_hopindex = 0;
|
||||
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_serverIpv6AddrIn;
|
||||
QString m_dnsServer;
|
||||
int m_serverPort = 0;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
};
|
||||
|
||||
#endif // INTERFACECONFIG_H
|
31
client/daemon/iputils.h
Normal file
31
client/daemon/iputils.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPUTILS_H
|
||||
#define IPUTILS_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
class IPUtils : public QObject {
|
||||
public:
|
||||
explicit IPUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~IPUtils() = default;
|
||||
|
||||
virtual bool addInterfaceIPs(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
qFatal("Have you forgotten to implement IPUtils::addInterfaceIPs?");
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual bool setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
qFatal("Have you forgotten to implement IPUtils::setMTUAndUp?");
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // IPUTILS_H
|
51
client/daemon/wireguardutils.h
Normal file
51
client/daemon/wireguardutils.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* 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 WIREGUARDUTILS_H
|
||||
#define WIREGUARDUTILS_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
constexpr const char* WG_INTERFACE = "moz0";
|
||||
|
||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||
|
||||
class WireguardUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
class PeerStatus {
|
||||
public:
|
||||
PeerStatus(const QString& pubkey = QString()) { m_pubkey = pubkey; }
|
||||
QString m_pubkey;
|
||||
qint64 m_handshake = 0;
|
||||
qint64 m_rxBytes = 0;
|
||||
qint64 m_txBytes = 0;
|
||||
};
|
||||
|
||||
explicit WireguardUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~WireguardUtils() = default;
|
||||
|
||||
virtual bool interfaceExists() = 0;
|
||||
virtual QString interfaceName() { return WG_INTERFACE; }
|
||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||
virtual bool deleteInterface() = 0;
|
||||
|
||||
virtual bool updatePeer(const InterfaceConfig& config) = 0;
|
||||
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 addExclusionRoute(const QHostAddress& address) = 0;
|
||||
virtual bool deleteExclusionRoute(const QHostAddress& address) = 0;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILS_H
|
Loading…
Add table
Add a link
Reference in a new issue