feat: drive WG via Tunnel coordinator for seamless server switch

This commit is contained in:
cd-amn
2026-05-18 16:50:38 +00:00
parent 78381d7f22
commit 542fc9d4ae
10 changed files with 281 additions and 54 deletions
@@ -28,6 +28,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
m_vpnConnection(vpnConnection) m_vpnConnection(vpnConnection)
{ {
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged); connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
connect(m_vpnConnection, &VpnConnection::serverSwitchFailed, this, &ConnectionController::serverSwitchFailed);
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection); connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
@@ -67,6 +67,7 @@ signals:
void closeConnectionRequested(); void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state); void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled); void killSwitchModeChangedRequested(bool enabled);
void serverSwitchFailed();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
void restoreConnectionRequested(); void restoreConnectionRequested();
@@ -95,6 +95,11 @@ void CoreSignalHandlers::initErrorMessagesHandler()
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected); m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
}); });
connect(m_coreController->m_connectionUiController, &ConnectionUiController::serverSwitchFailed, this, [this]() {
emit m_coreController->m_pageController->showNotificationMessage(
tr("Failed to switch server. Existing connection maintained."));
});
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController, connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
qOverload<ErrorCode>(&PageController::showErrorMessage)); qOverload<ErrorCode>(&PageController::showErrorMessage));
@@ -17,6 +17,7 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
m_serversController(serversController) m_serversController(serversController)
{ {
connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged); connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged);
connect(m_connectionController, &ConnectionController::serverSwitchFailed, this, &ConnectionUiController::serverSwitchFailed);
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection); connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
@@ -63,6 +64,12 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
m_connectionStateText = tr("Connected"); m_connectionStateText = tr("Connected");
break; break;
} }
case Vpn::ConnectionState::Switching: {
m_isConnectionInProgress = true;
m_isConnected = true;
m_connectionStateText = tr("Switching...");
break;
}
case Vpn::ConnectionState::Connecting: { case Vpn::ConnectionState::Connecting: {
m_isConnectionInProgress = true; m_isConnectionInProgress = true;
break; break;
@@ -48,6 +48,7 @@ signals:
void connectButtonClicked(); void connectButtonClicked();
void preparingConfig(); void preparingConfig();
void prepareConfig(); void prepareConfig();
void serverSwitchFailed();
private: private:
Vpn::ConnectionState getCurrentConnectionState(); Vpn::ConnectionState getCurrentConnectionState();
+7 -4
View File
@@ -74,19 +74,22 @@ ListViewType {
: AmneziaStyle.color.mutedGray : AmneziaStyle.color.mutedGray
checked: index === root.selectedIndex checked: index === root.selectedIndex
checkable: !ConnectionController.isConnected checkable: !ConnectionController.isConnectionInProgress
ButtonGroup.group: serversRadioButtonGroup ButtonGroup.group: serversRadioButtonGroup
onClicked: { onClicked: {
if (ConnectionController.isConnected) { if (ConnectionController.isConnectionInProgress) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) PageController.showNotificationMessage(qsTr("Unable to change server while connection is in progress"))
return return
} }
root.selectedIndex = index root.selectedIndex = index
ServersUiController.setDefaultServerAtIndex(index) ServersUiController.setDefaultServerAtIndex(index)
if (ConnectionController.isConnected) {
ConnectionController.openConnection()
}
} }
Keys.onEnterPressed: serverRadioButton.clicked() Keys.onEnterPressed: serverRadioButton.clicked()
@@ -192,15 +192,11 @@ PageType {
imageSource: "qrc:/images/controls/download.svg" imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex checked: index === ApiCountryModel.currentIndex
checkable: !ConnectionController.isConnected checkable: !ConnectionController.isConnectionInProgress
onClicked: { onClicked: {
if (ConnectionController.isConnectionInProgress) { if (ConnectionController.isConnectionInProgress) {
PageController.showNotificationMessage(qsTr("Unable change server location while trying to make an active connection")) PageController.showNotificationMessage(qsTr("Unable to change server location while connection is in progress"))
return
}
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
return return
} }
@@ -210,6 +206,8 @@ PageType {
ApiCountryModel.currentIndex = index ApiCountryModel.currentIndex = index
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) { if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex ApiCountryModel.currentIndex = prevIndex
} else if (ConnectionController.isConnected) {
ConnectionController.openConnection()
} }
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
@@ -110,6 +110,7 @@ void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
m_trayActionDisconnect->setEnabled(true); m_trayActionDisconnect->setEnabled(true);
break; break;
case Vpn::ConnectionState::Connected: case Vpn::ConnectionState::Connected:
case Vpn::ConnectionState::Switching:
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName)); setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
m_trayActionConnect->setEnabled(false); m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true); m_trayActionDisconnect->setEnabled(true);
+229 -40
View File
@@ -70,7 +70,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
switch (state) { switch (state) {
case Vpn::ConnectionState::Connected: { case Vpn::ConnectionState::Connected: {
m_trafficGuard->setupRoutes(m_vpnConfiguration, m_vpnProtocol, remoteAddress()); m_trafficGuard->setupRoutes(m_vpnConfiguration, vpnProtocol(), m_remoteAddress);
} break; } break;
default: default:
break; break;
@@ -88,26 +88,22 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
#endif #endif
} }
const QString &VpnConnection::remoteAddress() const
{
return m_remoteAddress;
}
void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository) void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository)
{ {
m_serversRepository = serversRepository; m_serversRepository = serversRepository;
m_appSettingsRepository = appSettingsRepository; m_appSettingsRepository = appSettingsRepository;
m_trafficGuard.reset(new VpnTrafficGuard(appSettingsRepository, this));
} }
QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
{ {
return m_vpnProtocol; return m_active ? m_active->protocol() : m_vpnProtocol;
} }
void VpnConnection::disconnectSlots() void VpnConnection::disconnectSlots()
{ {
if (m_vpnProtocol) { if (auto proto = vpnProtocol()) {
m_vpnProtocol->disconnect(); proto->disconnect();
} }
} }
@@ -117,11 +113,8 @@ ErrorCode VpnConnection::lastError() const
return ErrorCode::AndroidError; return ErrorCode::AndroidError;
#endif #endif
if (m_vpnProtocol.isNull()) { auto proto = vpnProtocol();
return ErrorCode::InternalError; return proto.isNull() ? ErrorCode::InternalError : proto->lastError();
}
return m_vpnProtocol.data()->lastError();
} }
Vpn::ConnectionState VpnConnection::connectionState() const Vpn::ConnectionState VpnConnection::connectionState() const
@@ -129,6 +122,38 @@ Vpn::ConnectionState VpnConnection::connectionState() const
return m_connectionState; return m_connectionState;
} }
QString VpnConnection::allocateIfname()
{
for (int i = 0; ; ++i) {
const QString name = QStringLiteral("amn") + QString::number(i);
if (!m_ifnamesInUse.contains(name)) {
m_ifnamesInUse.insert(name);
return name;
}
}
}
void VpnConnection::releaseIfname(const QString& ifname)
{
m_ifnamesInUse.remove(ifname);
}
void VpnConnection::wireTunnelSignals(Tunnel* tunnel, bool isActive)
{
connect(tunnel, &Tunnel::prepared, this, &VpnConnection::onTunnelPrepared);
connect(tunnel, &Tunnel::activated, this, &VpnConnection::onTunnelActivated);
connect(tunnel, &Tunnel::failed, this, &VpnConnection::onTunnelFailed);
if (isActive) {
connect(tunnel, &Tunnel::bytesChanged, this, &VpnConnection::onBytesChanged);
// Staging tunnel deliberately skips this wire: applying KS while the old
// primary is still serving would clobber its allow-rules. onTunnelActivated
// invokes applyFirewall manually after the make-before-break swap.
connect(tunnel, &Tunnel::addressesUpdated,
m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall);
}
}
void VpnConnection::connectToVpn(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration) void VpnConnection::connectToVpn(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration)
{ {
if (!m_appSettingsRepository || !m_serversRepository) { if (!m_appSettingsRepository || !m_serversRepository) {
@@ -142,9 +167,24 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
.arg(ContainerUtils::containerToString(container)) .arg(ContainerUtils::containerToString(container))
<< m_appSettingsRepository->routeMode(); << m_appSettingsRepository->routeMode();
m_remoteAddress = NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString()); const QString resolvedRemote =
NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString());
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (!m_trafficGuard->allowEndpoint(m_remoteAddress)) { // Seamless WG -> WG switch path: already connected via Tunnel, new container is also WG.
if (m_active
&& m_connectionState == Vpn::ConnectionState::Connected
&& VpnProtocol::isWireGuardBased(container)) {
if (!m_trafficGuard->allowEndpoint(resolvedRemote)) {
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
return;
}
startTunnelSwitch(container, vpnConfiguration, resolvedRemote);
return;
}
if (!m_trafficGuard->allowEndpoint(resolvedRemote)) {
setConnectionState(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed); emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
return; return;
@@ -152,21 +192,41 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
#endif #endif
setConnectionState(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
m_vpnConfiguration = vpnConfiguration; QJsonObject config = vpnConfiguration;
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (m_active) {
const QString oldIfname = m_active->ifname();
m_active->deactivate();
delete m_active;
m_active = nullptr;
releaseIfname(oldIfname);
}
if (m_vpnProtocol) { if (m_vpnProtocol) {
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
m_trafficGuard->teardown(); m_trafficGuard->teardown();
m_vpnProtocol->stop(); m_vpnProtocol->stop();
m_vpnProtocol.reset(); m_vpnProtocol.reset();
} }
appendKillSwitchConfig(); appendKillSwitchConfig(config);
#endif #endif
appendSplitTunnelingConfig(); appendSplitTunnelingConfig(config);
m_vpnConfiguration = config;
m_remoteAddress = resolvedRemote;
#ifdef AMNEZIA_DESKTOP
if (VpnProtocol::isWireGuardBased(container)) {
const QString ifname = allocateIfname();
m_active = new Tunnel(ifname, container, config, resolvedRemote, this);
wireTunnelSignals(m_active, /*isActive=*/true);
wireDaemonReconnectSignals();
m_trafficGuard->setConfig(config);
m_active->prepare();
return;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
if (!m_vpnProtocol) { if (!m_vpnProtocol) {
setConnectionState(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
@@ -201,26 +261,33 @@ void VpnConnection::createProtocolConnections()
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall); connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall);
wireDaemonReconnectSignals();
}
void VpnConnection::wireDaemonReconnectSignals()
{
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) { IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection); connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn,
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection); static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
}); });
#endif #endif
} }
void VpnConnection::appendKillSwitchConfig() void VpnConnection::appendKillSwitchConfig(QJsonObject &config)
{ {
if (!m_appSettingsRepository) { if (!m_appSettingsRepository) {
qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized"; qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized";
return; return;
} }
m_vpnConfiguration.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString()); config.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString());
m_vpnConfiguration.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue()); config.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue());
} }
void VpnConnection::appendSplitTunnelingConfig() void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
{ {
if (!m_appSettingsRepository) { if (!m_appSettingsRepository) {
qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized"; qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized";
@@ -230,14 +297,14 @@ void VpnConnection::appendSplitTunnelingConfig()
bool allowSiteBasedSplitTunneling = true; bool allowSiteBasedSplitTunneling = true;
// this block is for old native configs and for old self-hosted configs // this block is for old native configs and for old self-hosted configs
auto protocolName = m_vpnConfiguration.value(configKey::vpnProto).toString(); auto protocolName = config.value(configKey::vpnProto).toString();
if (protocolName == ProtocolUtils::protoToString(Proto::Awg) || protocolName == ProtocolUtils::protoToString(Proto::WireGuard)) { if (protocolName == ProtocolUtils::protoToString(Proto::Awg) || protocolName == ProtocolUtils::protoToString(Proto::WireGuard)) {
allowSiteBasedSplitTunneling = false; allowSiteBasedSplitTunneling = false;
auto configData = m_vpnConfiguration.value(protocolName + "_config_data").toObject(); auto configData = config.value(protocolName + "_config_data").toObject();
if (configData.value(configKey::allowedIps).isString()) { if (configData.value(configKey::allowedIps).isString()) {
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", ")); QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", "));
configData.insert(configKey::allowedIps, allowedIpsJsonArray); configData.insert(configKey::allowedIps, allowedIpsJsonArray);
m_vpnConfiguration.insert(protocolName + "_config_data", configData); config.insert(protocolName + "_config_data", configData);
} else if (configData.value(configKey::allowedIps).isUndefined()) { } else if (configData.value(configKey::allowedIps).isUndefined()) {
auto nativeConfig = configData.value(configKey::config).toString(); auto nativeConfig = configData.value(configKey::config).toString();
auto nativeConfigLines = nativeConfig.split("\n"); auto nativeConfigLines = nativeConfig.split("\n");
@@ -249,7 +316,7 @@ void VpnConnection::appendSplitTunnelingConfig()
} }
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", ")); QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", "));
configData.insert(configKey::allowedIps, allowedIpsJsonArray); configData.insert(configKey::allowedIps, allowedIpsJsonArray);
m_vpnConfiguration.insert(protocolName + "_config_data", configData); config.insert(protocolName + "_config_data", configData);
break; break;
} }
} }
@@ -265,7 +332,7 @@ void VpnConnection::appendSplitTunnelingConfig()
break; break;
} }
configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1)); configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1));
m_vpnConfiguration.insert(protocolName + "_config_data", configData); config.insert(protocolName + "_config_data", configData);
break; break;
} }
} }
@@ -301,14 +368,14 @@ void VpnConnection::appendSplitTunnelingConfig()
routeMode = amnezia::RouteMode::VpnAllSites; routeMode = amnezia::RouteMode::VpnAllSites;
} else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) { } else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) {
// Allow traffic to Amnezia DNS // Allow traffic to Amnezia DNS
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns1).toString()); sitesJsonArray.append(config.value(configKey::dns1).toString());
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns2).toString()); sitesJsonArray.append(config.value(configKey::dns2).toString());
} }
} }
} }
m_vpnConfiguration.insert(configKey::splitTunnelType, routeMode); config.insert(configKey::splitTunnelType, routeMode);
m_vpnConfiguration.insert(configKey::splitTunnelSites, sitesJsonArray); config.insert(configKey::splitTunnelSites, sitesJsonArray);
amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps; amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps;
QJsonArray appsJsonArray; QJsonArray appsJsonArray;
@@ -325,8 +392,8 @@ void VpnConnection::appendSplitTunnelingConfig()
} }
} }
m_vpnConfiguration.insert(configKey::appSplitTunnelType, appsRouteMode); config.insert(configKey::appSplitTunnelType, appsRouteMode);
m_vpnConfiguration.insert(configKey::splitTunnelApps, appsJsonArray); config.insert(configKey::splitTunnelApps, appsJsonArray);
qDebug() << QString("Site split tunneling is %1, route mode is %2") qDebug() << QString("Site split tunneling is %1, route mode is %2")
.arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled") .arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
@@ -368,9 +435,6 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
} }
void VpnConnection::reconnectToVpn() { void VpnConnection::reconnectToVpn() {
if (m_vpnProtocol.isNull())
return;
if (m_connectionState != Vpn::ConnectionState::Connected) { if (m_connectionState != Vpn::ConnectionState::Connected) {
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot") qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState)); .arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
@@ -381,6 +445,16 @@ void VpnConnection::reconnectToVpn() {
setConnectionState(Vpn::ConnectionState::Reconnecting); setConnectionState(Vpn::ConnectionState::Reconnecting);
#ifdef AMNEZIA_DESKTOP
if (m_active) {
m_active->restart();
return;
}
#endif
if (m_vpnProtocol.isNull())
return;
m_vpnProtocol->stop(); m_vpnProtocol->stop();
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) { if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
setConnectionState(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
@@ -396,6 +470,28 @@ void VpnConnection::disconnectFromVpn()
disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
#endif #endif
#ifdef AMNEZIA_DESKTOP
if (m_staging) {
m_trafficGuard->revokeEndpoint(m_staging->remoteAddress());
m_staging->deactivate();
releaseIfname(m_staging->ifname());
delete m_staging;
m_staging = nullptr;
}
if (m_active) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
m_trafficGuard->teardown();
m_trafficGuard->revokeEndpoint(m_remoteAddress);
m_active->deactivate();
releaseIfname(m_active->ifname());
delete m_active;
m_active = nullptr;
setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
#endif
if (m_vpnProtocol.isNull()) { if (m_vpnProtocol.isNull()) {
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
return; return;
@@ -435,3 +531,96 @@ void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
m_connectionState = state; m_connectionState = state;
emit connectionStateChanged(state); emit connectionStateChanged(state);
} }
void VpnConnection::startTunnelSwitch(DockerContainer container,
const QJsonObject &vpnConfiguration,
const QString &resolvedRemote)
{
QJsonObject config = vpnConfiguration;
#ifdef AMNEZIA_DESKTOP
appendKillSwitchConfig(config);
#endif
appendSplitTunnelingConfig(config);
const QString stagingIfname = allocateIfname();
m_staging = new Tunnel(stagingIfname, container, config, resolvedRemote, this);
wireTunnelSignals(m_staging, /*isActive=*/false);
setConnectionState(Vpn::ConnectionState::Switching);
m_staging->prepare();
}
void VpnConnection::onTunnelPrepared()
{
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
if (!tunnel) return;
tunnel->commit();
}
void VpnConnection::onTunnelActivated()
{
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
if (!tunnel) return;
if (tunnel == m_staging) {
// Make-before-break gate passed: new tunnel is primary, old still allowed by KS.
if (m_active) {
const QString oldRemote = m_active->remoteAddress();
const QString oldIfname = m_active->ifname();
m_active->deactivate();
delete m_active;
releaseIfname(oldIfname);
m_trafficGuard->revokeEndpoint(oldRemote);
}
m_active = m_staging;
m_staging = nullptr;
connect(m_active, &Tunnel::bytesChanged, this, &VpnConnection::onBytesChanged);
connect(m_active, &Tunnel::addressesUpdated,
m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall);
m_vpnConfiguration = m_active->config();
m_remoteAddress = m_active->remoteAddress();
m_trafficGuard->setConfig(m_vpnConfiguration);
if (auto proto = m_active->protocol()) {
m_trafficGuard->applyFirewall(proto->vpnGateway(),
proto->vpnLocalAddress());
}
setConnectionState(Vpn::ConnectionState::Connected);
return;
}
if (tunnel == m_active) {
setConnectionState(Vpn::ConnectionState::Connected);
}
}
void VpnConnection::onTunnelFailed(amnezia::ErrorCode error)
{
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
if (!tunnel) return;
if (tunnel == m_staging) {
m_trafficGuard->revokeEndpoint(m_staging->remoteAddress());
m_staging->deactivate();
releaseIfname(m_staging->ifname());
m_staging->deleteLater();
m_staging = nullptr;
setConnectionState(Vpn::ConnectionState::Connected);
emit serverSwitchFailed();
return;
}
if (tunnel == m_active) {
m_trafficGuard->teardown();
m_trafficGuard->revokeEndpoint(m_remoteAddress);
m_active->deactivate();
releaseIfname(m_active->ifname());
m_active->deleteLater();
m_active = nullptr;
setConnectionState(Vpn::ConnectionState::Error);
if (error != ErrorCode::NoError) {
emit vpnProtocolError(error);
}
}
}
+25 -4
View File
@@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include <QMetaObject> #include <QMetaObject>
#include <QSet>
#include <QString> #include <QString>
#include <QScopedPointer> #include <QScopedPointer>
#include <QRemoteObjectNode> #include <QRemoteObjectNode>
@@ -16,6 +17,7 @@
#include "core/repositories/secureAppSettingsRepository.h" #include "core/repositories/secureAppSettingsRepository.h"
#include "core/vpnTrafficGuard.h" #include "core/vpnTrafficGuard.h"
#include "core/tunnel.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "core/protocols/androidVpnProtocol.h" #include "core/protocols/androidVpnProtocol.h"
@@ -38,7 +40,7 @@ public:
QSharedPointer<VpnProtocol> vpnProtocol() const; QSharedPointer<VpnProtocol> vpnProtocol() const;
const QString &remoteAddress() const; const QString &remoteAddress() const { return m_remoteAddress; }
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
void restoreConnection(); void restoreConnection();
@@ -59,6 +61,7 @@ signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
void vpnProtocolError(amnezia::ErrorCode error); void vpnProtocolError(amnezia::ErrorCode error);
void serverSwitchFailed();
void serviceIsNotReady(); void serviceIsNotReady();
@@ -75,8 +78,12 @@ private:
QScopedPointer<VpnTrafficGuard> m_trafficGuard; QScopedPointer<VpnTrafficGuard> m_trafficGuard;
QJsonObject m_vpnConfiguration; QJsonObject m_vpnConfiguration;
QJsonObject m_routeMode;
QString m_remoteAddress; QString m_remoteAddress;
QJsonObject m_routeMode;
Tunnel* m_active = nullptr;
Tunnel* m_staging = nullptr;
QSet<QString> m_ifnamesInUse;
// Only for iOS for now, check counters // Only for iOS for now, check counters
QTimer m_checkTimer; QTimer m_checkTimer;
@@ -91,9 +98,23 @@ private:
Vpn::ConnectionState m_connectionState; Vpn::ConnectionState m_connectionState;
void createProtocolConnections(); void createProtocolConnections();
void wireTunnelSignals(Tunnel* tunnel, bool isActive);
void wireDaemonReconnectSignals();
void appendSplitTunnelingConfig(); QString allocateIfname();
void appendKillSwitchConfig(); void releaseIfname(const QString& ifname);
void appendSplitTunnelingConfig(QJsonObject &config);
void appendKillSwitchConfig(QJsonObject &config);
void startTunnelSwitch(DockerContainer container,
const QJsonObject &vpnConfiguration,
const QString &resolvedRemote);
private slots:
void onTunnelPrepared();
void onTunnelActivated();
void onTunnelFailed(amnezia::ErrorCode error);
}; };
#endif // VPNCONNECTION_H #endif // VPNCONNECTION_H