diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2885f4..1bc440e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.4.2.2 +project(${PROJECT} VERSION 4.5.0.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 8d6d8333..7dc05311 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -129,6 +129,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_BINARY_DIR}/version.h ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h + ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h ) # Mozilla headres @@ -164,6 +165,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp ) # Mozilla sources @@ -293,6 +295,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h ) @@ -304,6 +307,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index cca8032b..0fd9a394 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -343,6 +343,9 @@ void AmneziaApplication::initModels() m_awgConfigModel.reset(new AwgConfigModel(this)); m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); + m_xrayConfigModel.reset(new XrayConfigModel(this)); + m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get()); + #ifdef Q_OS_WINDOWS m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index bdbc52a6..51e5a929 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -36,6 +36,7 @@ #include "ui/models/protocols/openvpnConfigModel.h" #include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols/xrayConfigModel.h" #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" #include "ui/models/services/sftpConfigModel.h" @@ -102,6 +103,7 @@ private: QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; QScopedPointer m_cloakConfigModel; + QScopedPointer m_xrayConfigModel; QScopedPointer m_wireGuardConfigModel; QScopedPointer m_awgConfigModel; #ifdef Q_OS_WINDOWS diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp index 9c540967..88be71e1 100644 --- a/client/configurators/cloak_configurator.cpp +++ b/client/configurators/cloak_configurator.cpp @@ -48,6 +48,5 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(), serverController.genVarsForScript(credentials, container, containerConfig)); - // qDebug().noquote() << textCfg; return textCfg; } diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index c74a3d4f..f791e54f 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -6,14 +6,14 @@ #include "ssh_configurator.h" #include "wireguard_configurator.h" #include "awg_configurator.h" - +#include "xray_configurator.h" #include #include #include #include "containers/containers_defs.h" #include "settings.h" -#include "utilities.h" +#include "core/networkUtilities.h"" VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent) : ConfiguratorBase(settings, parent) @@ -25,6 +25,7 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *pa ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); awgConfigurator = std::shared_ptr(new AwgConfigurator(settings, this)); + xrayConfigurator = std::shared_ptr(new XrayConfigurator(settings, this)); } QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, @@ -45,6 +46,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Proto::Awg: return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode); + case Proto::Xray: + return xrayConfigurator->genXrayConfig(credentials, container, containerConfig, clientId, errorCode); + case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); default: return ""; @@ -61,13 +65,13 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) dns.first = server.value(config_key::dns1).toString(); dns.second = server.value(config_key::dns2).toString(); - if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) { + if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) { if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) { dns.first = protocols::dns::amneziaDnsIp; } else dns.first = m_settings->primaryDns(); } - if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) { + if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) { dns.second = m_settings->secondaryDns(); } diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 7164bd8e..453ff6ba 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -13,6 +13,7 @@ class WireguardConfigurator; class Ikev2Configurator; class SshConfigurator; class AwgConfigurator; +class XrayConfigurator; // Retrieve connection settings from server class VpnConfigurator : public ConfiguratorBase @@ -42,6 +43,7 @@ public: std::shared_ptr ikev2Configurator; std::shared_ptr sshConfigurator; std::shared_ptr awgConfigurator; + std::shared_ptr xrayConfigurator; signals: void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container, diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp new file mode 100644 index 00000000..f450527f --- /dev/null +++ b/client/configurators/xray_configurator.cpp @@ -0,0 +1,49 @@ +#include "xray_configurator.h" + +#include +#include +#include + +#include "core/scripts_registry.h" +#include "containers/containers_defs.h" +#include "core/controllers/serverController.h" + +XrayConfigurator::XrayConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) +{ + +} + +QString XrayConfigurator::genXrayConfig(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) +{ + ErrorCode e = ErrorCode::NoError; + ServerController serverController(m_settings); + + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); + + QString xrayPublicKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::xray::PublicKeyPath, &e); + xrayPublicKey.replace("\n", ""); + + QString xrayUuid = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::xray::uuidPath, &e); + xrayUuid.replace("\n", ""); + + QString xrayShortId = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::xray::shortidPath, &e); + xrayShortId.replace("\n", ""); + + if (e) { + if (errorCode) *errorCode = e; + return ""; + } + + config.replace("$XRAY_CLIENT_ID", xrayUuid); + config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); + config.replace("$XRAY_SHORT_ID", xrayShortId); + + return config; +} diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h new file mode 100644 index 00000000..cefdc9c2 --- /dev/null +++ b/client/configurators/xray_configurator.h @@ -0,0 +1,19 @@ +#ifndef XRAY_CONFIGURATOR_H +#define XRAY_CONFIGURATOR_H + +#include + +#include "configurator_base.h" +#include "core/defs.h" + +class XrayConfigurator : ConfiguratorBase +{ + Q_OBJECT +public: + XrayConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + + QString genXrayConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); +}; + +#endif // XRAY_CONFIGURATOR_H diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index d4b0d91e..2ed12d19 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -58,6 +58,8 @@ QVector ContainerProps::protocolsForContainer(amnezia::DockerCon case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; + case DockerContainer::Xray: return { Proto::Xray }; + case DockerContainer::Dns: return { Proto::Dns }; case DockerContainer::Sftp: return { Proto::Sftp }; @@ -85,6 +87,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::Awg, "AmneziaWG" }, + { DockerContainer::Xray, "XRay" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, @@ -111,6 +114,9 @@ QMap ContainerProps::containerDescriptions() QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " "but very resistant to blockages. " "Recommended for regions with high levels of censorship.") }, + { DockerContainer::Xray, + QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. " + "Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " "signal loss. It has native support on the latest versions of Android and iOS.") }, @@ -199,6 +205,17 @@ QMap ContainerProps::containerDetailedDescriptions() "* Minimum number of settings\n" "* Not recognised by DPI analysis systems, resistant to blocking\n" "* Works over UDP network protocol.") }, + { DockerContainer::Xray, + QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, " + "is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n" + "It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, " + "thus presenting an authentic TLS certificate and data. \n" + "This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, " + "legitimate sites without the need for specific configurations. \n" + "Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, " + "REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. " + "This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.") + }, { DockerContainer::Ipsec, QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" "One of its distinguishing features is its ability to swiftly switch between networks and devices, " @@ -235,6 +252,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::Awg: return Proto::Awg; + case DockerContainer::Xray: return Proto::Xray; case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::TorWebSite: return Proto::TorWebSite; @@ -278,7 +296,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #elif defined(Q_OS_LINUX) switch (c) { - case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; default: return true; } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 92ca4f18..fa10deb4 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -22,6 +22,7 @@ namespace amnezia Cloak, ShadowSocks, Ipsec, + Xray, // non-vpn TorWebSite, diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 81f638e7..88d8cbdc 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -26,6 +26,7 @@ #include "logger.h" #include "core/scripts_registry.h" #include "core/server_defs.h" +#include "core/networkUtilities.h" #include "settings.h" #include "utilities.h" @@ -444,9 +445,6 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden stdOut += data + "\n"; return ErrorCode::NoError; }; - // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { - // stdOut += data + "\n"; - // }; e = runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::build_container), @@ -466,9 +464,6 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti stdOut += data + "\n"; return ErrorCode::NoError; }; - // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { - // stdOut += data + "\n"; - // }; ErrorCode e = runScript(credentials, replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), @@ -537,6 +532,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); + const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; @@ -594,6 +590,10 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); + // Xray vars + vars.append({ { "$XRAY_SITE_NAME", + xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } }); + // Wireguard vars vars.append( { { "$WIREGUARD_SUBNET_IP", @@ -652,7 +652,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); - QString serverIp = Utils::getIPAddress(credentials.hostName); + QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); } else { diff --git a/client/core/defs.h b/client/core/defs.h index e6f3cece..060499ef 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -59,6 +59,8 @@ namespace amnezia CloakExecutableMissing = 602, AmneziaServiceConnectionFailed = 603, ExecutableMissing = 604, + XrayExecutableMissing = 605, + Tun2SockExecutableMissing = 606, // VPN errors OpenVpnAdaptersInUseError = 700, @@ -70,6 +72,8 @@ namespace amnezia OpenSslFailed = 800, ShadowSocksExecutableCrashed = 801, CloakExecutableCrashed = 802, + XrayExecutableCrashed = 803, + Tun2SockExecutableCrashed = 804, // import and install errors ImportInvalidConfigError = 900, diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp new file mode 100644 index 00000000..3dfab733 --- /dev/null +++ b/client/core/networkUtilities.cpp @@ -0,0 +1,439 @@ +#include "networkUtilities.h" + +#ifdef Q_OS_WIN + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif +#ifdef Q_OS_LINUX + #include + #include + #include + #include + #include + #include + #include +#endif +#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) + #include + #include + #include + #include + #include + #include +#endif + +#include +#include + +QRegularExpression NetworkUtilities::ipAddressRegExp() +{ + return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); +} + +QRegularExpression NetworkUtilities::ipAddressPortRegExp() +{ + return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); +} + +QRegExp NetworkUtilities::ipAddressWithSubnetRegExp() +{ + return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); +} + +QRegExp NetworkUtilities::ipNetwork24RegExp() +{ + return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "0$"); +} + +QRegExp NetworkUtilities::ipPortRegExp() +{ + return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); +} + +QRegExp NetworkUtilities::domainRegExp() +{ + return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-" + "9\\-]{1,30})\\.[a-z]{2,}"); +} + +QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip) +{ + if (!ip.contains("/")) + return "255.255.255.255"; + + bool ok; + int prefix = ip.split("/").at(1).toInt(&ok); + if (!ok) + return "255.255.255.255"; + + unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF; + + return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF); +} + +QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip) +{ + if (ip.count(".") != 3) + return ""; + return ip.split("/").first(); +} + +QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr) +{ + // QMap + // QHostAddress + + // QMap subnets; // <"a.b", > + + // for (const QString &ip : ips) { + // if (ip.count(".") != 3) continue; + + // const QStringList &parts = ip.split("."); + // subnets[parts.at(0) + "." + parts.at(1)].append(ip); + // } + + return QStringList(); +} + +QString NetworkUtilities::getIPAddress(const QString &host) +{ + if (ipAddressRegExp().match(host).hasMatch()) { + return host; + } + + QList addresses = QHostInfo::fromName(host).addresses(); + if (!addresses.isEmpty()) { + return addresses.first().toString(); + } + qDebug() << "Unable to resolve address for " << host; + return ""; +} + +QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b) +{ + int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length()); + if (ap < 0 || bp < 0) + return QString(); + ap += a.length(); + if (bp - ap <= 0) + return QString(); + return s.mid(ap, bp - ap).trimmed(); +} + +bool NetworkUtilities::checkIPv4Format(const QString &ip) +{ + if (ip.isEmpty()) + return false; + int count = ip.count("."); + if (count != 3) + return false; + + QHostAddress addr(ip); + return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); +} + +bool NetworkUtilities::checkIpSubnetFormat(const QString &ip) +{ + if (!ip.contains("/")) + return checkIPv4Format(ip); + + QStringList parts = ip.split("/"); + if (parts.size() != 2) + return false; + + bool ok; + int subnet = parts.at(1).toInt(&ok); + if (subnet >= 0 && subnet <= 32 && ok) + return checkIPv4Format(parts.at(0)); + else + return false; +} + +#ifdef Q_OS_WIN +DWORD GetAdaptersAddressesWrapper(const ULONG Family, + const ULONG Flags, + const PVOID Reserved, + _Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) { + DWORD dwRetVal = 0; + int iter = 0; + constexpr int max_iter = 3; + ULONG AdapterAddressesLen = 15000; + do { + // xassert2(pAdapterAddresses == nullptr); + pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen); + if (pAdapterAddresses == nullptr) { + qDebug() << "can not malloc" << AdapterAddressesLen << "bytes"; + return ERROR_OUTOFMEMORY; + } + + dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen); + + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + free(pAdapterAddresses); + pAdapterAddresses = nullptr; + } else { + break; + } + + iter++; + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter)); + + if (dwRetVal != NO_ERROR) { + qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen << + ", dwRetVal:" << dwRetVal << ", iter: " << iter; + if (pAdapterAddresses) { + free(pAdapterAddresses); + pAdapterAddresses = nullptr; + } + } + + return dwRetVal; +} +#endif + +QString NetworkUtilities::getGatewayAndIface() +{ +#ifdef Q_OS_WIN + constexpr int BUFF_LEN = 100; + char buff[BUFF_LEN] = {'\0'}; + QString result; + + PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr; + DWORD dwRetVal = + GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses); + + if (dwRetVal != NO_ERROR) { + qDebug() << "ipv4 stack detect GetAdaptersAddresses failed."; + return ""; + } + + PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses; + while (pCurAddress) { + PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress; + if (gateway) { + SOCKET_ADDRESS gateway_address = gateway->Address; + if (gateway->Address.lpSockaddr->sa_family == AF_INET) { + sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr; + QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN); + qDebug() << "gateway IPV4:" << gw; + struct sockaddr_in addr; + if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) { + qDebug() << "this is true v4 !"; + result = gw; + } + } + } + pCurAddress = pCurAddress->Next; + } + + free(pAdapterAddresses); + return result; +#endif +#ifdef Q_OS_LINUX + constexpr int BUFFER_SIZE = 100; + int received_bytes = 0, msg_len = 0, route_attribute_len = 0; + int sock = -1, msgseq = 0; + struct nlmsghdr *nlh, *nlmsg; + struct rtmsg *route_entry; + // 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 *ptr = buffer; + struct timeval tv; + + if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { + perror("socket failed"); + return ""; + } + + memset(msgbuf, 0, sizeof(msgbuf)); + memset(gateway_address, 0, sizeof(gateway_address)); + memset(interface, 0, sizeof(interface)); + memset(buffer, 0, sizeof(buffer)); + + /* point the header and the msg structure pointers into the buffer */ + nlmsg = (struct nlmsghdr *)msgbuf; + + /* Fill in the nlmsg header*/ + nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table . + nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump. + nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet. + nlmsg->nlmsg_pid = getpid(); // PID of process sending the request. + + /* 1 Sec Timeout to avoid stall */ + tv.tv_sec = 1; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval)); + /* send msg */ + if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) { + perror("send failed"); + return ""; + } + + /* receive response */ + do + { + received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0); + if (received_bytes < 0) { + perror("Error in recv"); + return ""; + } + + nlh = (struct nlmsghdr *) ptr; + + /* Check if the header is valid */ + if((NLMSG_OK(nlmsg, received_bytes) == 0) || + (nlmsg->nlmsg_type == NLMSG_ERROR)) + { + perror("Error in received packet"); + return ""; + } + + /* If we received all data break */ + if (nlh->nlmsg_type == NLMSG_DONE) + break; + else { + ptr += received_bytes; + msg_len += received_bytes; + } + + /* Break if its not a multi part message */ + if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) + break; + } + while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); + + /* parse response */ + for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) + { + /* Get the route data */ + route_entry = (struct rtmsg *) NLMSG_DATA(nlh); + + /* We are just interested in main routing table */ + if (route_entry->rtm_table != RT_TABLE_MAIN) + continue; + + route_attribute = (struct rtattr *) RTM_RTA(route_entry); + route_attribute_len = RTM_PAYLOAD(nlh); + + /* Loop through all attributes */ + for ( ; RTA_OK(route_attribute, route_attribute_len); + route_attribute = RTA_NEXT(route_attribute, route_attribute_len)) + { + switch(route_attribute->rta_type) { + case RTA_OIF: + if_indextoname(*(int *)RTA_DATA(route_attribute), interface); + break; + case RTA_GATEWAY: + inet_ntop(AF_INET, RTA_DATA(route_attribute), + gateway_address, sizeof(gateway_address)); + break; + default: + break; + } + } + + if ((*gateway_address) && (*interface)) { + qDebug() << "Gateway " << gateway_address << " for interface " << interface; + break; + } + } + close(sock); + return gateway_address; +#endif +#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) + QString gateway; + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY}; + int afinet_type[] = {AF_INET, AF_INET6}; + + for (int ip_type = 0; ip_type <= 1; ip_type++) + { + mib[3] = afinet_type[ip_type]; + + size_t needed = 0; + if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0) + return ""; + + char* buf; + if ((buf = new char[needed]) == 0) + return ""; + + if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0) + { + qDebug() << "sysctl: net.route.0.0.dump"; + delete[] buf; + return gateway; + } + + struct rt_msghdr* rt; + for (char* p = buf; p < buf + needed; p += rt->rtm_msglen) + { + rt = reinterpret_cast(p); + struct sockaddr* sa = reinterpret_cast(rt + 1); + struct sockaddr* sa_tab[RTAX_MAX]; + for (int i = 0; i < RTAX_MAX; i++) + { + if (rt->rtm_addrs & (1 << i)) + { + sa_tab[i] = sa; + sa = reinterpret_cast( + reinterpret_cast(sa) + + ((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long))); + } + else + { + sa_tab[i] = nullptr; + } + } + + if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) && + sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] && + sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type]) + { + if (afinet_type[ip_type] == AF_INET) + { + if ((reinterpret_cast(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) + { + char dstStr4[INET_ADDRSTRLEN]; + char srcStr4[INET_ADDRSTRLEN]; + memcpy(srcStr4, + &(reinterpret_cast(sa_tab[RTAX_GATEWAY]))->sin_addr, + sizeof(struct in_addr)); + if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr) + gateway = dstStr4; + break; + } + } + else if (afinet_type[ip_type] == AF_INET6) + { + if ((reinterpret_cast(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) + { + char dstStr6[INET6_ADDRSTRLEN]; + char srcStr6[INET6_ADDRSTRLEN]; + memcpy(srcStr6, + &(reinterpret_cast(sa_tab[RTAX_GATEWAY]))->sin6_addr, + sizeof(struct in6_addr)); + if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr) + gateway = dstStr6; + break; + } + } + } + } + free(buf); + } + + return gateway; +#endif +} diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h new file mode 100644 index 00000000..57c6cb6f --- /dev/null +++ b/client/core/networkUtilities.h @@ -0,0 +1,32 @@ +#ifndef NETWORKUTILITIES_H +#define NETWORKUTILITIES_H + +#include +#include +#include + +class NetworkUtilities : public QObject +{ + Q_OBJECT +public: + static QString getIPAddress(const QString &host); + static QString getStringBetween(const QString &s, const QString &a, const QString &b); + static bool checkIPv4Format(const QString &ip); + static bool checkIpSubnetFormat(const QString &ip); + static QString getGatewayAndIface(); + + static QRegularExpression ipAddressRegExp(); + static QRegularExpression ipAddressPortRegExp(); + static QRegExp ipAddressWithSubnetRegExp(); + static QRegExp ipNetwork24RegExp(); + static QRegExp ipPortRegExp(); + static QRegExp domainRegExp(); + + static QString netMaskFromIpWithSubnet(const QString ip); + static QString ipAddressFromIpWithSubnet(const QString ip); + + static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); + +}; + +#endif // NETWORKUTILITIES_H diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 101e6dfa..4e720845 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::WireGuard: return QLatin1String("wireguard"); case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); + case DockerContainer::Xray: return QLatin1String("xray"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); case DockerContainer::Sftp: return QLatin1String("sftp"); - default: return ""; + default: return QString(); } } @@ -47,6 +48,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); + case ProtocolScriptType::xray_template: return QLatin1String("template.json"); default: return QString(); } } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index 02fc94fd..d952dafb 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -27,7 +27,8 @@ enum ProtocolScriptType { container_startup, openvpn_template, wireguard_template, - awg_template + awg_template, + xray_template }; diff --git a/client/platforms/linux/daemon/linuxroutemonitor.cpp b/client/platforms/linux/daemon/linuxroutemonitor.cpp index 38f2c56c..2010ba9b 100644 --- a/client/platforms/linux/daemon/linuxroutemonitor.cpp +++ b/client/platforms/linux/daemon/linuxroutemonitor.cpp @@ -19,8 +19,10 @@ #include #include +#include "../utilities.h" #include "leakdetector.h" #include "logger.h" +#include "core/networkUtilities.h" namespace { Logger logger("LinuxRouteMonitor"); @@ -163,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type, if (rtm->rtm_type == RTN_THROW) { struct in_addr ip4; - inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4); + inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4); nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); rtm->rtm_type = RTN_UNICAST; @@ -221,122 +223,6 @@ void LinuxRouteMonitor::nlsockReady() { } } -#define BUFFER_SIZE 4096 - -QString LinuxRouteMonitor::getgatewayandiface() -{ - int received_bytes = 0, msg_len = 0, route_attribute_len = 0; - int sock = -1, msgseq = 0; - struct nlmsghdr *nlh, *nlmsg; - struct rtmsg *route_entry; - // 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 *ptr = buffer; - struct timeval tv; - - if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { - perror("socket failed"); - return ""; - } - - memset(msgbuf, 0, sizeof(msgbuf)); - memset(gateway_address, 0, sizeof(gateway_address)); - memset(interface, 0, sizeof(interface)); - memset(buffer, 0, sizeof(buffer)); - - /* point the header and the msg structure pointers into the buffer */ - nlmsg = (struct nlmsghdr *)msgbuf; - - /* Fill in the nlmsg header*/ - nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); - nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table . - nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump. - nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet. - nlmsg->nlmsg_pid = getpid(); // PID of process sending the request. - - /* 1 Sec Timeout to avoid stall */ - tv.tv_sec = 1; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval)); - /* send msg */ - if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) { - perror("send failed"); - return ""; - } - - /* receive response */ - do - { - received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0); - if (received_bytes < 0) { - perror("Error in recv"); - return ""; - } - - nlh = (struct nlmsghdr *) ptr; - - /* Check if the header is valid */ - if((NLMSG_OK(nlmsg, received_bytes) == 0) || - (nlmsg->nlmsg_type == NLMSG_ERROR)) - { - perror("Error in received packet"); - return ""; - } - - /* If we received all data break */ - if (nlh->nlmsg_type == NLMSG_DONE) - break; - else { - ptr += received_bytes; - msg_len += received_bytes; - } - - /* Break if its not a multi part message */ - if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) - break; - } - while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); - - /* parse response */ - for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) - { - /* Get the route data */ - route_entry = (struct rtmsg *) NLMSG_DATA(nlh); - - /* We are just interested in main routing table */ - if (route_entry->rtm_table != RT_TABLE_MAIN) - continue; - - route_attribute = (struct rtattr *) RTM_RTA(route_entry); - route_attribute_len = RTM_PAYLOAD(nlh); - - /* Loop through all attributes */ - for ( ; RTA_OK(route_attribute, route_attribute_len); - route_attribute = RTA_NEXT(route_attribute, route_attribute_len)) - { - switch(route_attribute->rta_type) { - case RTA_OIF: - if_indextoname(*(int *)RTA_DATA(route_attribute), interface); - break; - case RTA_GATEWAY: - inet_ntop(AF_INET, RTA_DATA(route_attribute), - gateway_address, sizeof(gateway_address)); - break; - default: - break; - } - } - - if ((*gateway_address) && (*interface)) { - logger.debug() << "Gateway " << gateway_address << " for interface " << interface; - break; - } - } - close(sock); - return gateway_address; -} - static bool buildAllowedIp(wg_allowedip* ip, const IPAddress& prefix) { const char* addrString = qPrintable(prefix.address().toString()); diff --git a/client/platforms/linux/daemon/linuxroutemonitor.h b/client/platforms/linux/daemon/linuxroutemonitor.h index f1c3ac1d..f3bf0236 100644 --- a/client/platforms/linux/daemon/linuxroutemonitor.h +++ b/client/platforms/linux/daemon/linuxroutemonitor.h @@ -31,7 +31,6 @@ class LinuxRouteMonitor final : public QObject { static QString addrToString(const QByteArray& data); bool rtmSendRoute(int action, int flags, int type, const IPAddress& prefix); - QString getgatewayandiface(); QString m_ifname; unsigned int m_ifindex = 0; int m_nlsock = -1; diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 1d728cf6..05d33ccb 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -26,7 +26,6 @@ OpenVpnProtocol::~OpenVpnProtocol() QString OpenVpnProtocol::defaultConfigFileName() { - // qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); } @@ -161,7 +160,6 @@ void OpenVpnProtocol::updateRouteGateway(QString line) ErrorCode OpenVpnProtocol::start() { - // qDebug() << "Start OpenVPN connection"; OpenVpnProtocol::stop(); if (!QFileInfo::exists(Utils::openVpnExecPath())) { @@ -196,9 +194,6 @@ ErrorCode OpenVpnProtocol::start() } #endif - // QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; - // Utils::createEmptyFile(vpnLogFileNamePath); - uint mgmtPort = selectMgmtPort(); qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort; @@ -212,7 +207,6 @@ ErrorCode OpenVpnProtocol::start() m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); if (!m_openVpnProcess) { - // qWarning() << "IpcProcess replica is not created!"; setLastError(ErrorCode::AmneziaServiceConnectionFailed); return ErrorCode::AmneziaServiceConnectionFailed; } @@ -242,8 +236,6 @@ ErrorCode OpenVpnProtocol::start() m_openVpnProcess->start(); - // startTimeoutTimer(); - return ErrorCode::NoError; } diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index a451014c..7cdcb3b8 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -71,6 +71,7 @@ QMap ProtocolProps::protocolHumanNames() { Proto::Awg, "AmneziaWG" }, { Proto::Ikev2, "IKEv2" }, { Proto::L2tp, "L2TP" }, + { Proto::Xray, "XRay" }, { Proto::TorWebSite, "Website in Tor network" }, { Proto::Dns, "DNS Service" }, @@ -92,6 +93,7 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::WireGuard: return ServiceType::Vpn; case Proto::Awg: return ServiceType::Vpn; case Proto::Ikev2: return ServiceType::Vpn; + case Proto::Xray: return ServiceType::Vpn; case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; @@ -122,6 +124,7 @@ int ProtocolProps::defaultPort(Proto p) case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt(); case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt(); case Proto::Awg: return QString(protocols::awg::defaultPort).toInt(); + case Proto::Xray: return QString(protocols::xray::defaultPort).toInt(); case Proto::Ikev2: return -1; case Proto::L2tp: return -1; @@ -162,6 +165,8 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p) case Proto::Awg: return TransportProto::Udp; case Proto::Ikev2: return TransportProto::Udp; case Proto::L2tp: return TransportProto::Udp; + case Proto::Xray: return TransportProto::Tcp; + // non-vpn case Proto::TorWebSite: return TransportProto::Tcp; case Proto::Dns: return TransportProto::Udp; @@ -180,12 +185,15 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) case Proto::Awg: return false; case Proto::Ikev2: return false; case Proto::L2tp: return false; + case Proto::Xray: return false; + // non-vpn case Proto::TorWebSite: return false; case Proto::Dns: return false; case Proto::Sftp: return false; default: return false; } + return false; } QString ProtocolProps::key_proto_config_data(Proto p) diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index f75ed39e..3d6da3be 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -82,6 +82,7 @@ namespace amnezia constexpr char cloak[] = "cloak"; constexpr char sftp[] = "sftp"; constexpr char awg[] = "awg"; + constexpr char xray[] = "xray"; constexpr char configVersion[] = "config_version"; @@ -134,6 +135,20 @@ namespace amnezia constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; } + namespace xray + { + constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json"; + constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key"; + constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key"; + constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key"; + constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key"; + constexpr char defaultSite[] = "www.googletagmanager.com"; + + constexpr char defaultPort[] = "443"; + constexpr char defaultLocalProxyPort[] = "10808"; + constexpr char defaultLocalAddr[] = "10.33.0.2"; + } + namespace cloak { constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; @@ -142,7 +157,6 @@ namespace amnezia constexpr char defaultPort[] = "443"; constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; constexpr char defaultCipher[] = "chacha20-poly1305"; - } namespace wireguard @@ -206,6 +220,7 @@ namespace amnezia Awg, Ikev2, L2tp, + Xray, // non-vpn TorWebSite, diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 2ddc0684..ffea0fa0 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -9,6 +9,7 @@ #include "openvpnprotocol.h" #include "shadowsocksvpnprotocol.h" #include "wireguardprotocol.h" + #include "xrayprotocol.h" #endif #ifdef Q_OS_WINDOWS @@ -114,6 +115,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); case DockerContainer::Awg: return new WireguardProtocol(configuration); + case DockerContainer::Xray: return new XrayProtocol(configuration); #endif default: return nullptr; } diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp new file mode 100644 index 00000000..8f4800bc --- /dev/null +++ b/client/protocols/xrayprotocol.cpp @@ -0,0 +1,231 @@ +#include "xrayprotocol.h" + +#include "utilities.h" +#include "containers/containers_defs.h" +#include "core/networkUtilities.h" + +#include +#include +#include +#include + + +XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent): + VpnProtocol(configuration, parent) +{ + readXrayConfiguration(configuration); + m_routeGateway = NetworkUtilities::getGatewayAndIface(); + m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr; + m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr; +} + +XrayProtocol::~XrayProtocol() +{ + XrayProtocol::stop(); + QThread::msleep(200); + m_xrayProcess.close(); +} + +ErrorCode XrayProtocol::start() +{ + qDebug().noquote() << "XrayProtocol xrayExecPath():" << xrayExecPath(); + + if (!QFileInfo::exists(xrayExecPath())) { + setLastError(ErrorCode::XrayExecutableMissing); + return lastError(); + } + + if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) { + Utils::killProcessByName(Utils::executable(xrayExecPath(), true)); + } + +#ifdef QT_DEBUG + m_xrayCfgFile.setAutoRemove(false); +#endif + m_xrayCfgFile.open(); + m_xrayCfgFile.write(QJsonDocument(m_xrayConfig).toJson()); + m_xrayCfgFile.close(); + + QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json"; + + qDebug().noquote() << "XrayProtocol::start()" + << xrayExecPath() << args.join(" "); + + m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels); + + m_xrayProcess.setProgram(xrayExecPath()); + m_xrayProcess.setArguments(args); + + connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() { +#ifdef QT_DEBUG + qDebug().noquote() << "xray:" << m_xrayProcess.readAllStandardOutput(); +#endif + }); + + connect(&m_xrayProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug().noquote() << "XrayProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; + setConnectionState(Vpn::ConnectionState::Disconnected); + if (exitStatus != QProcess::NormalExit) { + emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed); + stop(); + } + if (exitCode != 0) { + emit protocolError(amnezia::ErrorCode::InternalError); + stop(); + } + }); + + m_xrayProcess.start(); + m_xrayProcess.waitForStarted(); + + if (m_xrayProcess.state() == QProcess::ProcessState::Running) { + setConnectionState(Vpn::ConnectionState::Connecting); + QThread::msleep(1000); + return startTun2Sock(); + } + else return ErrorCode::XrayExecutableMissing; +} + + +ErrorCode XrayProtocol::startTun2Sock() +{ + if (!QFileInfo::exists(Utils::tun2socksPath())) { + setLastError(ErrorCode::Tun2SockExecutableMissing); + return lastError(); + } + + m_t2sProcess = IpcClient::CreatePrivilegedProcess(); + + if (!m_t2sProcess) { + setLastError(ErrorCode::AmneziaServiceConnectionFailed); + return ErrorCode::AmneziaServiceConnectionFailed; + } + + m_t2sProcess->waitForSource(1000); + if (!m_t2sProcess->isInitialized()) { + qWarning() << "IpcProcess replica is not connected!"; + setLastError(ErrorCode::AmneziaServiceConnectionFailed); + return ErrorCode::AmneziaServiceConnectionFailed; + } + + QString XrayConStr = "socks5://127.0.0.1:" + QString::number(m_localPort); + + m_t2sProcess->setProgram(PermittedProcess::Tun2Socks); +#ifdef Q_OS_WIN + QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr, "-tun-post-up", + QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255").arg(amnezia::protocols::xray::defaultLocalAddr)}); +#endif +#ifdef Q_OS_LINUX + QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr}); +#endif +#ifdef Q_OS_MAC + QStringList arguments({"-device", "utun22", "-proxy", XrayConStr}); +#endif + m_t2sProcess->setArguments(arguments); + + qDebug() << arguments.join(" "); + connect(m_t2sProcess.data(), &PrivilegedProcess::errorOccurred, + [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); + + connect(m_t2sProcess.data(), &PrivilegedProcess::stateChanged, + [&](QProcess::ProcessState newState) { + qDebug() << "PrivilegedProcess stateChanged" << newState; + if (newState == QProcess::Running) + { + setConnectionState(Vpn::ConnectionState::Connecting); + QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + +#ifdef Q_OS_MACOS + QThread::msleep(5000); + IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); + IpcClient::Interface()->updateResolvers("utun22", dnsAddr); + IpcClient::Interface()->enableKillSwitch(m_configData, 0); +#endif +#ifdef Q_OS_WINDOWS + QThread::msleep(15000); +#endif +#ifdef Q_OS_LINUX + QThread::msleep(1000); + IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); + IpcClient::Interface()->updateResolvers("tun2", dnsAddr); + IpcClient::Interface()->enableKillSwitch(m_configData, 0); +#endif + if (m_routeMode == 0) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); + } + IpcClient::Interface()->StopRoutingIpv6(); +#ifdef Q_OS_WIN + IpcClient::Interface()->updateResolvers("tun2", dnsAddr); + QList netInterfaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < netInterfaces.size(); i++) { + for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) + { + if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { + IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); + m_configData.insert("vpnGateway", m_vpnGateway); + IpcClient::Interface()->enablePeerTraffic(m_configData); + } + } + } +#endif + setConnectionState(Vpn::ConnectionState::Connected); + } + }); + + +#if !defined(Q_OS_MACOS) + connect(m_t2sProcess.data(), &PrivilegedProcess::finished, this, + [&]() { + setConnectionState(Vpn::ConnectionState::Disconnected); + IpcClient::Interface()->deleteTun("tun2"); + IpcClient::Interface()->StartRoutingIpv6(); + IpcClient::Interface()->clearSavedRoutes(); + }); +#endif + + m_t2sProcess->start(); + + + return ErrorCode::NoError; +} + +void XrayProtocol::stop() +{ +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + IpcClient::Interface()->disableKillSwitch(); +#endif + qDebug() << "XrayProtocol::stop()"; + m_xrayProcess.terminate(); + if (m_t2sProcess) { + m_t2sProcess->close(); + } + +#ifdef Q_OS_WIN + Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT); +#endif +} + +QString XrayProtocol::xrayExecPath() +{ +#ifdef Q_OS_WIN + return Utils::executable(QString("xray/xray"), true); +#else + return Utils::executable(QString("xray"), true); +#endif +} + +void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration) +{ + m_configData = configuration; + QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject(); + m_xrayConfig = xrayConfiguration; + m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt(); + m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString(); + m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt(); + m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString(); + m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString(); +} diff --git a/client/protocols/xrayprotocol.h b/client/protocols/xrayprotocol.h new file mode 100644 index 00000000..8df2afb2 --- /dev/null +++ b/client/protocols/xrayprotocol.h @@ -0,0 +1,41 @@ +#ifndef XRAYPROTOCOL_H +#define XRAYPROTOCOL_H + +#include "openvpnprotocol.h" +#include "QProcess" +#include "containers/containers_defs.h" + +class XrayProtocol : public VpnProtocol +{ +public: + XrayProtocol(const QJsonObject& configuration, QObject* parent = nullptr); + virtual ~XrayProtocol() override; + + ErrorCode start() override; + ErrorCode startTun2Sock(); + void stop() override; + +protected: + void readXrayConfiguration(const QJsonObject &configuration); + +protected: + QJsonObject m_xrayConfig; + +private: + static QString xrayExecPath(); + static QString tun2SocksExecPath(); +private: + int m_localPort; + QString m_remoteAddress; + int m_routeMode; + QJsonObject m_configData; + QString m_primaryDNS; + QString m_secondaryDNS; +#ifndef Q_OS_IOS + QProcess m_xrayProcess; + QSharedPointer m_t2sProcess; +#endif + QTemporaryFile m_xrayCfgFile; +}; + +#endif // XRAYPROTOCOL_H diff --git a/client/resources.qrc b/client/resources.qrc index cba5048c..35bbb6de 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -198,6 +198,7 @@ ui/qml/Pages2/PageProtocolOpenVpnSettings.qml ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml + ui/qml/Pages2/PageProtocolXraySettings.qml ui/qml/Pages2/PageProtocolRaw.qml ui/qml/Pages2/PageSettingsLogging.qml ui/qml/Pages2/PageServiceSftpSettings.qml @@ -224,6 +225,11 @@ ui/qml/Pages2/PageShareFullAccess.qml images/controls/close.svg images/controls/search.svg + server_scripts/xray/configure_container.sh + server_scripts/xray/Dockerfile + server_scripts/xray/run_container.sh + server_scripts/xray/start.sh + server_scripts/xray/template.json ui/qml/Pages2/PageProtocolWireGuardSettings.qml ui/qml/Components/HomeSplitTunnelingDrawer.qml images/controls/split-tunneling.svg diff --git a/client/server_scripts/xray/Dockerfile b/client/server_scripts/xray/Dockerfile new file mode 100644 index 00000000..b43d8a83 --- /dev/null +++ b/client/server_scripts/xray/Dockerfile @@ -0,0 +1,53 @@ +FROM alpine:3.15 +LABEL maintainer="AmneziaVPN" + +ARG XRAY_RELEASE="v1.8.6" + +RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz +RUN apk --update upgrade --no-cache + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +RUN mkdir -p /opt/amnezia/xray + +RUN curl -L https://github.com/XTLS/Xray-core/releases/download/${XRAY_RELEASE}/Xray-linux-64.zip > /root/xray.zip;\ + unzip /root/xray.zip -d /usr/bin/;\ + chmod a+x /usr/bin/xray; + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + net.core.default_qdisc=fq \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = bbr \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENV TZ=Asia/Shanghai + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] + diff --git a/client/server_scripts/xray/configure_container.sh b/client/server_scripts/xray/configure_container.sh new file mode 100644 index 00000000..541e155b --- /dev/null +++ b/client/server_scripts/xray/configure_container.sh @@ -0,0 +1,67 @@ +cd /opt/amnezia/xray +XRAY_CLIENT_ID=$(xray uuid) && echo $XRAY_CLIENT_ID > /opt/amnezia/xray/xray_uuid.key +XRAY_SHORT_ID=$(openssl rand -hex 8) && echo $XRAY_SHORT_ID > /opt/amnezia/xray/xray_short_id.key + +KEYPAIR=$(xray x25519) +LINE_NUM=1 +while IFS= read -r line; do + if [[ $LINE_NUM -gt 1 ]] + then + IFS=":" read FIST XRAY_PUBLIC_KEY <<< "$line" + else + LINE_NUM=$((LINE_NUM + 1)) + IFS=":" read FIST XRAY_PRIVATE_KEY <<< "$line" + fi +done <<< "$KEYPAIR" + +XRAY_PRIVATE_KEY=$(echo $XRAY_PRIVATE_KEY | tr -d ' ') +XRAY_PUBLIC_KEY=$(echo $XRAY_PUBLIC_KEY | tr -d ' ') + + +echo $XRAY_PUBLIC_KEY > /opt/amnezia/xray/xray_public.key +echo $XRAY_PRIVATE_KEY > /opt/amnezia/xray/xray_private.key + + +cat > /opt/amnezia/xray/server.json <getProcessedServerIndex(); + ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + + QString clientId; + QString config = + m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, Proto::Xray, clientId, &errorCode); + + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Xray, config); + QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + + QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_config.append(line + "\n"); + } + + emit exportConfigChanged(); +} + QString ExportController::getConfig() { return m_config; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index e5bd1657..06d62003 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -37,6 +37,7 @@ public slots: void generateAwgConfig(const QString &clientName); void generateShadowSocksConfig(); void generateCloakConfig(); + void generateXrayConfig(); QString getConfig(); QString getNativeConfigString(); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 3c73e0df..22e43732 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -19,6 +19,7 @@ namespace Amnezia, OpenVpn, WireGuard, + Xray, Backup, Invalid }; @@ -34,6 +35,9 @@ namespace const QString wireguardConfigPatternSectionInterface = "[Interface]"; const QString wireguardConfigPatternSectionPeer = "[Peer]"; + const QString xrayConfigPatternInbound = "inbounds"; + const QString xrayConfigPatternOutbound = "outbounds"; + const QString amneziaConfigPattern = "containers"; const QString amneziaFreeConfigPattern = "api_key"; const QString backupPattern = "Servers/serversList"; @@ -49,6 +53,8 @@ namespace } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { return ConfigTypes::WireGuard; + } else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) { + return ConfigTypes::Xray; } return ConfigTypes::Invalid; } @@ -109,6 +115,10 @@ bool ImportController::extractConfigFromData(QString data) m_config = extractWireGuardConfig(config); return m_config.empty() ? false : true; } + case ConfigTypes::Xray: { + m_config = extractXrayConfig(config); + return m_config.empty() ? false : true; + } case ConfigTypes::Amnezia: { m_config = QJsonDocument::fromJson(config.toUtf8()).object(); return m_config.empty() ? false : true; @@ -349,6 +359,42 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) return config; } +QJsonObject ImportController::extractXrayConfig(const QString &data) +{ + QJsonParseError parserErr; + QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr); + + QJsonObject xrayVpnConfig; + xrayVpnConfig[config_key::config] = jsonConf.toJson().constData(); + QJsonObject lastConfig; + lastConfig[config_key::last_config] = jsonConf.toJson().constData(); + lastConfig[config_key::isThirdPartyConfig] = true; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-xray")); + containers.insert(config_key::xray, QJsonValue(lastConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + + const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-xray"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + config[config_key::hostName] = hostName; + + return config; +} + #ifdef Q_OS_ANDROID static QMutex qrDecodeMutex; diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 95af9f35..0fabc36d 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -47,6 +47,7 @@ signals: private: QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data); + QJsonObject extractXrayConfig(const QString &data); #if defined Q_OS_ANDROID || defined Q_OS_IOS void stopDecodingQr(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index e25d9c06..120a7e7c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -9,6 +9,7 @@ #include "core/errorstrings.h" #include "core/controllers/serverController.h" +#include "core/networkUtilities.h" #include "utilities.h" #include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" @@ -352,12 +353,12 @@ void InstallController::removeCurrentlyProcessedContainer() QRegularExpression InstallController::ipAddressPortRegExp() { - return Utils::ipAddressPortRegExp(); + return NetworkUtilities::ipAddressPortRegExp(); } QRegularExpression InstallController::ipAddressRegExp() { - return Utils::ipAddressRegExp(); + return NetworkUtilities::ipAddressRegExp(); } void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, @@ -450,7 +451,6 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw process->write((password + "\n").toUtf8()); } - // qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; #endif } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index b802be47..9118cca8 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -48,6 +48,7 @@ namespace PageLoader PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, PageProtocolCloakSettings, + PageProtocolXraySettings, PageProtocolWireGuardSettings, PageProtocolAwgSettings, PageProtocolIKev2Settings, diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 8c420899..d54dbdd2 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -5,7 +5,7 @@ #include #include "systemController.h" -#include "utilities.h" +#include "core/networkUtilities.h" SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &vpnConnection, @@ -25,7 +25,7 @@ void SitesController::addSite(QString hostname) return; } - if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + if (!NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { // get domain name if it present hostname.replace("https://", ""); hostname.replace("http://", ""); @@ -40,7 +40,7 @@ void SitesController::addSite(QString hostname) if (!ip.isEmpty()) { QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << ip)); - } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + } else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << hostname)); } @@ -57,7 +57,7 @@ void SitesController::addSite(QString hostname) } }; - if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { processSite(hostname, ""); } else { processSite(hostname, ""); @@ -110,7 +110,7 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting) auto hostname = jsonObject.value("hostname").toString(""); auto ip = jsonObject.value("ip").toString(""); - if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { qDebug() << hostname << " not look like ip adress or domain name"; continue; } diff --git a/client/ui/models/protocols/xrayConfigModel.cpp b/client/ui/models/protocols/xrayConfigModel.cpp new file mode 100644 index 00000000..d5b50481 --- /dev/null +++ b/client/ui/models/protocols/xrayConfigModel.cpp @@ -0,0 +1,69 @@ +#include "xrayConfigModel.h" + +#include "protocols/protocols_defs.h" + +XrayConfigModel::XrayConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int XrayConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant XrayConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite); + } + + return QVariant(); +} + +void XrayConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::xray).toObject(); + + m_protocolConfig.insert(config_key::site, + protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite)); + + endResetModel(); +} + +QJsonObject XrayConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::xray, m_protocolConfig); + return m_fullConfig; +} + +QHash XrayConfigModel::roleNames() const +{ + QHash roles; + + roles[SiteRole] = "site"; + + return roles; +} diff --git a/client/ui/models/protocols/xrayConfigModel.h b/client/ui/models/protocols/xrayConfigModel.h new file mode 100644 index 00000000..cb57f858 --- /dev/null +++ b/client/ui/models/protocols/xrayConfigModel.h @@ -0,0 +1,38 @@ +#ifndef XRAYCONFIGMODEL_H +#define XRAYCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class XrayConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SiteRole + }; + + explicit XrayConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // XRAYCONFIGMODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 5826025e..b1bffa3f 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -79,6 +79,8 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; + case Proto::Xray: return PageLoader::PageEnum::PageProtocolXraySettings; + // non-vpn case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 794ab0b3..3a33e5cc 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -57,6 +57,12 @@ ListView { PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings) break } + case ContainerEnum.Xray: { + XrayConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolXraySettings) + break + } + case ContainerEnum.WireGuard: { WireGuardConfigModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolWireGuardSettings) diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml new file mode 100644 index 00000000..05b958ac --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -0,0 +1,156 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: XrayConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + headerText: qsTr("XRay settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("Disguised as traffic from") + textFieldText: site + + textField.onEditingFinished: { + if (textFieldText !== site) { + var tmpText = textFieldText + tmpText = tmpText.toLocaleLowerCase() + + var indexHttps = tmpText.indexOf("https://") + if (indexHttps === 0) { + tmpText = textFieldText.substring(8) + } else { + site = textFieldText + } + } + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 + + visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.Xray + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove XRay") + + onClicked: { + questionDrawer.headerText = qsTr("Remove XRay from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(XrayConfigModel.getConfig()) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } + +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index b75e7342..4d200004 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -84,6 +84,7 @@ PageType { case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 6ed8f5ab..1c13eb29 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -69,8 +69,8 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.backup)" : - "Config files (*.vpn *.ovpn *.conf)" + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : + "Config files (*.vpn *.ovpn *.conf *.json)" var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) if (fileName !== "") { if (ImportController.extractConfigFromFile(fileName)) { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 70b03d7f..79b48d44 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -24,7 +24,8 @@ PageType { WireGuard, Awg, ShadowSocks, - Cloak + Cloak, + Xray } signal revokeConfig(int index) @@ -88,6 +89,13 @@ PageType { shareConnectionDrawer.configFileName = "amnezia_for_cloak" break } + case PageShare.ConfigType.Xray: { + ExportController.generateXrayConfig() + shareConnectionDrawer.configCaption = qsTr("Save XRay config") + shareConnectionDrawer.configExtension = ".json" + shareConnectionDrawer.configFileName = "amnezia_for_xray" + break + } } PageController.showBusyIndicator(false) @@ -136,6 +144,11 @@ PageType { property string name: qsTr("Cloak native format") property var type: PageShare.ConfigType.Cloak } + QtObject { + id: xrayConnectionFormat + property string name: qsTr("XRay native format") + property var type: PageShare.ConfigType.Xray + } FlickableType { anchors.top: parent.top @@ -435,6 +448,8 @@ PageType { root.connectionTypesModel.push(openVpnConnectionFormat) root.connectionTypesModel.push(shadowSocksConnectionFormat) root.connectionTypesModel.push(cloakConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-xray")) { + root.connectionTypesModel.push(xrayConnectionFormat) } } } diff --git a/client/utilities.cpp b/client/utilities.cpp index 158bce93..46df1dda 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include #include #include @@ -118,60 +116,6 @@ bool Utils::processIsRunning(const QString &fileName) #endif } -QString Utils::getIPAddress(const QString &host) -{ - if (ipAddressRegExp().match(host).hasMatch()) { - return host; - } - - QList addresses = QHostInfo::fromName(host).addresses(); - if (!addresses.isEmpty()) { - return addresses.first().toString(); - } - qDebug() << "Unable to resolve address for " << host; - return ""; -} - -QString Utils::getStringBetween(const QString &s, const QString &a, const QString &b) -{ - int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length()); - if (ap < 0 || bp < 0) - return QString(); - ap += a.length(); - if (bp - ap <= 0) - return QString(); - return s.mid(ap, bp - ap).trimmed(); -} - -bool Utils::checkIPv4Format(const QString &ip) -{ - if (ip.isEmpty()) - return false; - int count = ip.count("."); - if (count != 3) - return false; - - QHostAddress addr(ip); - return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); -} - -bool Utils::checkIpSubnetFormat(const QString &ip) -{ - if (!ip.contains("/")) - return checkIPv4Format(ip); - - QStringList parts = ip.split("/"); - if (parts.size() != 2) - return false; - - bool ok; - int subnet = parts.at(1).toInt(&ok); - if (subnet >= 0 && subnet <= 32 && ok) - return checkIPv4Format(parts.at(0)); - else - return false; -} - void Utils::killProcessByName(const QString &name) { qDebug().noquote() << "Kill process" << name; @@ -184,45 +128,6 @@ void Utils::killProcessByName(const QString &name) #endif } -QString Utils::netMaskFromIpWithSubnet(const QString ip) -{ - if (!ip.contains("/")) - return "255.255.255.255"; - - bool ok; - int prefix = ip.split("/").at(1).toInt(&ok); - if (!ok) - return "255.255.255.255"; - - unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF; - - return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF); -} - -QString Utils::ipAddressFromIpWithSubnet(const QString ip) -{ - if (ip.count(".") != 3) - return ""; - return ip.split("/").first(); -} - -QStringList Utils::summarizeRoutes(const QStringList &ips, const QString cidr) -{ - // QMap - // QHostAddress - - // QMap subnets; // <"a.b", > - - // for (const QString &ip : ips) { - // if (ip.count(".") != 3) continue; - - // const QStringList &parts = ip.split("."); - // subnets[parts.at(0) + "." + parts.at(1)].append(ip); - // } - - return QStringList(); -} - QString Utils::openVpnExecPath() { #ifdef Q_OS_WIN @@ -259,6 +164,19 @@ QString Utils::certUtilPath() #endif } +QString Utils::tun2socksPath() +{ +#ifdef Q_OS_WIN + return Utils::executable("xray/tun2socks", true); +#elif defined Q_OS_LINUX + // We have service that runs OpenVPN on Linux. We need to make same + // path for client and service. + return Utils::executable("../../client/bin/tun2socks", true); +#else + return Utils::executable("/tun2socks", true); +#endif +} + #ifdef Q_OS_WIN // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 diff --git a/client/utilities.h b/client/utilities.h index 7ef0cd3f..8a4cdc7a 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -22,53 +22,13 @@ public: static bool createEmptyFile(const QString &path); static bool initializePath(const QString &path); - static QString getIPAddress(const QString &host); - static QString getStringBetween(const QString &s, const QString &a, const QString &b); - static bool checkIPv4Format(const QString &ip); - static bool checkIpSubnetFormat(const QString &ip); - static QRegularExpression ipAddressRegExp() - { - return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); - } - static QRegularExpression ipAddressPortRegExp() - { - return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); - } - - static QRegExp ipAddressWithSubnetRegExp() - { - return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); - } - - static QRegExp ipNetwork24RegExp() - { - return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "0$"); - } - - static QRegExp ipPortRegExp() - { - return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); - } - - static QRegExp domainRegExp() - { - return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-" - "9\\-]{1,30})\\.[a-z]{2,}"); - } static bool processIsRunning(const QString &fileName); static void killProcessByName(const QString &name); - static QString netMaskFromIpWithSubnet(const QString ip); - static QString ipAddressFromIpWithSubnet(const QString ip); - - static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); - static QString openVpnExecPath(); static QString wireguardExecPath(); static QString certUtilPath(); + static QString tun2socksPath(); #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index c3719562..08e7da7d 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -27,7 +27,7 @@ #include "platforms/ios/ios_controller.h" #endif -#include "utilities.h" +#include "core/networkUtilities.h"" #include "vpnconnection.h" VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, @@ -59,6 +59,8 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP + QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex()); + if (IpcClient::Interface()) { if (state == Vpn::ConnectionState::Connected) { IpcClient::Interface()->resetIpStack(); @@ -92,6 +94,9 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->clearSavedRoutes(); } + } else if (state == Vpn::ConnectionState::Connecting) { + + } else if (state == Vpn::ConnectionState::Disconnected) { } } #endif @@ -118,10 +123,10 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) QStringList sites; const QVariantMap &m = m_settings->vpnSites(mode); for (auto i = m.constBegin(); i != m.constEnd(); ++i) { - if (Utils::checkIpSubnetFormat(i.key())) { + if (NetworkUtilities::checkIpSubnetFormat(i.key())) { ips.append(i.key()); } else { - if (Utils::checkIpSubnetFormat(i.value().toString())) { + if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } sites.append(i.key()); @@ -246,8 +251,7 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); if (serverIndex >= 0) { - qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container - << proto; + qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container; QJsonObject protoObject = m_settings->protocolConfig(serverIndex, container, proto); protoObject.insert(config_key::last_config, configDataBeforeLocalProcessing); m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); @@ -461,6 +465,7 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes) void VpnConnection::disconnectFromVpn() { #ifdef AMNEZIA_DESKTOP + QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex()); if (IpcClient::Interface()) { IpcClient::Interface()->flushDns(); diff --git a/ipc/ipc.h b/ipc/ipc.h index cb6a1088..b1329657 100644 --- a/ipc/ipc.h +++ b/ipc/ipc.h @@ -13,6 +13,7 @@ namespace amnezia { enum PermittedProcess { OpenVPN, Wireguard, + Tun2Socks, CertUtil }; @@ -24,6 +25,8 @@ inline QString permittedProcessPath(PermittedProcess pid) return Utils::wireguardExecPath(); } else if (pid == PermittedProcess::CertUtil) { return Utils::certUtilPath(); + } else if (pid == PermittedProcess::Tun2Socks) { + return Utils::tun2socksPath(); } return ""; } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index b7305250..79f2d042 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -1,6 +1,7 @@ #include #include #include +#include #include "../client/daemon/interfaceconfig.h" class IpcInterface @@ -21,8 +22,15 @@ class IpcInterface SLOT( void cleanUp() ); SLOT( void setLogsEnabled(bool enabled) ); + SLOT( bool createTun(const QString &dev, const QString &subnet) ); + SLOT( bool deleteTun(const QString &dev) ); + + SLOT( void StartRoutingIpv6() ); + SLOT( void StopRoutingIpv6() ); + SLOT( bool disableKillSwitch() ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); + SLOT( bool updateResolvers(const QString& ifname, const QList& resolvers) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 3b35abf7..18a82ef8 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -160,6 +160,30 @@ void IpcServer::cleanUp() Logger::cleanUp(); } +bool IpcServer::createTun(const QString &dev, const QString &subnet) +{ + return Router::createTun(dev, subnet); +} + +bool IpcServer::deleteTun(const QString &dev) +{ + return Router::deleteTun(dev); +} + +bool IpcServer::updateResolvers(const QString& ifname, const QList& resolvers) +{ + return Router::updateResolvers(ifname, resolvers); +} + +void IpcServer::StartRoutingIpv6() +{ + Router::StartRoutingIpv6(); +} +void IpcServer::StopRoutingIpv6() +{ + Router::StopRoutingIpv6(); +} + void IpcServer::setLogsEnabled(bool enabled) { #ifdef MZ_DEBUG @@ -223,6 +247,7 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); QStringList dnsServers; dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 20bbb191..bd474481 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -27,9 +27,14 @@ public: virtual QStringList getTapList() override; virtual void cleanUp() override; virtual void setLogsEnabled(bool enabled) override; + virtual bool createTun(const QString &dev, const QString &subnet) override; + virtual bool deleteTun(const QString &dev) override; + virtual void StartRoutingIpv6() override; + virtual void StopRoutingIpv6() override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; + virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; private: int m_localpid = 0; diff --git a/ipc/ipcserverprocess.cpp b/ipc/ipcserverprocess.cpp index b8f085c3..497e89d7 100644 --- a/ipc/ipcserverprocess.cpp +++ b/ipc/ipcserverprocess.cpp @@ -32,6 +32,8 @@ void IpcServerProcess::start() if (m_process->program().isEmpty()) { qDebug() << "IpcServerProcess failed to start, program is empty"; } + + Utils::killProcessByName(m_process->program()); m_process->start(); qDebug() << "IpcServerProcess started, " << m_process->program() << m_process->arguments(); diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 742b8ae3..234dfafe 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -14,6 +14,7 @@ configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/vers set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h @@ -26,6 +27,7 @@ set(HEADERS set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp diff --git a/service/server/router.cpp b/service/server/router.cpp index 6b97e795..fdf03232 100644 --- a/service/server/router.cpp +++ b/service/server/router.cpp @@ -64,3 +64,61 @@ void Router::resetIpStack() #endif } +bool Router::createTun(const QString &dev, const QString &subnet) +{ +#ifdef Q_OS_LINUX + return RouterLinux::Instance().createTun(dev, subnet); +#endif +#ifdef Q_OS_MAC + return RouterMac::Instance().createTun(dev, subnet); +#endif + return true; +}; + +bool Router::deleteTun(const QString &dev) +{ +#ifdef Q_OS_LINUX + return RouterLinux::Instance().deleteTun(dev); +#endif +#ifdef Q_OS_MAC + return RouterMac::Instance().deleteTun(dev); +#endif + return true; +}; + +bool Router::updateResolvers(const QString& ifname, const QList& resolvers) +{ +#ifdef Q_OS_LINUX + return RouterLinux::Instance().updateResolvers(ifname, resolvers); +#endif +#ifdef Q_OS_MACOS + return RouterMac::Instance().updateResolvers(ifname, resolvers); +#endif +#ifdef Q_OS_WIN + return RouterWin::Instance().updateResolvers(ifname, resolvers); +#endif +} + + +void Router::StopRoutingIpv6() +{ +#ifdef Q_OS_WIN + RouterWin::Instance().StopRoutingIpv6(); +#elif defined (Q_OS_MAC) + // todo fixme +#elif defined Q_OS_LINUX + RouterLinux::Instance().StopRoutingIpv6(); +#endif +} + +void Router::StartRoutingIpv6() +{ +#ifdef Q_OS_WIN + RouterWin::Instance().StartRoutingIpv6(); +#elif defined (Q_OS_MAC) + // todo fixme +#elif defined Q_OS_LINUX + RouterLinux::Instance().StartRoutingIpv6(); +#endif +} + diff --git a/service/server/router.h b/service/server/router.h index c290f664..dfaf9021 100644 --- a/service/server/router.h +++ b/service/server/router.h @@ -7,6 +7,7 @@ #include #include #include +#include /** * @brief The Router class - General class for handling ip routing @@ -20,6 +21,11 @@ public: static int routeDeleteList(const QString &gw, const QStringList &ips); static void flushDns(); static void resetIpStack(); + static bool createTun(const QString &dev, const QString &subnet); + static bool deleteTun(const QString &dev); + static void StartRoutingIpv6(); + static void StopRoutingIpv6(); + static bool updateResolvers(const QString& ifname, const QList& resolvers); }; #endif // ROUTER_H diff --git a/service/server/router_linux.cpp b/service/server/router_linux.cpp index 9410b146..fb7a108c 100644 --- a/service/server/router_linux.cpp +++ b/service/server/router_linux.cpp @@ -5,10 +5,14 @@ #include #include #include +#include #include +#include +#include +#include +#include #include #include -#include #include #include #include @@ -16,6 +20,8 @@ #include #include +#include + RouterLinux &RouterLinux::Instance() { static RouterLinux s; @@ -24,10 +30,10 @@ RouterLinux &RouterLinux::Instance() bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const int &sock) { - QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); - QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipWithSubnet); - if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + if (!NetworkUtilities::checkIPv4Format(ip) || !NetworkUtilities::checkIPv4Format(gw)) { qCritical().noquote() << "Critical, trying to add invalid route: " << ip << gw; return false; } @@ -91,18 +97,18 @@ bool RouterLinux::clearSavedRoutes() bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, const int &sock) { #ifdef MZ_DEBUG - qDebug().noquote() << "RouterMac::routeDelete: " << ipWithSubnet << gw; + qDebug().noquote() << "RouterLinux::routeDelete: " << ipWithSubnet << gw; #endif - QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); - QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipWithSubnet); - if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + if (!NetworkUtilities::checkIPv4Format(ip) || !NetworkUtilities::checkIPv4Format(gw)) { qCritical().noquote() << "Critical, trying to remove invalid route: " << ip << gw; return false; } - if (ip == "0.0.0.0") { + if (ipWithSubnet == "0.0.0.0/0") { qDebug().noquote() << "Warning, trying to remove default route, skipping: " << ip << gw; return true; } @@ -170,3 +176,169 @@ void RouterLinux::flushDns() else qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output; } + +bool RouterLinux::createTun(const QString &dev, const QString &subnet) { + qDebug().noquote() << "createTun start"; + + QProcess process; + QStringList commands; + + commands << "ip" << "tuntap" << "add" << "mode" << "tun" << "dev" << dev; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start adding tun device!\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not add tun device!\n"; + return false; + } + commands.clear(); + + commands << "ip" << "addr" << "add" << QString("%1/24").arg(subnet) << "dev" << dev; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start adding a subnet for tun device!\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not add a subnet for tun device!\n"; + return false; + } + commands.clear(); + + commands << "ip" << "link" << "set" << "dev" << dev << "up"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start link set for tun device!\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not link set for tun device!\n"; + return false; + } + + return true; +} + +bool RouterLinux::deleteTun(const QString &dev) +{ + struct { + struct nlmsghdr nh; + struct ifinfomsg ifm; + unsigned char data[64]; + } req; + struct rtattr *rta; + int ret, rtnl; + + rtnl = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + if (rtnl < 0) { + qDebug().noquote() << "can't open rtnl: " << errno; + return 1; + } + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.ifm))); + req.nh.nlmsg_flags = NLM_F_REQUEST; + req.nh.nlmsg_type = RTM_DELLINK; + + req.ifm.ifi_family = AF_UNSPEC; + + rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nh.nlmsg_len)); + rta->rta_type = IFLA_IFNAME; + rta->rta_len = RTA_LENGTH(IFNAMSIZ); + req.nh.nlmsg_len += rta->rta_len; + memcpy(RTA_DATA(rta), dev.toStdString().c_str(), IFNAMSIZ); + + ret = send(rtnl, &req, req.nh.nlmsg_len, 0); + if (ret < 0) + qDebug().noquote() << "can't send: errno"; + ret = (unsigned int)ret != req.nh.nlmsg_len; + + close(rtnl); + qDebug().noquote() << "deleteTun ret" << ret; + return ret; +} + +bool RouterLinux::updateResolvers(const QString& ifname, const QList& resolvers) +{ + return m_dnsUtil->updateResolvers(ifname, resolvers); +} + +void RouterLinux::StartRoutingIpv6() +{ + QProcess process; + QStringList commands; + + commands << "sysctl" << "-w" << "net.ipv6.conf.all.disable_ipv6=0"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start activate ipv6\n"; + return; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not activate ipv6\n"; + return; + } + commands.clear(); + + commands << "sysctl" << "-w" << "net.ipv6.conf.default.disable_ipv6=0"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start activate ipv6\n"; + return; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not activate ipv6\n"; + return; + } + commands.clear(); + + qDebug().noquote() << "StartRoutingIpv6 OK"; +} + +void RouterLinux::StopRoutingIpv6() +{ + QProcess process; + QStringList commands; + + commands << "sysctl" << "-w" << "net.ipv6.conf.all.disable_ipv6=1"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start disable ipv6\n"; + return; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not disable ipv6\n"; + return; + } + commands.clear(); + + commands << "sysctl" << "-w" << "net.ipv6.conf.default.disable_ipv6=1"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start disable ipv6\n"; + return; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not disable ipv6\n"; + return; + } + commands.clear(); + + qDebug().noquote() << "StopRoutingIpv6 OK"; +} diff --git a/service/server/router_linux.h b/service/server/router_linux.h index 5b4897bd..04a26fd2 100644 --- a/service/server/router_linux.h +++ b/service/server/router_linux.h @@ -8,6 +8,8 @@ #include #include +#include "../client/platforms/linux/daemon/dnsutilslinux.h" + /** * @brief The Router class - General class for handling ip routing */ @@ -29,15 +31,20 @@ public: bool routeDeleteList(const QString &gw, const QStringList &ips); QString getgatewayandiface(); void flushDns(); - + bool createTun(const QString &dev, const QString &subnet); + bool deleteTun(const QString &dev); + void StartRoutingIpv6(); + void StopRoutingIpv6(); + bool updateResolvers(const QString& ifname, const QList& resolvers); public slots: private: - RouterLinux() {} + RouterLinux() {m_dnsUtil = new DnsUtilsLinux(this);} RouterLinux(RouterLinux const &) = delete; RouterLinux& operator= (RouterLinux const&) = delete; QList m_addedRoutes; + DnsUtilsLinux *m_dnsUtil; }; #endif // ROUTERLINUX_H diff --git a/service/server/router_mac.cpp b/service/server/router_mac.cpp index f2e88039..d1afb68f 100644 --- a/service/server/router_mac.cpp +++ b/service/server/router_mac.cpp @@ -3,7 +3,8 @@ #include #include -#include + +#include RouterMac &RouterMac::Instance() { @@ -13,14 +14,14 @@ RouterMac &RouterMac::Instance() bool RouterMac::routeAdd(const QString &ipWithSubnet, const QString &gw) { - QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); - QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipWithSubnet); #ifdef MZ_DEBUG qDebug().noquote() << "RouterMac::routeAdd: " << ipWithSubnet << gw; #endif - if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + if (!NetworkUtilities::checkIPv4Format(ip) || !NetworkUtilities::checkIPv4Format(gw)) { qCritical().noquote() << "Critical, trying to add invalid route: " << ip << gw; return false; } @@ -76,19 +77,19 @@ bool RouterMac::clearSavedRoutes() bool RouterMac::routeDelete(const QString &ipWithSubnet, const QString &gw) { - QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); - QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipWithSubnet); #ifdef MZ_DEBUG qDebug().noquote() << "RouterMac::routeDelete: " << ipWithSubnet << gw; #endif - if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + if (!NetworkUtilities::checkIPv4Format(ip) || !NetworkUtilities::checkIPv4Format(gw)) { qCritical().noquote() << "Critical, trying to remove invalid route: " << ip << gw; return false; } - if (ip == "0.0.0.0") { + if (ipWithSubnet == "0.0.0.0/0") { qDebug().noquote() << "Warning, trying to remove default route, skipping: " << ip << gw; return true; } @@ -129,6 +130,42 @@ bool RouterMac::routeDeleteList(const QString &gw, const QStringList &ips) return cnt; } +bool RouterMac::createTun(const QString &dev, const QString &subnet) { + qDebug().noquote() << "createTun start"; + + QProcess process; + QStringList commands; + + commands << "ifconfig" << dev << "inet" << subnet << subnet << "up"; + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start activate tun device!\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not activate tun device!\n"; + return false; + } + commands.clear(); + + return true; +} + +bool RouterMac::updateResolvers(const QString& ifname, const QList& resolvers) +{ + return m_dnsUtil->updateResolvers(ifname, resolvers); +} + + +bool RouterMac::deleteTun(const QString &dev) +{ + qDebug().noquote() << "deleteTun start"; + + return true; +} + void RouterMac::flushDns() { // sudo killall -HUP mDNSResponder diff --git a/service/server/router_mac.h b/service/server/router_mac.h index 210918a4..d23e40fa 100644 --- a/service/server/router_mac.h +++ b/service/server/router_mac.h @@ -8,6 +8,7 @@ #include #include +#include "../client/platforms/macos/daemon/dnsutilsmacos.h" /** * @brief The Router class - General class for handling ip routing @@ -29,15 +30,19 @@ public: bool routeDelete(const QString &ip, const QString &gw); bool routeDeleteList(const QString &gw, const QStringList &ips); void flushDns(); - + bool createTun(const QString &dev, const QString &subnet); + bool deleteTun(const QString &dev); + bool updateResolvers(const QString& ifname, const QList& resolvers); + public slots: private: - RouterMac() {} + RouterMac() {m_dnsUtil = new DnsUtilsMacos(this);} RouterMac(RouterMac const &) = delete; RouterMac& operator= (RouterMac const&) = delete; QList m_addedRoutes; + DnsUtilsMacos *m_dnsUtil; }; #endif // ROUTERMAC_H diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index 0c771f7c..998f4193 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -1,5 +1,4 @@ #include "router_win.h" -#include "../client/utilities.h" #include #include @@ -7,6 +6,8 @@ #include +#include + LONG (NTAPI * NtSuspendProcess)(HANDLE ProcessHandle) = NULL; LONG (NTAPI * NtResumeProcess)(HANDLE ProcessHandle) = NULL; @@ -35,7 +36,7 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) // .arg(ips.join("\n")); - if (!Utils::checkIPv4Format(gw)) { + if (!NetworkUtilities::checkIPv4Format(gw)) { qCritical().noquote() << "Trying to add invalid route, gw: " << gw; return 0; } @@ -58,7 +59,6 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); } - if (dwStatus != ERROR_SUCCESS) { qDebug() << "getIpForwardTable failed."; if (pIpForwardTable) @@ -66,7 +66,6 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) return 0; } - int success_count = 0; MIB_IPFORWARDROW ipfrow; @@ -77,7 +76,6 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) ipfrow.dwForwardType = MIB_IPROUTE_TYPE_INDIRECT; /* XXX - next hop != final dest */ ipfrow.dwForwardProto = MIB_IPPROTO_NETMGMT; /* XXX - MIB_PROTO_NETMGMT */ - // Set iface for route IPAddr dwGwAddr = inet_addr(gw.toStdString().c_str()); if (GetBestInterface(dwGwAddr, &ipfrow.dwForwardIfIndex) != NO_ERROR) { @@ -105,14 +103,14 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) for (int i = 0; i < ips.size(); ++i) { QString ipWithMask = ips.at(i); - QString ip = Utils::ipAddressFromIpWithSubnet(ipWithMask); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipWithMask); - if (!Utils::checkIPv4Format(ip)) { + if (!NetworkUtilities::checkIPv4Format(ip)) { qCritical().noquote() << "Critical, trying to add invalid route, ip: " << ip; continue; } - QString mask = Utils::netMaskFromIpWithSubnet(ipWithMask); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipWithMask); // address ipfrow.dwForwardDest = inet_addr(ip.toStdString().c_str()); @@ -137,7 +135,6 @@ int RouterWin::routeAddList(const QString &gw, const QStringList &ips) } } - // Free resources if (pIpForwardTable) free(pIpForwardTable); @@ -215,8 +212,7 @@ int RouterWin::routeDeleteList(const QString &gw, const QStringList &ips) DWORD dwSize = 0; BOOL bOrder = FALSE; DWORD dwStatus = 0; - ULONG gw_addr= inet_addr(gw.toStdString().c_str()); - + ULONG gw_addr = inet_addr(gw.toStdString().c_str()); // Find out how big our buffer needs to be. dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); @@ -230,7 +226,6 @@ int RouterWin::routeDeleteList(const QString &gw, const QStringList &ips) dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); } - if (dwStatus != ERROR_SUCCESS) { qDebug() << "getIpForwardTable failed."; if (pIpForwardTable) @@ -244,8 +239,8 @@ int RouterWin::routeDeleteList(const QString &gw, const QStringList &ips) for (int i = 0; i < ips.size(); ++i) { QString ipMask = ips.at(i); if (ipMask.isEmpty()) continue; - QString ip = Utils::ipAddressFromIpWithSubnet(ipMask); - QString mask = Utils::netMaskFromIpWithSubnet(ipMask); + QString ip = NetworkUtilities::ipAddressFromIpWithSubnet(ipMask); + QString mask = NetworkUtilities::netMaskFromIpWithSubnet(ipMask); if (ip.isEmpty()) continue; @@ -443,3 +438,53 @@ BOOL RouterWin::SuspendProcess(BOOL fSuspend, DWORD dwProcessId) return ok; } +bool RouterWin::updateResolvers(const QString& ifname, const QList& resolvers) +{ + return m_dnsUtil->updateResolvers(ifname, resolvers); +} + + +void RouterWin::StopRoutingIpv6() +{ + { + QProcess p; + QString command = QString("interface ipv6 add route fc00::/7 interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); + p.start(command); + p.waitForFinished(); + } + { + QProcess p; + QString command = QString("interface ipv6 add route 2000::/4 interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); + p.start(command); + p.waitForFinished(); + } + { + QProcess p; + QString command = QString("interface ipv6 add route 3000::/4 interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); + p.start(command); + p.waitForFinished(); + } +} + +void RouterWin::StartRoutingIpv6() +{ + { + QProcess p; + QString command = QString("interface ipv6 delete route fc00::/7 interface={NetworkInterface.IPv6LoopbackInterfaceIndex}"); + p.start(command); + p.waitForFinished(); + } + { + QProcess p; + QString command = QString("interface ipv6 delete route 2000::/4 interface={NetworkInterface.IPv6LoopbackInterfaceIndex}"); + p.start(command); + p.waitForFinished(); + } + { + QProcess p; + QString command = QString("interface ipv6 delete route 3000::/4 interface={NetworkInterface.IPv6LoopbackInterfaceIndex}"); + p.start(command); + p.waitForFinished(); + } +} + diff --git a/service/server/router_win.h b/service/server/router_win.h index bf23d208..07b5be35 100644 --- a/service/server/router_win.h +++ b/service/server/router_win.h @@ -8,6 +8,7 @@ #include #include +#include "../client/platforms/windows/daemon/dnsutilswindows.h" #include //includes Windows.h #include @@ -41,10 +42,13 @@ public: void flushDns(); void resetIpStack(); + void StartRoutingIpv6(); + void StopRoutingIpv6(); + void suspendWcmSvc(bool suspend); + bool updateResolvers(const QString& ifname, const QList& resolvers); private: - RouterWin() {} RouterWin(RouterWin const &) = delete; RouterWin& operator= (RouterWin const&) = delete; @@ -54,11 +58,11 @@ private: BOOL InitNtFunctions(); BOOL SuspendProcess(BOOL fSuspend, DWORD dwProcessId); - private: + RouterWin() {m_dnsUtil = new DnsUtilsWindows(this);} QMultiMap m_ipForwardRows; bool m_suspended = false; - + DnsUtilsWindows *m_dnsUtil; }; #endif // ROUTERWIN_H