mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: split daemon activation into bare bring-up and setPrimary
This commit is contained in:
+204
-193
@@ -34,8 +34,8 @@ Daemon::Daemon(QObject* parent) : QObject(parent) {
|
|||||||
Q_ASSERT(s_daemon == nullptr);
|
Q_ASSERT(s_daemon == nullptr);
|
||||||
s_daemon = this;
|
s_daemon = this;
|
||||||
|
|
||||||
m_handshakeTimer.setSingleShot(true);
|
m_activationTimer.setSingleShot(false);
|
||||||
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake);
|
connect(&m_activationTimer, &QTimer::timeout, this, &Daemon::checkActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
Daemon::~Daemon() {
|
Daemon::~Daemon() {
|
||||||
@@ -43,6 +43,9 @@ Daemon::~Daemon() {
|
|||||||
|
|
||||||
logger.debug() << "Daemon released";
|
logger.debug() << "Daemon released";
|
||||||
|
|
||||||
|
qDeleteAll(m_tunnels);
|
||||||
|
m_tunnels.clear();
|
||||||
|
|
||||||
Q_ASSERT(s_daemon == this);
|
Q_ASSERT(s_daemon == this);
|
||||||
s_daemon = nullptr;
|
s_daemon = nullptr;
|
||||||
}
|
}
|
||||||
@@ -53,69 +56,36 @@ Daemon* Daemon::instance() {
|
|||||||
return s_daemon;
|
return s_daemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::activate(const InterfaceConfig& config) {
|
bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
logger.debug() << "Activating tunnel";
|
||||||
|
|
||||||
// There are 3 possible scenarios in which this method is called:
|
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||||
//
|
if (!wg) {
|
||||||
// 1. the VPN is off: the method tries to enable the VPN.
|
wg = createWgUtils();
|
||||||
// 2. the VPN is on and the platform doesn't support the server-switching:
|
if (!wg) {
|
||||||
// this method calls deactivate() and then it continues as 1.
|
logger.error() << "Failed to create wireguard utils.";
|
||||||
// 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.
|
|
||||||
// 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_hopType)) {
|
|
||||||
if (supportServerSwitching(config)) {
|
|
||||||
logger.debug() << "Already connected. Server switching supported.";
|
|
||||||
|
|
||||||
if (!switchServer(config)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!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_hopType] = ConnectionState(config);
|
|
||||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
|
||||||
emit_failure_guard.dismiss();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
m_tunnels.insert(ifname, wg);
|
||||||
logger.warning() << "Already connected. Server switching not supported.";
|
|
||||||
if (!deactivate(false)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
|
||||||
if (activate(config)) {
|
|
||||||
emit_failure_guard.dismiss();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (m_primaryIfname.isEmpty()) {
|
||||||
|
m_primaryIfname = ifname;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionState& cs = m_connections[ifname];
|
||||||
|
cs.m_config = config;
|
||||||
|
cs.m_date = QDateTime();
|
||||||
|
cs.m_deadline = QDateTime::currentDateTime().addMSecs(ACTIVATION_TIMEOUT_MSEC);
|
||||||
|
|
||||||
|
auto failure_guard = qScopeGuard([this, ifname] {
|
||||||
|
deactivateTunnel(ifname);
|
||||||
|
});
|
||||||
|
|
||||||
prepareActivation(config);
|
prepareActivation(config);
|
||||||
|
|
||||||
// Bring up the wireguard interface if not already done.
|
// Bring up the wireguard interface if not already done.
|
||||||
if (!wgutils()->interfaceExists()) {
|
if (!wg->interfaceExists()) {
|
||||||
// Create the interface.
|
if (!wg->addInterface(config)) {
|
||||||
if (!wgutils()->addInterface(config)) {
|
|
||||||
logger.error() << "Interface creation failed.";
|
logger.error() << "Interface creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -131,38 +101,137 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure routing for excluded addresses.
|
if (!config.m_serverIpv4AddrIn.isEmpty()) {
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
addExclusionRoute(IPAddress(i));
|
}
|
||||||
|
if (!config.m_serverIpv6AddrIn.isEmpty()) {
|
||||||
|
addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the peer to this interface.
|
// Add the peer to this interface.
|
||||||
if (!wgutils()->updatePeer(config)) {
|
if (!wg->updatePeer(config)) {
|
||||||
logger.error() << "Peer creation failed.";
|
logger.error() << "Peer creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!maybeUpdateResolvers(config)) {
|
if (!m_activationTimer.isActive()) {
|
||||||
return false;
|
m_activationTimer.start(HANDSHAKE_POLL_MSEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon::setPrimary(const QString& ifname, const InterfaceConfig& config) {
|
||||||
|
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||||
|
if (!wg) {
|
||||||
|
logger.error() << "setPrimary: no tunnel for" << ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.debug() << "setPrimary" << wg->interfaceName();
|
||||||
|
|
||||||
|
const QString priorPrimary = m_primaryIfname;
|
||||||
|
m_primaryIfname = ifname;
|
||||||
|
|
||||||
|
auto failure_guard = qScopeGuard([this, ifname, priorPrimary] {
|
||||||
|
deactivateTunnel(ifname);
|
||||||
|
m_primaryIfname = priorPrimary;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
|
addExclusionRoute(IPAddress(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// set routing
|
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
if (!wg->updateRoutePrefix(ip)) {
|
||||||
logger.debug() << "Routing configuration failed for" << ip.toString();
|
logger.warning() << "setPrimary: route setup failed for" << ip.toString();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool status = run(Up, config);
|
if (!maybeUpdateResolvers(config)) {
|
||||||
logger.debug() << "Connection status:" << status;
|
logger.warning() << "setPrimary: DNS resolver update failed";
|
||||||
if (status) {
|
|
||||||
m_connections[config.m_hopType] = ConnectionState(config);
|
|
||||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
|
||||||
emit_failure_guard.dismiss();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
if (!run(Up, config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections[ifname].m_config = config;
|
||||||
|
|
||||||
|
// Demote the prior primary AFTER the new primary is fully installed.
|
||||||
|
// Delete-after-install order preserves coverage during the make-before-break overlap.
|
||||||
|
if (!priorPrimary.isEmpty() && priorPrimary != ifname) {
|
||||||
|
demotePrimary(priorPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Daemon::demotePrimary(const QString& ifname) {
|
||||||
|
WireguardUtils* wg = wgutilsFor(ifname);
|
||||||
|
if (!wg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ConnectionState cs = m_connections.value(ifname);
|
||||||
|
const InterfaceConfig& config = cs.m_config;
|
||||||
|
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
wg->deleteRoutePrefix(ip);
|
||||||
|
}
|
||||||
|
for (const QString& addr : config.m_excludedAddresses) {
|
||||||
|
if (addr.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IPAddress ip(addr);
|
||||||
|
if (m_excludedAddrSet.contains(ip)) {
|
||||||
|
delExclusionRoute(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon::deactivateTunnel(const QString& ifname) {
|
||||||
|
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||||
|
const ConnectionState cs = m_connections.value(ifname);
|
||||||
|
const InterfaceConfig& config = cs.m_config;
|
||||||
|
const bool wasPrimary = (ifname == m_primaryIfname);
|
||||||
|
|
||||||
|
if (wg) {
|
||||||
|
logger.debug() << "deactivateTunnel" << wg->interfaceName();
|
||||||
|
if (wasPrimary) {
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
wg->deleteRoutePrefix(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg->deletePeer(config);
|
||||||
|
|
||||||
|
auto removeExclusion = [&](const QString& addr) {
|
||||||
|
if (addr.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IPAddress ip(addr);
|
||||||
|
if (m_excludedAddrSet.contains(ip)) {
|
||||||
|
delExclusionRoute(ip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
removeExclusion(config.m_serverIpv4AddrIn);
|
||||||
|
removeExclusion(config.m_serverIpv6AddrIn);
|
||||||
|
if (wasPrimary) {
|
||||||
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
|
removeExclusion(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg->deleteInterface();
|
||||||
|
m_tunnels.remove(ifname);
|
||||||
|
delete wg;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections.remove(ifname);
|
||||||
|
if (wasPrimary) {
|
||||||
|
m_primaryIfname.clear();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||||
@@ -180,7 +249,8 @@ bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
|||||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
|
const QString ifname = wgutilsFor(config.m_ifname)->interfaceName();
|
||||||
|
if (!dnsutils()->updateResolvers(ifname, resolvers)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,7 +284,7 @@ bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
|||||||
m_excludedAddrSet[prefix]++;
|
m_excludedAddrSet[prefix]++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!wgutils()->addExclusionRoute(prefix)) {
|
if (!primaryWgutils()->addExclusionRoute(prefix)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_excludedAddrSet[prefix] = 1;
|
m_excludedAddrSet[prefix] = 1;
|
||||||
@@ -228,7 +298,8 @@ bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
m_excludedAddrSet.remove(prefix);
|
m_excludedAddrSet.remove(prefix);
|
||||||
return wgutils()->deleteExclusionRoute(prefix);
|
WireguardUtils* wg = primaryWgutils();
|
||||||
|
return wg && wg->deleteExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -440,18 +511,16 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
if (!obj.value("I5").isNull()) {
|
if (!obj.value("I5").isNull()) {
|
||||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||||
}
|
}
|
||||||
config.m_ifname = obj.value("ifname").toString(WG_INTERFACE);
|
config.m_ifname = obj.value("ifname").toString();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::deactivate(bool emitSignals) {
|
bool Daemon::deactivate(bool emitSignals) {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
const QString primary = m_primaryIfname;
|
||||||
|
|
||||||
// Deactivate the main interface.
|
if (m_connections.contains(primary)) {
|
||||||
if (!m_connections.isEmpty()) {
|
if (!run(Down, m_connections.value(primary).m_config)) {
|
||||||
const ConnectionState& state = m_connections.first();
|
|
||||||
if (!run(Down, state.m_config)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,31 +529,22 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||||||
emit disconnected();
|
emit disconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup DNS
|
|
||||||
if (!dnsutils()->restoreResolvers()) {
|
if (!dnsutils()->restoreResolvers()) {
|
||||||
logger.warning() << "Failed to restore DNS resolvers.";
|
logger.warning() << "Failed to restore DNS resolvers.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup peers and routing
|
const QStringList ifnames = m_tunnels.keys();
|
||||||
for (const ConnectionState& state : m_connections) {
|
for (const QString& ifname : ifnames) {
|
||||||
const InterfaceConfig& config = state.m_config;
|
if (ifname != primary) {
|
||||||
logger.debug() << "Deleting routes for" << config.m_hopType;
|
deactivateTunnel(ifname);
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
|
||||||
wgutils()->deleteRoutePrefix(ip);
|
|
||||||
}
|
}
|
||||||
wgutils()->deletePeer(config);
|
}
|
||||||
|
if (m_tunnels.contains(primary)) {
|
||||||
|
deactivateTunnel(primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup routing for excluded addresses.
|
m_activationTimer.stop();
|
||||||
for (auto iterator = m_excludedAddrSet.constBegin();
|
return true;
|
||||||
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
|
|
||||||
wgutils()->deleteExclusionRoute(iterator.key());
|
|
||||||
}
|
|
||||||
m_excludedAddrSet.clear();
|
|
||||||
|
|
||||||
m_connections.clear();
|
|
||||||
// Delete the interface
|
|
||||||
return wgutils()->deleteInterface();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Daemon::logs() {
|
QString Daemon::logs() {
|
||||||
@@ -493,79 +553,18 @@ QString Daemon::logs() {
|
|||||||
|
|
||||||
void Daemon::cleanLogs() { }
|
void Daemon::cleanLogs() { }
|
||||||
|
|
||||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
|
||||||
if (!m_connections.contains(config.m_hopType)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const InterfaceConfig& current =
|
|
||||||
m_connections.value(config.m_hopType).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" << config.m_hopType;
|
|
||||||
|
|
||||||
Q_ASSERT(m_connections.contains(config.m_hopType));
|
|
||||||
const InterfaceConfig& lastConfig =
|
|
||||||
m_connections.value(config.m_hopType).m_config;
|
|
||||||
|
|
||||||
// Configure routing for new excluded addresses.
|
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
|
||||||
addExclusionRoute(IPAddress(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)) {
|
|
||||||
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) {
|
|
||||||
delExclusionRoute(QHostAddress(i));
|
|
||||||
}
|
|
||||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
|
||||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
|
||||||
wgutils()->deleteRoutePrefix(ip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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_hopType] = ConnectionState(config);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject Daemon::getStatus() {
|
QJsonObject Daemon::getStatus() {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
logger.debug() << "Status request";
|
logger.debug() << "Status request";
|
||||||
|
|
||||||
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
WireguardUtils* wg = primaryWgutils();
|
||||||
|
if (!wg || !wg->interfaceExists() || !m_connections.contains(m_primaryIfname)) {
|
||||||
json.insert("connected", QJsonValue(false));
|
json.insert("connected", QJsonValue(false));
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionState& connection = m_connections.first();
|
const ConnectionState& connection = m_connections.value(m_primaryIfname);
|
||||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
QList<WireguardUtils::PeerStatus> peers = wg->getPeerStatus();
|
||||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||||
continue;
|
continue;
|
||||||
@@ -585,38 +584,50 @@ QJsonObject Daemon::getStatus() {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Daemon::checkHandshake() {
|
void Daemon::checkActivations() {
|
||||||
Q_ASSERT(wgutils() != nullptr);
|
const QDateTime now = QDateTime::currentDateTime();
|
||||||
|
QStringList timedOut;
|
||||||
|
bool anyPending = false;
|
||||||
|
|
||||||
logger.debug() << "Checking for handshake...";
|
for (auto it = m_connections.begin(); it != m_connections.end(); ++it) {
|
||||||
|
const QString& ifname = it.key();
|
||||||
|
ConnectionState& cs = it.value();
|
||||||
|
if (cs.m_date.isValid()) {
|
||||||
|
continue; // already handshaked
|
||||||
|
}
|
||||||
|
logger.debug() << "awaiting" << cs.m_config.m_serverPublicKey;
|
||||||
|
|
||||||
int pendingHandshakes = 0;
|
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
bool handshaked = false;
|
||||||
for (ConnectionState& connection : m_connections) {
|
if (wg) {
|
||||||
const InterfaceConfig& config = connection.m_config;
|
for (const WireguardUtils::PeerStatus& status : wg->getPeerStatus()) {
|
||||||
if (connection.m_date.isValid()) {
|
if (status.m_pubkey != cs.m_config.m_serverPublicKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (status.m_handshake != 0) {
|
||||||
|
cs.m_date.setMSecsSinceEpoch(status.m_handshake);
|
||||||
|
emit tunnelConnected(ifname, status.m_pubkey);
|
||||||
|
handshaked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handshaked) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
if (cs.m_deadline.isValid() && now > cs.m_deadline) {
|
||||||
|
timedOut.append(ifname);
|
||||||
// Check if the handshake has completed.
|
} else {
|
||||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
anyPending = true;
|
||||||
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.
|
for (const QString& ifname : timedOut) {
|
||||||
if (pendingHandshakes > 0) {
|
logger.warning() << "Tunnel handshake timed out:" << m_tunnels.value(ifname)->interfaceName();
|
||||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
emit tunnelHandshakeFailed(ifname);
|
||||||
|
deactivateTunnel(ifname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyPending) {
|
||||||
|
m_activationTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -22,7 +22,6 @@ class Daemon : public QObject {
|
|||||||
enum Op {
|
enum Op {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
Switch,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Daemon(QObject* parent);
|
explicit Daemon(QObject* parent);
|
||||||
@@ -32,10 +31,15 @@ class Daemon : public QObject {
|
|||||||
|
|
||||||
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
||||||
|
|
||||||
virtual bool activate(const InterfaceConfig& config);
|
bool activate(const QString& ifname, const InterfaceConfig& config);
|
||||||
|
bool setPrimary(const QString& ifname, const InterfaceConfig& config);
|
||||||
|
bool deactivateTunnel(const QString& ifname);
|
||||||
virtual bool deactivate(bool emitSignals = true);
|
virtual bool deactivate(bool emitSignals = true);
|
||||||
virtual QJsonObject getStatus();
|
virtual QJsonObject getStatus();
|
||||||
|
|
||||||
|
const QString& primaryIfname() const { return m_primaryIfname; }
|
||||||
|
WireguardUtils* wgutilsFor(const QString& ifname) const { return m_tunnels.value(ifname); }
|
||||||
|
|
||||||
// Callback before any Activating measure is done
|
// Callback before any Activating measure is done
|
||||||
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
||||||
Q_UNUSED(config) };
|
Q_UNUSED(config) };
|
||||||
@@ -46,12 +50,8 @@ class Daemon : public QObject {
|
|||||||
void cleanLogs();
|
void cleanLogs();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connected(const QString& pubkey);
|
void tunnelConnected(const QString& ifname, const QString& pubkey);
|
||||||
/**
|
void tunnelHandshakeFailed(const QString& ifname);
|
||||||
* Can be fired if a call to activate() was unsucessfull
|
|
||||||
* and connected systems should rollback
|
|
||||||
*/
|
|
||||||
void activationFailure();
|
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||||
|
|
||||||
@@ -59,6 +59,10 @@ class Daemon : public QObject {
|
|||||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||||
bool addExclusionRoute(const IPAddress& address);
|
bool addExclusionRoute(const IPAddress& address);
|
||||||
bool delExclusionRoute(const IPAddress& address);
|
bool delExclusionRoute(const IPAddress& address);
|
||||||
|
void demotePrimary(const QString& ifname);
|
||||||
|
void checkActivations();
|
||||||
|
WireguardUtils* primaryWgutils() const { return m_tunnels.value(m_primaryIfname); }
|
||||||
|
QTimer m_activationTimer;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||||
@@ -66,13 +70,11 @@ class Daemon : public QObject {
|
|||||||
Q_UNUSED(config);
|
Q_UNUSED(config);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
virtual bool supportServerSwitching(const InterfaceConfig& config) const;
|
|
||||||
virtual bool switchServer(const InterfaceConfig& config);
|
|
||||||
virtual WireguardUtils* wgutils() const = 0;
|
|
||||||
virtual WireguardUtils* createWgUtils() = 0;
|
virtual WireguardUtils* createWgUtils() = 0;
|
||||||
virtual void replaceActiveWgUtils(WireguardUtils* newUtils) = 0;
|
|
||||||
|
|
||||||
WireguardUtils* m_stagingWgutils = nullptr;
|
QMap<QString, WireguardUtils*> m_tunnels;
|
||||||
|
QString m_primaryIfname;
|
||||||
|
|
||||||
virtual bool supportIPUtils() const { return false; }
|
virtual bool supportIPUtils() const { return false; }
|
||||||
virtual IPUtils* iputils() { return nullptr; }
|
virtual IPUtils* iputils() { return nullptr; }
|
||||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||||
@@ -80,18 +82,16 @@ class Daemon : public QObject {
|
|||||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||||
QStringList& list);
|
QStringList& list);
|
||||||
|
|
||||||
void checkHandshake();
|
|
||||||
|
|
||||||
class ConnectionState {
|
class ConnectionState {
|
||||||
public:
|
public:
|
||||||
ConnectionState(){};
|
ConnectionState(){};
|
||||||
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
||||||
QDateTime m_date;
|
QDateTime m_date;
|
||||||
|
QDateTime m_deadline;
|
||||||
InterfaceConfig m_config;
|
InterfaceConfig m_config;
|
||||||
};
|
};
|
||||||
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
QMap<QString, ConnectionState> m_connections;
|
||||||
QHash<IPAddress, int> m_excludedAddrSet;
|
QHash<IPAddress, int> m_excludedAddrSet;
|
||||||
QTimer m_handshakeTimer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DAEMON_H
|
#endif // DAEMON_H
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
|
|||||||
&DaemonLocalServerConnection::readData);
|
&DaemonLocalServerConnection::readData);
|
||||||
|
|
||||||
Daemon* daemon = Daemon::instance();
|
Daemon* daemon = Daemon::instance();
|
||||||
connect(daemon, &Daemon::connected, this,
|
connect(daemon, &Daemon::tunnelConnected,
|
||||||
&DaemonLocalServerConnection::connected);
|
this, &DaemonLocalServerConnection::onTunnelConnected);
|
||||||
|
connect(daemon, &Daemon::tunnelHandshakeFailed,
|
||||||
|
this, &DaemonLocalServerConnection::onTunnelHandshakeFailed);
|
||||||
connect(daemon, &Daemon::disconnected, this,
|
connect(daemon, &Daemon::disconnected, this,
|
||||||
&DaemonLocalServerConnection::disconnected);
|
&DaemonLocalServerConnection::disconnected);
|
||||||
connect(daemon, &Daemon::backendFailure, this,
|
connect(daemon, &Daemon::backendFailure, this,
|
||||||
@@ -107,19 +109,44 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
|||||||
InterfaceConfig config;
|
InterfaceConfig config;
|
||||||
if (!Daemon::parseConfig(obj, config)) {
|
if (!Daemon::parseConfig(obj, config)) {
|
||||||
logger.error() << "Invalid configuration";
|
logger.error() << "Invalid configuration";
|
||||||
emit disconnected();
|
disconnected();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!Daemon::instance()->activate(config.m_ifname, config)) {
|
||||||
if (!Daemon::instance()->activate(config)) {
|
|
||||||
logger.error() << "Failed to activate the interface";
|
logger.error() << "Failed to activate the interface";
|
||||||
emit disconnected();
|
disconnected();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "deactivate") {
|
if (type == "deactivate") {
|
||||||
Daemon::instance()->deactivate(true);
|
const QString ifname = obj.value("ifname").toString();
|
||||||
|
if (!ifname.isEmpty()) {
|
||||||
|
Daemon::instance()->deactivateTunnel(ifname);
|
||||||
|
} else {
|
||||||
|
Daemon::instance()->deactivate(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "setPrimary") {
|
||||||
|
InterfaceConfig config;
|
||||||
|
if (!Daemon::parseConfig(obj, config)) {
|
||||||
|
logger.error() << "setPrimary: invalid configuration";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Daemon::instance()->setPrimary(config.m_ifname, config)) {
|
||||||
|
logger.error() << "setPrimary failed";
|
||||||
|
QJsonObject reply;
|
||||||
|
reply.insert("type", "primaryFailed");
|
||||||
|
reply.insert("ifname", config.m_ifname);
|
||||||
|
write(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject reply;
|
||||||
|
reply.insert("type", "primaryReady");
|
||||||
|
reply.insert("ifname", config.m_ifname);
|
||||||
|
write(reply);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +173,19 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
|||||||
logger.warning() << "Invalid command:" << type;
|
logger.warning() << "Invalid command:" << type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DaemonLocalServerConnection::connected(const QString& pubkey) {
|
void DaemonLocalServerConnection::onTunnelConnected(const QString& ifname,
|
||||||
|
const QString& pubkey) {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj.insert("type", "connected");
|
obj.insert("type", "connected");
|
||||||
obj.insert("pubkey", QJsonValue(pubkey));
|
obj.insert("ifname", ifname);
|
||||||
|
obj.insert("pubkey", pubkey);
|
||||||
|
write(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonLocalServerConnection::onTunnelHandshakeFailed(const QString& ifname) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj.insert("type", "disconnected");
|
||||||
|
obj.insert("ifname", ifname);
|
||||||
write(obj);
|
write(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#define DAEMONLOCALSERVERCONNECTION_H
|
#define DAEMONLOCALSERVERCONNECTION_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "daemonerrors.h"
|
#include "daemonerrors.h"
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@ class DaemonLocalServerConnection final : public QObject {
|
|||||||
|
|
||||||
void parseCommand(const QByteArray& json);
|
void parseCommand(const QByteArray& json);
|
||||||
|
|
||||||
void connected(const QString& pubkey);
|
void onTunnelConnected(const QString& ifname, const QString& pubkey);
|
||||||
|
void onTunnelHandshakeFailed(const QString& ifname);
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure(DaemonError err);
|
void backendFailure(DaemonError err);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
|
|
||||||
|
constexpr int ACTIVATION_TIMEOUT_MSEC = 30000;
|
||||||
|
|
||||||
class InterfaceConfig {
|
class InterfaceConfig {
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
#include "interfaceconfig.h"
|
#include "interfaceconfig.h"
|
||||||
|
|
||||||
constexpr const char* WG_INTERFACE = "amn0";
|
|
||||||
|
|
||||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||||
|
|
||||||
class WireguardUtils : public QObject {
|
class WireguardUtils : public QObject {
|
||||||
@@ -35,7 +33,7 @@ class WireguardUtils : public QObject {
|
|||||||
virtual ~WireguardUtils() = default;
|
virtual ~WireguardUtils() = default;
|
||||||
|
|
||||||
virtual bool interfaceExists() = 0;
|
virtual bool interfaceExists() = 0;
|
||||||
virtual QString interfaceName() { return WG_INTERFACE; }
|
virtual QString interfaceName() = 0;
|
||||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||||
virtual bool deleteInterface() = 0;
|
virtual bool deleteInterface() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
|
|||||||
|
|
||||||
logger.debug() << "Daemon created";
|
logger.debug() << "Daemon created";
|
||||||
|
|
||||||
m_wgutils = new WireguardUtilsLinux(this);
|
|
||||||
m_dnsutils = new DnsUtilsLinux(this);
|
m_dnsutils = new DnsUtilsLinux(this);
|
||||||
m_iputils = new IPUtilsLinux(this);
|
m_iputils = new IPUtilsLinux(this);
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
#include "wireguardutilslinux.h"
|
#include "wireguardutilslinux.h"
|
||||||
|
|
||||||
class LinuxDaemon final : public Daemon {
|
class LinuxDaemon final : public Daemon {
|
||||||
friend class IPUtilsMacos;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LinuxDaemon();
|
LinuxDaemon();
|
||||||
~LinuxDaemon();
|
~LinuxDaemon();
|
||||||
@@ -23,7 +21,6 @@ class LinuxDaemon final : public Daemon {
|
|||||||
bool deactivate(bool emitSignals = true) override;
|
bool deactivate(bool emitSignals = true) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
@@ -32,13 +29,7 @@ class LinuxDaemon final : public Daemon {
|
|||||||
return new WireguardUtilsLinux(this);
|
return new WireguardUtilsLinux(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceActiveWgUtils(WireguardUtils* newUtils) override {
|
|
||||||
delete m_wgutils;
|
|
||||||
m_wgutils = static_cast<WireguardUtilsLinux*>(newUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WireguardUtilsLinux* m_wgutils = nullptr;
|
|
||||||
DnsUtilsLinux* m_dnsutils = nullptr;
|
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||||
IPUtilsLinux* m_iputils = nullptr;
|
IPUtilsLinux* m_iputils = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,8 +193,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
|
|||||||
QStringList result;
|
QStringList result;
|
||||||
for (const QString& server : servers)
|
for (const QString& server : servers)
|
||||||
{
|
{
|
||||||
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o amn+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o amn+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||||
@@ -278,7 +278,7 @@ void LinuxFirewall::install()
|
|||||||
});
|
});
|
||||||
|
|
||||||
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
||||||
QStringLiteral("-o amn0+ -j ACCEPT"),
|
QStringLiteral("-o amn+ -j ACCEPT"),
|
||||||
QStringLiteral("-o tun0+ -j ACCEPT"),
|
QStringLiteral("-o tun0+ -j ACCEPT"),
|
||||||
QStringLiteral("-o tun2+ -j ACCEPT"),
|
QStringLiteral("-o tun2+ -j ACCEPT"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,33 +37,6 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
// Descriptor for a set of firewall rules to be appled.
|
|
||||||
//
|
|
||||||
struct FirewallParams
|
|
||||||
{
|
|
||||||
QStringList dnsServers;
|
|
||||||
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
|
||||||
QStringList allowAddrs;
|
|
||||||
QStringList blockAddrs;
|
|
||||||
// The follow flags indicate which general rulesets are needed. Note that
|
|
||||||
// this is after some sanity filtering, i.e. an allow rule may be listed
|
|
||||||
// as not needed if there were no block rules preceding it. The rulesets
|
|
||||||
// should be thought of as in last-match order.
|
|
||||||
|
|
||||||
bool blockAll; // Block all traffic by default
|
|
||||||
bool allowVPN; // Exempt traffic through VPN tunnel
|
|
||||||
bool allowDHCP; // Exempt DHCP traffic
|
|
||||||
bool blockIPv6; // Block all IPv6 traffic
|
|
||||||
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
|
||||||
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
|
||||||
bool allowPIA; // Exempt PIA executables
|
|
||||||
bool allowLoopback; // Exempt loopback traffic
|
|
||||||
bool allowHnsd; // Exempt Handshake DNS traffic
|
|
||||||
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
|
||||||
bool allowNets;
|
|
||||||
bool blockNets;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LinuxFirewall
|
class LinuxFirewall
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ typedef struct wg_allowedip {
|
|||||||
struct wg_allowedip *next_allowedip;
|
struct wg_allowedip *next_allowedip;
|
||||||
} wg_allowedip;
|
} wg_allowedip;
|
||||||
|
|
||||||
constexpr const char* WG_INTERFACE = "amn0";
|
|
||||||
|
|
||||||
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
int attrtype, const void* attrdata,
|
int attrtype, const void* attrdata,
|
||||||
size_t attrlen);
|
size_t attrlen);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "linuxfirewall.h"
|
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ifname = config.m_ifname.isEmpty() ? QString(WG_INTERFACE) : config.m_ifname;
|
const QString ifname = config.m_ifname;
|
||||||
|
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
if (!wgRuntimeDir.exists()) {
|
if (!wgRuntimeDir.exists()) {
|
||||||
@@ -147,29 +146,6 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||||
} else {
|
|
||||||
if (config.m_killSwitchEnabled) {
|
|
||||||
FirewallParams params { };
|
|
||||||
params.dnsServers.append(config.m_primaryDnsServer);
|
|
||||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
|
||||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
|
||||||
}
|
|
||||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
|
||||||
params.blockAll = true;
|
|
||||||
if (config.m_excludedAddresses.size()) {
|
|
||||||
params.allowNets = true;
|
|
||||||
foreach (auto net, config.m_excludedAddresses) {
|
|
||||||
params.allowAddrs.append(net.toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params.blockNets = true;
|
|
||||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
|
||||||
params.blockAddrs.append(net.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
applyFirewallRules(params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (err == 0);
|
return (err == 0);
|
||||||
@@ -454,27 +430,3 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
|||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
|
|
||||||
{
|
|
||||||
// double-check + ensure our firewall is installed and enabled
|
|
||||||
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
|
|
||||||
|
|
||||||
// Note: rule precedence is handled inside IpTablesFirewall
|
|
||||||
LinuxFirewall::ensureRootAnchorPriority();
|
|
||||||
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
|
|
||||||
LinuxFirewall::updateAllowNets(params.allowAddrs);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
|
|
||||||
LinuxFirewall::updateBlockNets(params.blockAddrs);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
|
||||||
LinuxFirewall::updateDNSServers(params.dnsServers);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include "daemon/wireguardutils.h"
|
#include "daemon/wireguardutils.h"
|
||||||
#include "linuxroutemonitor.h"
|
#include "linuxroutemonitor.h"
|
||||||
#include "linuxfirewall.h"
|
|
||||||
|
|
||||||
|
|
||||||
class WireguardUtilsLinux final : public WireguardUtils {
|
class WireguardUtilsLinux final : public WireguardUtils {
|
||||||
@@ -40,7 +39,6 @@ public:
|
|||||||
|
|
||||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
void applyFirewallRules(FirewallParams& params);
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,12 @@ bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||||
Q_UNUSED(config);
|
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
if (!wg) {
|
||||||
|
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString ifname = wg->interfaceName();
|
||||||
struct ifreq ifr;
|
struct ifreq ifr;
|
||||||
|
|
||||||
// Create socket file descriptor to perform the ioctl operations on
|
// Create socket file descriptor to perform the ioctl operations on
|
||||||
@@ -80,8 +84,12 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||||
Q_UNUSED(config);
|
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
if (!wg) {
|
||||||
|
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString ifname = wg->interfaceName();
|
||||||
struct ifaliasreq ifr;
|
struct ifaliasreq ifr;
|
||||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||||
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
||||||
@@ -130,8 +138,12 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||||
Q_UNUSED(config);
|
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
if (!wg) {
|
||||||
|
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString ifname = wg->interfaceName();
|
||||||
struct in6_aliasreq ifr6;
|
struct in6_aliasreq ifr6;
|
||||||
|
|
||||||
// Name the interface and set family
|
// Name the interface and set family
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
|||||||
|
|
||||||
logger.debug() << "Daemon created";
|
logger.debug() << "Daemon created";
|
||||||
|
|
||||||
m_wgutils = new WireguardUtilsMacos(this);
|
|
||||||
m_dnsutils = new DnsUtilsMacos(this);
|
m_dnsutils = new DnsUtilsMacos(this);
|
||||||
m_iputils = new IPUtilsMacos(this);
|
m_iputils = new IPUtilsMacos(this);
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
#include "wireguardutilsmacos.h"
|
#include "wireguardutilsmacos.h"
|
||||||
|
|
||||||
class MacOSDaemon final : public Daemon {
|
class MacOSDaemon final : public Daemon {
|
||||||
friend class IPUtilsMacos;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MacOSDaemon();
|
MacOSDaemon();
|
||||||
~MacOSDaemon();
|
~MacOSDaemon();
|
||||||
@@ -22,7 +20,6 @@ class MacOSDaemon final : public Daemon {
|
|||||||
bool deactivate(bool emitSignals = true) override;
|
bool deactivate(bool emitSignals = true) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
@@ -31,13 +28,7 @@ class MacOSDaemon final : public Daemon {
|
|||||||
return new WireguardUtilsMacos(this);
|
return new WireguardUtilsMacos(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceActiveWgUtils(WireguardUtils* newUtils) override {
|
|
||||||
delete m_wgutils;
|
|
||||||
m_wgutils = static_cast<WireguardUtilsMacos*>(newUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WireguardUtilsMacos* m_wgutils = nullptr;
|
|
||||||
DnsUtilsMacos* m_dnsutils = nullptr;
|
DnsUtilsMacos* m_dnsutils = nullptr;
|
||||||
IPUtilsMacos* m_iputils = nullptr;
|
IPUtilsMacos* m_iputils = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,35 +36,6 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
// Descriptor for a set of firewall rules to be appled.
|
|
||||||
//
|
|
||||||
struct FirewallParams
|
|
||||||
{
|
|
||||||
QStringList dnsServers;
|
|
||||||
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
|
||||||
|
|
||||||
QStringList allowAddrs;
|
|
||||||
QStringList blockAddrs;
|
|
||||||
|
|
||||||
// The follow flags indicate which general rulesets are needed. Note that
|
|
||||||
// this is after some sanity filtering, i.e. an allow rule may be listed
|
|
||||||
// as not needed if there were no block rules preceding it. The rulesets
|
|
||||||
// should be thought of as in last-match order.
|
|
||||||
|
|
||||||
bool blockAll; // Block all traffic by default
|
|
||||||
bool blockNets;
|
|
||||||
bool allowNets;
|
|
||||||
bool allowVPN; // Exempt traffic through VPN tunnel
|
|
||||||
bool allowDHCP; // Exempt DHCP traffic
|
|
||||||
bool blockIPv6; // Block all IPv6 traffic
|
|
||||||
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
|
||||||
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
|
||||||
bool allowPIA; // Exempt PIA executables
|
|
||||||
bool allowLoopback; // Exempt loopback traffic
|
|
||||||
bool allowHnsd; // Exempt Handshake DNS traffic
|
|
||||||
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacOSFirewall
|
class MacOSFirewall
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ifname = config.m_ifname.isEmpty() ? QString(WG_INTERFACE) : config.m_ifname;
|
const QString ifname = config.m_ifname;
|
||||||
|
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
if (!wgRuntimeDir.exists()) {
|
if (!wgRuntimeDir.exists()) {
|
||||||
@@ -146,30 +146,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||||
} else {
|
|
||||||
if (config.m_killSwitchEnabled) {
|
|
||||||
FirewallParams params { };
|
|
||||||
params.dnsServers.append(config.m_primaryDnsServer);
|
|
||||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
|
||||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
|
||||||
params.blockAll = true;
|
|
||||||
if (config.m_excludedAddresses.size()) {
|
|
||||||
params.allowNets = true;
|
|
||||||
foreach (auto net, config.m_excludedAddresses) {
|
|
||||||
params.allowAddrs.append(net.toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params.blockNets = true;
|
|
||||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
|
||||||
params.blockAddrs.append(net.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
applyFirewallRules(params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (err == 0);
|
return (err == 0);
|
||||||
}
|
}
|
||||||
@@ -455,28 +431,3 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
|
|||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
|
|
||||||
{
|
|
||||||
// double-check + ensure our firewall is installed and enabled. This is necessary as
|
|
||||||
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
|
|
||||||
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
|
|
||||||
|
|
||||||
MacOSFirewall::ensureRootAnchorPriority();
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
|
|
||||||
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
|
|
||||||
QStringLiteral("allownets"), params.allowAddrs);
|
|
||||||
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
|
|
||||||
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
|
|
||||||
QStringLiteral("blocknets"), params.blockAddrs);
|
|
||||||
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
|
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
|
|
||||||
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include "daemon/wireguardutils.h"
|
#include "daemon/wireguardutils.h"
|
||||||
#include "macosroutemonitor.h"
|
#include "macosroutemonitor.h"
|
||||||
#include "macosfirewall.h"
|
|
||||||
|
|
||||||
class WireguardUtilsMacos final : public WireguardUtils {
|
class WireguardUtilsMacos final : public WireguardUtils {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -38,8 +37,6 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
|||||||
|
|
||||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
void applyFirewallRules(FirewallParams& params);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,8 @@ WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
|
|||||||
m_firewallManager = WindowsFirewall::create(this);
|
m_firewallManager = WindowsFirewall::create(this);
|
||||||
Q_ASSERT(m_firewallManager != nullptr);
|
Q_ASSERT(m_firewallManager != nullptr);
|
||||||
|
|
||||||
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
|
|
||||||
m_dnsutils = new DnsUtilsWindows(this);
|
m_dnsutils = new DnsUtilsWindows(this);
|
||||||
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
||||||
|
|
||||||
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
|
|
||||||
&WindowsDaemon::monitorBackendFailure);
|
|
||||||
connect(this, &WindowsDaemon::activationFailure,
|
|
||||||
[this]() { m_firewallManager->disableKillSwitch(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsDaemon::~WindowsDaemon() {
|
WindowsDaemon::~WindowsDaemon() {
|
||||||
@@ -120,7 +114,3 @@ WireguardUtils* WindowsDaemon::createWgUtils() {
|
|||||||
&WindowsDaemon::monitorBackendFailure);
|
&WindowsDaemon::monitorBackendFailure);
|
||||||
return utils.release();
|
return utils.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsDaemon::replaceActiveWgUtils(WireguardUtils* newUtils) {
|
|
||||||
m_wgutils.reset(static_cast<WireguardUtilsWindows*>(newUtils));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ class WindowsDaemon final : public Daemon {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool run(Op op, const InterfaceConfig& config) override;
|
bool run(Op op, const InterfaceConfig& config) override;
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
WireguardUtils* createWgUtils() override;
|
WireguardUtils* createWgUtils() override;
|
||||||
void replaceActiveWgUtils(WireguardUtils* newUtils) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void monitorBackendFailure();
|
void monitorBackendFailure();
|
||||||
@@ -44,7 +42,6 @@ class WindowsDaemon final : public Daemon {
|
|||||||
|
|
||||||
int m_inetAdapterIndex = -1;
|
int m_inetAdapterIndex = -1;
|
||||||
|
|
||||||
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
|
|
||||||
DnsUtilsWindows* m_dnsutils = nullptr;
|
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||||
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
||||||
QPointer<WindowsFirewall> m_firewallManager;
|
QPointer<WindowsFirewall> m_firewallManager;
|
||||||
|
|||||||
Reference in New Issue
Block a user