mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: drive WG via Tunnel coordinator for seamless server switch
This commit is contained in:
@@ -28,6 +28,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
||||
m_vpnConnection(vpnConnection)
|
||||
{
|
||||
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::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||
|
||||
@@ -67,6 +67,7 @@ signals:
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
void serverSwitchFailed();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
|
||||
@@ -95,6 +95,11 @@ void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
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,
|
||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
|
||||
m_serversController(serversController)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -63,6 +64,12 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
||||
m_connectionStateText = tr("Connected");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Switching: {
|
||||
m_isConnectionInProgress = true;
|
||||
m_isConnected = true;
|
||||
m_connectionStateText = tr("Switching...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Connecting: {
|
||||
m_isConnectionInProgress = true;
|
||||
break;
|
||||
|
||||
@@ -48,6 +48,7 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void serverSwitchFailed();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
|
||||
@@ -74,19 +74,22 @@ ListViewType {
|
||||
: AmneziaStyle.color.mutedGray
|
||||
|
||||
checked: index === root.selectedIndex
|
||||
checkable: !ConnectionController.isConnected
|
||||
checkable: !ConnectionController.isConnectionInProgress
|
||||
|
||||
ButtonGroup.group: serversRadioButtonGroup
|
||||
|
||||
onClicked: {
|
||||
if (ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
|
||||
if (ConnectionController.isConnectionInProgress) {
|
||||
PageController.showNotificationMessage(qsTr("Unable to change server while connection is in progress"))
|
||||
return
|
||||
}
|
||||
|
||||
root.selectedIndex = index
|
||||
|
||||
ServersUiController.setDefaultServerAtIndex(index)
|
||||
|
||||
if (ConnectionController.isConnected) {
|
||||
ConnectionController.openConnection()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: serverRadioButton.clicked()
|
||||
|
||||
@@ -192,15 +192,11 @@ PageType {
|
||||
imageSource: "qrc:/images/controls/download.svg"
|
||||
|
||||
checked: index === ApiCountryModel.currentIndex
|
||||
checkable: !ConnectionController.isConnected
|
||||
checkable: !ConnectionController.isConnectionInProgress
|
||||
|
||||
onClicked: {
|
||||
if (ConnectionController.isConnectionInProgress) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change server location while trying to make an active connection"))
|
||||
return
|
||||
}
|
||||
if (ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
|
||||
PageController.showNotificationMessage(qsTr("Unable to change server location while connection is in progress"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -210,6 +206,8 @@ PageType {
|
||||
ApiCountryModel.currentIndex = index
|
||||
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
|
||||
ApiCountryModel.currentIndex = prevIndex
|
||||
} else if (ConnectionController.isConnected) {
|
||||
ConnectionController.openConnection()
|
||||
}
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case Vpn::ConnectionState::Connected:
|
||||
case Vpn::ConnectionState::Switching:
|
||||
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
|
||||
+229
-40
@@ -70,7 +70,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
switch (state) {
|
||||
case Vpn::ConnectionState::Connected: {
|
||||
m_trafficGuard->setupRoutes(m_vpnConfiguration, m_vpnProtocol, remoteAddress());
|
||||
m_trafficGuard->setupRoutes(m_vpnConfiguration, vpnProtocol(), m_remoteAddress);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
@@ -88,26 +88,22 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
#endif
|
||||
}
|
||||
|
||||
const QString &VpnConnection::remoteAddress() const
|
||||
{
|
||||
return m_remoteAddress;
|
||||
}
|
||||
|
||||
void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository)
|
||||
{
|
||||
m_serversRepository = serversRepository;
|
||||
m_appSettingsRepository = appSettingsRepository;
|
||||
m_trafficGuard.reset(new VpnTrafficGuard(appSettingsRepository, this));
|
||||
}
|
||||
|
||||
QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
|
||||
{
|
||||
return m_vpnProtocol;
|
||||
return m_active ? m_active->protocol() : m_vpnProtocol;
|
||||
}
|
||||
|
||||
void VpnConnection::disconnectSlots()
|
||||
{
|
||||
if (m_vpnProtocol) {
|
||||
m_vpnProtocol->disconnect();
|
||||
if (auto proto = vpnProtocol()) {
|
||||
proto->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +113,8 @@ ErrorCode VpnConnection::lastError() const
|
||||
return ErrorCode::AndroidError;
|
||||
#endif
|
||||
|
||||
if (m_vpnProtocol.isNull()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
return m_vpnProtocol.data()->lastError();
|
||||
auto proto = vpnProtocol();
|
||||
return proto.isNull() ? ErrorCode::InternalError : proto->lastError();
|
||||
}
|
||||
|
||||
Vpn::ConnectionState VpnConnection::connectionState() const
|
||||
@@ -129,6 +122,38 @@ Vpn::ConnectionState VpnConnection::connectionState() const
|
||||
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)
|
||||
{
|
||||
if (!m_appSettingsRepository || !m_serversRepository) {
|
||||
@@ -142,9 +167,24 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
|
||||
.arg(ContainerUtils::containerToString(container))
|
||||
<< 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
|
||||
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);
|
||||
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return;
|
||||
@@ -152,21 +192,41 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
|
||||
#endif
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
|
||||
m_vpnConfiguration = vpnConfiguration;
|
||||
QJsonObject config = vpnConfiguration;
|
||||
|
||||
#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) {
|
||||
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
m_trafficGuard->teardown();
|
||||
m_vpnProtocol->stop();
|
||||
m_vpnProtocol.reset();
|
||||
}
|
||||
appendKillSwitchConfig();
|
||||
appendKillSwitchConfig(config);
|
||||
#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));
|
||||
if (!m_vpnProtocol) {
|
||||
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(), &VpnProtocol::tunnelAddressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall);
|
||||
|
||||
wireDaemonReconnectSignals();
|
||||
}
|
||||
|
||||
void VpnConnection::wireDaemonReconnectSignals()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
|
||||
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn,
|
||||
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
|
||||
}
|
||||
|
||||
void VpnConnection::appendKillSwitchConfig()
|
||||
void VpnConnection::appendKillSwitchConfig(QJsonObject &config)
|
||||
{
|
||||
if (!m_appSettingsRepository) {
|
||||
qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized";
|
||||
return;
|
||||
}
|
||||
|
||||
m_vpnConfiguration.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString());
|
||||
m_vpnConfiguration.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue());
|
||||
config.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString());
|
||||
config.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue());
|
||||
}
|
||||
|
||||
void VpnConnection::appendSplitTunnelingConfig()
|
||||
void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
||||
{
|
||||
if (!m_appSettingsRepository) {
|
||||
qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized";
|
||||
@@ -230,14 +297,14 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
bool allowSiteBasedSplitTunneling = true;
|
||||
|
||||
// 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)) {
|
||||
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()) {
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", "));
|
||||
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()) {
|
||||
auto nativeConfig = configData.value(configKey::config).toString();
|
||||
auto nativeConfigLines = nativeConfig.split("\n");
|
||||
@@ -249,7 +316,7 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
}
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", "));
|
||||
configData.insert(configKey::allowedIps, allowedIpsJsonArray);
|
||||
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||
config.insert(protocolName + "_config_data", configData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +332,7 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
break;
|
||||
}
|
||||
configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1));
|
||||
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||
config.insert(protocolName + "_config_data", configData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -301,14 +368,14 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
routeMode = amnezia::RouteMode::VpnAllSites;
|
||||
} else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) {
|
||||
// Allow traffic to Amnezia DNS
|
||||
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns1).toString());
|
||||
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns2).toString());
|
||||
sitesJsonArray.append(config.value(configKey::dns1).toString());
|
||||
sitesJsonArray.append(config.value(configKey::dns2).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_vpnConfiguration.insert(configKey::splitTunnelType, routeMode);
|
||||
m_vpnConfiguration.insert(configKey::splitTunnelSites, sitesJsonArray);
|
||||
config.insert(configKey::splitTunnelType, routeMode);
|
||||
config.insert(configKey::splitTunnelSites, sitesJsonArray);
|
||||
|
||||
amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps;
|
||||
QJsonArray appsJsonArray;
|
||||
@@ -325,8 +392,8 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
}
|
||||
}
|
||||
|
||||
m_vpnConfiguration.insert(configKey::appSplitTunnelType, appsRouteMode);
|
||||
m_vpnConfiguration.insert(configKey::splitTunnelApps, appsJsonArray);
|
||||
config.insert(configKey::appSplitTunnelType, appsRouteMode);
|
||||
config.insert(configKey::splitTunnelApps, appsJsonArray);
|
||||
|
||||
qDebug() << QString("Site split tunneling is %1, route mode is %2")
|
||||
.arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
|
||||
@@ -368,9 +435,6 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
|
||||
}
|
||||
|
||||
void VpnConnection::reconnectToVpn() {
|
||||
if (m_vpnProtocol.isNull())
|
||||
return;
|
||||
|
||||
if (m_connectionState != Vpn::ConnectionState::Connected) {
|
||||
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
|
||||
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
|
||||
@@ -381,6 +445,16 @@ void VpnConnection::reconnectToVpn() {
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Reconnecting);
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_active) {
|
||||
m_active->restart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_vpnProtocol.isNull())
|
||||
return;
|
||||
|
||||
m_vpnProtocol->stop();
|
||||
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
@@ -396,6 +470,28 @@ void VpnConnection::disconnectFromVpn()
|
||||
disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
|
||||
#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()) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
@@ -435,3 +531,96 @@ void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
|
||||
m_connectionState = 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
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QMetaObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QScopedPointer>
|
||||
#include <QRemoteObjectNode>
|
||||
@@ -16,6 +17,7 @@
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
#include "core/vpnTrafficGuard.h"
|
||||
#include "core/tunnel.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "core/protocols/androidVpnProtocol.h"
|
||||
@@ -38,7 +40,7 @@ public:
|
||||
|
||||
QSharedPointer<VpnProtocol> vpnProtocol() const;
|
||||
|
||||
const QString &remoteAddress() const;
|
||||
const QString &remoteAddress() const { return m_remoteAddress; }
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnection();
|
||||
@@ -59,6 +61,7 @@ signals:
|
||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void vpnProtocolError(amnezia::ErrorCode error);
|
||||
void serverSwitchFailed();
|
||||
|
||||
void serviceIsNotReady();
|
||||
|
||||
@@ -75,8 +78,12 @@ private:
|
||||
QScopedPointer<VpnTrafficGuard> m_trafficGuard;
|
||||
|
||||
QJsonObject m_vpnConfiguration;
|
||||
QJsonObject m_routeMode;
|
||||
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
|
||||
QTimer m_checkTimer;
|
||||
@@ -91,9 +98,23 @@ private:
|
||||
Vpn::ConnectionState m_connectionState;
|
||||
|
||||
void createProtocolConnections();
|
||||
void wireTunnelSignals(Tunnel* tunnel, bool isActive);
|
||||
void wireDaemonReconnectSignals();
|
||||
|
||||
void appendSplitTunnelingConfig();
|
||||
void appendKillSwitchConfig();
|
||||
QString allocateIfname();
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user