diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index 497757757..8e02b48d9 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -64,6 +64,7 @@ set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/core/utils/utilities.h ${CLIENT_ROOT_DIR}/core/utils/managementServer.h ${CLIENT_ROOT_DIR}/core/utils/constants.h + ${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h ) # Mozilla headres @@ -141,6 +142,7 @@ set(SOURCES ${SOURCES} ${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp ${CLIENT_ROOT_DIR}/core/utils/utilities.cpp ${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp + ${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp ) # Mozilla sources diff --git a/client/core/controllers/api/subscriptionController.cpp b/client/core/controllers/api/subscriptionController.cpp index ce34d4690..fc3b2a43a 100644 --- a/client/core/controllers/api/subscriptionController.cpp +++ b/client/core/controllers/api/subscriptionController.cpp @@ -421,7 +421,23 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons } const bool isTestPurchase = apiV2->apiConfig.isTestPurchase; QString serviceProtocol = apiV2->serviceProtocol(); - ProtocolData protocolData = generateProtocolData(serviceProtocol); + ProtocolData protocolData; + if (serviceProtocol == configKey::awg && !isConnectEvent) { + DockerContainer container = apiV2->defaultContainer; + ContainerConfig containerConfig = apiV2->containerConfig(container); + AwgProtocolConfig* awgConfig = containerConfig.protocolConfig.as(); + if (awgConfig && awgConfig->hasClientConfig()) { + AwgClientConfig clientConfig = awgConfig->clientConfig.value(); + QString clientPrivKey = clientConfig.clientPrivateKey; + QString clientPubKey = clientConfig.clientPublicKey; + protocolData.wireGuardClientPubKey = clientPubKey; + protocolData.wireGuardClientPrivKey = clientPrivKey; + } else { + protocolData = generateProtocolData(serviceProtocol); + } + } else { + protocolData = generateProtocolData(serviceProtocol); + } QJsonObject authDataJson = apiV2->authData.toJson(); GatewayRequestData gatewayRequestData { QSysInfo::productType(), diff --git a/client/core/controllers/connectionController.cpp b/client/core/controllers/connectionController.cpp index 122727e76..ca08caf01 100644 --- a/client/core/controllers/connectionController.cpp +++ b/client/core/controllers/connectionController.cpp @@ -31,6 +31,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos { connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged); connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); + connect(this, &ConnectionController::switchConnectionRequested, m_vpnConnection, &VpnConnection::switchToVpn, 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::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection); @@ -86,8 +87,11 @@ ErrorCode ConnectionController::openConnection(int serverIndex) if (errorCode != ErrorCode::NoError) { return errorCode; } - - emit openConnectionRequested(serverIndex, container, vpnConfiguration); + if (isConnected()) { + emit switchConnectionRequested(serverIndex, container, vpnConfiguration); + } else { + emit openConnectionRequested(serverIndex, container, vpnConfiguration); + } return ErrorCode::NoError; } diff --git a/client/core/controllers/connectionController.h b/client/core/controllers/connectionController.h index c4c9904bb..780476b89 100644 --- a/client/core/controllers/connectionController.h +++ b/client/core/controllers/connectionController.h @@ -61,6 +61,7 @@ public: signals: void connectionStateChanged(Vpn::ConnectionState state); void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); + void switchConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); void closeConnectionRequested(); void setConnectionStateRequested(Vpn::ConnectionState state); void killSwitchModeChangedRequested(bool enabled); diff --git a/client/core/controllers/updateController.cpp b/client/core/controllers/updateController.cpp index 24009705e..4e7302315 100644 --- a/client/core/controllers/updateController.cpp +++ b/client/core/controllers/updateController.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "amneziaApplication.h" #include "logger.h" diff --git a/client/core/protocols/openVpnProtocol.cpp b/client/core/protocols/openVpnProtocol.cpp index 660846e51..a533268cf 100644 --- a/client/core/protocols/openVpnProtocol.cpp +++ b/client/core/protocols/openVpnProtocol.cpp @@ -56,15 +56,6 @@ void OpenVpnProtocol::stop() m_managementServer.stop(); } -#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - IpcClient::withInterface([](QSharedPointer iface) { - QRemoteObjectPendingReply reply = iface->disableKillSwitch(); - if (!reply.waitForFinished(1000) && !reply.returnValue()) { - qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch"; - } - }); -#endif - setConnectionState(Vpn::ConnectionState::Disconnected); } @@ -180,20 +171,6 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } -#ifdef AMNEZIA_DESKTOP - const ErrorCode res = IpcClient::withInterface([&](QSharedPointer iface) { - QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()); - QRemoteObjectPendingReply reply = iface->addKillSwitchAllowedRange(QStringList(ip)); - if (!reply.waitForFinished(1000) || !reply.returnValue()) { - return ErrorCode::AmneziaServiceConnectionFailed; - } - return ErrorCode::NoError; - }); - if (res != ErrorCode::NoError) { - return res; - } -#endif - // Detect default gateway #ifdef Q_OS_MAC QProcess p; @@ -348,38 +325,9 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) m_vpnGateway = l.split(" ").at(2); #ifdef Q_OS_WIN QThread::msleep(300); - IpcClient::withInterface([&](QSharedPointer iface) { - QList netInterfaces = QNetworkInterface::allInterfaces(); - for (int i = 0; i < netInterfaces.size(); i++) { - for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) - { - // killSwitch toggle - if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { - if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) { - iface->enableKillSwitch(m_configData, netInterfaces.at(i).index()); - } - m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); - m_configData.insert("vpnGateway", m_vpnGateway); - m_configData.insert("vpnServer", - NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString())); - iface->enablePeerTraffic(m_configData); - } - } - } - }); #endif -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - // killSwitch toggle - if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) { - m_configData.insert("vpnServer", - NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString())); - IpcClient::withInterface([&](QSharedPointer iface) { - QRemoteObjectPendingReply reply = iface->enableKillSwitch(m_configData, 0); - if (!reply.waitForFinished(1000) || !reply.returnValue()) { - qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch"; - } - }); - } +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress); #endif qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); } diff --git a/client/core/protocols/vpnProtocol.cpp b/client/core/protocols/vpnProtocol.cpp index 82d8b6bef..3756beff8 100644 --- a/client/core/protocols/vpnProtocol.cpp +++ b/client/core/protocols/vpnProtocol.cpp @@ -160,3 +160,10 @@ bool VpnProtocol::isDisconnected() const { return m_connectionState == Vpn::ConnectionState::Disconnected; } + +ErrorCode VpnProtocol::switchServer(const QJsonObject &newConfig) +{ + stop(); + m_rawConfig = newConfig; + return start(); +} diff --git a/client/core/protocols/vpnProtocol.h b/client/core/protocols/vpnProtocol.h index 68a79ec78..6d98ecb4b 100644 --- a/client/core/protocols/vpnProtocol.h +++ b/client/core/protocols/vpnProtocol.h @@ -60,6 +60,7 @@ public: virtual bool isDisconnected() const; virtual ErrorCode start() = 0; virtual void stop() = 0; + virtual ErrorCode switchServer(const QJsonObject &newConfig); Vpn::ConnectionState connectionState() const; ErrorCode lastError() const; diff --git a/client/core/protocols/wireGuardProtocol.cpp b/client/core/protocols/wireGuardProtocol.cpp index c401147c9..783822810 100644 --- a/client/core/protocols/wireGuardProtocol.cpp +++ b/client/core/protocols/wireGuardProtocol.cpp @@ -33,8 +33,10 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) || (!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) { - emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress); - } + if (m_connectionState == Vpn::ConnectionState::Connected) { + emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress); + } + } }); connect(m_impl.get(), &ControllerImpl::disconnected, this, @@ -77,3 +79,9 @@ ErrorCode WireguardProtocol::start() { return startMzImpl(); } + +ErrorCode WireguardProtocol::switchServer(const QJsonObject &newConfig) +{ + m_rawConfig = newConfig; + return startMzImpl(); +} \ No newline at end of file diff --git a/client/core/protocols/wireGuardProtocol.h b/client/core/protocols/wireGuardProtocol.h index a6c502c56..a8f6f3eb1 100644 --- a/client/core/protocols/wireGuardProtocol.h +++ b/client/core/protocols/wireGuardProtocol.h @@ -24,6 +24,7 @@ public: ErrorCode startMzImpl(); ErrorCode stopMzImpl(); + ErrorCode switchServer(const QJsonObject &newConfig); private: diff --git a/client/core/protocols/xrayProtocol.cpp b/client/core/protocols/xrayProtocol.cpp index 893b8367e..113ac5281 100755 --- a/client/core/protocols/xrayProtocol.cpp +++ b/client/core/protocols/xrayProtocol.cpp @@ -102,14 +102,6 @@ void XrayProtocol::stop() qDebug() << "XrayProtocol::stop()"; IpcClient::withInterface([](QSharedPointer iface) { - auto disableKillSwitch = iface->disableKillSwitch(); - if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue()) - qWarning() << "Failed to disable killswitch"; - - auto StartRoutingIpv6 = iface->StartRoutingIpv6(); - if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) - qWarning() << "Failed to start routing ipv6"; - auto restoreResolvers = iface->restoreResolvers(); if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue()) qWarning() << "Failed to restore resolvers"; @@ -221,66 +213,7 @@ ErrorCode XrayProtocol::setupRouting() return ErrorCode::InternalError; } -#ifdef Q_OS_WIN - int vpnAdapterIndex = -1; - QList netInterfaces = QNetworkInterface::allInterfaces(); - for (auto &netInterface : netInterfaces) { - for (auto &address : netInterface.addressEntries()) { - if (m_vpnLocalAddress == address.ip().toString()) - vpnAdapterIndex = netInterface.index(); - } - } -#else - static const int vpnAdapterIndex = 0; -#endif - const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool(); - if (killSwitchEnabled) { - if (vpnAdapterIndex != -1) { - QJsonObject config = m_rawConfig; - config.insert("vpnServer", m_remoteAddress); - - auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex); - if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) { - qCritical() << "Failed to enable killswitch"; - return ErrorCode::InternalError; - } - } else - qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled"; - } - - if (m_routeMode == amnezia::RouteMode::VpnAllSites) { - static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", - "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" }; - - auto routeAddList = iface->routeAddList(m_vpnGateway, subnets); - if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) { - qCritical() << "Failed to set routes for TUN"; - return ErrorCode::InternalError; - } - } - - auto StopRoutingIpv6 = iface->StopRoutingIpv6(); - if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) { - qCritical() << "Failed to disable IPv6 routing"; - return ErrorCode::InternalError; - } - -#ifdef Q_OS_WIN - if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) { - QJsonObject config = m_rawConfig; - config.insert("inetAdapterIndex", inetAdapterIndex); - config.insert("vpnAdapterIndex", vpnAdapterIndex); - config.insert("vpnGateway", m_vpnGateway); - config.insert("vpnServer", m_remoteAddress); - - auto enablePeerTraffic = iface->enablePeerTraffic(config); - if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) { - qCritical() << "Failed to enable peer traffic"; - return ErrorCode::InternalError; - } - } else - qWarning() << "Failed to get adapter indexes. Split-tunneling disabled"; -#endif + emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress); return ErrorCode::NoError; }, []() { return ErrorCode::AmneziaServiceConnectionFailed; }); diff --git a/client/core/utils/networkUtilities.cpp b/client/core/utils/networkUtilities.cpp index 96b9131be..10cd2d689 100644 --- a/client/core/utils/networkUtilities.cpp +++ b/client/core/utils/networkUtilities.cpp @@ -286,7 +286,7 @@ QPair NetworkUtilities::getGatewayAndIface() return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) }; #endif #ifdef Q_OS_LINUX - constexpr int BUFFER_SIZE = 100; + constexpr int BUFFER_SIZE = 8192; int received_bytes = 0, msg_len = 0, route_attribute_len = 0; int sock = -1, msgseq = 0; struct nlmsghdr *nlh, *nlmsg; @@ -294,7 +294,7 @@ QPair NetworkUtilities::getGatewayAndIface() // This struct contain route attributes (route type) struct rtattr *route_attribute; char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE]; - char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE]; + char msgbuf[100], buffer[BUFFER_SIZE]; char *ptr = buffer; struct timeval tv; @@ -339,8 +339,8 @@ QPair NetworkUtilities::getGatewayAndIface() nlh = (struct nlmsghdr *) ptr; /* Check if the header is valid */ - if((NLMSG_OK(nlmsg, received_bytes) == 0) || - (nlmsg->nlmsg_type == NLMSG_ERROR)) + if((NLMSG_OK(nlh, received_bytes) == 0) || + (nlh->nlmsg_type == NLMSG_ERROR)) { perror("Error in received packet"); return {}; @@ -355,13 +355,15 @@ QPair NetworkUtilities::getGatewayAndIface() } /* Break if its not a multi part message */ - if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) + if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0) break; } - while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); + while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid())); /* parse response */ - for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) + int remaining = msg_len + received_bytes; + nlh = (struct nlmsghdr *) buffer; + for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining)) { /* Get the route data */ route_entry = (struct rtmsg *) NLMSG_DATA(nlh); @@ -370,6 +372,10 @@ QPair NetworkUtilities::getGatewayAndIface() if (route_entry->rtm_table != RT_TABLE_MAIN) continue; + /* Reset per-route to avoid cross-route state pollution */ + memset(gateway_address, 0, sizeof(gateway_address)); + memset(interface, 0, sizeof(interface)); + route_attribute = (struct rtattr *) RTM_RTA(route_entry); route_attribute_len = RTM_PAYLOAD(nlh); @@ -395,6 +401,8 @@ QPair NetworkUtilities::getGatewayAndIface() break; } } + if (!(*gateway_address) || !(*interface)) + qDebug() << "getGatewayAndIface: no gateway found"; close(sock); return { gateway_address, QNetworkInterface::interfaceFromName(interface) }; #endif diff --git a/client/core/vpnTrafficGuard.cpp b/client/core/vpnTrafficGuard.cpp new file mode 100644 index 000000000..f8681a778 --- /dev/null +++ b/client/core/vpnTrafficGuard.cpp @@ -0,0 +1,252 @@ +#include "vpnTrafficGuard.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef AMNEZIA_DESKTOP + #include "core/utils/ipcClient.h" +#endif + +#include "core/utils/networkUtilities.h" + +VpnTrafficGuard::VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject *parent) + : QObject(parent), m_appSettingsRepository(appSettings) +{ + +} + +VpnTrafficGuard::~VpnTrafficGuard() +{ +} + +void VpnTrafficGuard::setConfig(const QJsonObject &config) +{ + m_config = config; +} + +bool VpnTrafficGuard::allowEndpoint(const QString &remoteAddress) +{ +#ifdef AMNEZIA_DESKTOP + if (remoteAddress.isEmpty()) { + return false; + } + return IpcClient::withInterface([&](QSharedPointer iface) { + QRemoteObjectPendingReply reply = iface->addKillSwitchAllowedRange(QStringList(remoteAddress)); + return reply.waitForFinished(1000) && reply.returnValue(); + }); +#else + Q_UNUSED(remoteAddress) + return true; +#endif +} + +void VpnTrafficGuard::setupRoutes(const QJsonObject &vpnConfiguration, const QSharedPointer &protocol, const QString &remoteAddress) +{ +#ifdef AMNEZIA_DESKTOP + if (!m_appSettingsRepository) { + qCritical() << "VpnTrafficGuard::setupRoutes: repositories not initialized"; + return; + } + IpcClient::withInterface([&](QSharedPointer iface) { + iface->resetIpStack(); + auto flushDns = iface->flushDns(); + if (flushDns.waitForFinished() && flushDns.returnValue()) + qDebug() << "VpnTrafficGuard::setupRoutes: Successfully flushed DNS"; + else + qWarning() << "VpnTrafficGuard::setupRoutes: Failed to flush DNS"; + + const QString proto = vpnConfiguration.value(configKey::vpnProto).toString(); + const bool isWgBased = (proto == ProtocolUtils::protoToString(Proto::Awg) || + proto == ProtocolUtils::protoToString(Proto::WireGuard)); + if (!isWgBased) { + QString dns1 = vpnConfiguration.value(configKey::dns1).toString(); + QString dns2 = vpnConfiguration.value(configKey::dns2).toString(); +#ifdef Q_OS_MACOS + if (!m_appSettingsRepository->isSitesSplitTunnelingEnabled() || m_appSettingsRepository->routeMode() != amnezia::RouteMode::VpnAllExceptSites) { + iface->routeAddList(protocol->vpnGateway(), QStringList() << dns1 << dns2); + } +#else + iface->routeAddList(protocol->vpnGateway(), QStringList() << dns1 << dns2); +#endif + // TODO: add error code handling for all routeAddList (or rework the code below) + if (!protocol) { + qWarning() << "VpnTrafficGuard::setupRoutes: protocol is null"; + return; + } + iface->routeAddList(protocol->vpnGateway(), QStringList() << dns1 << dns2); + + if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) { + iface->routeDeleteList(protocol->vpnGateway(), QStringList() << "0.0.0.0"); + if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnOnlyForwardSites) { + QPointer protocolPtr(protocol.data()); + QTimer::singleShot(1000, protocol.data(), + [this, protocolPtr]() { + if (!protocolPtr) { + return; + } + addSplitTunnelRoutes(protocolPtr->vpnGateway(), m_appSettingsRepository->routeMode()); + }); + } else if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnAllExceptSites) { + iface->routeAddList(protocol->vpnGateway(), QStringList() << "0.0.0.0/1"); + iface->routeAddList(protocol->vpnGateway(), QStringList() << "128.0.0.0/1"); + + iface->routeAddList(protocol->routeGateway(), QStringList() << remoteAddress); +#ifdef Q_OS_MACOS + iface->routeAddList(protocol->routeGateway(), QStringList() << dns1 << dns2); +#endif + addSplitTunnelRoutes(protocol->routeGateway(), m_appSettingsRepository->routeMode()); + } + } + } + }); +#endif +} + +void VpnTrafficGuard::addSplitTunnelRoutes(const QString &gw, amnezia::RouteMode mode) +{ +#ifdef AMNEZIA_DESKTOP + if (!m_appSettingsRepository) { + qCritical() << "VpnTrafficGuard::addSplitTunnelRoutes: repositories not initialized"; + return; + } + + QStringList ips; + QStringList sites; + const QVariantMap &m = m_appSettingsRepository->vpnSites(mode); + for (auto i = m.constBegin(); i != m.constEnd(); ++i) { + if (NetworkUtilities::checkIpSubnetFormat(i.key())) { + ips.append(i.key()); + } else { + if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) { + ips.append(i.value().toString()); + } + sites.append(i.key()); + } + } + ips.removeDuplicates(); + + IpcClient::withInterface([&](QSharedPointer iface) { + iface->routeAddList(gw, ips); + }); + + // re-resolve domains + for (const QString &site : sites) { + const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) { + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + const QString &ip = addr.toString(); + if (!ips.contains(ip)) { + IpcClient::withInterface([gw, ip](QSharedPointer iface) { + iface->routeAddList(gw, QStringList() << ip); + }); + m_appSettingsRepository->addVpnSite(mode, site, ip); + } + IpcClient::withInterface([](QSharedPointer iface) { + auto reply = iface->flushDns(); + if (!reply.waitForFinished() || !reply.returnValue()) + qWarning() << "VpnTrafficGuard::addSplitTunnelRoutes: Failed to flush DNS"; + }); + break; + } + } + }; + QHostInfo::lookupHost(site, this, cbResolv); + } +#endif +} + +void VpnTrafficGuard::applyFirewall(const QString &gateway, const QString &localAddress) +{ +#ifdef AMNEZIA_DESKTOP + QJsonObject updatedConfig = m_config; + IpcClient::withInterface([&](QSharedPointer iface) { +#ifdef Q_OS_WIN + QList netInterfaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < netInterfaces.size(); i++) { + for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) + { + if (localAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { + updatedConfig.insert("vpnAdapterIndex", netInterfaces.at(i).index()); + updatedConfig.insert("vpnGateway", gateway); + updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(config_key::hostName).toString())); + if (QVariant(updatedConfig.value(config_key::killSwitchOption).toString()).toBool()) { + iface->enableKillSwitch(updatedConfig, netInterfaces.at(i).index()); + } + iface->enablePeerTraffic(updatedConfig); + } + } + } +#endif +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) { + updatedConfig.insert("vpnServer", + NetworkUtilities::getIPAddress(updatedConfig.value(amnezia::configKey::hostName).toString())); + QRemoteObjectPendingReply reply = iface->enableKillSwitch(updatedConfig, 0); + //TODO: why it takes so long? + if (!reply.waitForFinished(5000) || !reply.returnValue()) { + qWarning() << "VpnTrafficGuard::applyFirewall: Failed to enable killswitch"; + } else { + qDebug() << "VpnTrafficGuard::applyFirewall: Successfully enabled killswitch"; + } + } +#endif + const QString proto = updatedConfig.value(configKey::vpnProto).toString(); + const bool isXrayBased = (proto == ProtocolUtils::protoToString(Proto::Xray) || + proto == ProtocolUtils::protoToString(Proto::SSXray)); + if (isXrayBased) { + if (updatedConfig.value(configKey::splitTunnelType).toInt() == amnezia::route_mode_ns::VpnAllSites) { + static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" }; + auto routeAddList = iface->routeAddList(gateway, subnets); + if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) { + qCritical() << "Failed to set routes for TUN"; + } + } + auto StopRoutingIpv6 = iface->StopRoutingIpv6(); + if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) { + qCritical() << "Failed to disable IPv6 routing"; + } else { + m_ipv6RoutingStopped = true; + } + } + }); +#endif +} + +void VpnTrafficGuard::teardown() +{ +#ifdef AMNEZIA_DESKTOP + IpcClient::withInterface([&](QSharedPointer iface) { + QRemoteObjectPendingReply reply = iface->disableKillSwitch(); + //TODO: why it takes so long? + if (!reply.waitForFinished(5000) || !reply.returnValue()) { + qWarning() << "VpnTrafficGuard::teardown: Failed to disable killswitch"; + } else { + qDebug() << "VpnTrafficGuard::teardown: Successfully disabled killswitch"; + } + auto flushDns = iface->flushDns(); + if (flushDns.waitForFinished() && flushDns.returnValue()) + qDebug() << "VpnTrafficGuard::teardown: Successfully flushed DNS"; + else + qWarning() << "VpnTrafficGuard::teardown: Failed to flush DNS"; + + auto clearSavedRoutes = iface->clearSavedRoutes(); + if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue()) + qDebug() << "VpnTrafficGuard::teardown: Successfully cleared saved routes"; + else + qWarning() << "VpnTrafficGuard::teardown: Failed to clear saved routes"; + if (m_ipv6RoutingStopped) { + auto StartRoutingIpv6 = iface->StartRoutingIpv6(); + if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) { + qCritical() << "Failed to enable IPv6 routing"; + } else { + m_ipv6RoutingStopped = false; + } + } + }); +#endif +} diff --git a/client/core/vpnTrafficGuard.h b/client/core/vpnTrafficGuard.h new file mode 100644 index 000000000..e8e5468c8 --- /dev/null +++ b/client/core/vpnTrafficGuard.h @@ -0,0 +1,31 @@ +#ifndef VPNTRAFFICGUARD_H +#define VPNTRAFFICGUARD_H + +#include +#include "core/repositories/secureAppSettingsRepository.h" +#include "protocols/vpnprotocol.h" + +class VpnTrafficGuard : public QObject +{ + Q_OBJECT + +public: + explicit VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject* parent = nullptr); + ~VpnTrafficGuard() override; + void setConfig(const QJsonObject &config); + void setupRoutes(const QJsonObject &vpnConfiguration, + const QSharedPointer &protocol, + const QString &remoteAddress); + + void teardown(); + bool allowEndpoint(const QString &remoteAddress); + void applyFirewall(const QString &vpnGateway, const QString &vpnLocalAddress); +private: + void addSplitTunnelRoutes(const QString &gateway, amnezia::RouteMode mode); + SecureAppSettingsRepository* m_appSettingsRepository; + QJsonObject m_config; + bool m_ipv6RoutingStopped = false; + +}; + +#endif // VPNTRAFFICGUARD_H diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index e74a613f5..51934bb20 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -501,9 +501,7 @@ bool Daemon::supportServerSwitching(const InterfaceConfig& config) const { 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; + current.m_deviceIpv6Address == config.m_deviceIpv6Address; } bool Daemon::switchServer(const InterfaceConfig& config) { @@ -613,7 +611,7 @@ void Daemon::checkHandshake() { pendingHandshakes++; } } - + // Check again if there were connections that haven't completed a handshake. if (pendingHandshakes > 0) { m_handshakeTimer.start(HANDSHAKE_POLL_MSEC); diff --git a/client/main.cpp b/client/main.cpp index 53a1fd574..edb3ec65a 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,12 +1,12 @@ #include #include +#include #include "amneziaApplication.h" #include "core/utils/osSignalHandler.h" #include "core/utils/migrations.h" #include "version.h" -#include #ifdef Q_OS_WIN #include "Windows.h" diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 1b7cddc8e..ab5334910 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -25,6 +25,34 @@ constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; namespace { Logger logger("WireguardUtilsLinux"); Logger logwireguard("WireguardGo"); + +bool appendAwgInterfaceParams(const InterfaceConfig& config, QTextStream& out) { + bool hasParams = false; + const auto appendField = [&](const char* key, const QString& value) { + if (!value.isEmpty()) { + out << key << "=" << value << "\n"; + hasParams = true; + } + }; + + appendField("jc", config.m_junkPacketCount); + appendField("jmin", config.m_junkPacketMinSize); + appendField("jmax", config.m_junkPacketMaxSize); + appendField("s1", config.m_initPacketJunkSize); + appendField("s2", config.m_responsePacketJunkSize); + appendField("s3", config.m_cookieReplyPacketJunkSize); + appendField("s4", config.m_transportPacketJunkSize); + appendField("h1", config.m_initPacketMagicHeader); + appendField("h2", config.m_responsePacketMagicHeader); + appendField("h3", config.m_underloadPacketMagicHeader); + appendField("h4", config.m_transportPacketMagicHeader); + + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + hasParams = true; + } + return hasParams; +} }; // namespace WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent) @@ -59,7 +87,6 @@ void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) { } bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { - Q_UNUSED(config); if (m_tunnel.state() != QProcess::NotRunning) { logger.warning() << "Unable to start: tunnel process already running"; return false; @@ -104,45 +131,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; - - - if (!config.m_junkPacketCount.isEmpty()) { - out << "jc=" << config.m_junkPacketCount << "\n"; - } - if (!config.m_junkPacketMinSize.isEmpty()) { - out << "jmin=" << config.m_junkPacketMinSize << "\n"; - } - if (!config.m_junkPacketMaxSize.isEmpty()) { - out << "jmax=" << config.m_junkPacketMaxSize << "\n"; - } - if (!config.m_initPacketJunkSize.isEmpty()) { - out << "s1=" << config.m_initPacketJunkSize << "\n"; - } - if (!config.m_responsePacketJunkSize.isEmpty()) { - out << "s2=" << config.m_responsePacketJunkSize << "\n"; - } - if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { - out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; - } - if (!config.m_transportPacketJunkSize.isEmpty()) { - out << "s4=" << config.m_transportPacketJunkSize << "\n"; - } - if (!config.m_initPacketMagicHeader.isEmpty()) { - out << "h1=" << config.m_initPacketMagicHeader << "\n"; - } - if (!config.m_responsePacketMagicHeader.isEmpty()) { - out << "h2=" << config.m_responsePacketMagicHeader << "\n"; - } - if (!config.m_underloadPacketMagicHeader.isEmpty()) { - out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; - } - if (!config.m_transportPacketMagicHeader.isEmpty()) { - out << "h4=" << config.m_transportPacketMagicHeader << "\n"; - } - - for (const QString& key : config.m_specialJunk.keys()) { - out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; - } + appendAwgInterfaceParams(config, out); int err = uapiErrno(uapiCommand(message)); if (err != 0) { @@ -201,7 +190,6 @@ bool WireguardUtilsLinux::deleteInterface() { return true; } -// dummy implementations for now bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); @@ -214,6 +202,7 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { QString message; QTextStream out(&message); out << "set=1\n"; + appendAwgInterfaceParams(config, out); out << "public_key=" << QString(publicKey.toHex()) << "\n"; if (!config.m_serverPskKey.isNull()) { out << "preshared_key=" << QString(pskKey.toHex()) << "\n"; diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 55a5526be..40f9f2772 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -24,6 +24,34 @@ constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; namespace { Logger logger("WireguardUtilsMacos"); Logger logwireguard("WireguardGo"); + +bool appendAwgInterfaceParams(const InterfaceConfig& config, QTextStream& out) { + bool hasParams = false; + const auto appendField = [&](const char* key, const QString& value) { + if (!value.isEmpty()) { + out << key << "=" << value << "\n"; + hasParams = true; + } + }; + + appendField("jc", config.m_junkPacketCount); + appendField("jmin", config.m_junkPacketMinSize); + appendField("jmax", config.m_junkPacketMaxSize); + appendField("s1", config.m_initPacketJunkSize); + appendField("s2", config.m_responsePacketJunkSize); + appendField("s3", config.m_cookieReplyPacketJunkSize); + appendField("s4", config.m_transportPacketJunkSize); + appendField("h1", config.m_initPacketMagicHeader); + appendField("h2", config.m_responsePacketMagicHeader); + appendField("h3", config.m_underloadPacketMagicHeader); + appendField("h4", config.m_transportPacketMagicHeader); + + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + hasParams = true; + } + return hasParams; +} }; // namespace WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent) @@ -58,7 +86,6 @@ void WireguardUtilsMacos::tunnelErrorOccurred(QProcess::ProcessError error) { } bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { - Q_UNUSED(config); if (m_tunnel.state() != QProcess::NotRunning) { logger.warning() << "Unable to start: tunnel process already running"; return false; @@ -103,44 +130,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; - - if (!config.m_junkPacketCount.isEmpty()) { - out << "jc=" << config.m_junkPacketCount << "\n"; - } - if (!config.m_junkPacketMinSize.isEmpty()) { - out << "jmin=" << config.m_junkPacketMinSize << "\n"; - } - if (!config.m_junkPacketMaxSize.isEmpty()) { - out << "jmax=" << config.m_junkPacketMaxSize << "\n"; - } - if (!config.m_initPacketJunkSize.isEmpty()) { - out << "s1=" << config.m_initPacketJunkSize << "\n"; - } - if (!config.m_responsePacketJunkSize.isEmpty()) { - out << "s2=" << config.m_responsePacketJunkSize << "\n"; - } - if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { - out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; - } - if (!config.m_transportPacketJunkSize.isEmpty()) { - out << "s4=" << config.m_transportPacketJunkSize << "\n"; - } - if (!config.m_initPacketMagicHeader.isEmpty()) { - out << "h1=" << config.m_initPacketMagicHeader << "\n"; - } - if (!config.m_responsePacketMagicHeader.isEmpty()) { - out << "h2=" << config.m_responsePacketMagicHeader << "\n"; - } - if (!config.m_underloadPacketMagicHeader.isEmpty()) { - out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; - } - if (!config.m_transportPacketMagicHeader.isEmpty()) { - out << "h4=" << config.m_transportPacketMagicHeader << "\n"; - } - - for (const QString& key : config.m_specialJunk.keys()) { - out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; - } + appendAwgInterfaceParams(config, out); int err = uapiErrno(uapiCommand(message)); if (err != 0) { @@ -200,7 +190,6 @@ bool WireguardUtilsMacos::deleteInterface() { return true; } -// dummy implementations for now bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); @@ -214,6 +203,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { QString message; QTextStream out(&message); out << "set=1\n"; + appendAwgInterfaceParams(config, out); out << "public_key=" << QString(publicKey.toHex()) << "\n"; if (!config.m_serverPskKey.isNull()) { out << "preshared_key=" << QString(pskKey.toHex()) << "\n"; diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index a5c9c84d1..49fd43fe5 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -20,6 +20,34 @@ namespace { Logger logger("WireguardUtilsWindows"); + +bool appendAwgInterfaceParams(const InterfaceConfig& config, QTextStream& out) { + bool hasParams = false; + const auto appendField = [&](const char* key, const QString& value) { + if (!value.isEmpty()) { + out << key << "=" << value << "\n"; + hasParams = true; + } + }; + + appendField("jc", config.m_junkPacketCount); + appendField("jmin", config.m_junkPacketMinSize); + appendField("jmax", config.m_junkPacketMaxSize); + appendField("s1", config.m_initPacketJunkSize); + appendField("s2", config.m_responsePacketJunkSize); + appendField("s3", config.m_cookieReplyPacketJunkSize); + appendField("s4", config.m_transportPacketJunkSize); + appendField("h1", config.m_initPacketMagicHeader); + appendField("h2", config.m_responsePacketMagicHeader); + appendField("h3", config.m_underloadPacketMagicHeader); + appendField("h4", config.m_transportPacketMagicHeader); + + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + hasParams = true; + } + return hasParams; +} }; // namespace std::unique_ptr WireguardUtilsWindows::create( @@ -165,6 +193,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { QString message; QTextStream out(&message); out << "set=1\n"; + appendAwgInterfaceParams(config, out); out << "public_key=" << QString(publicKey.toHex()) << "\n"; if (!config.m_serverPskKey.isNull()) { out << "preshared_key=" << QString(pskKey.toHex()) << "\n"; diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index e54508096..c5796d5d4 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -184,24 +184,23 @@ 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")) - return - } if (index !== ApiCountryModel.currentIndex) { PageController.showBusyIndicator(true) var prevIndex = ApiCountryModel.currentIndex + var wasConnected = ConnectionController.isConnected ApiCountryModel.currentIndex = index if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.getProcessedServerIndex(), countryCode, countryName)) { ApiCountryModel.currentIndex = prevIndex + } else if (wasConnected) { + ConnectionController.openConnection() } PageController.showBusyIndicator(false) } diff --git a/client/vpnConnection.cpp b/client/vpnConnection.cpp index ad8ce917d..2d4e7aaec 100644 --- a/client/vpnConnection.cpp +++ b/client/vpnConnection.cpp @@ -30,12 +30,11 @@ #endif #include "core/utils/networkUtilities.h" -#include "vpnConnection.h" using namespace ProtocolUtils; VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject *parent) - : QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this) + : QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this), m_trafficGuard(new VpnTrafficGuard(appSettingsRepository, this)) { #if defined(Q_OS_IOS) || defined(MACOS_NE) m_checkTimer.setInterval(1000); @@ -69,74 +68,17 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled) void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP - if (!m_serversRepository || !m_appSettingsRepository) { - qCritical() << "VpnConnection::onConnectionStateChanged: repositories not initialized"; - return; + switch (state) { + case Vpn::ConnectionState::Connected: { + m_trafficGuard->setupRoutes(m_vpnConfiguration, m_vpnProtocol, remoteAddress()); + } break; + case Vpn::ConnectionState::Disconnected: + case Vpn::ConnectionState::Error: { + m_trafficGuard->teardown(); + } break; + default: + break; } - - ServerConfig defaultServer = m_serversRepository->server(m_serversRepository->defaultServerIndex()); - DockerContainer container = defaultServer.defaultContainer(); - - IpcClient::withInterface([&](QSharedPointer iface) { - switch (state) { - case Vpn::ConnectionState::Connected: { - iface->resetIpStack(); - - auto flushDns = iface->flushDns(); - if (flushDns.waitForFinished() && flushDns.returnValue()) - qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS"; - else - qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS"; - - if (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard) { - QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString(); - QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString(); - -#ifdef Q_OS_MACOS - if (!m_appSettingsRepository->isSitesSplitTunnelingEnabled() || m_appSettingsRepository->routeMode() != amnezia::RouteMode::VpnAllExceptSites) { - iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); - } -#else - iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); -#endif - - if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) { - iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - RouteMode routeMode = m_appSettingsRepository->routeMode(); - if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) { - QTimer::singleShot(1000, m_vpnProtocol.data(), - [this, routeMode]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), routeMode); }); - } else if (routeMode == amnezia::RouteMode::VpnAllExceptSites) { - iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); - iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); - - iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); -#ifdef Q_OS_MACOS - iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << dns1 << dns2); -#endif - addSitesRoutes(m_vpnProtocol->routeGateway(), routeMode); - } - } - } - } break; - case Vpn::ConnectionState::Disconnected: - case Vpn::ConnectionState::Error: { - auto flushDns = iface->flushDns(); - if (flushDns.waitForFinished() && flushDns.returnValue()) - qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS"; - else - qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS"; - - auto clearSavedRoutes = iface->clearSavedRoutes(); - if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue()) - qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes"; - else - qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes"; - } break; - default: - break; - } - }); #endif #if defined(Q_OS_IOS) || defined(MACOS_NE) @@ -161,62 +103,6 @@ void VpnConnection::setRepositories(SecureServersRepository* serversRepository, m_appSettingsRepository = appSettingsRepository; } -void VpnConnection::addSitesRoutes(const QString &gw, amnezia::RouteMode mode) -{ -#ifdef AMNEZIA_DESKTOP - if (!m_appSettingsRepository) { - qCritical() << "VpnConnection::addSitesRoutes: repositories not initialized"; - return; - } - - QStringList ips; - QStringList sites; - const QVariantMap &m = m_appSettingsRepository->vpnSites(mode); - for (auto i = m.constBegin(); i != m.constEnd(); ++i) { - if (NetworkUtilities::checkIpSubnetFormat(i.key())) { - ips.append(i.key()); - } else { - if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) { - ips.append(i.value().toString()); - } - sites.append(i.key()); - } - } - ips.removeDuplicates(); - - IpcClient::withInterface([&](QSharedPointer iface) { - iface->routeAddList(gw, ips); - }); - - // re-resolve domains - for (const QString &site : sites) { - const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) { - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr : hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - const QString &ip = addr.toString(); - // qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; - if (!ips.contains(ip)) { - IpcClient::withInterface([&gw, &ip](QSharedPointer iface) { - iface->routeAddList(gw, QStringList() << ip); - }); - m_appSettingsRepository->addVpnSite(mode, site, ip); - } - IpcClient::withInterface([](QSharedPointer iface) { - auto reply = iface->flushDns(); - if (reply.waitForFinished() || !reply.returnValue()) - qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS"; - }); - break; - } - } - }; - QHostInfo::lookupHost(site, this, cbResolv); - } -#endif -} - QSharedPointer VpnConnection::vpnProtocol() const { return m_vpnProtocol; @@ -261,6 +147,13 @@ void VpnConnection::connectToVpn(int serverIndex, DockerContainer container, con << m_appSettingsRepository->routeMode(); m_remoteAddress = NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString()); +#ifdef AMNEZIA_DESKTOP + if (!m_trafficGuard->allowEndpoint(m_remoteAddress)) { + setConnectionState(Vpn::ConnectionState::Error); + emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed); + return; + } +#endif setConnectionState(Vpn::ConnectionState::Connecting); m_vpnConfiguration = vpnConfiguration; @@ -283,6 +176,7 @@ void VpnConnection::connectToVpn(int serverIndex, DockerContainer container, con return; } m_vpnProtocol->prepare(); + m_trafficGuard->setConfig(m_vpnConfiguration); #elif defined Q_OS_ANDROID androidVpnProtocol = createDefaultAndroidVpnProtocol(); createAndroidConnections(); @@ -303,11 +197,27 @@ void VpnConnection::connectToVpn(int serverIndex, DockerContainer container, con } } +void VpnConnection::switchToVpn(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration) +{ + if (!m_vpnProtocol.isNull() && ContainerUtils::isAwgContainer(container) && m_connectionState == Vpn::ConnectionState::Connected) { + m_remoteAddress = NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString()); + m_trafficGuard->allowEndpoint(m_remoteAddress); + m_vpnConfiguration = vpnConfiguration; + appendKillSwitchConfig(); + appendSplitTunnelingConfig(); + m_trafficGuard->setConfig(m_vpnConfiguration); + m_vpnProtocol->switchServer(m_vpnConfiguration); + } else { + connectToVpn(serverIndex, container, vpnConfiguration); + } +} + void VpnConnection::createProtocolConnections() { connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); + connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall); #ifdef AMNEZIA_DESKTOP IpcClient::withInterface([this](QSharedPointer rep) { diff --git a/client/vpnConnection.h b/client/vpnConnection.h index 0a6857447..60d096b11 100644 --- a/client/vpnConnection.h +++ b/client/vpnConnection.h @@ -15,9 +15,7 @@ #include "core/repositories/secureServersRepository.h" #include "core/repositories/secureAppSettingsRepository.h" -#ifdef AMNEZIA_DESKTOP -#include "core/utils/ipcClient.h" -#endif +#include "core/vpnTrafficGuard.h" #ifdef Q_OS_ANDROID #include "core/protocols/androidVpnProtocol.h" @@ -41,7 +39,6 @@ public: QSharedPointer vpnProtocol() const; const QString &remoteAddress() const; - void addSitesRoutes(const QString &gw, amnezia::RouteMode mode); #ifdef Q_OS_ANDROID void restoreConnection(); @@ -50,6 +47,7 @@ public: public slots: void setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository); void connectToVpn(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); + void switchToVpn(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration); void reconnectToVpn(); void disconnectFromVpn(); @@ -75,6 +73,7 @@ protected: private: SecureServersRepository* m_serversRepository; SecureAppSettingsRepository* m_appSettingsRepository; + QScopedPointer m_trafficGuard; QJsonObject m_vpnConfiguration; QJsonObject m_routeMode;