mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +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)
|
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();
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user