Compare commits

..

1 Commits

Author SHA1 Message Date
MrMirDan
4bf8f52149 update translation RU 2026-08-03 17:48:33 +03:00
112 changed files with 2256 additions and 6809 deletions

2
.gitignore vendored
View File

@@ -10,8 +10,6 @@ deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
.venv/
.cursor*
# Qt-es

View File

@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 5.0.1.0)
set(AMNEZIAVPN_VERSION 5.0.0.5)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2141)
set(APP_ANDROID_VERSION_CODE 2138)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -26,9 +26,6 @@
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#ifdef Q_OS_IOS
#include "platforms/ios/ioscontextmenu.h"
#endif
bool AmneziaApplication::m_forceQuit = false;
@@ -142,10 +139,6 @@ void AmneziaApplication::init()
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
#ifdef Q_OS_IOS
m_engine->rootContext()->setContextProperty("IosContextMenu", new IosContextMenu(this));
#endif
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();

View File

@@ -31,7 +31,6 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
@@ -43,7 +42,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
@@ -51,12 +49,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
)
# The context menu helper uses ARC-only constructs (weak references); the
# rest of the Objective-C++ sources build with manual reference counting.
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.mm
PROPERTIES COMPILE_OPTIONS "-fobjc-arc"
)
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
@@ -77,6 +69,7 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
@@ -85,7 +78,7 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_EMBED_APP_EXTENSIONS networkextension
)
if(DEPLOY)
if(DEFINED DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"

View File

@@ -5,7 +5,6 @@ set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(OBJCXX)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
@@ -34,6 +33,7 @@ set(LIBS ${LIBS}
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
@@ -44,12 +44,19 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
@@ -62,7 +69,6 @@ set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
@@ -73,10 +79,14 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)

View File

@@ -96,17 +96,13 @@ ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credential
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
newClientConfig.headerProtectionKey = configMap.value(configKey::headerProtectionKey);
newClientConfig.contentPaddingAddition = configMap.value(configKey::contentPaddingAddition);
newClientConfig.rekeyAfterTime = configMap.value(configKey::rekeyAfterTime);
newClientConfig.rekeyTimeout = configMap.value(configKey::rekeyTimeout);
newClientConfig.rejectAfterTime = configMap.value(configKey::rejectAfterTime);
newClientConfig.keepaliveTimeout = configMap.value(configKey::keepaliveTimeout);
newClientConfig.maxHandshakeAttempts = configMap.value(configKey::maxHandshakeAttempts);
if (container == DockerContainer::Awg2) {
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
}
newClientConfig.isObfuscationEnabled = false;
protocolConfig.setClientConfig(newClientConfig);
return protocolConfig;

View File

@@ -228,20 +228,11 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
}
}
const bool isAwg3 = awgServerConfig && awgServerConfig->protocolVersion == protocols::awg::awgV3;
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = m_sshSession->replaceVars(scriptData, vars);
// The template lists every possible key, but each parameter is optional -
// drop the lines whose value came out empty
static const QRegularExpression emptyValueLine(R"(^\s*\S+\s*=\s*$)");
auto configTemplateLines = config.split("\n");
configTemplateLines.removeIf([](const QString &line) { return emptyValueLine.match(line).hasMatch(); });
config = configTemplateLines.join("\n");
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return WireGuardProtocolConfig{};
@@ -275,8 +266,7 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
clientConfig.presharedKey = connData.pskKey;
clientConfig.clientId = connData.clientPubKey;
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
clientConfig.persistentKeepAlive = isAwg3 ? protocols::awg::defaultPersistentKeepAlive
: protocols::wireguard::defaultPersistentKeepAlive;
clientConfig.persistentKeepAlive = protocols::wireguard::defaultPersistentKeepAlive;
clientConfig.mtu = mtu;
clientConfig.isObfuscationEnabled = false;

View File

@@ -85,12 +85,6 @@ namespace {
return t.toLower();
}
// xray wants int ranges as "from-to" string, not a {from,to} object.
QString makeRangeString(const QString &minV, const QString &maxV)
{
return minV + QLatin1Char('-') + maxV;
}
void putIntRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
const char *fallbackMax)
{
@@ -100,23 +94,10 @@ namespace {
minV = QString::fromLatin1(fallbackMin);
if (maxV.isEmpty())
maxV = QString::fromLatin1(fallbackMax);
obj[QString::fromUtf8(key)] = makeRangeString(minV, maxV);
}
QString effectiveClientFlow(const amnezia::XrayServerConfig &srv)
{
const bool rawTransport = srv.transport.isEmpty() || srv.transport == QLatin1String("raw");
const bool secureFlow =
srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality");
return (rawTransport && secureFlow) ? srv.flow : QString();
}
QString effectiveSecurity(const amnezia::XrayServerConfig &srv)
{
if (srv.transport == QLatin1String("mkcp") && srv.security == QLatin1String("reality")) {
return QStringLiteral("none");
}
return srv.security;
QJsonObject r;
r[QStringLiteral("from")] = minV.toInt();
r[QStringLiteral("to")] = maxV.toInt();
obj[QString::fromUtf8(key)] = r;
}
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
@@ -216,7 +197,7 @@ QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServ
{
QJsonObject streamSettings = buildStreamSettings(srv, QString());
if (effectiveSecurity(srv) != QLatin1String("reality")) {
if (srv.security != QLatin1String("reality")) {
return streamSettings;
}
@@ -263,10 +244,10 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
<< "appendClient=" << appendNewClient;
const QString flowValue = effectiveClientFlow(srv);
const QString flowValue = srv.flow;
QString realityPublicKey;
QString realityShortId;
if (effectiveSecurity(srv) == QLatin1String("reality")) {
if (srv.security == QLatin1String("reality")) {
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
@@ -382,129 +363,6 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
return ErrorCode::NoError;
}
ErrorCode XrayConfigurator::readContainerKeyFile(DockerContainer container, const ServerCredentials &credentials,
const QString &path, QString &out) const
{
out.clear();
for (int attempt = 0; attempt < 3; ++attempt) {
ErrorCode fileError = ErrorCode::NoError;
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
out.replace(QLatin1Char('\n'), QString());
out.replace(QLatin1Char('\r'), QString());
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
return ErrorCode::NoError;
}
if (attempt < 2) {
QThread::msleep(500);
}
}
logger.error() << "Xray readContainerKeyFile: failed path=" << path;
return ErrorCode::XrayRealityKeysReadFailed;
}
ErrorCode XrayConfigurator::writeServerConfigForSetup(const ServerCredentials &credentials, DockerContainer container,
ContainerConfig &containerConfig, const DnsSettings &dnsSettings)
{
Q_UNUSED(dnsSettings);
namespace px = amnezia::protocols::xray;
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
if (!xrayCfg) {
logger.error() << "Xray writeServerConfigForSetup: missing XrayProtocolConfig";
return ErrorCode::InternalError;
}
const XrayServerConfig &srv = xrayCfg->serverConfig;
if (srv.isThirdPartyConfig) {
logger.info() << "Xray writeServerConfigForSetup: skipped (third-party/native profile)";
return ErrorCode::NoError;
}
logger.info() << "Xray writeServerConfigForSetup: start container=" << static_cast<int>(container)
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port;
ErrorCode errorCode = ErrorCode::NoError;
QString clientId;
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::uuidPath), clientId);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
const QString securityEff = effectiveSecurity(srv);
QString realityPrivateKey;
QString realityPublicKey;
QString realityShortId;
if (securityEff == QLatin1String("reality")) {
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PrivateKeyPath), realityPrivateKey);
if (errorCode != ErrorCode::NoError)
return errorCode;
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PublicKeyPath), realityPublicKey);
if (errorCode != ErrorCode::NoError)
return errorCode;
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::shortidPath), realityShortId);
if (errorCode != ErrorCode::NoError)
return errorCode;
}
QJsonObject streamSettings = buildStreamSettings(srv, clientId);
if (securityEff == QLatin1String("reality")) {
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(px::defaultSite) : srv.site;
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
QJsonObject rs;
rs[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
rs[px::fingerprint] = fpEff;
rs[QStringLiteral("privateKey")] = realityPrivateKey;
rs[px::serverNames] = QJsonArray { sniEff };
rs[QStringLiteral("shortIds")] = QJsonArray { realityShortId };
streamSettings[px::realitySettings] = rs;
}
QJsonObject clientEntry;
clientEntry[px::id] = clientId;
const QString flowValue = effectiveClientFlow(srv);
if (!flowValue.isEmpty()) {
clientEntry[px::flow] = flowValue;
}
QJsonObject settings;
settings[px::clients] = QJsonArray { clientEntry };
settings[QStringLiteral("decryption")] = QStringLiteral("none");
QJsonObject inbound;
inbound[px::port] = srv.port.isEmpty() ? QString(px::defaultPort).toInt() : srv.port.toInt();
inbound[QStringLiteral("protocol")] = QStringLiteral("vless");
inbound[px::settings] = settings;
inbound[px::streamSettings] = streamSettings;
QJsonObject serverConfig;
serverConfig[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
serverConfig[px::inbounds] = QJsonArray { inbound };
serverConfig[px::outbounds] =
QJsonArray { QJsonObject { { QStringLiteral("protocol"), QStringLiteral("freedom") } } };
const QString json = QString::fromUtf8(QJsonDocument(serverConfig).toJson());
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, json,
QString::fromLatin1(px::serverConfigPath),
libssh::ScpOverwriteMode::ScpOverwriteExisting);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray writeServerConfigForSetup: upload failed, error=" << static_cast<int>(errorCode);
return errorCode;
}
XrayProtocolConfig updated =
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Xray writeServerConfigForSetup: buildClientProtocolConfig failed, error="
<< static_cast<int>(errorCode);
return errorCode;
}
containerConfig.protocolConfig = updated;
logger.info() << "Xray writeServerConfigForSetup: done, clientId=" << clientId;
return ErrorCode::NoError;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
@@ -531,9 +389,7 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
QString xrayPublicKey = prefetchedRealityPublicKey;
QString xrayShortId = prefetchedRealityShortId;
const QString securityEff = effectiveSecurity(srv);
if (securityEff == QLatin1String("reality")) {
if (srv.security == QLatin1String("reality")) {
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
if (errorCode != ErrorCode::NoError) {
@@ -545,9 +401,8 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
QJsonObject userObj;
userObj[amnezia::protocols::xray::id] = clientId;
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
const QString flowValue = effectiveClientFlow(srv);
if (!flowValue.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = flowValue;
if (!srv.flow.isEmpty()) {
userObj[amnezia::protocols::xray::flow] = srv.flow;
}
QJsonObject vnextEntry;
@@ -564,7 +419,7 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
outbound[amnezia::protocols::xray::settings] = outboundSettings;
QJsonObject streamObj = buildStreamSettings(srv, clientId);
if (securityEff == QLatin1String("reality")) {
if (srv.security == QLatin1String("reality")) {
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
rs[amnezia::protocols::xray::shortId] = xrayShortId;
@@ -613,24 +468,18 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
networkValue = QStringLiteral("kcp");
streamSettings[px::network] = networkValue;
const QString securityEff = effectiveSecurity(srv);
streamSettings[px::security] = securityEff;
streamSettings[px::security] = srv.security;
if (securityEff == QLatin1String("tls")) {
if (srv.security == QLatin1String("tls")) {
QJsonObject tlsSettings;
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
tlsSettings[px::serverName] = sniEff;
const QString alpnEff = srv.alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : srv.alpn;
QJsonArray alpnArray;
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
QString t = a.trimmed();
if (t.isEmpty())
continue;
if (t.compare(QLatin1String("HTTP/2"), Qt::CaseInsensitive) == 0)
t = QStringLiteral("h2");
else if (t.compare(QLatin1String("HTTP/1.1"), Qt::CaseInsensitive) == 0)
t = QStringLiteral("http/1.1");
alpnArray.append(t);
const QString t = a.trimmed();
if (!t.isEmpty())
alpnArray.append(t);
}
if (!alpnArray.isEmpty())
tlsSettings[QStringLiteral("alpn")] = alpnArray;
@@ -639,7 +488,7 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
streamSettings[QStringLiteral("tlsSettings")] = tlsSettings;
}
if (securityEff == QLatin1String("reality")) {
if (srv.security == QLatin1String("reality")) {
QJsonObject realSettings;
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
realSettings[px::fingerprint] = fpEff;
@@ -655,13 +504,13 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
xo[QStringLiteral("host")] = hostEff;
if (!xhttp.path.isEmpty())
xo[QStringLiteral("path")] = xhttp.path;
QString modeEff = normalizeXhttpMode(xhttp.mode);
if (modeEff == QLatin1String("auto") || modeEff == QLatin1String("packet-up")) {
modeEff = QStringLiteral("stream-one");
}
xo[QStringLiteral("mode")] = modeEff;
xo[QStringLiteral("mode")] = normalizeXhttpMode(xhttp.mode);
// No "Host" in headers: xray rejects it when the top-level "host" field is set.
if (xhttp.headersTemplate.compare(QLatin1String("HTTP"), Qt::CaseInsensitive) == 0) {
QJsonObject headers;
headers[QStringLiteral("Host")] = hostEff;
xo[QStringLiteral("headers")] = headers;
}
const QString methodEff =
xhttp.uplinkMethod.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkMethod) : xhttp.uplinkMethod;
@@ -672,27 +521,27 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
const QString sessPl = normalizeSessionSeqPlacement(xhttp.sessionPlacement);
if (!sessPl.isEmpty())
xo[QStringLiteral("sessionIDPlacement")] = sessPl;
xo[QStringLiteral("sessionPlacement")] = sessPl;
const QString seqPl = normalizeSessionSeqPlacement(xhttp.seqPlacement);
if (!seqPl.isEmpty())
xo[QStringLiteral("seqPlacement")] = seqPl;
if (!xhttp.sessionKey.isEmpty())
xo[QStringLiteral("sessionIDKey")] = xhttp.sessionKey;
xo[QStringLiteral("sessionKey")] = xhttp.sessionKey;
if (!xhttp.seqKey.isEmpty())
xo[QStringLiteral("seqKey")] = xhttp.seqKey;
const QString uDataPl = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
const bool uDataNeedsPacketUp =
uDataPl == QLatin1String("header") || uDataPl == QLatin1String("cookie");
if (!(uDataNeedsPacketUp && modeEff != QLatin1String("packet-up")))
xo[QStringLiteral("uplinkDataPlacement")] = uDataPl;
xo[QStringLiteral("uplinkDataPlacement")] = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
if (!xhttp.uplinkDataKey.isEmpty())
xo[QStringLiteral("uplinkDataKey")] = xhttp.uplinkDataKey;
const QString ucs = xhttp.uplinkChunkSize.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkChunkSize)
: xhttp.uplinkChunkSize;
if (!ucs.isEmpty() && ucs != QLatin1String("0")) {
xo[QStringLiteral("uplinkChunkSize")] = ucs.toInt();
const int v = ucs.toInt();
QJsonObject chunkR;
chunkR[QStringLiteral("from")] = v;
chunkR[QStringLiteral("to")] = v;
xo[QStringLiteral("uplinkChunkSize")] = chunkR;
}
if (!xhttp.scMaxBufferedPosts.isEmpty())
@@ -709,20 +558,17 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
xo[QStringLiteral("xPaddingObfsMode")] = pad.obfsMode;
if (pad.obfsMode) {
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
const int fromV = pad.bytesMin.isEmpty()
? QString::fromLatin1(px::defaultXPaddingBytesMin).toInt()
: pad.bytesMin.toInt();
int toV = pad.bytesMax.isEmpty()
? QString::fromLatin1(px::defaultXPaddingBytesMax).toInt()
: pad.bytesMax.toInt();
QJsonObject br;
const int fromV = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
int toV = pad.bytesMax.isEmpty() ? 256 : pad.bytesMax.toInt();
if (toV < fromV)
toV = fromV;
xo[QStringLiteral("xPaddingBytes")] = makeRangeString(QString::number(fromV), QString::number(toV));
br[QStringLiteral("from")] = fromV;
br[QStringLiteral("to")] = toV;
xo[QStringLiteral("xPaddingBytes")] = br;
}
xo[QStringLiteral("xPaddingKey")] =
pad.key.isEmpty() ? QString::fromLatin1(px::defaultXPaddingKey) : pad.key;
xo[QStringLiteral("xPaddingHeader")] =
pad.header.isEmpty() ? QString::fromLatin1(px::defaultXPaddingHeader) : pad.header;
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
xo[QStringLiteral("xPaddingHeader")] = pad.header.isEmpty() ? QStringLiteral("X-Padding") : pad.header;
xo[QStringLiteral("xPaddingPlacement")] = normalizeXPaddingPlacement(
pad.placement.isEmpty() ? QString::fromLatin1(px::defaultXPaddingPlacement) : pad.placement);
xo[QStringLiteral("xPaddingMethod")] = normalizeXPaddingMethod(
@@ -733,14 +579,12 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
if (xhttp.xmux.enabled) {
QJsonObject mux;
auto addMuxRange = [&](const char *key, const QString &a, const QString &b) {
// omit empty / 0-0 ranges (xray may reject "0-0")
const bool aZero = a.isEmpty() || a == QLatin1String("0");
const bool bZero = b.isEmpty() || b == QLatin1String("0");
if (aZero && bZero)
if (a.isEmpty() && b.isEmpty())
return;
const QString aV = a.isEmpty() ? QStringLiteral("0") : a;
const QString bV = b.isEmpty() ? QStringLiteral("0") : b;
mux[QString::fromUtf8(key)] = makeRangeString(aV, bV);
QJsonObject r;
r[QStringLiteral("from")] = a.isEmpty() ? 0 : a.toInt();
r[QStringLiteral("to")] = b.isEmpty() ? 0 : b.toInt();
mux[QString::fromUtf8(key)] = r;
};
addMuxRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
addMuxRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);

View File

@@ -30,16 +30,7 @@ public:
bool appendNewClient,
QString *outClientId = nullptr);
amnezia::ErrorCode writeServerConfigForSetup(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings);
private:
amnezia::ErrorCode readContainerKeyFile(amnezia::DockerContainer container,
const amnezia::ServerCredentials &credentials,
const QString &path, QString &out) const;
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);

View File

@@ -188,14 +188,24 @@ ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByt
// TODO looks like this block can be removed after v1 configs EOL
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
serverProtocolConfig[configKey::junkPacketCount] = clientProtocolConfig.value(configKey::junkPacketCount);
serverProtocolConfig[configKey::junkPacketMinSize] = clientProtocolConfig.value(configKey::junkPacketMinSize);
serverProtocolConfig[configKey::junkPacketMaxSize] = clientProtocolConfig.value(configKey::junkPacketMaxSize);
serverProtocolConfig[configKey::initPacketJunkSize] = clientProtocolConfig.value(configKey::initPacketJunkSize);
serverProtocolConfig[configKey::responsePacketJunkSize] = clientProtocolConfig.value(configKey::responsePacketJunkSize);
serverProtocolConfig[configKey::initPacketMagicHeader] = clientProtocolConfig.value(configKey::initPacketMagicHeader);
serverProtocolConfig[configKey::responsePacketMagicHeader] = clientProtocolConfig.value(configKey::responsePacketMagicHeader);
serverProtocolConfig[configKey::underloadPacketMagicHeader] = clientProtocolConfig.value(configKey::underloadPacketMagicHeader);
serverProtocolConfig[configKey::transportPacketMagicHeader] = clientProtocolConfig.value(configKey::transportPacketMagicHeader);
for (const QString &key : awgProtocolKeys) {
const QJsonValue value = clientProtocolConfig.value(key);
if (value.isString() && !value.toString().isEmpty()) {
serverProtocolConfig[key] = value;
}
}
serverProtocolConfig[configKey::cookieReplyPacketJunkSize] = clientProtocolConfig.value(configKey::cookieReplyPacketJunkSize);
serverProtocolConfig[configKey::transportPacketJunkSize] = clientProtocolConfig.value(configKey::transportPacketJunkSize);
serverProtocolConfig[configKey::specialJunk1] = clientProtocolConfig.value(configKey::specialJunk1);
serverProtocolConfig[configKey::specialJunk2] = clientProtocolConfig.value(configKey::specialJunk2);
serverProtocolConfig[configKey::specialJunk3] = clientProtocolConfig.value(configKey::specialJunk3);
serverProtocolConfig[configKey::specialJunk4] = clientProtocolConfig.value(configKey::specialJunk4);
serverProtocolConfig[configKey::specialJunk5] = clientProtocolConfig.value(configKey::specialJunk5);
serverProtocolConfig[configKey::headerProtectionKey] = clientProtocolConfig.value(configKey::headerProtectionKey);
//

View File

@@ -1,7 +1,6 @@
#include "ipSplitTunnelingController.h"
#include "core/utils/networkUtilities.h"
#include <QJsonObject>
#include <QDebug>
IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent)
: QObject(parent),
@@ -15,56 +14,47 @@ IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsReposito
fillSites();
}
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QStringList &ips)
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip)
{
QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode);
if (existing.contains(hostname) && ips.isEmpty()) {
if (existing.contains(hostname) && ip.isEmpty()) {
return false;
}
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname) {
bool changed = false;
for (const QString &ip : ips) {
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
m_sites[i].second.append(ip);
changed = true;
}
}
if (!changed) {
return false;
}
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
m_sites[i].second = ip;
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
return true;
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
return false;
}
}
m_sites.append(qMakePair(hostname, ips));
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
m_sites.append(qMakePair(hostname, ip));
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
return true;
}
void IpSplitTunnelingController::addSites(const QMap<QString, QStringList> &sites, bool replaceExisting)
void IpSplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
{
if (replaceExisting) {
m_sites.clear();
}
for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) {
const QString &hostname = it.key();
const QStringList &ips = it.value();
const QString &ip = it.value();
bool found = false;
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname) {
for (const QString &ip : ips) {
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
m_sites[i].second.append(ip);
}
if (!ip.isEmpty()) {
m_sites[i].second = ip;
}
found = true;
break;
}
}
if (!found) {
m_sites.append(qMakePair(hostname, ips));
m_sites.append(qMakePair(hostname, ip));
}
}
if (replaceExisting) {
@@ -82,11 +72,11 @@ bool IpSplitTunnelingController::addSite(const QString &hostname)
}
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) {
processSite(normalizedHostname, {});
processSite(normalizedHostname, "");
return true;
}
if (addSiteInternal(normalizedHostname, {})) {
if (addSiteInternal(normalizedHostname, "")) {
QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo)));
return true;
}
@@ -134,7 +124,7 @@ bool IpSplitTunnelingController::isSplitTunnelingEnabled() const
return m_appSettingsRepository->isSitesSplitTunnelingEnabled();
}
QVector<QPair<QString, QStringList>> IpSplitTunnelingController::getCurrentSites() const
QVector<QPair<QString, QString>> IpSplitTunnelingController::getCurrentSites() const
{
return m_sites;
}
@@ -144,7 +134,7 @@ void IpSplitTunnelingController::fillSites()
QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode);
m_sites.clear();
for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) {
m_sites.append(qMakePair(it.key(), SecureAppSettingsRepository::siteIpList(it.value())));
m_sites.append(qMakePair(it.key(), it.value().toString()));
}
}
@@ -174,40 +164,29 @@ void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo)
{
const QList<QHostAddress> &addresses = hostInfo.addresses();
QString hostname = hostInfo.hostName();
QStringList allIpv4;
for (const QHostAddress &addr : addresses) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
allIpv4.append(addr.toString());
}
}
allIpv4.removeDuplicates();
qDebug() << "[SplitTunneling] Host resolved:" << hostname
<< "-> adding all IPv4 addresses to list:" << allIpv4;
if (!allIpv4.isEmpty()) {
processSiteAfterResolve(hostname, allIpv4);
}
}
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QStringList &ips)
{
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname) {
for (const QString &ip : ips) {
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
m_sites[i].second.append(ip);
}
}
processSiteAfterResolve(hostname, addr.toString());
break;
}
}
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
}
void IpSplitTunnelingController::processSite(const QString &hostname, const QStringList &ips)
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip)
{
addSiteInternal(hostname, ips);
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) {
m_sites[i].second = ip;
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
break;
}
}
}
void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip)
{
addSiteInternal(hostname, ip);
}
bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage)
@@ -226,25 +205,12 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
}
QJsonArray jsonArray = jsonDocument.array();
QMap<QString, QStringList> sites;
QMap<QString, QString> sites;
for (auto jsonValue : jsonArray) {
QJsonObject jsonObject = jsonValue.toObject();
QString hostname = jsonObject.value("hostname").toString("");
QStringList ips;
if (jsonObject.value("ips").isArray()) {
const QJsonArray ipsArray = jsonObject.value("ips").toArray();
for (const auto &ipValue : ipsArray) {
ips.append(ipValue.toString());
}
}
const QString singleIp = jsonObject.value("ip").toString("");
if (!singleIp.isEmpty()) {
ips.append(singleIp);
}
ips.removeAll(QString());
ips.removeDuplicates();
QString ip = jsonObject.value("ip").toString("");
QString normalizedHostname = normalizeHostname(hostname);
@@ -253,7 +219,7 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
continue;
}
sites.insert(normalizedHostname, ips);
sites.insert(normalizedHostname, ip);
}
addSites(sites, replaceExisting);
@@ -263,21 +229,13 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
QByteArray IpSplitTunnelingController::exportSitesToJson() const
{
QVector<QPair<QString, QStringList>> sites = getCurrentSites();
QVector<QPair<QString, QString>> sites = getCurrentSites();
QJsonArray jsonArray;
for (const auto &site : sites) {
QJsonObject jsonObject;
jsonObject["hostname"] = site.first;
QJsonArray ipsArray;
for (const QString &ip : site.second) {
ipsArray.append(ip);
}
jsonObject["ips"] = ipsArray;
// Keep the legacy "ip" field (first address) for backward compatibility.
jsonObject["ip"] = site.second.isEmpty() ? QString() : site.second.first();
jsonObject["ip"] = site.second;
jsonArray.append(jsonObject);
}

View File

@@ -25,7 +25,7 @@ public:
explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr);
bool addSite(const QString &hostname);
void addSites(const QMap<QString, QStringList> &sites, bool replaceExisting);
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
bool removeSite(const QString &hostname);
void removeSites();
void setRouteMode(RouteMode routeMode);
@@ -33,7 +33,7 @@ public:
RouteMode getRouteMode() const;
bool isSplitTunnelingEnabled() const;
QVector<QPair<QString, QStringList>> getCurrentSites() const;
QVector<QPair<QString, QString>> getCurrentSites() const;
bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage);
QByteArray exportSitesToJson() const;
@@ -43,15 +43,15 @@ private slots:
private:
void fillSites();
bool addSiteInternal(const QString &hostname, const QStringList &ips);
bool addSiteInternal(const QString &hostname, const QString &ip);
QString normalizeHostname(const QString &hostname) const;
bool validateHostname(const QString &hostname) const;
void processSiteAfterResolve(const QString &hostname, const QStringList &ips);
void processSite(const QString &hostname, const QStringList &ips);
void processSiteAfterResolve(const QString &hostname, const QString &ip);
void processSite(const QString &hostname, const QString &ip);
SecureAppSettingsRepository* m_appSettingsRepository;
RouteMode m_currentRouteMode;
QVector<QPair<QString, QStringList>> m_sites;
QVector<QPair<QString, QString>> m_sites;
};
#endif // IPSPLITTUNNELINGCONTROLLER_H

View File

@@ -572,19 +572,47 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
QString protocolName = configKey::wireguard;
QString protocolVersion;
ConfigTypes detectedType = ConfigTypes::WireGuard;
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
configKey::transportPacketMagicHeader };
bool hasAwgKeys = std::any_of(awgProtocolKeys.begin(), awgProtocolKeys.end(),
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
if (hasAwgKeys) {
for (const QString &key : awgProtocolKeys) {
if (!configMap.value(key).isEmpty()) {
lastConfig[key] = configMap.value(key);
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
configKey::transportPacketJunkSize,
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
configKey::specialJunk4, configKey::specialJunk5
};
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
if (hasAllRequiredFields) {
for (const QString &field : requiredJunkFields) {
lastConfig[field] = configMap.value(field);
}
for (const QString &field : optionalJunkFields) {
if (!configMap.value(field).isEmpty()) {
lastConfig[field] = configMap.value(field);
}
}
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
!configMap.value(configKey::specialJunk2).isEmpty() ||
!configMap.value(configKey::specialJunk3).isEmpty() ||
!configMap.value(configKey::specialJunk4).isEmpty() ||
!configMap.value(configKey::specialJunk5).isEmpty();
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
protocolVersion = "2";
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
protocolVersion = "1.5";
}
protocolName = configKey::awg;
detectedType = ConfigTypes::Awg;
}
@@ -602,6 +630,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
wireguardConfig[configKey::isThirdPartyConfig] = true;
wireguardConfig[configKey::port] = port;
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
wireguardConfig[configKey::protocolVersion] = protocolVersion;
}
QJsonObject containers;
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;

View File

@@ -11,7 +11,6 @@
#include <QtConcurrent>
#include "core/configurators/configuratorBase.h"
#include "core/configurators/xrayConfigurator.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
@@ -153,15 +152,6 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer configureContainerWorker finished";
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession);
e = xrayConfigurator.writeServerConfigForSetup(credentials, container, config, dnsSettings);
if (e)
return e;
qDebug().noquote() << "InstallController::setupContainer xray writeServerConfigForSetup finished";
}
setupServerFirewall(credentials, sshSession);
qDebug().noquote() << "InstallController::setupContainer setupServerFirewall finished";
@@ -201,21 +191,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
SshSession sshSession;
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
reinstallRequired = true;
}
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
ErrorCode errorCode = ErrorCode::NoError;
if (reinstallRequired) {
errorCode = setupContainer(credentials, container, newConfig, true);
// Reinstall pulls the latest container image, so the server runs the latest protocol version
if (errorCode == ErrorCode::NoError && container == DockerContainer::Awg2) {
if (auto* awgConfig = newConfig.getAwgProtocolConfig()) {
awgConfig->serverConfig.protocolVersion = protocols::awg::awgV3;
}
}
} else {
errorCode = configureContainerWorker(credentials, container, newConfig, sshSession);
if (errorCode == ErrorCode::NoError) {
@@ -417,11 +397,6 @@ ErrorCode InstallController::prepareContainerConfig(DockerContainer container, c
}
if (ContainerUtils::containerService(container) != ServiceType::Other) {
if ((container == DockerContainer::Xray || container == DockerContainer::SSXray)
&& containerConfig.protocolConfig.hasClientConfig()) {
return ErrorCode::NoError;
}
Proto protocol = ContainerUtils::defaultProtocol(container);
DnsSettings dnsSettings = {
@@ -509,12 +484,6 @@ ErrorCode InstallController::buildContainerWorker(const ServerCredentials &crede
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
return ErrorCode::DockerPullRateLimit;
if (stdOut.contains("returned a non-zero code")
|| stdOut.contains("failed to solve")
|| stdOut.contains("Unable to find image")
|| stdOut.contains("Couldn't connect to server"))
return ErrorCode::ServerDockerFailedError;
return error;
}
@@ -539,8 +508,6 @@ ErrorCode InstallController::runContainerWorker(const ServerCredentials &credent
return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("invalid publish"))
return ErrorCode::ServerDockerFailedError;
if (stdOut.contains("Unable to find image") || stdOut.contains("No such image"))
return ErrorCode::ServerDockerFailedError;
return e;
}

View File

@@ -1,10 +1,11 @@
#include "awgInstaller.h"
#include <QPair>
#include <QRandomGenerator>
#include <QSet>
#include <QStringList>
#include <QVector>
#include "core/configurators/wireguardConfigurator.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
@@ -27,57 +28,109 @@ AwgInstaller::AwgInstaller(QObject *parent)
ContainerConfig AwgInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto)
{
ContainerConfig config = createBaseConfig(container, port, transportProto);
bool isAwg2 = (container == DockerContainer::Awg2);
if (auto* awgConfig = config.getAwgProtocolConfig()) {
generateAwgParameters(awgConfig->serverConfig);
awgConfig->serverConfig.protocolVersion = protocols::awg::awgV3;
generateAwgParameters(awgConfig->serverConfig, isAwg2);
if (isAwg2) {
awgConfig->serverConfig.protocolVersion = "2";
}
}
return config;
}
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig)
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig, bool isAwg2)
{
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50);
int s1 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::initPacketJunkSizeMax);
int s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
int s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
int s4 = protocols::awg::defaultTransportPacketJunkSize;
int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150);
int s3 = QRandomGenerator::global()->bounded(0, 64);
int s4 = QRandomGenerator::global()->bounded(0, 20);
// Ensure all values are unique and don't create equal packet sizes
QSet<int> usedValues { s1, s4 };
QSet<int> usedValues;
usedValues.insert(s1);
while (usedValues.contains(s2) || s1 + amnezia::AwgConstant::messageInitiationSize == s2 + amnezia::AwgConstant::messageResponseSize) {
s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
s2 = QRandomGenerator::global()->bounded(15, 150);
}
usedValues.insert(s2);
while (usedValues.contains(s3) || s1 + amnezia::AwgConstant::messageInitiationSize == s3 + amnezia::AwgConstant::messageCookieReplySize
|| s2 + amnezia::AwgConstant::messageResponseSize == s3 + amnezia::AwgConstant::messageCookieReplySize) {
s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
s3 = QRandomGenerator::global()->bounded(0, 64);
}
usedValues.insert(s3);
while (usedValues.contains(s4)) {
s4 = QRandomGenerator::global()->bounded(0, 20);
}
QString initPacketJunkSize = QString::number(s1);
QString responsePacketJunkSize = QString::number(s2);
QString cookieReplyPacketJunkSize = QString::number(s3);
QString transportPacketJunkSize = QString::number(s4);
QString initPacketMagicHeader;
QString responsePacketMagicHeader;
QString underloadPacketMagicHeader;
QString transportPacketMagicHeader;
if (isAwg2) {
// AWG 2.0: use range format for magic headers
QVector<QPair<QString, QString>> headersValue;
int min = 5;
auto max = (std::numeric_limits<qint32>::max)();
while (headersValue.size() != 4) {
auto first = QRandomGenerator::global()->bounded(min, max);
auto second = QRandomGenerator::global()->bounded(first, max);
min = second;
headersValue.push_back(QPair<QString, QString>(QString::number(first), QString::number(second)));
}
initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second;
responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second;
underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second;
transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second;
} else {
// AWG legacy: use single values for magic headers
QSet<QString> headersValue;
while (headersValue.size() != 4) {
auto max = (std::numeric_limits<qint32>::max)();
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max)));
}
auto headersValueList = headersValue.values();
initPacketMagicHeader = headersValueList.at(0);
responsePacketMagicHeader = headersValueList.at(1);
underloadPacketMagicHeader = headersValueList.at(2);
transportPacketMagicHeader = headersValueList.at(3);
}
serverConfig.junkPacketCount = junkPacketCount;
serverConfig.junkPacketMinSize = junkPacketMinSize;
serverConfig.junkPacketMaxSize = junkPacketMaxSize;
serverConfig.initPacketJunkSize = QString::number(s1);
serverConfig.responsePacketJunkSize = QString::number(s2);
serverConfig.cookieReplyPacketJunkSize = QString::number(s3);
serverConfig.transportPacketJunkSize = QString::number(s4);
serverConfig.initPacketJunkSize = initPacketJunkSize;
serverConfig.responsePacketJunkSize = responsePacketJunkSize;
serverConfig.initPacketMagicHeader = initPacketMagicHeader;
serverConfig.responsePacketMagicHeader = responsePacketMagicHeader;
serverConfig.underloadPacketMagicHeader = underloadPacketMagicHeader;
serverConfig.transportPacketMagicHeader = transportPacketMagicHeader;
serverConfig.initPacketMagicHeader = protocols::awg::defaultInitPacketMagicHeader;
serverConfig.responsePacketMagicHeader = protocols::awg::defaultResponsePacketMagicHeader;
serverConfig.underloadPacketMagicHeader = protocols::awg::defaultUnderloadPacketMagicHeader;
serverConfig.transportPacketMagicHeader = protocols::awg::defaultTransportPacketMagicHeader;
serverConfig.cookieReplyPacketJunkSize = cookieReplyPacketJunkSize;
serverConfig.transportPacketJunkSize = transportPacketJunkSize;
serverConfig.headerProtectionKey = WireguardConfigurator::genClientKeys().clientPrivKey;
serverConfig.contentPaddingAddition = protocols::awg::defaultContentPaddingAddition;
serverConfig.rekeyAfterTime = protocols::awg::defaultRekeyAfterTime;
serverConfig.rekeyTimeout = protocols::awg::defaultRekeyTimeout;
serverConfig.rejectAfterTime = protocols::awg::defaultRejectAfterTime;
serverConfig.keepaliveTimeout = protocols::awg::defaultKeepaliveTimeout;
serverConfig.maxHandshakeAttempts = protocols::awg::defaultMaxHandshakeAttempts;
serverConfig.specialJunk1 = protocols::awg::defaultSpecialJunk1;
serverConfig.specialJunk2 = protocols::awg::defaultSpecialJunk2;
serverConfig.specialJunk3 = protocols::awg::defaultSpecialJunk3;
serverConfig.specialJunk4 = protocols::awg::defaultSpecialJunk4;
serverConfig.specialJunk5 = protocols::awg::defaultSpecialJunk5;
}
ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
@@ -134,20 +187,14 @@ ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, co
awgConfig->serverConfig.specialJunk4 = serverConfigMap.value(QString("# ") + configKey::specialJunk4);
awgConfig->serverConfig.specialJunk5 = serverConfigMap.value(QString("# ") + configKey::specialJunk5);
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
awgConfig->serverConfig.headerProtectionKey = serverConfigMap.value(configKey::headerProtectionKey);
awgConfig->serverConfig.contentPaddingAddition = serverConfigMap.value(configKey::contentPaddingAddition);
awgConfig->serverConfig.rekeyAfterTime = serverConfigMap.value(configKey::rekeyAfterTime);
awgConfig->serverConfig.rekeyTimeout = serverConfigMap.value(configKey::rekeyTimeout);
awgConfig->serverConfig.rejectAfterTime = serverConfigMap.value(configKey::rejectAfterTime);
awgConfig->serverConfig.keepaliveTimeout = serverConfigMap.value(configKey::keepaliveTimeout);
awgConfig->serverConfig.maxHandshakeAttempts = serverConfigMap.value(configKey::maxHandshakeAttempts);
awgConfig->serverConfig.protocolVersion = awgConfig->serverProtocolVersion();
// AWG 2.0 specific fields
if (container == DockerContainer::Awg2) {
awgConfig->serverConfig.protocolVersion = "2";
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
}
}
return ErrorCode::NoError;
}

View File

@@ -14,7 +14,7 @@ public:
SshSession* serverController, amnezia::ContainerConfig &config) override;
private:
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig);
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig, bool isAwg2 = false);
};
#endif // AWGINSTALLER_H

View File

@@ -56,7 +56,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
AwgProtocolConfig awgConfig;
awgConfig.serverConfig.port = portStr;
awgConfig.serverConfig.transportProto = transportProtoStr;
awgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
config.protocolConfig = awgConfig;
break;
}
@@ -64,7 +63,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
WireGuardProtocolConfig wgConfig;
wgConfig.serverConfig.port = portStr;
wgConfig.serverConfig.transportProto = transportProtoStr;
wgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
config.protocolConfig = wgConfig;
break;
}
@@ -78,16 +76,8 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
case Proto::Xray:
case Proto::SSXray: {
XrayProtocolConfig xrayConfig;
XrayServerConfig &srv = xrayConfig.serverConfig;
srv.port = portStr;
srv.transportProto = transportProtoStr;
srv.transport = protocols::xray::defaultTransport;
srv.security = protocols::xray::defaultSecurity;
srv.flow = protocols::xray::defaultFlow;
srv.site = protocols::xray::defaultSite;
srv.sni = protocols::xray::defaultSni;
srv.fingerprint = protocols::xray::defaultFingerprint;
srv.alpn = protocols::xray::defaultAlpn;
xrayConfig.serverConfig.port = portStr;
xrayConfig.serverConfig.transportProto = transportProtoStr;
config.protocolConfig = xrayConfig;
break;
}

View File

@@ -2,7 +2,6 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
@@ -21,8 +20,6 @@ namespace {
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxySecretPath("/data/secret");
constexpr QLatin1String kMtProxyMetaPath("/data/mtproxy-meta");
constexpr QLatin1String kMtProxyStartScriptPath("/opt/amnezia/start.sh");
}
MtProxyInstaller::MtProxyInstaller(QObject *parent)
@@ -56,98 +53,14 @@ ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container
}
}
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
const auto addExtra = [&](const QString &s) {
if (hex32.match(s).hasMatch() && !mt->additionalSecrets.contains(s)) {
mt->additionalSecrets.append(s);
}
};
ErrorCode secretErr = ErrorCode::NoError;
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
mt->secret = sec;
}
bool metaRestored = false;
ErrorCode metaErr = ErrorCode::NoError;
const QByteArray metaRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyMetaPath), metaErr);
if (metaErr == ErrorCode::NoError && !metaRaw.trimmed().isEmpty()) {
QString mode, domain, workersMode, workers, natInternal, natExternal;
bool natEnabled = false;
const QList<QByteArray> lines = metaRaw.split('\n');
for (const QByteArray &rawLine : lines) {
const QString line = QString::fromUtf8(rawLine).trimmed();
const int eq = line.indexOf('=');
if (eq < 0) {
continue;
}
const QString key = line.left(eq);
const QString val = line.mid(eq + 1).trimmed();
if (key == QLatin1String("mode")) mode = val;
else if (key == QLatin1String("domain")) domain = val;
else if (key == QLatin1String("tag")) { if (mt->tag.isEmpty()) mt->tag = val; }
else if (key == QLatin1String("additional")) {
for (const QString &s : val.split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
}
else if (key == QLatin1String("workers_mode")) workersMode = val;
else if (key == QLatin1String("workers")) workers = val;
else if (key == QLatin1String("nat_enabled")) natEnabled = (val == QLatin1String("1"));
else if (key == QLatin1String("nat_internal")) natInternal = val;
else if (key == QLatin1String("nat_external")) natExternal = val;
else if (key == QLatin1String("public_host")) { if (mt->publicHost.isEmpty()) mt->publicHost = val; }
}
if (!mode.isEmpty()) {
mt->transportMode = mode;
if (!domain.isEmpty()) mt->tlsDomain = domain;
if (!workersMode.isEmpty()) mt->workersMode = workersMode;
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual) && !workers.isEmpty()) {
mt->workers = workers;
}
if (natEnabled) {
mt->natEnabled = true;
mt->natInternalIp = natInternal;
mt->natExternalIp = natExternal;
}
metaRestored = true;
}
}
if (!metaRestored) {
ErrorCode startErr = ErrorCode::NoError;
const QByteArray startRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyStartScriptPath), startErr);
if (startErr == ErrorCode::NoError && !startRaw.trimmed().isEmpty()) {
const QString start = QString::fromUtf8(startRaw);
static const QRegularExpression modeRe(QStringLiteral("\\[ \"(standard|faketls)\" = \"faketls\" \\]"));
const QRegularExpressionMatch m = modeRe.match(start);
if (m.hasMatch()) {
mt->transportMode = m.captured(1);
if (m.captured(1) == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) {
static const QRegularExpression domRe(QStringLiteral("--domain ([A-Za-z0-9.\\-]+)"));
const QRegularExpressionMatch dm = domRe.match(start);
if (dm.hasMatch()) mt->tlsDomain = dm.captured(1);
}
}
static const QRegularExpression tagRe(QStringLiteral("-P ([0-9a-fA-F]{32})"));
const QRegularExpressionMatch tm = tagRe.match(start);
if (tm.hasMatch() && mt->tag.isEmpty()) mt->tag = tm.captured(1);
static const QRegularExpression addRe(QStringLiteral("echo \"([0-9a-fA-F,]+)\" \\| tr ',' ' '"));
const QRegularExpressionMatch am = addRe.match(start);
if (am.hasMatch()) {
for (const QString &s : am.captured(1).split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
}
static const QRegularExpression natRe(QStringLiteral("NAT_VALUE=\"([0-9.]+):([0-9.]+)\""));
const QRegularExpressionMatch nm = natRe.match(start);
if (nm.hasMatch()) {
mt->natEnabled = true;
mt->natInternalIp = nm.captured(1);
mt->natExternalIp = nm.captured(2);
}
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
mt->secret = sec;
}
}

View File

@@ -2,7 +2,6 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
@@ -20,7 +19,6 @@ namespace {
constexpr QLatin1String kTelemtClientJsonPath("/data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtClientJsonUploadPath("data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtSecretPath("/data/secret");
constexpr QLatin1String kTelemtConfigTomlPath("/data/config.toml");
}
TelemtInstaller::TelemtInstaller(QObject *parent) : InstallerBase(parent) {}
@@ -56,83 +54,10 @@ ErrorCode TelemtInstaller::extractConfigFromContainer(DockerContainer container,
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtSecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
tc->secret = sec;
}
ErrorCode tomlErr = ErrorCode::NoError;
const QByteArray tomlRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtConfigTomlPath), tomlErr);
if (tomlErr == ErrorCode::NoError && !tomlRaw.trimmed().isEmpty()) {
QString section;
bool userNameSet = false;
const QList<QByteArray> lines = tomlRaw.split('\n');
for (const QByteArray &rawLine : lines) {
const QString line = QString::fromUtf8(rawLine).trimmed();
if (line.isEmpty() || line.startsWith('#')) {
continue;
}
if (line.startsWith('[')) {
section = line;
continue;
}
const int eq = line.indexOf('=');
if (eq < 0) {
continue;
}
const QString key = line.left(eq).trimmed();
QString val = line.mid(eq + 1).trimmed();
if (val.startsWith('"')) {
const int last = val.lastIndexOf('"');
val = (last > 0) ? val.mid(1, last - 1) : val.mid(1);
} else {
const int inlineComment = val.indexOf(QLatin1String(" #"));
if (inlineComment >= 0) {
val = val.left(inlineComment).trimmed();
}
}
if (section == QLatin1String("[access.users]")) {
if (key.startsWith(QLatin1String("extra"))) {
if (hex32.match(val).hasMatch() && !tc->additionalSecrets.contains(val)) {
tc->additionalSecrets.append(val);
}
} else if (!userNameSet) {
tc->userName = key;
userNameSet = true;
if (tc->secret.isEmpty() && hex32.match(val).hasMatch()) {
tc->secret = val;
}
}
continue;
}
if (key == QLatin1String("tls") && section == QLatin1String("[general.modes]")) {
tc->transportMode = (val == QLatin1String("true"))
? QString::fromUtf8(protocols::telemt::transportModeFakeTLS)
: QString::fromUtf8(protocols::telemt::transportModeStandard);
} else if (key == QLatin1String("tls_domain") && section == QLatin1String("[censorship]")) {
tc->tlsDomain = val;
} else if (key == QLatin1String("mask") && section == QLatin1String("[censorship]")) {
tc->maskEnabled = (val == QLatin1String("true"));
} else if (key == QLatin1String("tls_emulation") && section == QLatin1String("[censorship]")) {
tc->tlsEmulation = (val == QLatin1String("true"));
} else if (key == QLatin1String("use_middle_proxy") && section == QLatin1String("[general]")) {
tc->useMiddleProxy = (val == QLatin1String("true"));
} else if (key == QLatin1String("middle_proxy_nat_ip") && section == QLatin1String("[general]")) {
if (!val.isEmpty()) {
tc->natExternalIp = val;
tc->natEnabled = true;
}
} else if (key == QLatin1String("ad_tag") && section == QLatin1String("[general]") && tc->tag.isEmpty()) {
tc->tag = val;
} else if (key == QLatin1String("public_host") && section == QLatin1String("[general.links]")
&& tc->publicHost.isEmpty()) {
tc->publicHost = val;
} else if (key == QLatin1String("port") && section == QLatin1String("[server]") && tc->port.isEmpty()) {
tc->port = val;
}
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
tc->secret = sec;
}
}

View File

@@ -26,31 +26,6 @@ namespace
}
return fp;
}
// Parse an xray int range: "from-to" string, plain int, or legacy {from,to} object.
void parseIntRange(const QJsonValue &v, QString &minOut, QString &maxOut)
{
if (v.isString()) {
const QString s = v.toString().trimmed();
const int dash = s.indexOf(QLatin1Char('-'), 1);
if (dash > 0) {
minOut = s.left(dash).trimmed();
maxOut = s.mid(dash + 1).trimmed();
} else if (!s.isEmpty()) {
minOut = s;
maxOut = s;
}
} else if (v.isDouble()) {
minOut = QString::number(v.toInt());
maxOut = minOut;
} else if (v.isObject()) {
const QJsonObject o = v.toObject();
if (o.contains(QLatin1String("from")) || o.contains(QLatin1String("to"))) {
minOut = QString::number(o.value(QLatin1String("from")).toInt());
maxOut = QString::number(o.value(QLatin1String("to")).toInt());
}
}
}
}
using namespace amnezia;
@@ -150,13 +125,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
QJsonArray alpnArr = tls.value("alpn").toArray();
QStringList alpnList;
for (const QJsonValue &v : alpnArr) {
QString t = v.toString().trimmed();
if (t.compare(QLatin1String("HTTP/2"), Qt::CaseInsensitive) == 0)
t = QStringLiteral("h2");
else if (t.compare(QLatin1String("HTTP/1.1"), Qt::CaseInsensitive) == 0)
t = QStringLiteral("http/1.1");
if (!t.isEmpty())
alpnList << t;
alpnList << v.toString();
}
srv.alpn = alpnList.join(",");
}
@@ -190,6 +159,12 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
srv.xhttp.host = xhttpObj.value("host").toString();
srv.xhttp.path = xhttpObj.value("path").toString();
{
const QJsonObject hdrs = xhttpObj.value("headers").toObject();
if (hdrs.contains(QLatin1String("Host")) || !hdrs.isEmpty())
srv.xhttp.headersTemplate = QStringLiteral("HTTP");
}
if (xhttpObj.contains(QLatin1String("uplinkHTTPMethod")))
srv.xhttp.uplinkMethod = xhttpObj.value("uplinkHTTPMethod").toString();
else
@@ -209,9 +184,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
return QStringLiteral("Query");
return core;
};
QString sess = xhttpObj.value("sessionIDPlacement").toString();
if (sess.isEmpty())
sess = xhttpObj.value("sessionPlacement").toString();
QString sess = xhttpObj.value("sessionPlacement").toString();
if (sess.isEmpty())
sess = xhttpObj.value("scSessionPlacement").toString();
srv.xhttp.sessionPlacement = sessionSeqUi(sess);
@@ -237,17 +210,14 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
udata = xhttpObj.value("scUplinkDataPlacement").toString();
srv.xhttp.uplinkDataPlacement = uplinkDataUi(udata);
srv.xhttp.sessionKey = xhttpObj.value("sessionIDKey").toString();
if (srv.xhttp.sessionKey.isEmpty())
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
srv.xhttp.seqKey = xhttpObj.value("seqKey").toString();
srv.xhttp.uplinkDataKey = xhttpObj.value("uplinkDataKey").toString();
if (xhttpObj.contains(QLatin1String("uplinkChunkSize"))) {
QString ucMin, ucMax;
parseIntRange(xhttpObj.value("uplinkChunkSize"), ucMin, ucMax);
if (!ucMin.isEmpty())
srv.xhttp.uplinkChunkSize = ucMin;
QJsonObject uc = xhttpObj.value("uplinkChunkSize").toObject();
if (!uc.isEmpty())
srv.xhttp.uplinkChunkSize = QString::number(uc.value("from").toInt());
} else if (xhttpObj.contains(QLatin1String("xhttpUplinkChunkSize"))) {
srv.xhttp.uplinkChunkSize = QString::number(xhttpObj.value("xhttpUplinkChunkSize").toInt());
}
@@ -256,7 +226,11 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
}
auto readRange = [&](const char *key, QString &minOut, QString &maxOut) {
parseIntRange(xhttpObj.value(QLatin1String(key)), minOut, maxOut);
QJsonObject r = xhttpObj.value(QLatin1String(key)).toObject();
if (!r.isEmpty()) {
minOut = QString::number(r.value("from").toInt());
maxOut = QString::number(r.value("to").toInt());
}
};
readRange("scMaxEachPostBytes", srv.xhttp.scMaxEachPostBytesMin, srv.xhttp.scMaxEachPostBytesMax);
readRange("scMinPostsIntervalMs", srv.xhttp.scMinPostsIntervalMsMin, srv.xhttp.scMinPostsIntervalMsMax);
@@ -269,11 +243,10 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
srv.xhttp.xPadding.header = pad.value("xPaddingHeader").toString();
srv.xhttp.xPadding.placement = pad.value("xPaddingPlacement").toString();
srv.xhttp.xPadding.method = pad.value("xPaddingMethod").toString();
QString bytesMin, bytesMax;
parseIntRange(pad.value("xPaddingBytes"), bytesMin, bytesMax);
if (!bytesMin.isEmpty()) {
srv.xhttp.xPadding.bytesMin = bytesMin;
srv.xhttp.xPadding.bytesMax = bytesMax;
QJsonObject bytesRange = pad.value("xPaddingBytes").toObject();
if (!bytesRange.isEmpty()) {
srv.xhttp.xPadding.bytesMin = QString::number(bytesRange.value("from").toInt());
srv.xhttp.xPadding.bytesMax = QString::number(bytesRange.value("to").toInt());
}
QString pl = srv.xhttp.xPadding.placement.toLower();
if (pl == QLatin1String("cookie"))
@@ -291,7 +264,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
srv.xhttp.xPadding.method = QStringLiteral("Tokenish");
};
if (xhttpObj.contains(QLatin1String("xPaddingObfsMode")) || xhttpObj.contains(QLatin1String("xPaddingKey"))
|| xhttpObj.contains(QLatin1String("xPaddingBytes"))) {
|| !xhttpObj.value("xPaddingBytes").toObject().isEmpty()) {
loadPaddingFromObject(xhttpObj);
} else if (xhttpObj.contains(QLatin1String("xPadding")) && xhttpObj.value("xPadding").isObject()) {
const QJsonObject nested = xhttpObj.value("xPadding").toObject();
@@ -307,7 +280,11 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
srv.xhttp.xmux.enabled = true;
auto readMuxRange = [&](const char *key, QString &minOut, QString &maxOut) {
parseIntRange(mux.value(QLatin1String(key)), minOut, maxOut);
QJsonObject r = mux.value(QLatin1String(key)).toObject();
if (!r.isEmpty()) {
minOut = QString::number(r.value("from").toInt());
maxOut = QString::number(r.value("to").toInt());
}
};
readMuxRange("maxConcurrency", srv.xhttp.xmux.maxConcurrencyMin, srv.xhttp.xmux.maxConcurrencyMax);
readMuxRange("maxConnections", srv.xhttp.xmux.maxConnectionsMin, srv.xhttp.xmux.maxConnectionsMax);

View File

@@ -2,10 +2,6 @@
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QSet>
#include <algorithm>
#include "../../../core/utils/protocolEnum.h"
#include "../../../core/protocols/protocolUtils.h"
@@ -17,41 +13,6 @@ using namespace ProtocolUtils;
namespace amnezia
{
namespace
{
template <typename T>
QString awgVersionOf(const T &config)
{
auto hasValue = [](const QString &value) { return !value.trimmed().isEmpty(); };
const QStringList awg3Params = { config.headerProtectionKey, config.contentPaddingAddition,
config.rekeyAfterTime, config.rekeyTimeout,
config.rejectAfterTime, config.keepaliveTimeout,
config.maxHandshakeAttempts };
if (std::any_of(awg3Params.begin(), awg3Params.end(), hasValue)) {
return protocols::awg::awgV3;
}
const QStringList junkSizes = { config.cookieReplyPacketJunkSize, config.transportPacketJunkSize };
const QStringList magicHeaders = { config.initPacketMagicHeader, config.responsePacketMagicHeader,
config.underloadPacketMagicHeader, config.transportPacketMagicHeader };
bool hasJunkSizes = std::any_of(junkSizes.begin(), junkSizes.end(), hasValue);
bool hasHeaderRanges = std::any_of(magicHeaders.begin(), magicHeaders.end(),
[](const QString &header) { return header.contains('-'); });
if (hasJunkSizes || hasHeaderRanges) {
return protocols::awg::awgV2;
}
const QStringList specialJunk = { config.specialJunk1, config.specialJunk2, config.specialJunk3,
config.specialJunk4, config.specialJunk5 };
if (std::any_of(specialJunk.begin(), specialJunk.end(), hasValue)) {
return protocols::awg::awgV1_5;
}
return QString();
}
} // namespace
QJsonObject AwgServerConfig::toJson() const
{
QJsonObject obj;
@@ -134,7 +95,7 @@ QJsonObject AwgServerConfig::toJson() const
if (!maxHandshakeAttempts.isEmpty()) {
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
}
if (isThirdPartyConfig) {
obj[configKey::isThirdPartyConfig] = isThirdPartyConfig;
}
@@ -178,7 +139,7 @@ AwgServerConfig AwgServerConfig::fromJson(const QJsonObject& json)
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
return config;
@@ -302,7 +263,11 @@ QJsonObject AwgClientConfig::toJson() const
if (!maxHandshakeAttempts.isEmpty()) {
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
}
if (isObfuscationEnabled) {
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
}
return obj;
}
@@ -353,18 +318,21 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
return config;
}
QJsonObject AwgProtocolConfig::toJson() const
{
QJsonObject obj = serverConfig.toJson();
if (clientConfig.has_value()) {
QJsonObject clientJson = clientConfig->toJson();
obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
}
return obj;
}
@@ -385,24 +353,6 @@ AwgProtocolConfig AwgProtocolConfig::fromJson(const QJsonObject& json)
return config;
}
QString AwgProtocolConfig::serverProtocolVersion() const
{
return awgVersionOf(serverConfig);
}
QString AwgProtocolConfig::clientProtocolVersion() const
{
return clientConfig.has_value() ? awgVersionOf(clientConfig.value()) : QString();
}
QString AwgProtocolConfig::protocolVersionString(const QString &version)
{
if (version == protocols::awg::awgV3) return QObject::tr(" (version 3)");
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}
bool AwgProtocolConfig::hasClientConfig() const
{
return clientConfig.has_value();
@@ -431,30 +381,24 @@ bool AwgServerConfig::hasEqualServerSettings(const AwgServerConfig& other) const
specialJunk1 != other.specialJunk1 || specialJunk2 != other.specialJunk2 ||
specialJunk3 != other.specialJunk3 || specialJunk4 != other.specialJunk4 ||
specialJunk5 != other.specialJunk5 ||
cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
transportPacketJunkSize != other.transportPacketJunkSize ||
headerProtectionKey != other.headerProtectionKey ||
contentPaddingAddition != other.contentPaddingAddition ||
rekeyAfterTime != other.rekeyAfterTime || rekeyTimeout != other.rekeyTimeout ||
rejectAfterTime != other.rejectAfterTime || keepaliveTimeout != other.keepaliveTimeout ||
maxHandshakeAttempts != other.maxHandshakeAttempts) {
headerProtectionKey != other.headerProtectionKey) {
return false;
}
bool isV2 = protocolVersion == protocols::awg::awgV2;
if (isV2) {
if (cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
transportPacketJunkSize != other.transportPacketJunkSize) {
return false;
}
}
return true;
}
bool AwgProtocolConfig::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4)
{
QSet<QString> uniqueHeaders;
int filledHeaders = 0;
for (const QString &header : { h1, h2, h3, h4 }) {
if (!header.trimmed().isEmpty()) {
++filledHeaders;
uniqueHeaders.insert(header);
}
}
return uniqueHeaders.size() != filledHeaders;
return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4);
}
bool AwgProtocolConfig::isPacketSizeEqual(int s1, int s2, int s3, int s4)

View File

@@ -90,6 +90,8 @@ struct AwgClientConfig {
QString rejectAfterTime;
QString keepaliveTimeout;
QString maxHandshakeAttempts;
bool isObfuscationEnabled = false;
QJsonObject toJson() const;
static AwgClientConfig fromJson(const QJsonObject& json);
};
@@ -101,10 +103,6 @@ struct AwgProtocolConfig {
QJsonObject toJson() const;
static AwgProtocolConfig fromJson(const QJsonObject& json);
QString serverProtocolVersion() const;
QString clientProtocolVersion() const;
static QString protocolVersionString(const QString &version);
bool hasClientConfig() const;
void setClientConfig(const AwgClientConfig& config);
void clearClientConfig();

View File

@@ -99,6 +99,9 @@ bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolCo
const auto normTransport = [](const QString &t) {
return t.isEmpty() ? QString(protocols::telemt::transportModeStandard) : t;
};
const auto normWorkersMode = [](const QString &m) {
return m.isEmpty() ? QString(protocols::telemt::workersModeAuto) : m;
};
if (normPort(port) != normPort(other.port)) {
return false;
@@ -130,9 +133,18 @@ bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolCo
if (userName != other.userName) {
return false;
}
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
return false;
}
if (workers != other.workers) {
return false;
}
if (natEnabled != other.natEnabled) {
return false;
}
if (natInternalIp != other.natInternalIp) {
return false;
}
if (natExternalIp != other.natExternalIp) {
return false;
}

View File

@@ -105,17 +105,11 @@ QJsonObject WireGuardClientConfig::toJson() const
if (!mtu.isEmpty()) {
obj[configKey::mtu] = mtu;
}
for (auto it = awgParams.constBegin(); it != awgParams.constEnd(); ++it) {
if (!it.value().isEmpty()) {
obj[it.key()] = it.value();
}
}
if (isObfuscationEnabled) {
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
}
return obj;
}
@@ -139,16 +133,9 @@ WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json)
}
config.persistentKeepAlive = json.value(configKey::persistentKeepAlive).toString();
config.mtu = json.value(configKey::mtu).toString();
for (const QString &key : configKey::awgProtocolKeys()) {
const QString value = json.value(key).toString();
if (!value.isEmpty()) {
config.awgParams.insert(key, value);
}
}
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
return config;
}

View File

@@ -2,7 +2,6 @@
#define WIREGUARDPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QMap>
#include <QString>
#include <QStringList>
#include <optional>
@@ -37,10 +36,8 @@ struct WireGuardClientConfig {
QStringList allowedIps;
QString persistentKeepAlive;
QString mtu;
QMap<QString, QString> awgParams;
bool isObfuscationEnabled = false;
QJsonObject toJson() const;
static WireGuardClientConfig fromJson(const QJsonObject& json);
};

View File

@@ -81,6 +81,7 @@ QJsonObject XrayXhttpConfig::toJson() const
if (!mode.isEmpty()) obj[configKey::xhttpMode] = mode;
if (!host.isEmpty()) obj[configKey::xhttpHost] = host;
if (!path.isEmpty()) obj[configKey::xhttpPath] = path;
if (!headersTemplate.isEmpty()) obj[configKey::xhttpHeadersTemplate] = headersTemplate;
if (!uplinkMethod.isEmpty()) obj[configKey::xhttpUplinkMethod] = uplinkMethod;
obj[configKey::xhttpDisableGrpc] = disableGrpc;
obj[configKey::xhttpDisableSse] = disableSse;
@@ -115,6 +116,7 @@ namespace
c.mode = QString();
c.host = QString();
c.path = QString();
c.headersTemplate = QString();
c.uplinkMethod = QString();
c.disableGrpc = false;
c.disableSse = false;
@@ -153,6 +155,9 @@ XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
if (json.contains(configKey::xhttpPath)) {
c.path = json.value(configKey::xhttpPath).toString();
}
if (json.contains(configKey::xhttpHeadersTemplate)) {
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
}
if (json.contains(configKey::xhttpUplinkMethod)) {
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
}

View File

@@ -48,6 +48,7 @@ struct XrayXhttpConfig {
QString mode = protocols::xray::defaultXhttpMode; // Auto|Packet-up|Stream-up|Stream-one
QString host = protocols::xray::defaultXhttpHost;
QString path;
QString headersTemplate = protocols::xray::defaultXhttpHeadersTemplate; // HTTP|None
QString uplinkMethod = protocols::xray::defaultXhttpUplinkMethod; // POST|PUT|PATCH
bool disableGrpc = true;
bool disableSse = true;

View File

@@ -64,11 +64,7 @@ QString getProtocolName(DockerContainer defaultContainer, const QMap<DockerConta
const auto it = containers.constFind(defaultContainer);
if (it != containers.cend()) {
if (const AwgProtocolConfig *awg = it->getAwgProtocolConfig()) {
QString version = awg->clientProtocolVersion();
if (version.isEmpty()) {
version = awg->serverProtocolVersion();
}
protocolVersion = AwgProtocolConfig::protocolVersionString(version);
protocolVersion = ProtocolUtils::getProtocolVersionString(awg->toJson());
if (defaultContainer == DockerContainer::Awg && !awg->serverConfig.isThirdPartyConfig) {
containerName = QStringLiteral("AmneziaWG Legacy");
}

View File

@@ -222,18 +222,6 @@ ErrorCode OpenVpnProtocol::start()
}
#endif
#ifdef Q_OS_WIN
// In "all except sites" mode the config uses redirect-gateway !ipv4, so OpenVPN
// never reports net_route_v4_best_gw and m_routeGateway would stay empty
const QString winGateway = NetworkUtilities::getGatewayAndIface().first;
if (!winGateway.isEmpty()) {
m_routeGateway = winGateway;
qDebug() << "Set VPN route gateway" << m_routeGateway;
} else {
qWarning() << "Unable to detect physical default gateway";
}
#endif
uint mgmtPort = selectMgmtPort();
qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort;

View File

@@ -209,3 +209,16 @@ QString ProtocolUtils::key_proto_config_path(Proto p)
return protoToString(p) + "_config_path";
}
QString ProtocolUtils::getProtocolVersion(const QJsonObject &protocolConfig)
{
return protocolConfig.value(configKey::protocolVersion).toString();
}
QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfig)
{
auto version = getProtocolVersion(protocolConfig);
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}

View File

@@ -39,6 +39,8 @@ namespace amnezia
QString key_proto_config_data(Proto p);
QString key_proto_config_path(Proto p);
QString getProtocolVersion(const QJsonObject &protocolConfig);
QString getProtocolVersionString(const QJsonObject &protocolConfig);
}
}

View File

@@ -120,60 +120,34 @@ QVariantMap SecureAppSettingsRepository::vpnSites(RouteMode mode) const
return value("Conf/" + routeModeString(mode)).toMap();
}
QStringList SecureAppSettingsRepository::siteIpList(const QVariant &value)
{
// QVariant::toStringList() handles both a QStringList/QVariantList and a single QString
// (a single string is returned as a one-element list), which covers the legacy format.
QStringList result = value.toStringList();
result.removeAll(QString());
result.removeDuplicates();
return result;
}
void SecureAppSettingsRepository::setVpnSites(RouteMode mode, const QVariantMap &sites)
{
setValue("Conf/" + routeModeString(mode), sites);
}
bool SecureAppSettingsRepository::addVpnSite(RouteMode mode, const QString &site, const QStringList &ips)
bool SecureAppSettingsRepository::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
{
QVariantMap sites = vpnSites(mode);
const bool siteExisted = sites.contains(site);
if (siteExisted && ips.isEmpty())
if (sites.contains(site) && ip.isEmpty())
return false;
QStringList mergedIps = siteIpList(sites.value(site));
bool changed = !siteExisted;
for (const QString &ip : ips) {
if (!ip.isEmpty() && !mergedIps.contains(ip)) {
mergedIps.append(ip);
changed = true;
}
}
if (!changed)
return false;
sites.insert(site, mergedIps);
sites.insert(site, ip);
setVpnSites(mode, sites);
emit sitesChanged(mode);
return true;
}
void SecureAppSettingsRepository::addVpnSites(RouteMode mode, const QMap<QString, QStringList> &sites)
void SecureAppSettingsRepository::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites)
{
QVariantMap allSites = vpnSites(mode);
for (auto i = sites.constBegin(); i != sites.constEnd(); ++i) {
const QString &site = i.key();
const QString &ip = i.value();
QStringList mergedIps = siteIpList(allSites.value(site));
for (const QString &ip : i.value()) {
if (!ip.isEmpty() && !mergedIps.contains(ip))
mergedIps.append(ip);
}
if (allSites.contains(site) && allSites.value(site) == ip)
continue;
allSites.insert(site, mergedIps);
allSites.insert(site, ip);
}
setVpnSites(mode, allSites);

View File

@@ -38,15 +38,11 @@ public:
RouteMode routeMode() const;
void setRouteMode(RouteMode mode);
bool addVpnSite(RouteMode mode, const QString &site, const QStringList &ips = {});
void addVpnSites(RouteMode mode, const QMap<QString, QStringList> &sites);
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites);
void removeVpnSite(RouteMode mode, const QString &site);
void removeAllVpnSites(RouteMode mode);
QVariantMap vpnSites(RouteMode mode) const;
// Normalizes a stored vpn site value into a list of IPs.
// Supports both the legacy format (a single IP string) and the current one (a list of IPs).
static QStringList siteIpList(const QVariant &value);
bool isSitesSplitTunnelingEnabled() const;
void setSitesSplitTunnelingEnabled(bool enabled);

View File

@@ -2,7 +2,6 @@
#define CONFIGKEYS_H
#include <QLatin1String>
#include <QStringList>
namespace amnezia
{
@@ -71,8 +70,6 @@ namespace amnezia
constexpr QLatin1String lastConfig("last_config");
constexpr QLatin1String protocolVersion("protocol_version");
constexpr QLatin1String isThirdPartyConfig("isThirdPartyConfig");
constexpr QLatin1String isObfuscationEnabled("isObfuscationEnabled");
@@ -101,32 +98,7 @@ namespace amnezia
constexpr QLatin1String keepaliveTimeout("KeepaliveTimeout");
constexpr QLatin1String maxHandshakeAttempts("MaxHandshakeAttempts");
inline QStringList awgProtocolKeys()
{
return { junkPacketCount,
junkPacketMinSize,
junkPacketMaxSize,
initPacketJunkSize,
responsePacketJunkSize,
cookieReplyPacketJunkSize,
transportPacketJunkSize,
initPacketMagicHeader,
responsePacketMagicHeader,
underloadPacketMagicHeader,
transportPacketMagicHeader,
specialJunk1,
specialJunk2,
specialJunk3,
specialJunk4,
specialJunk5,
headerProtectionKey,
contentPaddingAddition,
rekeyAfterTime,
rekeyTimeout,
rejectAfterTime,
keepaliveTimeout,
maxHandshakeAttempts };
}
constexpr QLatin1String protocolVersion("protocol_version");
constexpr QLatin1String openvpn("openvpn");
constexpr QLatin1String wireguard("wireguard");
@@ -186,6 +158,7 @@ namespace amnezia
constexpr QLatin1String xhttpMode("xhttp_mode"); // Auto | Packet-up | Stream-up | Stream-one
constexpr QLatin1String xhttpHost("xhttp_host");
constexpr QLatin1String xhttpPath("xhttp_path");
constexpr QLatin1String xhttpHeadersTemplate("xhttp_headers_template"); // HTTP | None
constexpr QLatin1String xhttpUplinkMethod("xhttp_uplink_method"); // POST | PUT | PATCH
constexpr QLatin1String xhttpDisableGrpc("xhttp_disable_grpc"); // bool
constexpr QLatin1String xhttpDisableSse("xhttp_disable_sse"); // bool

View File

@@ -65,12 +65,13 @@ namespace amnezia
constexpr char defaultTransport[] = "raw";
constexpr char defaultFingerprint[] = "chrome";
constexpr char defaultSni[] = "www.googletagmanager.com";
constexpr char defaultAlpn[] = "h2";
constexpr char defaultAlpn[] = "HTTP/2";
constexpr char defaultXhttpMode[] = "Auto";
constexpr char defaultXhttpHeadersTemplate[] = "HTTP";
constexpr char defaultXhttpUplinkMethod[] = "POST";
constexpr char defaultXhttpSessionPlacement[] = "Path";
constexpr char defaultXhttpSessionKey[] = "";
constexpr char defaultXhttpSessionKey[] = "Path";
constexpr char defaultXhttpSeqPlacement[] = "Path";
constexpr char defaultXhttpUplinkDataPlacement[] = "Body";
@@ -85,10 +86,6 @@ namespace amnezia
constexpr char defaultXPaddingPlacement[] = "Cookie";
constexpr char defaultXPaddingMethod[] = "Repeat-x";
constexpr char defaultXPaddingKey[] = "x_padding";
constexpr char defaultXPaddingHeader[] = "X-Padding";
constexpr char defaultXPaddingBytesMin[] = "1";
constexpr char defaultXPaddingBytesMax[] = "256";
constexpr char defaultMkcpTti[] = "50";
constexpr char defaultMkcpUplinkCapacity[] = "5";
@@ -186,16 +183,15 @@ namespace amnezia
constexpr char defaultJunkPacketCount[] = "3";
constexpr char defaultJunkPacketMinSize[] = "10";
constexpr char defaultJunkPacketMaxSize[] = "30";
constexpr int junkPacketSizeMin = 12;
constexpr int initPacketJunkSizeMax = 150;
constexpr int responsePacketJunkSizeMax = 150;
constexpr int cookieReplyPacketJunkSizeMax = 64;
constexpr int defaultTransportPacketJunkSize = 12;
constexpr char defaultInitPacketJunkSize[] = "15";
constexpr char defaultResponsePacketJunkSize[] = "18";
constexpr char defaultCookieReplyPacketJunkSize[] = "20";
constexpr char defaultTransportPacketJunkSize[] = "23";
constexpr char defaultInitPacketMagicHeader[] = "1";
constexpr char defaultResponsePacketMagicHeader[] = "2";
constexpr char defaultUnderloadPacketMagicHeader[] = "3";
constexpr char defaultTransportPacketMagicHeader[] = "4";
constexpr char defaultInitPacketMagicHeader[] = "1020325451";
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = "";
@@ -204,17 +200,6 @@ namespace amnezia
constexpr char awgV1_5[] = "1.5";
constexpr char awgV2[] = "2";
constexpr char awgV3[] = "3";
constexpr char defaultContentPaddingAddition[] = "10-100";
constexpr char defaultRekeyAfterTime[] = "100-120";
constexpr char defaultRekeyTimeout[] = "3-7";
constexpr char defaultRejectAfterTime[] = "150-180";
constexpr char defaultKeepaliveTimeout[] = "5-15";
constexpr char defaultMaxHandshakeAttempts[] = "15-20";
constexpr char defaultPersistentKeepAlive[] = "25-35";
}
namespace socks5Proxy
@@ -250,8 +235,7 @@ namespace amnezia
constexpr char defaultPort[] = "443";
constexpr char defaultWorkers[] = "2";
// mtproto-proxy loses connectivity with -M >= 20; keep the cap at the highest known-good value.
constexpr int maxWorkers = 19;
constexpr int maxWorkers = 32;
constexpr int botTagHexLength = 32;
constexpr char defaultTlsDomain[] = "googletagmanager.com";
}
@@ -270,6 +254,7 @@ namespace amnezia
constexpr char tlsEmulationKey[] = "telemt_tls_emulation";
constexpr char useMiddleProxyKey[] = "telemt_use_middle_proxy";
constexpr char userNameKey[] = "telemt_user_name";
// Stored for UI only (Telemt server ignores these; same controls as MTProxy page)
constexpr char additionalSecretsKey[] = "telemt_additional_secrets";
constexpr char workersKey[] = "telemt_workers";
constexpr char workersModeKey[] = "telemt_workers_mode";

View File

@@ -24,13 +24,6 @@ QList<QString> qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data)
return chunks;
}
QString qrCodeUtils::generatePlainQrCodeImage(const QByteArray &data)
{
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 1));
return svgToBase64(svg);
}
QString qrCodeUtils::svgToBase64(const QString &image)
{
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());

View File

@@ -10,7 +10,6 @@ namespace qrCodeUtils
constexpr const qint16 qrMagicCode = 1984;
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
QString generatePlainQrCodeImage(const QByteArray &data);
qrcodegen::QrCode generateQrCode(const QByteArray &data);
QString svgToBase64(const QString &image);
};

View File

@@ -254,20 +254,8 @@ amnezia::ScriptVars amnezia::genAwgVars(const ContainerConfig &containerConfig)
vars.append({ { "$SPECIAL_JUNK_3", config.specialJunk3 } });
vars.append({ { "$SPECIAL_JUNK_4", config.specialJunk4 } });
vars.append({ { "$SPECIAL_JUNK_5", config.specialJunk5 } });
const bool isAwg3 = config.protocolVersion == protocols::awg::awgV3;
vars.append({ { "$PERSISTENT_KEEPALIVE", isAwg3 ? QString(protocols::awg::defaultPersistentKeepAlive)
: QString(protocols::wireguard::defaultPersistentKeepAlive) } });
vars.append({ { "$HEADER_PROTECTION_KEY", config.headerProtectionKey } });
vars.append({ { "$CONTENT_PADDING_ADDITION", config.contentPaddingAddition } });
vars.append({ { "$REKEY_AFTER_TIME", config.rekeyAfterTime } });
vars.append({ { "$REKEY_TIMEOUT", config.rekeyTimeout } });
vars.append({ { "$REJECT_AFTER_TIME", config.rejectAfterTime } });
vars.append({ { "$KEEPALIVE_TIMEOUT", config.keepaliveTimeout } });
vars.append({ { "$MAX_HANDSHAKE_ATTEMPTS", config.maxHandshakeAttempts } });
}
return vars;
}
@@ -341,7 +329,6 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf
workers = (transportMode == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) ? QStringLiteral("0")
: QStringLiteral("2");
}
vars.append({{"$MTPROXY_WORKERS_MODE", workersMode}});
vars.append({{"$MTPROXY_WORKERS", workers}});
vars.append({{"$MTPROXY_NAT_ENABLED", c.natEnabled ? QStringLiteral("1") : QStringLiteral("0")}});
@@ -388,12 +375,6 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
}
}
vars.append({ { "$TELEMT_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(',')) } });
QString middleProxyNatIp;
if (c.natEnabled && !c.natExternalIp.isEmpty()) {
middleProxyNatIp = c.natExternalIp;
}
vars.append({ { "$TELEMT_MIDDLE_PROXY_NAT_IP", middleProxyNatIp } });
}
return vars;

View File

@@ -176,8 +176,7 @@ QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const
errorCode = ErrorCode::NoError;
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2' 2>/dev/null || od -An -v -tx1 '%2'\"")
.arg(ContainerUtils::containerToString(container), path);
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerUtils::containerToString(container), path);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {

View File

@@ -118,44 +118,42 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "DNS = " << dnsServers.join(", ") << "\n";
}
if (!m_junkPacketCount.isEmpty()) {
if (!m_junkPacketCount.isNull()) {
out << "Jc = " << m_junkPacketCount << "\n";
}
if (!m_junkPacketMinSize.isEmpty()) {
if (!m_junkPacketMinSize.isNull()) {
out << "JMin = " << m_junkPacketMinSize << "\n";
}
if (!m_junkPacketMaxSize.isEmpty()) {
if (!m_junkPacketMaxSize.isNull()) {
out << "JMax = " << m_junkPacketMaxSize << "\n";
}
if (!m_initPacketJunkSize.isEmpty()) {
if (!m_initPacketJunkSize.isNull()) {
out << "S1 = " << m_initPacketJunkSize << "\n";
}
if (!m_responsePacketJunkSize.isEmpty()) {
if (!m_responsePacketJunkSize.isNull()) {
out << "S2 = " << m_responsePacketJunkSize << "\n";
}
if (!m_cookieReplyPacketJunkSize.isEmpty()) {
if (!m_cookieReplyPacketJunkSize.isNull()) {
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
}
if (!m_transportPacketJunkSize.isEmpty()) {
if (!m_transportPacketJunkSize.isNull()) {
out << "S4 = " << m_transportPacketJunkSize << "\n";
}
if (!m_initPacketMagicHeader.isEmpty()) {
if (!m_initPacketMagicHeader.isNull()) {
out << "H1 = " << m_initPacketMagicHeader << "\n";
}
if (!m_responsePacketMagicHeader.isEmpty()) {
if (!m_responsePacketMagicHeader.isNull()) {
out << "H2 = " << m_responsePacketMagicHeader << "\n";
}
if (!m_underloadPacketMagicHeader.isEmpty()) {
if (!m_underloadPacketMagicHeader.isNull()) {
out << "H3 = " << m_underloadPacketMagicHeader << "\n";
}
if (!m_transportPacketMagicHeader.isEmpty()) {
if (!m_transportPacketMagicHeader.isNull()) {
out << "H4 = " << m_transportPacketMagicHeader << "\n";
}
for (const QString& key : m_specialJunk.keys()) {
if (!m_specialJunk[key].isEmpty()) {
out << key << " = " << m_specialJunk[key] << "\n";
}
out << key << " = " << m_specialJunk[key] << "\n";
}
if (!m_headerProtectionKey.isEmpty()) {

View File

@@ -18,10 +18,11 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"

View File

@@ -31,6 +31,8 @@
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>

View File

@@ -7,14 +7,21 @@ add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"

View File

@@ -24,7 +24,7 @@
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>

View File

@@ -18,7 +18,6 @@
#include <QLocalSocket>
#include <QObject>
#include <QStandardPaths>
#include <QStringList>
#include <QTimer>
#include "leakdetector.h"
@@ -252,13 +251,64 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::configKey::killSwitchOption, rawConfig.value(amnezia::configKey::killSwitchOption));
const QStringList awgProtocolKeys = amnezia::configKey::awgProtocolKeys();
for (const QString &key : awgProtocolKeys) {
const QJsonValue value = wgConfig.value(key);
if (value.isString() && !value.toString().isEmpty()) {
json.insert(key, value);
}
if (protocolName == amnezia::configKey::awg) {
json.insert(amnezia::configKey::junkPacketCount, wgConfig.value(amnezia::configKey::junkPacketCount));
json.insert(amnezia::configKey::junkPacketMinSize, wgConfig.value(amnezia::configKey::junkPacketMinSize));
json.insert(amnezia::configKey::junkPacketMaxSize, wgConfig.value(amnezia::configKey::junkPacketMaxSize));
json.insert(amnezia::configKey::initPacketJunkSize, wgConfig.value(amnezia::configKey::initPacketJunkSize));
json.insert(amnezia::configKey::responsePacketJunkSize, wgConfig.value(amnezia::configKey::responsePacketJunkSize));
json.insert(amnezia::configKey::cookieReplyPacketJunkSize, wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize));
json.insert(amnezia::configKey::transportPacketJunkSize, wgConfig.value(amnezia::configKey::transportPacketJunkSize));
json.insert(amnezia::configKey::initPacketMagicHeader, wgConfig.value(amnezia::configKey::initPacketMagicHeader));
json.insert(amnezia::configKey::responsePacketMagicHeader, wgConfig.value(amnezia::configKey::responsePacketMagicHeader));
json.insert(amnezia::configKey::underloadPacketMagicHeader, wgConfig.value(amnezia::configKey::underloadPacketMagicHeader));
json.insert(amnezia::configKey::transportPacketMagicHeader, wgConfig.value(amnezia::configKey::transportPacketMagicHeader));
json.insert(amnezia::configKey::specialJunk1, wgConfig.value(amnezia::configKey::specialJunk1));
json.insert(amnezia::configKey::specialJunk2, wgConfig.value(amnezia::configKey::specialJunk2));
json.insert(amnezia::configKey::specialJunk3, wgConfig.value(amnezia::configKey::specialJunk3));
json.insert(amnezia::configKey::specialJunk4, wgConfig.value(amnezia::configKey::specialJunk4));
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
json.insert(amnezia::configKey::headerProtectionKey, wgConfig.value(amnezia::configKey::headerProtectionKey));
json.insert(amnezia::configKey::contentPaddingAddition, wgConfig.value(amnezia::configKey::contentPaddingAddition));
json.insert(amnezia::configKey::rekeyAfterTime, wgConfig.value(amnezia::configKey::rekeyAfterTime));
json.insert(amnezia::configKey::rekeyTimeout, wgConfig.value(amnezia::configKey::rekeyTimeout));
json.insert(amnezia::configKey::rejectAfterTime, wgConfig.value(amnezia::configKey::rejectAfterTime));
json.insert(amnezia::configKey::keepaliveTimeout, wgConfig.value(amnezia::configKey::keepaliveTimeout));
json.insert(amnezia::configKey::maxHandshakeAttempts, wgConfig.value(amnezia::configKey::maxHandshakeAttempts));
} else if (!wgConfig.value(amnezia::configKey::junkPacketCount).isUndefined()
&& !wgConfig.value(amnezia::configKey::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::responsePacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::transportPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::configKey::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::configKey::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::configKey::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::configKey::transportPacketMagicHeader).isUndefined()) {
json.insert(amnezia::configKey::junkPacketCount, wgConfig.value(amnezia::configKey::junkPacketCount));
json.insert(amnezia::configKey::junkPacketMinSize, wgConfig.value(amnezia::configKey::junkPacketMinSize));
json.insert(amnezia::configKey::junkPacketMaxSize, wgConfig.value(amnezia::configKey::junkPacketMaxSize));
json.insert(amnezia::configKey::initPacketJunkSize, wgConfig.value(amnezia::configKey::initPacketJunkSize));
json.insert(amnezia::configKey::responsePacketJunkSize, wgConfig.value(amnezia::configKey::responsePacketJunkSize));
json.insert(amnezia::configKey::cookieReplyPacketJunkSize, wgConfig.value(amnezia::configKey::cookieReplyPacketJunkSize));
json.insert(amnezia::configKey::transportPacketJunkSize, wgConfig.value(amnezia::configKey::transportPacketJunkSize));
json.insert(amnezia::configKey::initPacketMagicHeader, wgConfig.value(amnezia::configKey::initPacketMagicHeader));
json.insert(amnezia::configKey::responsePacketMagicHeader, wgConfig.value(amnezia::configKey::responsePacketMagicHeader));
json.insert(amnezia::configKey::underloadPacketMagicHeader, wgConfig.value(amnezia::configKey::underloadPacketMagicHeader));
json.insert(amnezia::configKey::transportPacketMagicHeader, wgConfig.value(amnezia::configKey::transportPacketMagicHeader));
json.insert(amnezia::configKey::specialJunk1, wgConfig.value(amnezia::configKey::specialJunk1));
json.insert(amnezia::configKey::specialJunk2, wgConfig.value(amnezia::configKey::specialJunk2));
json.insert(amnezia::configKey::specialJunk3, wgConfig.value(amnezia::configKey::specialJunk3));
json.insert(amnezia::configKey::specialJunk4, wgConfig.value(amnezia::configKey::specialJunk4));
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
json.insert(amnezia::configKey::headerProtectionKey, wgConfig.value(amnezia::configKey::headerProtectionKey));
json.insert(amnezia::configKey::contentPaddingAddition, wgConfig.value(amnezia::configKey::contentPaddingAddition));
json.insert(amnezia::configKey::rekeyAfterTime, wgConfig.value(amnezia::configKey::rekeyAfterTime));
json.insert(amnezia::configKey::rekeyTimeout, wgConfig.value(amnezia::configKey::rekeyTimeout));
json.insert(amnezia::configKey::rejectAfterTime, wgConfig.value(amnezia::configKey::rejectAfterTime));
json.insert(amnezia::configKey::keepaliveTimeout, wgConfig.value(amnezia::configKey::keepaliveTimeout));
json.insert(amnezia::configKey::maxHandshakeAttempts, wgConfig.value(amnezia::configKey::maxHandshakeAttempts));
}
write(json);

View File

@@ -7,6 +7,10 @@ struct OpenVPNConfig: Decodable {
let config: String
let splitTunnelType: Int
let splitTunnelSites: [String]
var str: String {
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
}
}
extension PacketTunnelProvider {
@@ -26,6 +30,11 @@ extension PacketTunnelProvider {
do {
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
let wrapperPreview = String(decoding: openVPNConfigData.prefix(512), as: UTF8.self)
let ovpnPreview = String(openVPNConfig.config.prefix(512))
ovpnLog(.info, title: "config wrapper", message: "bytes=\(openVPNConfigData.count) preview=\(wrapperPreview)")
ovpnLog(.info, title: "config raw", message: "chars=\(openVPNConfig.config.count) preview=\(ovpnPreview)")
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
splitTunnelType = openVPNConfig.splitTunnelType
splitTunnelSites = openVPNConfig.splitTunnelSites
@@ -96,6 +105,12 @@ extension PacketTunnelProvider {
let hasTlsAuthClose = configString.contains("</tls-auth>")
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
let lines = configString.split(separator: "\n")
let head = lines.prefix(10).joined(separator: "\n")
let tail = lines.suffix(10).joined(separator: "\n")
ovpnLog(.debug, title: "ConfigHead", message: head)
ovpnLog(.debug, title: "ConfigTail", message: tail)
if hasTlsAuthOpen && hasTlsAuthClose {
ovpnLog(.info, title: "TLSAuthSanitized", message: "preserve original tls-auth block")
}
@@ -140,6 +155,8 @@ extension PacketTunnelProvider {
normalizedConfig.append("\n")
}
let normalizedLines = normalizedConfig.split(whereSeparator: \.isNewline)
let normalizedTail = normalizedLines.suffix(10).joined(separator: "\n")
ovpnLog(.debug, title: "ConfigTailSanitized", message: normalizedTail)
let redirectLines = normalizedLines
.map(String.init)
.filter { $0.lowercased().contains("redirect-gateway") }

View File

@@ -16,6 +16,7 @@ extension PacketTunnelProvider {
do {
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
let wgConfigStr = wgConfig.str
wg_log(.info, title: "config: ", message: wgConfig.redux)
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)

View File

@@ -148,4 +148,24 @@ struct WGConfig: Decodable {
\(persistentKeepAlive == nil ? "" : "PersistentKeepalive = \(persistentKeepAlive!)")
"""
}
var redux: String {
"""
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = ***
\(settings)
[Peer]
PublicKey = ***
PresharedKey = ***
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
\(persistentKeepAlive == nil ? "" : "PersistentKeepalive = \(persistentKeepAlive!)")
SplitTunnelType = \(splitTunnelType)
SplitTunnelSites = \(splitTunnelSites.joined(separator: ", "))
"""
}
}

View File

@@ -552,18 +552,6 @@ bool IosController::setupOpenVPN()
return startOpenVPN(openVPNConfigStr);
}
static void insertNonEmptyAwgParams(QJsonObject &wgConfig, const QJsonObject &config)
{
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
for (const QString &key : awgProtocolKeys) {
const QJsonValue value = config.value(key);
if (value.isString() && !value.toString().isEmpty()) {
wgConfig.insert(key, value);
}
}
}
bool IosController::setupWireGuard()
{
QJsonObject config = m_rawConfig[ProtocolUtils::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
@@ -605,7 +593,29 @@ bool IosController::setupWireGuard()
wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]);
}
insertNonEmptyAwgParams(wgConfig, config);
if (config.contains(configKey::isObfuscationEnabled) && config.value(configKey::isObfuscationEnabled).toBool()) {
wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]);
wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]);
wgConfig.insert(configKey::underloadPacketMagicHeader, config[configKey::underloadPacketMagicHeader]);
wgConfig.insert(configKey::transportPacketMagicHeader, config[configKey::transportPacketMagicHeader]);
wgConfig.insert(configKey::initPacketJunkSize, config[configKey::initPacketJunkSize]);
wgConfig.insert(configKey::responsePacketJunkSize, config[configKey::responsePacketJunkSize]);
wgConfig.insert(configKey::cookieReplyPacketJunkSize, config[configKey::cookieReplyPacketJunkSize]);
wgConfig.insert(configKey::transportPacketJunkSize, config[configKey::transportPacketJunkSize]);
wgConfig.insert(configKey::junkPacketCount, config[configKey::junkPacketCount]);
wgConfig.insert(configKey::junkPacketMinSize, config[configKey::junkPacketMinSize]);
wgConfig.insert(configKey::junkPacketMaxSize, config[configKey::junkPacketMaxSize]);
wgConfig.insert(configKey::headerProtectionKey, config[configKey::headerProtectionKey]);
wgConfig.insert(configKey::contentPaddingAddition, config[configKey::contentPaddingAddition]);
wgConfig.insert(configKey::rekeyAfterTime, config[configKey::rekeyAfterTime]);
wgConfig.insert(configKey::rekeyTimeout, config[configKey::rekeyTimeout]);
wgConfig.insert(configKey::rejectAfterTime, config[configKey::rejectAfterTime]);
wgConfig.insert(configKey::keepaliveTimeout, config[configKey::keepaliveTimeout]);
wgConfig.insert(configKey::maxHandshakeAttempts, config[configKey::maxHandshakeAttempts]);
}
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@@ -695,7 +705,33 @@ bool IosController::setupAwg()
wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]);
}
insertNonEmptyAwgParams(wgConfig, config);
wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]);
wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]);
wgConfig.insert(configKey::underloadPacketMagicHeader, config[configKey::underloadPacketMagicHeader]);
wgConfig.insert(configKey::transportPacketMagicHeader, config[configKey::transportPacketMagicHeader]);
wgConfig.insert(configKey::initPacketJunkSize, config[configKey::initPacketJunkSize]);
wgConfig.insert(configKey::responsePacketJunkSize, config[configKey::responsePacketJunkSize]);
wgConfig.insert(configKey::cookieReplyPacketJunkSize, config[configKey::cookieReplyPacketJunkSize]);
wgConfig.insert(configKey::transportPacketJunkSize, config[configKey::transportPacketJunkSize]);
wgConfig.insert(configKey::junkPacketCount, config[configKey::junkPacketCount]);
wgConfig.insert(configKey::junkPacketMinSize, config[configKey::junkPacketMinSize]);
wgConfig.insert(configKey::junkPacketMaxSize, config[configKey::junkPacketMaxSize]);
wgConfig.insert(configKey::specialJunk1, config[configKey::specialJunk1]);
wgConfig.insert(configKey::specialJunk2, config[configKey::specialJunk2]);
wgConfig.insert(configKey::specialJunk3, config[configKey::specialJunk3]);
wgConfig.insert(configKey::specialJunk4, config[configKey::specialJunk4]);
wgConfig.insert(configKey::specialJunk5, config[configKey::specialJunk5]);
wgConfig.insert(configKey::headerProtectionKey, config[configKey::headerProtectionKey]);
wgConfig.insert(configKey::contentPaddingAddition, config[configKey::contentPaddingAddition]);
wgConfig.insert(configKey::rekeyAfterTime, config[configKey::rekeyAfterTime]);
wgConfig.insert(configKey::rekeyTimeout, config[configKey::rekeyTimeout]);
wgConfig.insert(configKey::rejectAfterTime, config[configKey::rejectAfterTime]);
wgConfig.insert(configKey::keepaliveTimeout, config[configKey::keepaliveTimeout]);
wgConfig.insert(configKey::maxHandshakeAttempts, config[configKey::maxHandshakeAttempts]);
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));

View File

@@ -1,24 +0,0 @@
#ifndef IOSCONTEXTMENU_H
#define IOSCONTEXTMENU_H
#include <QObject>
#include <QQuickItem>
// Presents the native iOS edit menu (UIEditMenuInteraction) for a text
// control. The menu items (Cut/Copy/Paste/Select All) are provided by the
// system based on the first responder, which is Qt's text input responder
// for the focused control.
//
// Needed because the ContextMenu attached type is backed by a native menu
// on iOS only since Qt 6.10 — on Qt 6.9 it opens a Qt-drawn menu instead.
class IosContextMenu : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
Q_INVOKABLE bool isAvailable() const;
Q_INVOKABLE void present(QQuickItem *target, qreal x, qreal y);
};
#endif // IOSCONTEXTMENU_H

View File

@@ -1,215 +0,0 @@
#import "ioscontextmenu.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QPointer>
#include <QtGui/QGuiApplication>
#include <QtGui/QInputMethod>
#include <QtQuick/QQuickWindow>
namespace
{
// Keys for attaching the helper objects to the UIView.
const void *kEditMenuInteractionKey = &kEditMenuInteractionKey;
const void *kEditMenuDelegateKey = &kEditMenuDelegateKey;
const void *kEditMenuResponderKey = &kEditMenuResponderKey;
// Menu titles reuse the ContextMenuType translations; the accelerator
// ampersands are meaningless on iOS and get stripped.
NSString *menuTitle(const char *sourceText)
{
QString title = QCoreApplication::translate("ContextMenuType", sourceText);
title.remove(QLatin1Char('&'));
return title.toNSString();
}
// The handler outlives the delegate call, so it must own its own copy of the
// guarded pointer: the local variable here is captured by the block by value
// (copy-constructed when the block is copied to the heap). Capturing a
// C++ lambda's reference capture instead would leave the block with a
// dangling pointer into the delegate method's stack frame.
API_AVAILABLE(ios(16.0))
UIAction *makeEditAction(NSString *title, const QPointer<QQuickItem> &target, const char *slot)
{
const QPointer<QQuickItem> guardedTarget = target;
return [UIAction actionWithTitle:title
image:nil
identifier:nil
handler:^(UIAction *) {
if (QQuickItem *item = guardedTarget.data()) {
// Queued: the handler fires mid-dismissal
// of the menu, let UIKit unwind first.
QMetaObject::invokeMethod(item, slot, Qt::QueuedConnection);
}
}];
}
}
// UIKit presents an edit menu only when the first responder is inside the
// interaction view's hierarchy. While the virtual keyboard is up that is
// Qt's text input responder, but for read-only fields (or before the
// keyboard appears) nothing suitable is first responder and the present is
// silently ignored ("did not have performable commands and/or actions").
// This zero-sized subview steps in as the first responder for those cases.
@interface AmneziaEditMenuResponderView : UIView
@end
@implementation AmneziaEditMenuResponderView
- (BOOL)canBecomeFirstResponder
{
return YES;
}
@end
// Builds the edit menu from the state of the focused QML text control and
// invokes its slots directly. The system's suggested actions can't be used:
// they are collected from the first responder, and Qt's text responder is
// first responder only while the virtual keyboard is up (never for read-only
// fields), which would leave the menu empty.
API_AVAILABLE(ios(16.0))
@interface AmneziaEditMenuDelegate : NSObject <UIEditMenuInteractionDelegate>
@property (nonatomic, weak) UIView *responderView;
- (void)setTargetItem:(QQuickItem *)item;
@end
@implementation AmneziaEditMenuDelegate {
QPointer<QQuickItem> m_target;
}
- (void)setTargetItem:(QQuickItem *)item
{
m_target = item;
}
- (UIMenu *)editMenuInteraction:(UIEditMenuInteraction *)interaction
menuForConfiguration:(UIEditMenuConfiguration *)configuration
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions
{
QQuickItem *item = m_target.data();
if (!item) {
return nil;
}
// The system's suggested actions are UICommands and get re-validated
// against the first responder right before the menu shows, which makes
// them hostage to Qt's input-method state. UIActions with handlers skip
// that validation entirely, so the menu is always built by hand from the
// QML control's state.
const bool hasSelection = !item->property("selectedText").toString().isEmpty();
const bool readOnly = item->property("readOnly").toBool();
const bool canPaste = item->property("canPaste").toBool();
const bool hasText = item->property("length").toInt() > 0;
NSMutableArray<UIMenuElement *> *actions = [NSMutableArray array];
if (hasSelection && !readOnly) {
[actions addObject:makeEditAction(menuTitle("C&ut"), m_target, "cut")];
}
if (hasSelection) {
[actions addObject:makeEditAction(menuTitle("&Copy"), m_target, "copy")];
}
if (canPaste && !readOnly) {
[actions addObject:makeEditAction(menuTitle("&Paste"), m_target, "paste")];
}
if (hasText) {
[actions addObject:makeEditAction(menuTitle("&SelectAll"), m_target, "selectAll")];
}
if (actions.count == 0) {
return nil;
}
return [UIMenu menuWithTitle:@"" children:actions];
}
- (void)editMenuInteraction:(UIEditMenuInteraction *)interaction
willDismissMenuForConfiguration:(UIEditMenuConfiguration *)configuration
animator:(id<UIEditMenuInteractionAnimating>)animator
{
// Give the borrowed first-responder status back once the menu goes away.
if (self.responderView.isFirstResponder) {
[self.responderView resignFirstResponder];
}
}
@end
bool IosContextMenu::isAvailable() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
// Since Qt 6.10 the ContextMenu attached type is backed by a native menu
// on iOS, so the helper must stay out of the way.
return false;
#else
// UIEditMenuInteraction needs iOS 16, which is the deployment target.
return true;
#endif
}
void IosContextMenu::present(QQuickItem *target, qreal x, qreal y)
{
if (!target || !target->window()) {
return;
}
// On iOS QWindow::winId() is the backing UIView.
UIView *view = (__bridge UIView *)reinterpret_cast<void *>(target->window()->winId());
if (!view) {
return;
}
// Scene coordinates match the backing view's coordinate space.
const QPointF scenePos = target->mapToScene(QPointF(x, y));
AmneziaEditMenuDelegate *delegate = objc_getAssociatedObject(view, kEditMenuDelegateKey);
UIEditMenuInteraction *interaction = objc_getAssociatedObject(view, kEditMenuInteractionKey);
AmneziaEditMenuResponderView *responderView = objc_getAssociatedObject(view, kEditMenuResponderKey);
if (!interaction) {
responderView = [[AmneziaEditMenuResponderView alloc] initWithFrame:CGRectZero];
[view addSubview:responderView];
delegate = [[AmneziaEditMenuDelegate alloc] init];
delegate.responderView = responderView;
interaction = [[UIEditMenuInteraction alloc] initWithDelegate:delegate];
[view addInteraction:interaction];
objc_setAssociatedObject(view, kEditMenuResponderKey, responderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(view, kEditMenuDelegateKey, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(view, kEditMenuInteractionKey, interaction, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[delegate setTargetItem:target];
// While the keyboard is up for the target field, Qt's text input
// responder is the first responder and lives in this view's responder
// chain, which satisfies UIKit. Otherwise (read-only fields never raise
// the keyboard) borrow first-responder status. Qt's idea of the keyboard
// state is not trustworthy here, so key off the field being editable.
const bool readOnly = target->property("readOnly").toBool();
if (readOnly || !QGuiApplication::inputMethod()->isVisible()) {
[responderView becomeFirstResponder];
}
// Defer the actual present to the next main-loop iteration: the request
// arrives while the long-press touch is still active, and presenting
// mid-gesture makes UIKit discard the menu. Requests are also coalesced —
// a double tap asks for the menu twice (the TapHandler and the ContextMenu
// attached type both fire), and re-presenting makes the menu flicker.
static BOOL presentPending = NO;
if (presentPending) {
return;
}
presentPending = YES;
UIEditMenuConfiguration *configuration =
[UIEditMenuConfiguration configurationWithIdentifier:nil
sourcePoint:CGPointMake(scenePos.x(), scenePos.y())];
dispatch_async(dispatch_get_main_queue(), ^{
presentPending = NO;
[interaction presentEditMenuWithConfiguration:configuration];
});
}

View File

@@ -5,10 +5,8 @@
#ifndef MACOSSTATUSICON_H
#define MACOSSTATUSICON_H
#include <QMenu>
#include <QObject>
#include <QString>
class QMenu;
class MacOSStatusIcon final : public QObject {
Q_OBJECT
@@ -18,12 +16,12 @@ class MacOSStatusIcon final : public QObject {
explicit MacOSStatusIcon(QObject* parent);
~MacOSStatusIcon();
void setIcon(const QString& iconPath);
void setMenu(QMenu* menu);
public:
void setIcon(const QString& iconUrl);
void setIndicatorColor(const QColor& indicatorColor);
void setMenu(NSMenu* statusBarMenu);
void setToolTip(const QString& tooltip);
void showMessage(const QString& title, const QString& message);
private:
void* m_statusItem = nullptr;
};
#endif // MACOSSTATUSICON_H

View File

@@ -4,79 +4,201 @@
#include "macosstatusicon.h"
#include "leakdetector.h"
#include <QDebug>
#include <QMenu>
#include "logger.h"
#import <Cocoa/Cocoa.h>
#import <UserNotifications/UserNotifications.h>
#import <QResource>
/**
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
* set as a subview to the button item of the status item. The view serves as
* an indicator that can be displayed in color eventhough the icon is set as a
* template. In that way we give the system control over its effective
* appearance.
*/
@interface MacOSStatusIconDelegate : NSObject
@property(assign) NSStatusItem* statusItem;
@property(assign) NSView* statusIndicator;
- (void)setIcon:(NSData*)imageData;
- (void)setIndicator;
- (void)setIndicatorColor:(NSColor*)color;
- (void)setMenu:(NSMenu*)statusBarMenu;
- (void)setToolTip:(NSString*)tooltip;
@end
@implementation MacOSStatusIconDelegate
/**
* Initializes and sets the status item and indicator objects.
*
* @return An instance of MacOSStatusIconDelegate.
*/
- (id)init {
self = [super init];
// Create status item
self.statusItem =
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
self.statusItem.visible = true;
// Add the indicator as a subview
[self setIndicator];
return self;
}
/**
* Sets the image for the status icon.
*
* @param iconPath The data for the icon image.
*/
- (void)setIcon:(NSData*)imageData {
NSImage* image = [[NSImage alloc] initWithData:imageData];
[image setTemplate:true];
[self.statusItem.button setImage:image];
[image release];
}
/**
* Adds status indicator as a subview to the status item button.
*/
- (void)setIndicator {
float viewHeight = NSHeight([self.statusItem.button bounds]);
float dotSize = viewHeight * 0.35;
float dotOrigin = (viewHeight - dotSize) * 0.8;
NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)];
self.statusIndicator = dot;
self.statusIndicator.wantsLayer = true;
self.statusIndicator.layer.cornerRadius = dotSize * 0.5;
[self.statusItem.button addSubview:self.statusIndicator];
[dot release];
}
/**
* Sets the color if the indicator.
*
* @param color The indicator background color.
*/
- (void)setIndicatorColor:(NSColor*)color {
if (self.statusIndicator) {
self.statusIndicator.layer.backgroundColor = color.CGColor;
}
}
/**
* Sets the status bar menu to the status item.
*
* @param statusBarMenu The menu object that is passed from QT.
*/
- (void)setMenu:(NSMenu*)statusBarMenu {
[self.statusItem setMenu:statusBarMenu];
}
/**
* Sets the tooltip string for the status item.
*
* @param tooltip The tooltip string.
*/
- (void)setToolTip:(NSString*)tooltip {
[self.statusItem.button setToolTip:tooltip];
}
@end
namespace {
Logger logger("MacOSStatusIcon");
MacOSStatusIconDelegate* m_statusBarIcon = nullptr;
}
MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(MacOSStatusIcon);
NSStatusItem* item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
item.visible = YES;
m_statusItem = [item retain];
logger.debug() << "Register delegate";
Q_ASSERT(!m_statusBarIcon);
m_statusBarIcon = [[MacOSStatusIconDelegate alloc] init];
}
MacOSStatusIcon::~MacOSStatusIcon() {
MZ_COUNT_DTOR(MacOSStatusIcon);
NSStatusItem* item = static_cast<NSStatusItem*>(m_statusItem);
item.menu = nil;
[[NSStatusBar systemStatusBar] removeStatusItem:item];
[item release];
m_statusItem = nullptr;
logger.debug() << "Remove delegate";
Q_ASSERT(m_statusBarIcon);
[static_cast<MacOSStatusIconDelegate*>(m_statusBarIcon) dealloc];
m_statusBarIcon = nullptr;
}
void MacOSStatusIcon::setIcon(const QString& iconPath) {
QResource resource(iconPath);
if (!resource.isValid()) {
qWarning() << "MacOSStatusIcon: invalid icon resource" << iconPath;
logger.debug() << "Set icon" << iconPath;
QResource imageResource = QResource(iconPath);
Q_ASSERT(imageResource.isValid());
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
}
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
logger.debug() << "Set indicator color";
if (!indicatorColor.isValid()) {
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
return;
}
NSImage* image = [[NSImage alloc] initWithData:resource.uncompressedData().toNSData()];
CGFloat side = [[NSStatusBar systemStatusBar] thickness] - 4.0;
image.size = NSMakeSize(side, side);
static_cast<NSStatusItem*>(m_statusItem).button.image = image;
[image release];
NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f
green:indicatorColor.green() / 255.0f
blue:indicatorColor.blue() / 255.0f
alpha:indicatorColor.alpha() / 255.0f];
[m_statusBarIcon setIndicatorColor:color];
}
void MacOSStatusIcon::setMenu(QMenu* menu) {
static_cast<NSStatusItem*>(m_statusItem).menu = menu ? menu->toNSMenu() : nil;
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
logger.debug() << "Set menu";
[m_statusBarIcon setMenu:statusBarMenu];
}
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
logger.debug() << "Set tooltip";
[m_statusBarIcon setToolTip:tooltip.toNSString()];
}
void MacOSStatusIcon::showMessage(const QString& title, const QString& message) {
logger.debug() << "Show message";
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
// This is a no-op if authorization has already been granted.
// This is a no-op is authorization has been granted.
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL, NSError* _Nullable error) {
completionHandler:^(BOOL granted, NSError* _Nullable error) {
if (error) {
// Note: this error may happen if the application is not signed.
qWarning() << "MacOSStatusIcon: notification authorization error:"
<< QString::fromNSString(error.localizedDescription);
// Note: This error may happen if the application is not signed.
NSLog(@"Error asking for permission to send notifications %@", error);
return;
}
}];
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.title = [title.toNSString() autorelease];
content.body = [message.toNSString() autorelease];
content.sound = [UNNotificationSound defaultSound];
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:nil];
[content release];
trigger:trigger];
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
qWarning() << "MacOSStatusIcon: local notification failed:"
<< QString::fromNSString(error.localizedDescription);
logger.error() << "Local Notification failed" << error;
}
}];
}

View File

@@ -25,19 +25,9 @@ H1 = $INIT_PACKET_MAGIC_HEADER
H2 = $RESPONSE_PACKET_MAGIC_HEADER
H3 = $UNDERLOAD_PACKET_MAGIC_HEADER
H4 = $TRANSPORT_PACKET_MAGIC_HEADER
HeaderProtectionKey = $HEADER_PROTECTION_KEY
ContentPaddingAddition = $CONTENT_PADDING_ADDITION
RekeyAfterTime = $REKEY_AFTER_TIME
RekeyTimeout = $REKEY_TIMEOUT
RejectAfterTime = $REJECT_AFTER_TIME
KeepaliveTimeout = $KEEPALIVE_TIMEOUT
MaxHandshakeAttempts = $MAX_HANDSHAKE_ATTEMPTS
# I1 = $SPECIAL_JUNK_1
# I2 = $SPECIAL_JUNK_2
# I3 = $SPECIAL_JUNK_3
# I4 = $SPECIAL_JUNK_4
# I5 = $SPECIAL_JUNK_5
EOF
# Every AWG parameter is optional - drop the lines whose value came out empty
sed -i '/^[^=]*= *$/d' /opt/amnezia/awg/awg0.conf

View File

@@ -18,17 +18,10 @@ I2 = $SPECIAL_JUNK_2
I3 = $SPECIAL_JUNK_3
I4 = $SPECIAL_JUNK_4
I5 = $SPECIAL_JUNK_5
HeaderProtectionKey = $HEADER_PROTECTION_KEY
ContentPaddingAddition = $CONTENT_PADDING_ADDITION
RekeyAfterTime = $REKEY_AFTER_TIME
RekeyTimeout = $REKEY_TIMEOUT
RejectAfterTime = $REJECT_AFTER_TIME
KeepaliveTimeout = $KEEPALIVE_TIMEOUT
MaxHandshakeAttempts = $MAX_HANDSHAKE_ATTEMPTS
[Peer]
PublicKey = $WIREGUARD_SERVER_PUBLIC_KEY
PresharedKey = $WIREGUARD_PSK
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = $SERVER_IP_ADDRESS:$AWG_SERVER_PORT
PersistentKeepalive = $PERSISTENT_KEEPALIVE
PersistentKeepalive = 25

View File

@@ -47,20 +47,6 @@ else
FAKETLS_SECRET=""
fi
# Persist deployment state for restore on re-scan.
{
printf 'mode=%s\n' "$TRANSPORT_MODE"
printf 'domain=%s\n' "$MTPROXY_TLS_DOMAIN"
printf 'tag=%s\n' "$MTPROXY_TAG"
printf 'additional=%s\n' "$MTPROXY_ADDITIONAL_SECRETS"
printf 'workers_mode=%s\n' "$MTPROXY_WORKERS_MODE"
printf 'workers=%s\n' "$MTPROXY_WORKERS"
printf 'nat_enabled=%s\n' "$MTPROXY_NAT_ENABLED"
printf 'nat_internal=%s\n' "$MTPROXY_NAT_INTERNAL_IP"
printf 'nat_external=%s\n' "$MTPROXY_NAT_EXTERNAL_IP"
printf 'public_host=%s\n' "$MTPROXY_PUBLIC_HOST"
} > /data/mtproxy-meta
# Active link secret depends on transport mode
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$FAKETLS_SECRET" ]; then
LINK_SECRET="$FAKETLS_SECRET"

View File

@@ -28,9 +28,6 @@ rm -f /data/config.toml
if [ -n "$TELEMT_TAG" ]; then
echo "ad_tag = \"$TELEMT_TAG\""
fi
if [ -n "$TELEMT_MIDDLE_PROXY_NAT_IP" ]; then
echo "middle_proxy_nat_ip = \"$TELEMT_MIDDLE_PROXY_NAT_IP\""
fi
echo ""
echo "[general.modes]"
echo "classic = false"

View File

@@ -1,7 +1,7 @@
FROM alpine:3.15
LABEL maintainer="AmneziaVPN"
ARG XRAY_RELEASE="v26.7.28"
ARG XRAY_RELEASE="v25.8.3"
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
RUN apk --update upgrade --no-cache

View File

@@ -2,11 +2,17 @@ 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
# Parse x25519 keypair by label (v26.7 output has an extra Hash32 line; line-index parsing breaks).
KEYPAIR=$(xray x25519)
XRAY_PRIVATE_KEY=$(printf '%s\n' "$KEYPAIR" | sed -n 's/.*[Pp]rivate[ ]*[Kk]ey:[[:space:]]*//p' | head -1)
XRAY_PUBLIC_KEY=$(printf '%s\n' "$KEYPAIR" | sed -n 's/.*(PublicKey):[[:space:]]*//p' | head -1)
[ -z "$XRAY_PUBLIC_KEY" ] && XRAY_PUBLIC_KEY=$(printf '%s\n' "$KEYPAIR" | sed -n 's/.*[Pp]ublic[ ]*[Kk]ey:[[:space:]]*//p' | head -1)
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 ' ')
@@ -15,4 +21,47 @@ 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
# server.json is written by the client (writeServerConfigForSetup); this script only makes keys.
cat > /opt/amnezia/xray/server.json <<EOF
{
"log": {
"loglevel": "error"
},
"inbounds": [
{
"port": $XRAY_SERVER_PORT,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "$XRAY_CLIENT_ID",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"dest": "$XRAY_SITE_NAME:443",
"serverNames": [
"$XRAY_SITE_NAME"
],
"privateKey": "$XRAY_PRIVATE_KEY",
"shortIds": [
"$XRAY_SHORT_ID"
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}
EOF

View File

@@ -5,7 +5,6 @@ sudo docker run -d \
--restart always \
--cap-add=NET_ADMIN \
-p $XRAY_SERVER_PORT:$XRAY_SERVER_PORT/tcp \
-p $XRAY_SERVER_PORT:$XRAY_SERVER_PORT/udp \
--name $CONTAINER_NAME $CONTAINER_NAME
sudo docker network connect amnezia-dns-net $CONTAINER_NAME

View File

@@ -10,8 +10,6 @@ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport $XRAY_SERVER_PORT -j ACCEPT
iptables -A INPUT -p udp --dport $XRAY_SERVER_PORT -j ACCEPT
iptables -P INPUT DROP
ip6tables -A INPUT -i lo -j ACCEPT

File diff suppressed because it is too large Load Diff

View File

@@ -69,14 +69,6 @@ void ExportUiController::generateQrFromString(const QString &text)
emit exportConfigChanged();
}
void ExportUiController::generateQrFromStringRaw(const QString &text)
{
clearPreviousConfig();
m_config = text;
m_qrCodes = { qrCodeUtils::generatePlainQrCodeImage(text.toUtf8()) };
emit exportConfigChanged();
}
QString ExportUiController::getConfig()
{
return m_config;

View File

@@ -26,7 +26,6 @@ public slots:
void generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName);
void generateXrayConfig(const QString &serverId, const QString &clientName);
void generateQrFromString(const QString &text);
void generateQrFromStringRaw(const QString &text);
QString getConfig();
QString getNativeConfigString();

View File

@@ -567,12 +567,12 @@ void InstallUiController::clearProcessedServerCredentials()
void InstallUiController::setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData)
{
m_processedServerCredentials.hostName = hostName.trimmed();
m_processedServerCredentials.hostName = hostName;
if (m_processedServerCredentials.hostName.contains(":")) {
m_processedServerCredentials.port = m_processedServerCredentials.hostName.split(":").at(1).toInt();
m_processedServerCredentials.hostName = m_processedServerCredentials.hostName.split(":").at(0);
}
m_processedServerCredentials.userName = userName.trimmed();
m_processedServerCredentials.userName = userName;
m_processedServerCredentials.secretData = secretData;
}

View File

@@ -5,8 +5,6 @@
#include "core/utils/protocolEnum.h"
#include "core/models/protocolConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/utils/constants/protocolConstants.h"
using namespace amnezia;
@@ -387,59 +385,6 @@ bool ServersUiController::isDefaultServerCurrentlyProcessed() const
return m_serversController->getDefaultServerId() == m_processedServerId;
}
bool ServersUiController::serverHasOutdatedAwgContainer(const QString &serverId) const
{
// The warning suggests reinstalling the container, so it only makes sense
// for servers the user can administer
if (!isServerHasWriteAccess(serverId)) {
return false;
}
const QMap<DockerContainer, ContainerConfig> containers = m_serversController->getServerContainersMap(serverId);
for (DockerContainer container : { DockerContainer::Awg, DockerContainer::Awg2 }) {
if (!containers.contains(container)) {
continue;
}
if (const auto* awgConfig = containers.value(container).getAwgProtocolConfig()) {
if (awgConfig->serverConfig.protocolVersion != protocols::awg::awgV3) {
return true;
}
}
}
return false;
}
bool ServersUiController::defaultServerHasOutdatedAwgContainer() const
{
return serverHasOutdatedAwgContainer(getDefaultServerId());
}
bool ServersUiController::isContainerOutdatedAwg(int containerIndex) const
{
if (!isProcessedServerHasWriteAccess()) {
return false;
}
DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (!ContainerUtils::isAwgContainer(container)) {
return false;
}
const QMap<DockerContainer, ContainerConfig> containers = m_serversController->getServerContainersMap(m_processedServerId);
if (!containers.contains(container)) {
return false;
}
if (const auto* awgConfig = containers.value(container).getAwgProtocolConfig()) {
return awgConfig->serverConfig.protocolVersion != protocols::awg::awgV3;
}
return false;
}
bool ServersUiController::isProcessedContainerOutdatedAwg() const
{
return isContainerOutdatedAwg(m_processedContainerIndex);
}
bool ServersUiController::isProcessedServerHasWriteAccess() const
{
return isServerHasWriteAccess(m_processedServerId);

View File

@@ -27,7 +27,6 @@ class ServersUiController : public QObject
Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerIdChanged)
Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerIdChanged)
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIdChanged)
Q_PROPERTY(bool defaultServerHasOutdatedAwgContainer READ defaultServerHasOutdatedAwgContainer NOTIFY defaultServerIdChanged)
Q_PROPERTY(QString processedServerId READ getProcessedServerId WRITE setProcessedServerId NOTIFY processedServerIdChanged)
Q_PROPERTY(int processedContainerIndex READ getProcessedContainerIndex WRITE setProcessedContainerIndex NOTIFY processedContainerIndexChanged)
@@ -73,11 +72,6 @@ public slots:
bool isDefaultServerFromApi() const;
bool hasServerWithWriteAccess() const;
bool serverHasOutdatedAwgContainer(const QString &serverId) const;
bool defaultServerHasOutdatedAwgContainer() const;
bool isContainerOutdatedAwg(int containerIndex) const;
bool isProcessedContainerOutdatedAwg() const;
QString serverName(const QString &serverId) const;
QString serverHostName(const QString &serverId) const;
int serverDefaultContainer(const QString &serverId) const;

View File

@@ -22,7 +22,7 @@ QVariant IpSplitTunnelingModel::data(const QModelIndex &index, int role) const
break;
}
case IpRole: {
return m_sites.at(index.row()).second.join(", ");
return m_sites.at(index.row()).second;
break;
}
default: {
@@ -33,7 +33,7 @@ QVariant IpSplitTunnelingModel::data(const QModelIndex &index, int role) const
return QVariant();
}
void IpSplitTunnelingModel::updateModel(const QVector<QPair<QString, QStringList>> &sites)
void IpSplitTunnelingModel::updateModel(const QVector<QPair<QString, QString>> &sites)
{
beginResetModel();
m_sites = sites;

View File

@@ -4,7 +4,6 @@
#include <QAbstractListModel>
#include <QVector>
#include <QPair>
#include <QStringList>
class IpSplitTunnelingModel : public QAbstractListModel
{
@@ -23,13 +22,13 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public slots:
void updateModel(const QVector<QPair<QString, QStringList>> &sites);
void updateModel(const QVector<QPair<QString, QString>> &sites);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QVector<QPair<QString, QStringList>> m_sites;
QVector<QPair<QString, QString>> m_sites;
};
#endif // IPSPLITTUNNELINGMODEL_H

View File

@@ -2,8 +2,6 @@
#include <QJsonDocument>
#include "core/configurators/wireguardConfigurator.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
@@ -44,12 +42,6 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
case Roles::ClientSpecialJunk3Role: m_protocolConfig.clientConfig->specialJunk3 = strValue; break;
case Roles::ClientSpecialJunk4Role: m_protocolConfig.clientConfig->specialJunk4 = strValue; break;
case Roles::ClientSpecialJunk5Role: m_protocolConfig.clientConfig->specialJunk5 = strValue; break;
case Roles::ClientContentPaddingAdditionRole: m_protocolConfig.clientConfig->contentPaddingAddition = strValue; break;
case Roles::ClientRekeyAfterTimeRole: m_protocolConfig.clientConfig->rekeyAfterTime = strValue; break;
case Roles::ClientRekeyTimeoutRole: m_protocolConfig.clientConfig->rekeyTimeout = strValue; break;
case Roles::ClientRejectAfterTimeRole: m_protocolConfig.clientConfig->rejectAfterTime = strValue; break;
case Roles::ClientKeepaliveTimeoutRole: m_protocolConfig.clientConfig->keepaliveTimeout = strValue; break;
case Roles::ClientMaxHandshakeAttemptsRole: m_protocolConfig.clientConfig->maxHandshakeAttempts = strValue; break;
case Roles::ServerJunkPacketCountRole: m_protocolConfig.serverConfig.junkPacketCount = strValue; break;
case Roles::ServerJunkPacketMinSizeRole: m_protocolConfig.serverConfig.junkPacketMinSize = strValue; break;
case Roles::ServerJunkPacketMaxSizeRole: m_protocolConfig.serverConfig.junkPacketMaxSize = strValue; break;
@@ -66,24 +58,6 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
case Roles::ServerSpecialJunk3Role: m_protocolConfig.serverConfig.specialJunk3 = strValue; break;
case Roles::ServerSpecialJunk4Role: m_protocolConfig.serverConfig.specialJunk4 = strValue; break;
case Roles::ServerSpecialJunk5Role: m_protocolConfig.serverConfig.specialJunk5 = strValue; break;
case Roles::ServerContentPaddingAdditionRole: m_protocolConfig.serverConfig.contentPaddingAddition = strValue; break;
case Roles::ServerRekeyAfterTimeRole: m_protocolConfig.serverConfig.rekeyAfterTime = strValue; break;
case Roles::ServerRekeyTimeoutRole: m_protocolConfig.serverConfig.rekeyTimeout = strValue; break;
case Roles::ServerRejectAfterTimeRole: m_protocolConfig.serverConfig.rejectAfterTime = strValue; break;
case Roles::ServerKeepaliveTimeoutRole: m_protocolConfig.serverConfig.keepaliveTimeout = strValue; break;
case Roles::ServerMaxHandshakeAttemptsRole: m_protocolConfig.serverConfig.maxHandshakeAttempts = strValue; break;
case Roles::ServerHeaderProtectionEnabledRole: {
if (value.toBool()) {
if (m_protocolConfig.serverConfig.headerProtectionKey.isEmpty()) {
const QString originalKey = m_originalProtocolConfig.serverConfig.headerProtectionKey;
m_protocolConfig.serverConfig.headerProtectionKey =
originalKey.isEmpty() ? WireguardConfigurator::genClientKeys().clientPrivKey : originalKey;
}
} else {
m_protocolConfig.serverConfig.headerProtectionKey.clear();
}
break;
}
default:
return false;
}
@@ -111,13 +85,6 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
case Roles::ClientSpecialJunk3Role: return m_protocolConfig.clientConfig->specialJunk3;
case Roles::ClientSpecialJunk4Role: return m_protocolConfig.clientConfig->specialJunk4;
case Roles::ClientSpecialJunk5Role: return m_protocolConfig.clientConfig->specialJunk5;
case Roles::ClientContentPaddingAdditionRole: return m_protocolConfig.clientConfig->contentPaddingAddition;
case Roles::ClientRekeyAfterTimeRole: return m_protocolConfig.clientConfig->rekeyAfterTime;
case Roles::ClientRekeyTimeoutRole: return m_protocolConfig.clientConfig->rekeyTimeout;
case Roles::ClientRejectAfterTimeRole: return m_protocolConfig.clientConfig->rejectAfterTime;
case Roles::ClientKeepaliveTimeoutRole: return m_protocolConfig.clientConfig->keepaliveTimeout;
case Roles::ClientMaxHandshakeAttemptsRole: return m_protocolConfig.clientConfig->maxHandshakeAttempts;
case Roles::ClientHeaderProtectionEnabledRole: return !m_protocolConfig.clientConfig->headerProtectionKey.isEmpty();
case Roles::ServerJunkPacketCountRole: return m_protocolConfig.serverConfig.junkPacketCount;
case Roles::ServerJunkPacketMinSizeRole: return m_protocolConfig.serverConfig.junkPacketMinSize;
@@ -136,19 +103,7 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
case Roles::ServerSpecialJunk4Role: return m_protocolConfig.serverConfig.specialJunk4;
case Roles::ServerSpecialJunk5Role: return m_protocolConfig.serverConfig.specialJunk5;
case Roles::ServerContentPaddingAdditionRole: return m_protocolConfig.serverConfig.contentPaddingAddition;
case Roles::ServerRekeyAfterTimeRole: return m_protocolConfig.serverConfig.rekeyAfterTime;
case Roles::ServerRekeyTimeoutRole: return m_protocolConfig.serverConfig.rekeyTimeout;
case Roles::ServerRejectAfterTimeRole: return m_protocolConfig.serverConfig.rejectAfterTime;
case Roles::ServerKeepaliveTimeoutRole: return m_protocolConfig.serverConfig.keepaliveTimeout;
case Roles::ServerMaxHandshakeAttemptsRole: return m_protocolConfig.serverConfig.maxHandshakeAttempts;
case Roles::ServerHeaderProtectionEnabledRole: return !m_protocolConfig.serverConfig.headerProtectionKey.isEmpty();
case Roles::IsAwg2Role: {
QString version = serverProtocolVersion();
return version == protocols::awg::awgV2 || version == protocols::awg::awgV3;
}
case Roles::IsAwg3Role: return serverProtocolVersion() == protocols::awg::awgV3;
case Roles::IsAwg2Role: return m_protocolConfig.serverConfig.protocolVersion == protocols::awg::awgV2;
}
return QVariant();
@@ -161,6 +116,8 @@ void AwgConfigModel::updateModel(amnezia::DockerContainer container, const amnez
m_protocolConfig = protocolConfig;
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
if (!m_protocolConfig.clientConfig.has_value()) {
m_protocolConfig.clientConfig = amnezia::AwgClientConfig{};
}
@@ -171,9 +128,68 @@ void AwgConfigModel::updateModel(amnezia::DockerContainer container, const amnez
endResetModel();
}
QString AwgConfigModel::serverProtocolVersion() const
void AwgConfigModel::applyDefaultsToServerConfig(amnezia::AwgServerConfig& config)
{
return m_protocolConfig.serverConfig.protocolVersion;
if (config.subnetAddress.isEmpty()) {
config.subnetAddress = protocols::wireguard::defaultSubnetAddress;
}
if (config.port.isEmpty()) {
config.port = protocols::awg::defaultPort;
}
if (config.transportProto.isEmpty()) {
config.transportProto = ProtocolUtils::transportProtoToString(
ProtocolUtils::defaultTransportProto(amnezia::Proto::Awg), amnezia::Proto::Awg);
}
if (config.junkPacketCount.isEmpty()) {
config.junkPacketCount = protocols::awg::defaultJunkPacketCount;
}
if (config.junkPacketMinSize.isEmpty()) {
config.junkPacketMinSize = protocols::awg::defaultJunkPacketMinSize;
}
if (config.junkPacketMaxSize.isEmpty()) {
config.junkPacketMaxSize = protocols::awg::defaultJunkPacketMaxSize;
}
if (config.initPacketJunkSize.isEmpty()) {
config.initPacketJunkSize = protocols::awg::defaultInitPacketJunkSize;
}
if (config.responsePacketJunkSize.isEmpty()) {
config.responsePacketJunkSize = protocols::awg::defaultResponsePacketJunkSize;
}
if (config.protocolVersion == protocols::awg::awgV2) {
if (config.cookieReplyPacketJunkSize.isEmpty()) {
config.cookieReplyPacketJunkSize = protocols::awg::defaultCookieReplyPacketJunkSize;
}
if (config.transportPacketJunkSize.isEmpty()) {
config.transportPacketJunkSize = protocols::awg::defaultTransportPacketJunkSize;
}
}
if (config.initPacketMagicHeader.isEmpty()) {
config.initPacketMagicHeader = protocols::awg::defaultInitPacketMagicHeader;
}
if (config.responsePacketMagicHeader.isEmpty()) {
config.responsePacketMagicHeader = protocols::awg::defaultResponsePacketMagicHeader;
}
if (config.underloadPacketMagicHeader.isEmpty()) {
config.underloadPacketMagicHeader = protocols::awg::defaultUnderloadPacketMagicHeader;
}
if (config.transportPacketMagicHeader.isEmpty()) {
config.transportPacketMagicHeader = protocols::awg::defaultTransportPacketMagicHeader;
}
if (config.specialJunk1.isEmpty()) {
config.specialJunk1 = protocols::awg::defaultSpecialJunk1;
}
if (config.specialJunk2.isEmpty()) {
config.specialJunk2 = protocols::awg::defaultSpecialJunk2;
}
if (config.specialJunk3.isEmpty()) {
config.specialJunk3 = protocols::awg::defaultSpecialJunk3;
}
if (config.specialJunk4.isEmpty()) {
config.specialJunk4 = protocols::awg::defaultSpecialJunk4;
}
if (config.specialJunk5.isEmpty()) {
config.specialJunk5 = protocols::awg::defaultSpecialJunk5;
}
}
void AwgConfigModel::applyDefaultsToClientConfig(amnezia::AwgClientConfig& config)
@@ -181,6 +197,46 @@ void AwgConfigModel::applyDefaultsToClientConfig(amnezia::AwgClientConfig& confi
if (config.mtu.isEmpty()) {
config.mtu = protocols::awg::defaultMtu;
}
if (config.junkPacketCount.isEmpty()) {
config.junkPacketCount = m_protocolConfig.serverConfig.junkPacketCount.isEmpty()
? protocols::awg::defaultJunkPacketCount
: m_protocolConfig.serverConfig.junkPacketCount;
}
if (config.junkPacketMinSize.isEmpty()) {
config.junkPacketMinSize = m_protocolConfig.serverConfig.junkPacketMinSize.isEmpty()
? protocols::awg::defaultJunkPacketMinSize
: m_protocolConfig.serverConfig.junkPacketMinSize;
}
if (config.junkPacketMaxSize.isEmpty()) {
config.junkPacketMaxSize = m_protocolConfig.serverConfig.junkPacketMaxSize.isEmpty()
? protocols::awg::defaultJunkPacketMaxSize
: m_protocolConfig.serverConfig.junkPacketMaxSize;
}
if (config.specialJunk1.isEmpty()) {
config.specialJunk1 = m_protocolConfig.serverConfig.specialJunk1.isEmpty()
? protocols::awg::defaultSpecialJunk1
: m_protocolConfig.serverConfig.specialJunk1;
}
if (config.specialJunk2.isEmpty()) {
config.specialJunk2 = m_protocolConfig.serverConfig.specialJunk2.isEmpty()
? protocols::awg::defaultSpecialJunk2
: m_protocolConfig.serverConfig.specialJunk2;
}
if (config.specialJunk3.isEmpty()) {
config.specialJunk3 = m_protocolConfig.serverConfig.specialJunk3.isEmpty()
? protocols::awg::defaultSpecialJunk3
: m_protocolConfig.serverConfig.specialJunk3;
}
if (config.specialJunk4.isEmpty()) {
config.specialJunk4 = m_protocolConfig.serverConfig.specialJunk4.isEmpty()
? protocols::awg::defaultSpecialJunk4
: m_protocolConfig.serverConfig.specialJunk4;
}
if (config.specialJunk5.isEmpty()) {
config.specialJunk5 = m_protocolConfig.serverConfig.specialJunk5.isEmpty()
? protocols::awg::defaultSpecialJunk5
: m_protocolConfig.serverConfig.specialJunk5;
}
}
amnezia::AwgProtocolConfig AwgConfigModel::getProtocolConfig()
@@ -191,6 +247,21 @@ amnezia::AwgProtocolConfig AwgConfigModel::getProtocolConfig()
m_protocolConfig.clearClientConfig();
}
if (m_protocolConfig.serverConfig.protocolVersion.isEmpty() ||
m_protocolConfig.serverConfig.protocolVersion != protocols::awg::awgV2) {
bool hasSpecialJunk = !m_protocolConfig.serverConfig.specialJunk1.trimmed().isEmpty() ||
!m_protocolConfig.serverConfig.specialJunk2.trimmed().isEmpty() ||
!m_protocolConfig.serverConfig.specialJunk3.trimmed().isEmpty() ||
!m_protocolConfig.serverConfig.specialJunk4.trimmed().isEmpty() ||
!m_protocolConfig.serverConfig.specialJunk5.trimmed().isEmpty();
if (hasSpecialJunk) {
m_protocolConfig.serverConfig.protocolVersion = protocols::awg::awgV1_5;
} else if (m_protocolConfig.serverConfig.protocolVersion.isEmpty()) {
m_protocolConfig.serverConfig.protocolVersion = QString();
}
}
return m_protocolConfig;
}
@@ -225,13 +296,6 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
roles[ClientSpecialJunk3Role] = "clientSpecialJunk3";
roles[ClientSpecialJunk4Role] = "clientSpecialJunk4";
roles[ClientSpecialJunk5Role] = "clientSpecialJunk5";
roles[ClientContentPaddingAdditionRole] = "clientContentPaddingAddition";
roles[ClientRekeyAfterTimeRole] = "clientRekeyAfterTime";
roles[ClientRekeyTimeoutRole] = "clientRekeyTimeout";
roles[ClientRejectAfterTimeRole] = "clientRejectAfterTime";
roles[ClientKeepaliveTimeoutRole] = "clientKeepaliveTimeout";
roles[ClientMaxHandshakeAttemptsRole] = "clientMaxHandshakeAttempts";
roles[ClientHeaderProtectionEnabledRole] = "clientHeaderProtectionEnabled";
roles[ServerJunkPacketCountRole] = "serverJunkPacketCount";
roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize";
@@ -251,16 +315,7 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
roles[ServerSpecialJunk4Role] = "serverSpecialJunk4";
roles[ServerSpecialJunk5Role] = "serverSpecialJunk5";
roles[ServerContentPaddingAdditionRole] = "serverContentPaddingAddition";
roles[ServerRekeyAfterTimeRole] = "serverRekeyAfterTime";
roles[ServerRekeyTimeoutRole] = "serverRekeyTimeout";
roles[ServerRejectAfterTimeRole] = "serverRejectAfterTime";
roles[ServerKeepaliveTimeoutRole] = "serverKeepaliveTimeout";
roles[ServerMaxHandshakeAttemptsRole] = "serverMaxHandshakeAttempts";
roles[ServerHeaderProtectionEnabledRole] = "serverHeaderProtectionEnabled";
roles[IsAwg2Role] = "isAwg2";
roles[IsAwg3Role] = "isAwg3";
return roles;
}

View File

@@ -26,13 +26,6 @@ public:
ClientSpecialJunk3Role,
ClientSpecialJunk4Role,
ClientSpecialJunk5Role,
ClientContentPaddingAdditionRole,
ClientRekeyAfterTimeRole,
ClientRekeyTimeoutRole,
ClientRejectAfterTimeRole,
ClientKeepaliveTimeoutRole,
ClientMaxHandshakeAttemptsRole,
ClientHeaderProtectionEnabledRole,
ServerJunkPacketCountRole,
ServerJunkPacketMinSizeRole,
@@ -52,16 +45,7 @@ public:
ServerSpecialJunk4Role,
ServerSpecialJunk5Role,
ServerContentPaddingAdditionRole,
ServerRekeyAfterTimeRole,
ServerRekeyTimeoutRole,
ServerRejectAfterTimeRole,
ServerKeepaliveTimeoutRole,
ServerMaxHandshakeAttemptsRole,
ServerHeaderProtectionEnabledRole,
IsAwg2Role,
IsAwg3Role
IsAwg2Role
};
explicit AwgConfigModel(QObject *parent = nullptr);
@@ -87,7 +71,7 @@ private:
amnezia::AwgProtocolConfig m_protocolConfig;
amnezia::AwgProtocolConfig m_originalProtocolConfig;
QString serverProtocolVersion() const;
void applyDefaultsToServerConfig(amnezia::AwgServerConfig& config);
void applyDefaultsToClientConfig(amnezia::AwgClientConfig& config);
};

View File

@@ -71,6 +71,8 @@ void WireGuardConfigModel::updateModel(amnezia::DockerContainer container, const
m_protocolConfig = protocolConfig;
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
if (!m_protocolConfig.clientConfig.has_value()) {
m_protocolConfig.clientConfig = amnezia::WireGuardClientConfig{};
}
@@ -81,6 +83,20 @@ void WireGuardConfigModel::updateModel(amnezia::DockerContainer container, const
endResetModel();
}
void WireGuardConfigModel::applyDefaultsToServerConfig(amnezia::WireGuardServerConfig& config)
{
if (config.subnetAddress.isEmpty()) {
config.subnetAddress = protocols::wireguard::defaultSubnetAddress;
}
if (config.port.isEmpty()) {
config.port = protocols::wireguard::defaultPort;
}
if (config.transportProto.isEmpty()) {
config.transportProto = ProtocolUtils::transportProtoToString(
ProtocolUtils::defaultTransportProto(amnezia::Proto::WireGuard), amnezia::Proto::WireGuard);
}
}
void WireGuardConfigModel::applyDefaultsToClientConfig(amnezia::WireGuardClientConfig& config)
{
if (config.mtu.isEmpty()) {

View File

@@ -40,6 +40,7 @@ private:
amnezia::WireGuardProtocolConfig m_protocolConfig;
amnezia::WireGuardProtocolConfig m_originalProtocolConfig;
void applyDefaultsToServerConfig(amnezia::WireGuardServerConfig& config);
void applyDefaultsToClientConfig(amnezia::WireGuardClientConfig& config);
};

View File

@@ -70,6 +70,8 @@ bool XrayConfigModel::setData(const QModelIndex& index, const QVariant& value, i
break;
case Roles::XhttpPathRole: xhttp.path = str;
break;
case Roles::XhttpHeadersTemplateRole: xhttp.headersTemplate = str;
break;
case Roles::XhttpUplinkMethodRole: xhttp.uplinkMethod = str;
break;
case Roles::XhttpDisableGrpcRole: xhttp.disableGrpc = value.toBool();
@@ -204,6 +206,7 @@ QVariant XrayConfigModel::data(const QModelIndex& index, int role) const
case Roles::XhttpModeRole: return xhttp.mode;
case Roles::XhttpHostRole: return xhttp.host;
case Roles::XhttpPathRole: return xhttp.path;
case Roles::XhttpHeadersTemplateRole: return xhttp.headersTemplate;
case Roles::XhttpUplinkMethodRole: return xhttp.uplinkMethod;
case Roles::XhttpDisableGrpcRole: return xhttp.disableGrpc;
case Roles::XhttpDisableSseRole: return xhttp.disableSse;
@@ -332,6 +335,9 @@ void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &con
if (config.xhttp.mode.isEmpty()) {
config.xhttp.mode = protocols::xray::defaultXhttpMode;
}
if (config.xhttp.headersTemplate.isEmpty()) {
config.xhttp.headersTemplate = protocols::xray::defaultXhttpHeadersTemplate;
}
if (config.xhttp.uplinkMethod.isEmpty()) {
config.xhttp.uplinkMethod = protocols::xray::defaultXhttpUplinkMethod;
}
@@ -398,6 +404,7 @@ QHash<int, QByteArray> XrayConfigModel::roleNames() const
roles[XhttpModeRole] = "xhttpMode";
roles[XhttpHostRole] = "xhttpHost";
roles[XhttpPathRole] = "xhttpPath";
roles[XhttpHeadersTemplateRole] = "xhttpHeadersTemplate";
roles[XhttpUplinkMethodRole] = "xhttpUplinkMethod";
roles[XhttpDisableGrpcRole] = "xhttpDisableGrpc";
roles[XhttpDisableSseRole] = "xhttpDisableSse";
@@ -474,6 +481,7 @@ void XrayConfigModel::applyServerConfig(const amnezia::XrayServerConfig &serverC
m_protocolConfig.serverConfig = serverConfig;
// Clear client config since server settings changed
m_protocolConfig.clearClientConfig();
m_originalProtocolConfig = m_protocolConfig;
endResetModel();
if (wasUnsavedChanges != hasUnsavedChanges()) {
@@ -507,7 +515,7 @@ QStringList XrayConfigModel::fingerprintOptions()
QStringList XrayConfigModel::alpnOptions()
{
return { "h2", "http/1.1", "h2,http/1.1" };
return { "HTTP/2", "HTTP/1.1", "HTTP/2,HTTP/1.1" };
}
QStringList XrayConfigModel::xhttpModeOptions()
@@ -515,6 +523,11 @@ QStringList XrayConfigModel::xhttpModeOptions()
return { "Auto", "Packet-up", "Stream-up", "Stream-one" };
}
QStringList XrayConfigModel::xhttpHeadersTemplateOptions()
{
return { "HTTP", "None" };
}
QStringList XrayConfigModel::xhttpUplinkMethodOptions()
{
return { "POST", "PUT", "PATCH" };
@@ -522,12 +535,17 @@ QStringList XrayConfigModel::xhttpUplinkMethodOptions()
QStringList XrayConfigModel::xhttpSessionPlacementOptions()
{
return { "Path", "Header", "Cookie", "Query", "None" };
return { "Path", "Header", "Cookie", "None" };
}
QStringList XrayConfigModel::xhttpSessionKeyOptions()
{
return { "Path", "Header", "None" };
}
QStringList XrayConfigModel::xhttpSeqPlacementOptions()
{
return { "Path", "Header", "Cookie", "Query", "None" };
return { "Path", "Header", "Cookie", "None" };
}
QStringList XrayConfigModel::xhttpUplinkDataPlacementOptions()
@@ -572,76 +590,6 @@ QString XrayConfigModel::mkcpDefaultWriteBufferSize()
return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
}
QString XrayConfigModel::portDefault()
{
return QString::fromLatin1(protocols::xray::defaultPort);
}
QString XrayConfigModel::sniDefault()
{
return QString::fromLatin1(protocols::xray::defaultSni);
}
QString XrayConfigModel::xhttpHostDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpHost);
}
QString XrayConfigModel::xhttpUplinkChunkSizeDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpUplinkChunkSize);
}
QString XrayConfigModel::scMaxEachPostBytesMinDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScMaxEachPostBytesMin);
}
QString XrayConfigModel::scMaxEachPostBytesMaxDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScMaxEachPostBytesMax);
}
QString XrayConfigModel::scMinPostsIntervalMsMinDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScMinPostsIntervalMsMin);
}
QString XrayConfigModel::scMinPostsIntervalMsMaxDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScMinPostsIntervalMsMax);
}
QString XrayConfigModel::scStreamUpServerSecsMinDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScStreamUpServerSecsMin);
}
QString XrayConfigModel::scStreamUpServerSecsMaxDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpScStreamUpServerSecsMax);
}
QString XrayConfigModel::xPaddingKeyDefault()
{
return QString::fromLatin1(protocols::xray::defaultXPaddingKey);
}
QString XrayConfigModel::xPaddingHeaderDefault()
{
return QString::fromLatin1(protocols::xray::defaultXPaddingHeader);
}
QString XrayConfigModel::xPaddingBytesMinDefault()
{
return QString::fromLatin1(protocols::xray::defaultXPaddingBytesMin);
}
QString XrayConfigModel::xPaddingBytesMaxDefault()
{
return QString::fromLatin1(protocols::xray::defaultXPaddingBytesMax);
}
namespace {
bool isValidSingleHost(const QString &t)
{

View File

@@ -33,6 +33,7 @@ public:
XhttpModeRole,
XhttpHostRole,
XhttpPathRole,
XhttpHeadersTemplateRole,
XhttpUplinkMethodRole,
XhttpDisableGrpcRole,
XhttpDisableSseRole,
@@ -101,8 +102,10 @@ public:
Q_INVOKABLE static QStringList fingerprintOptions();
Q_INVOKABLE static QStringList alpnOptions();
Q_INVOKABLE static QStringList xhttpModeOptions();
Q_INVOKABLE static QStringList xhttpHeadersTemplateOptions();
Q_INVOKABLE static QStringList xhttpUplinkMethodOptions();
Q_INVOKABLE static QStringList xhttpSessionPlacementOptions();
Q_INVOKABLE static QStringList xhttpSessionKeyOptions();
Q_INVOKABLE static QStringList xhttpSeqPlacementOptions();
Q_INVOKABLE static QStringList xhttpUplinkDataPlacementOptions();
Q_INVOKABLE static QStringList xPaddingPlacementOptions();
@@ -115,21 +118,6 @@ public:
Q_INVOKABLE static QString mkcpDefaultReadBufferSize();
Q_INVOKABLE static QString mkcpDefaultWriteBufferSize();
Q_INVOKABLE static QString portDefault();
Q_INVOKABLE static QString sniDefault();
Q_INVOKABLE static QString xhttpHostDefault();
Q_INVOKABLE static QString xhttpUplinkChunkSizeDefault();
Q_INVOKABLE static QString scMaxEachPostBytesMinDefault();
Q_INVOKABLE static QString scMaxEachPostBytesMaxDefault();
Q_INVOKABLE static QString scMinPostsIntervalMsMinDefault();
Q_INVOKABLE static QString scMinPostsIntervalMsMaxDefault();
Q_INVOKABLE static QString scStreamUpServerSecsMinDefault();
Q_INVOKABLE static QString scStreamUpServerSecsMaxDefault();
Q_INVOKABLE static QString xPaddingKeyDefault();
Q_INVOKABLE static QString xPaddingHeaderDefault();
Q_INVOKABLE static QString xPaddingBytesMinDefault();
Q_INVOKABLE static QString xPaddingBytesMaxDefault();
Q_INVOKABLE static bool isValidHost(const QString &host);
Q_INVOKABLE static bool isValidSni(const QString &sni);
Q_INVOKABLE static bool isValidPath(const QString &path);

View File

@@ -6,36 +6,8 @@ import QtQuick.Layouts
import "../Controls2"
TextFieldWithHeaderType {
id: root
property var scroller: null
property bool rangeValidation: false
signal edited(string text)
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
checkEmptyText: false
property RegularExpressionValidator rangeValidator: RegularExpressionValidator {
regularExpression: /^\d*(-\d*)?$/
}
textField.validator: rangeValidation ? rangeValidator : null
textField.onEditingFinished: {
if (root.rangeValidation) {
root.textField.text = root.textField.text.replace(/^-+|-+$/g, "")
}
root.edited(root.textField.text)
}
textField.onActiveFocusChanged: {
if (root.textField.activeFocus && root.scroller) {
root.scroller.scrollToItem(root)
}
}
checkEmptyText: true
}

View File

@@ -59,7 +59,6 @@ ListViewType {
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
if (!isInstalled) {
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
ServersUiController.processedContainerIndex = containerIndex
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.closeTriggered()

View File

@@ -93,20 +93,6 @@ ListViewType {
Keys.onReturnPressed: serverRadioButton.clicked()
}
ImageButtonType {
id: outdatedContainerWarningIcon
objectName: "outdatedContainerWarningIcon"
visible: ServersUiController.serverHasOutdatedAwgContainer(serverId)
hoverEnabled: false
image: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
implicitWidth: 40
implicitHeight: 56
}
ImageButtonType {
id: serverInfoButton
objectName: "serverInfoButton"

View File

@@ -5,7 +5,6 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
@@ -19,14 +18,11 @@ ListViewType {
delegate: ColumnLayout {
width: root.width
property bool isOutdatedAwgContainer: Boolean(isInstalled && ServersUiController.isContainerOutdatedAwg(root.model.mapToSource(index)))
LabelWithButtonType {
Layout.fillWidth: true
text: name
descriptionText: description
rightWarningImageSource: isOutdatedAwgContainer ? "qrc:/images/controls/alert-circle.svg" : ""
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
clickedFunction: function() {

View File

@@ -232,7 +232,6 @@ Popup {
textField.placeholderTextColor: AmneziaStyle.color.mutedGray
textField.inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText
textField.maximumLength: 6
textField.validator: RegularExpressionValidator { regularExpression: /^[0-9]{0,6}$/ }
textField.font.letterSpacing: 2
textField.onAccepted: {

View File

@@ -6,20 +6,6 @@ Menu {
popupType: Popup.Native
// On Qt < 6.10 the ContextMenu attached type has no native backing on iOS
// and opens this Qt-drawn menu instead. In that case the native edit menu
// is presented through UIEditMenuInteraction (IosContextMenu is registered
// only in iOS builds; on Qt >= 6.10 isAvailable() returns false and the
// attached type shows the native menu itself).
readonly property bool useNativeEditMenu: typeof IosContextMenu !== "undefined" && IosContextMenu.isAvailable()
function requestNative(position) {
if (useNativeEditMenu && textObj) {
textObj.forceActiveFocus()
IosContextMenu.present(textObj, position.x, position.y)
}
}
property Item inputBlocker: null
Component {

View File

@@ -22,8 +22,6 @@ Item {
property string buttonImageSource
property string rightImageSource
property string leftImageSource
property string rightWarningImageSource
property string rightWarningImageColor: AmneziaStyle.color.goldenApricot
property bool isLeftImageHoverEnabled: true
property bool isSmallLeftImage: false
@@ -227,33 +225,6 @@ Item {
}
}
Rectangle {
id: rightWarningImageBackground
visible: rightWarningImageSource !== ""
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignRight
radius: 12
color: AmneziaStyle.color.transparent
Image {
id: rightWarningImage
anchors.centerIn: parent
source: rightWarningImageSource
visible: false
}
ColorOverlay {
anchors.fill: rightWarningImage
source: rightWarningImage
color: rightWarningImageColor
}
}
ImageButtonType {
id: eyeImage
visible: buttonImageSource !== ""

View File

@@ -22,8 +22,6 @@ ListViewType {
property int selectedIndex: 0
property string currentValue: ""
width: rootWidth
height: root.contentItem.height
@@ -133,7 +131,7 @@ ListViewType {
}
ButtonGroup.group: buttonGroup
checked: root.currentValue !== "" ? (name === root.currentValue) : (root.selectedIndex === index)
checked: root.selectedIndex === index
onClicked: {
root.selectedIndex = index

View File

@@ -24,9 +24,6 @@ Item {
property int minLimit: 0
property int maxLimit: 2147483647
property string minPlaceholder: ""
property string maxPlaceholder: ""
property string hintText: root.minLimit > 0
? (root.minLimit + "" + root.maxLimit)
: ("≤ " + root.maxLimit)
@@ -76,7 +73,6 @@ Item {
property string lastValid: ""
Layout.fillWidth: true
headerText: qsTr("Min")
placeholderText: root.minPlaceholder
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onActiveFocusChanged: {
@@ -91,9 +87,10 @@ Item {
if (!isNaN(mx) && parseInt(v, 10) > mx)
root.maxChanged(v)
}
if (minField.textField.text !== v)
if (v !== root.minValue)
root.minChanged(v)
else if (minField.textField.text !== v)
minField.textField.text = v
root.minChanged(v)
}
Binding {
@@ -111,7 +108,6 @@ Item {
property string lastValid: ""
Layout.fillWidth: true
headerText: qsTr("Max")
placeholderText: root.maxPlaceholder
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onActiveFocusChanged: {
@@ -126,9 +122,10 @@ Item {
if (!isNaN(mn) && parseInt(v, 10) < mn)
v = String(mn)
}
if (maxField.textField.text !== v)
if (v !== root.maxValue)
root.maxChanged(v)
else if (maxField.textField.text !== v)
maxField.textField.text = v
root.maxChanged(v)
}
Binding {

View File

@@ -93,23 +93,7 @@ Rectangle {
wrapMode: Text.Wrap
ContextMenu.menu: contextMenu.useNativeEditMenu ? null : contextMenu
ContextMenu.onRequested: function(position) {
contextMenu.requestNative(position)
}
// Native iOS text fields select the word and show the edit
// menu on double tap; Qt does neither on touch.
TapHandler {
enabled: contextMenu.useNativeEditMenu
acceptedDevices: PointerDevice.TouchScreen
onDoubleTapped: function(eventPoint) {
textArea.forceActiveFocus()
textArea.cursorPosition = textArea.positionAt(eventPoint.position.x, eventPoint.position.y)
textArea.selectWord()
contextMenu.requestNative(eventPoint.position)
}
}
ContextMenu.menu: contextMenu
ContextMenuType {
id: contextMenu

View File

@@ -79,23 +79,7 @@ Rectangle {
wrapMode: Text.Wrap
ContextMenu.menu: contextMenu.useNativeEditMenu ? null : contextMenu
ContextMenu.onRequested: function(position) {
contextMenu.requestNative(position)
}
// Native iOS text fields select the word and show the edit
// menu on double tap; Qt does neither on touch.
TapHandler {
enabled: contextMenu.useNativeEditMenu
acceptedDevices: PointerDevice.TouchScreen
onDoubleTapped: function(eventPoint) {
textArea.forceActiveFocus()
textArea.cursorPosition = textArea.positionAt(eventPoint.position.x, eventPoint.position.y)
textArea.selectWord()
contextMenu.requestNative(eventPoint.position)
}
}
ContextMenu.menu: contextMenu
ContextMenuType {
id: contextMenu

View File

@@ -11,7 +11,6 @@ Item {
property string headerText
property string subtitleText // optional line under header (e.g. default value hint)
property string hintText // optional (i) info tooltip next to the header (e.g. range / max / default)
property string headerTextDisabledColor: AmneziaStyle.color.charcoalGray
property string headerTextColor: AmneziaStyle.color.mutedGray
@@ -24,7 +23,6 @@ Item {
property var clickedFunc
property alias textField: textField
property alias placeholderText: textField.placeholderText
property string textFieldTextColor: AmneziaStyle.color.paleGray
property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray
@@ -146,23 +144,7 @@ Item {
}
}
ContextMenu.menu: contextMenu.useNativeEditMenu ? null : contextMenu
ContextMenu.onRequested: function(position) {
contextMenu.requestNative(position)
}
// Native iOS text fields select the word and show the
// edit menu on double tap; Qt does neither on touch.
TapHandler {
enabled: contextMenu.useNativeEditMenu
acceptedDevices: PointerDevice.TouchScreen
onDoubleTapped: function(eventPoint) {
textField.forceActiveFocus()
textField.cursorPosition = textField.positionAt(eventPoint.position.x, eventPoint.position.y)
textField.selectWord()
contextMenu.requestNative(eventPoint.position)
}
}
ContextMenu.menu: contextMenu
ContextMenuType {
id: contextMenu
@@ -233,52 +215,6 @@ Item {
}
}
// (i) hint: tap to reveal a tooltip with hintText
ImageButtonType {
id: hintButton
visible: root.hintText !== ""
focusPolicy: Qt.NoFocus
hoverEnabled: true
image: "qrc:/images/controls/info.svg"
imageColor: hintTooltip.opened ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray
anchors.top: content.top
anchors.right: content.right
anchors.topMargin: 10
anchors.rightMargin: 12
implicitWidth: 28
implicitHeight: 28
onClicked: hintTooltip.opened ? hintTooltip.close() : hintTooltip.open()
ToolTip {
id: hintTooltip
parent: hintButton
x: hintButton.width - width
y: -height - 6
width: Math.min(280, root.width - 24)
delay: 0
timeout: 8000
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape
contentItem: Text {
text: root.hintText
color: AmneziaStyle.color.paleGray
wrapMode: Text.WordWrap
font.pixelSize: 14
font.family: "PT Root UI VF"
}
background: Rectangle {
color: AmneziaStyle.color.slateGray
radius: 12
border.color: AmneziaStyle.color.charcoalGray
border.width: 1
}
}
}
function getBackgroundBorderColor(noneFocusedColor) {
return textField.focus ? root.borderFocusedColor : noneFocusedColor
}

View File

@@ -299,26 +299,6 @@ PageType {
}
}
ImageButtonType {
id: outdatedContainerWarningIcon
objectName: "outdatedContainerWarningIcon"
Layout.rightMargin: 8
visible: drawer.isCollapsedStateActive() && ServersUiController.defaultServerHasOutdatedAwgContainer
hoverEnabled: false
image: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
icon.width: 18
icon.height: 18
backgroundRadius: 16
horizontalPadding: 4
topPadding: 4
bottomPadding: 3
}
Header1TextType {
id: collapsedButtonHeader
objectName: "collapsedButtonHeader"

View File

@@ -99,168 +99,187 @@ PageType {
AwgTextField {
id: junkPacketCountTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: "Jc - Junk packet count"
textField.text: clientJunkPacketCount
scroller: smartScroll
onEdited: (text) => { clientJunkPacketCount = text }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketCount) {
clientJunkPacketCount = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
}
AwgTextField {
id: junkPacketMinSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: "Jmin - Junk packet minimum size"
textField.text: clientJunkPacketMinSize
scroller: smartScroll
onEdited: (text) => { clientJunkPacketMinSize = text }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
}
AwgTextField {
id: junkPacketMaxSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: "Jmax - Junk packet maximum size"
textField.text: clientJunkPacketMaxSize
scroller: smartScroll
onEdited: (text) => { clientJunkPacketMaxSize = text }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
}
AwgTextField {
id: specialJunk1TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I1 - First special junk packet")
textField.text: clientSpecialJunk1
textField.validator: null
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { clientSpecialJunk1 = text }
textField.onEditingFinished: {
if (textField.text !== clientSpecialJunk1) {
clientSpecialJunk1 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk1TextField)
}
}
}
AwgTextField {
id: specialJunk2TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I2 - Second special junk packet")
textField.text: clientSpecialJunk2
textField.validator: null
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { clientSpecialJunk2 = text }
textField.onEditingFinished: {
if (textField.text !== clientSpecialJunk2) {
clientSpecialJunk2 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk2TextField)
}
}
}
AwgTextField {
id: specialJunk3TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I3 - Third special junk packet")
textField.text: clientSpecialJunk3
textField.validator: null
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { clientSpecialJunk3 = text }
textField.onEditingFinished: {
if (textField.text !== clientSpecialJunk3) {
clientSpecialJunk3 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk3TextField)
}
}
}
AwgTextField {
id: specialJunk4TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I4 - Fourth special junk packet")
textField.text: clientSpecialJunk4
textField.validator: null
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { clientSpecialJunk4 = text }
textField.onEditingFinished: {
if (textField.text !== clientSpecialJunk4) {
clientSpecialJunk4 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk4TextField)
}
}
}
AwgTextField {
id: specialJunk5TextField
headerText: qsTr("I5 - Fifth special junk packet")
textField.text: clientSpecialJunk5
scroller: smartScroll
onEdited: (text) => { clientSpecialJunk5 = text }
}
CheckBoxType {
id: headerProtectionCheckBox
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: qsTr("I5 - Fifth special junk packet")
textField.text: clientSpecialJunk5
textField.validator: null
checkEmptyText: false
text: qsTr("HeaderProtectionKey")
textField.onEditingFinished: {
if (textField.text !== clientSpecialJunk5 ) {
clientSpecialJunk5 = textField.text
}
}
checked: clientHeaderProtectionEnabled
}
AwgTextField {
id: contentPaddingAdditionTextField
rangeValidation: true
headerText: qsTr("ContentPaddingAddition - Content padding addition")
textField.text: clientContentPaddingAddition
scroller: smartScroll
onEdited: (text) => { clientContentPaddingAddition = text }
}
AwgTextField {
id: rekeyAfterTimeTextField
rangeValidation: true
headerText: qsTr("RekeyAfterTime - Rekey after time")
textField.text: clientRekeyAfterTime
scroller: smartScroll
onEdited: (text) => { clientRekeyAfterTime = text }
}
AwgTextField {
id: rekeyTimeoutTextField
rangeValidation: true
headerText: qsTr("RekeyTimeout - Rekey timeout")
textField.text: clientRekeyTimeout
scroller: smartScroll
onEdited: (text) => { clientRekeyTimeout = text }
}
AwgTextField {
id: rejectAfterTimeTextField
rangeValidation: true
headerText: qsTr("RejectAfterTime - Reject after time")
textField.text: clientRejectAfterTime
scroller: smartScroll
onEdited: (text) => { clientRejectAfterTime = text }
}
AwgTextField {
id: keepaliveTimeoutTextField
rangeValidation: true
headerText: qsTr("KeepaliveTimeout - Keepalive timeout")
textField.text: clientKeepaliveTimeout
scroller: smartScroll
onEdited: (text) => { clientKeepaliveTimeout = text }
}
AwgTextField {
id: maxHandshakeAttemptsTextField
rangeValidation: true
headerText: qsTr("MaxHandshakeAttempts - Max handshake attempts")
textField.text: clientMaxHandshakeAttempts
scroller: smartScroll
onEdited: (text) => { clientMaxHandshakeAttempts = text }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk5TextField)
}
}
}
@@ -276,6 +295,9 @@ PageType {
AwgTextField {
id: portTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: qsTr("Port")
@@ -285,6 +307,9 @@ PageType {
AwgTextField {
id: initPacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "S1 - Init packet junk size"
@@ -294,6 +319,9 @@ PageType {
AwgTextField {
id: responsePacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "S2 - Response packet junk size"
@@ -304,6 +332,10 @@ PageType {
id: cookieReplyPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "S3 - Cookie Reply packet junk size"
@@ -314,6 +346,10 @@ PageType {
id: transportPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "S4 - Transport packet junk size"
@@ -323,6 +359,9 @@ PageType {
AwgTextField {
id: initPacketMagicHeaderTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "H1 - Init packet magic header"
@@ -332,6 +371,9 @@ PageType {
AwgTextField {
id: responsePacketMagicHeaderTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "H2 - Response packet magic header"
@@ -341,6 +383,9 @@ PageType {
AwgTextField {
id: underloadPacketMagicHeaderTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "H3 - Underload packet magic header"
@@ -350,12 +395,14 @@ PageType {
AwgTextField {
id: transportPacketMagicHeaderTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: false
headerText: "H4 - Transport packet magic header"
textField.text: serverTransportPacketMagicHeader
}
}
}

View File

@@ -128,271 +128,374 @@ PageType {
AwgTextField {
id: junkPacketCountTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Jc - Junk packet count")
textField.text: serverJunkPacketCount
scroller: smartScroll
onEdited: (text) => { serverJunkPacketCount = text }
textField.onEditingFinished: {
if (textField.text !== serverJunkPacketCount) {
serverJunkPacketCount = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
}
AwgTextField {
id: junkPacketMinSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Jmin - Junk packet minimum size")
textField.text: serverJunkPacketMinSize
scroller: smartScroll
onEdited: (text) => { serverJunkPacketMinSize = text }
textField.onEditingFinished: {
if (textField.text !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
}
AwgTextField {
id: junkPacketMaxSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size")
textField.text: serverJunkPacketMaxSize
scroller: smartScroll
onEdited: (text) => { serverJunkPacketMaxSize = text }
textField.onEditingFinished: {
if (textField.text !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
}
AwgTextField {
id: initPacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("S1 - Init packet junk size")
textField.text: serverInitPacketJunkSize
scroller: smartScroll
onEdited: (text) => { serverInitPacketJunkSize = text }
textField.onEditingFinished: {
if (textField.text !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketJunkSizeTextField)
}
}
}
AwgTextField {
id: responsePacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("S2 - Response packet junk size")
textField.text: serverResponsePacketJunkSize
scroller: smartScroll
onEdited: (text) => { serverResponsePacketJunkSize = text }
textField.onEditingFinished: {
if (textField.text !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketJunkSizeTextField)
}
}
}
AwgTextField {
id: cookieReplyPacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isAwg2
headerText: qsTr("S3 - Cookie reply packet junk size")
textField.text: serverCookieReplyPacketJunkSize
scroller: smartScroll
onEdited: (text) => { serverCookieReplyPacketJunkSize = text }
textField.onEditingFinished: {
if (textField.text !== serverCookieReplyPacketJunkSize) {
serverCookieReplyPacketJunkSize = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(cookieReplyPacketJunkSizeTextField)
}
}
}
AwgTextField {
id: transportPacketJunkSizeTextField
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isAwg2
headerText: qsTr("S4 - Transport packet junk size")
textField.text: serverTransportPacketJunkSize
scroller: smartScroll
onEdited: (text) => { serverTransportPacketJunkSize = text }
}
textField.onEditingFinished: {
if (textField.text !== serverTransportPacketJunkSize) {
serverTransportPacketJunkSize = textField.text
}
}
AwgTextField {
id: initPacketMagicHeaderTextField
rangeValidation: true
headerText: qsTr("H1 - Init packet magic header")
textField.text: serverInitPacketMagicHeader
scroller: smartScroll
onEdited: (text) => { serverInitPacketMagicHeader = text }
}
AwgTextField {
id: responsePacketMagicHeaderTextField
rangeValidation: true
headerText: qsTr("H2 - Response packet magic header")
textField.text: serverResponsePacketMagicHeader
scroller: smartScroll
onEdited: (text) => { serverResponsePacketMagicHeader = text }
}
AwgTextField {
id: underloadPacketMagicHeaderTextField
rangeValidation: true
headerText: qsTr("H3 - Underload packet magic header")
textField.text: serverUnderloadPacketMagicHeader
scroller: smartScroll
onEdited: (text) => { serverUnderloadPacketMagicHeader = text }
}
AwgTextField {
id: transportPacketMagicHeaderTextField
rangeValidation: true
headerText: qsTr("H4 - Transport packet magic header")
textField.text: serverTransportPacketMagicHeader
scroller: smartScroll
onEdited: (text) => { serverTransportPacketMagicHeader = text }
}
AwgTextField {
id: specialJunk1TextField
headerText: qsTr("I1 - Special junk 1")
textField.text: serverSpecialJunk1
scroller: smartScroll
onEdited: (text) => { serverSpecialJunk1 = text }
}
AwgTextField {
id: specialJunk2TextField
headerText: qsTr("I2 - Special junk 2")
textField.text: serverSpecialJunk2
scroller: smartScroll
onEdited: (text) => { serverSpecialJunk2 = text }
}
AwgTextField {
id: specialJunk3TextField
headerText: qsTr("I3 - Special junk 3")
textField.text: serverSpecialJunk3
scroller: smartScroll
onEdited: (text) => { serverSpecialJunk3 = text }
}
AwgTextField {
id: specialJunk4TextField
headerText: qsTr("I4 - Special junk 4")
textField.text: serverSpecialJunk4
scroller: smartScroll
onEdited: (text) => { serverSpecialJunk4 = text }
}
AwgTextField {
id: specialJunk5TextField
headerText: qsTr("I5 - Special junk 5")
textField.text: serverSpecialJunk5
scroller: smartScroll
onEdited: (text) => { serverSpecialJunk5 = text }
}
CheckBoxType {
id: headerProtectionCheckBox
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isAwg3
text: qsTr("HeaderProtectionKey")
checked: serverHeaderProtectionEnabled
onCheckedChanged: {
if (checked !== serverHeaderProtectionEnabled) {
serverHeaderProtectionEnabled = checked
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(transportPacketJunkSizeTextField)
}
}
}
AwgTextField {
id: contentPaddingAdditionTextField
id: initPacketMagicHeaderTextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("ContentPaddingAddition - Content padding addition")
textField.text: serverContentPaddingAddition
headerText: qsTr("H1 - Init packet magic header")
textField.text: serverInitPacketMagicHeader
textField.validator: RegularExpressionValidator {
regularExpression: /^(\d+)(-\d+)?$/
}
scroller: smartScroll
onEdited: (text) => { serverContentPaddingAddition = text }
textField.onEditingFinished: {
if (textField.text !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketMagicHeaderTextField)
}
}
}
AwgTextField {
id: rekeyAfterTimeTextField
id: responsePacketMagicHeaderTextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("RekeyAfterTime - Rekey after time")
textField.text: serverRekeyAfterTime
headerText: qsTr("H2 - Response packet magic header")
textField.text: serverResponsePacketMagicHeader
textField.validator: RegularExpressionValidator {
regularExpression: /^(\d+)(-\d+)?$/
}
scroller: smartScroll
onEdited: (text) => { serverRekeyAfterTime = text }
textField.onEditingFinished: {
if (textField.text !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketMagicHeaderTextField)
}
}
}
AwgTextField {
id: rekeyTimeoutTextField
id: underloadPacketMagicHeaderTextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("RekeyTimeout - Rekey timeout")
textField.text: serverRekeyTimeout
headerText: qsTr("H3 - Underload packet magic header")
textField.text: serverUnderloadPacketMagicHeader
textField.validator: RegularExpressionValidator {
regularExpression: /^(\d+)(-\d+)?$/
}
scroller: smartScroll
onEdited: (text) => { serverRekeyTimeout = text }
textField.onEditingFinished: {
if (textField.text !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(underloadPacketMagicHeaderTextField)
}
}
}
AwgTextField {
id: rejectAfterTimeTextField
id: transportPacketMagicHeaderTextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("RejectAfterTime - Reject after time")
textField.text: serverRejectAfterTime
headerText: qsTr("H4 - Transport packet magic header")
textField.text: serverTransportPacketMagicHeader
textField.validator: RegularExpressionValidator {
regularExpression: /^(\d+)(-\d+)?$/
}
scroller: smartScroll
onEdited: (text) => { serverRejectAfterTime = text }
textField.onEditingFinished: {
if (textField.text !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(transportPacketMagicHeaderTextField)
}
}
}
AwgTextField {
id: keepaliveTimeoutTextField
id: specialJunk1TextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("KeepaliveTimeout - Keepalive timeout")
textField.text: serverKeepaliveTimeout
headerText: qsTr("I1 - Special junk 1")
textField.text: serverSpecialJunk1
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { serverKeepaliveTimeout = text }
textField.onEditingFinished: {
if (textField.text !== serverSpecialJunk1) {
serverSpecialJunk1 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk1TextField)
}
}
}
AwgTextField {
id: maxHandshakeAttemptsTextField
id: specialJunk2TextField
visible: isAwg3
rangeValidation: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("MaxHandshakeAttempts - Max handshake attempts")
textField.text: serverMaxHandshakeAttempts
headerText: qsTr("I2 - Special junk 2")
textField.text: serverSpecialJunk2
checkEmptyText: false
scroller: smartScroll
onEdited: (text) => { serverMaxHandshakeAttempts = text }
textField.onEditingFinished: {
if (textField.text !== serverSpecialJunk2) {
serverSpecialJunk2 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk2TextField)
}
}
}
AwgTextField {
id: specialJunk3TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I3 - Special junk 3")
textField.text: serverSpecialJunk3
checkEmptyText: false
textField.onEditingFinished: {
if (textField.text !== serverSpecialJunk3) {
serverSpecialJunk3 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk3TextField)
}
}
}
AwgTextField {
id: specialJunk4TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I4 - Special junk 4")
textField.text: serverSpecialJunk4
checkEmptyText: false
textField.onEditingFinished: {
if (textField.text !== serverSpecialJunk4) {
serverSpecialJunk4 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk4TextField)
}
}
}
AwgTextField {
id: specialJunk5TextField
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("I5 - Special junk 5")
textField.text: serverSpecialJunk5
checkEmptyText: false
textField.onEditingFinished: {
if (textField.text !== serverSpecialJunk5) {
serverSpecialJunk5 = textField.text
}
}
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk5TextField)
}
}
}
BasicButtonType {
@@ -408,12 +511,6 @@ PageType {
transportPacketMagicHeaderTextField.errorText === "" &&
responsePacketMagicHeaderTextField.errorText === "" &&
initPacketMagicHeaderTextField.errorText === "" &&
contentPaddingAdditionTextField.errorText === "" &&
rekeyAfterTimeTextField.errorText === "" &&
rekeyTimeoutTextField.errorText === "" &&
rejectAfterTimeTextField.errorText === "" &&
keepaliveTimeoutTextField.errorText === "" &&
maxHandshakeAttemptsTextField.errorText === "" &&
responsePacketJunkSizeTextField.errorText === "" &&
cookieReplyPacketJunkSizeTextField.errorText === "" &&
transportPacketJunkSizeTextField.errorText === "" &&
@@ -443,10 +540,10 @@ PageType {
return
}
if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text) || 0,
parseInt(responsePacketJunkSizeTextField.textField.text) || 0,
parseInt(cookieReplyPacketJunkSizeTextField.textField.text) || 0,
parseInt(transportPacketJunkSizeTextField.textField.text) || 0)) {
if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text),
parseInt(responsePacketJunkSizeTextField.textField.text),
parseInt(cookieReplyPacketJunkSizeTextField.textField.text),
parseInt(transportPacketJunkSizeTextField.textField.text))) {
PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92) + S3 + cookie reply size (64) + S4 + transport packet size (32)"))
return
}

View File

@@ -37,9 +37,6 @@ PageType {
width: listView.width
spacing: 0
// xtls-rprx-vision requires the RAW (TCP) transport; it is invalid with XHTTP / mKCP.
readonly property bool visionAllowed: transport === "" || transport === "raw"
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
@@ -54,7 +51,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Empty")
checked: flow === "" || !visionAllowed
checked: flow === ""
onClicked: flow = ""
}
@@ -66,8 +63,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: "xtls-rprx-vision"
enabled: visionAllowed
checked: flow === "xtls-rprx-vision" && visionAllowed
checked: flow === "xtls-rprx-vision"
onClicked: flow = "xtls-rprx-vision"
}
@@ -79,24 +75,13 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: "xtls-rprx-vision-udp443"
enabled: visionAllowed
checked: flow === "xtls-rprx-vision-udp443" && visionAllowed
checked: flow === "xtls-rprx-vision-udp443"
onClicked: flow = "xtls-rprx-vision-udp443"
}
DividerType {
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: !visionAllowed
color: AmneziaStyle.color.goldenApricot
text: qsTr("xtls-rprx-vision is available only with the RAW (TCP) transport.")
}
Item {
Layout.preferredHeight: 16
}

View File

@@ -39,9 +39,6 @@ PageType {
width: listView.width
spacing: 0
// REALITY is not supported with the mKCP transport (xray refuses reality+mkcp).
readonly property bool realityAllowed: transport !== "mkcp"
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
@@ -56,7 +53,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("None")
checked: security === "none" || (security === "reality" && !realityAllowed)
checked: security === "none"
onClicked: security = "none"
}
@@ -80,21 +77,10 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Reality")
enabled: realityAllowed
checked: security === "reality" && realityAllowed
checked: security === "reality"
onClicked: security = "reality"
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: !realityAllowed
color: AmneziaStyle.color.goldenApricot
text: qsTr("REALITY is not supported with the mKCP transport. Use None or TLS.")
}
DividerType {
}
@@ -117,7 +103,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: alpn
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.alpnOptions()
@@ -170,7 +155,6 @@ PageType {
}
}
}
currentValue: fingerprint
clickedFunction: function () {
fingerprint = selectedText
tlsFingerprintDropDown.text = selectedText
@@ -201,7 +185,6 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
placeholderText: XrayConfigModel.sniDefault()
textField.text: sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== sni)
@@ -217,7 +200,7 @@ PageType {
// ── Reality fields ────────────────────────────────────────
ColumnLayout {
visible: security === "reality" && realityAllowed
visible: security === "reality"
Layout.fillWidth: true
spacing: 0
@@ -242,7 +225,6 @@ PageType {
}
}
}
currentValue: fingerprint
clickedFunction: function () {
fingerprint = selectedText
realityFingerprintDropDown.text = selectedText
@@ -273,7 +255,6 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
placeholderText: XrayConfigModel.sniDefault()
textField.text: sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== sni)

View File

@@ -35,10 +35,6 @@ PageType {
return value
}
function formatFlow(value) {
return value === "" ? qsTr("Empty") : value
}
BackButtonType {
id: backButton
@@ -113,8 +109,7 @@ PageType {
Layout.rightMargin: 16
enabled: listView.enabled
headerText: qsTr("Port")
hintText: qsTr("Valid range: 165535.")
placeholderText: XrayConfigModel.portDefault()
subtitleText: qsTr("165535")
Binding {
target: textFieldWithHeaderType.textField
@@ -173,7 +168,7 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Security")
descriptionText: root.formatSecurity((security === "reality" && transport === "mkcp") ? "none" : security)
descriptionText: root.formatSecurity(security)
rightImageSource: "qrc:/images/controls/chevron-right.svg"
enabled: listView.enabled
clickedFunction: function() {
@@ -187,7 +182,7 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Flow")
descriptionText: root.formatFlow((transport === "" || transport === "raw") ? flow : "")
descriptionText: flow
rightImageSource: "qrc:/images/controls/chevron-right.svg"
enabled: listView.enabled
clickedFunction: function() {
@@ -252,9 +247,9 @@ PageType {
visible: listView.enabled
clickedFunction: function() {
var yesButtonFunction = function() {
XrayConfigModel.resetToDefaults()
PageController.showNotificationMessage(
qsTr("Settings were reset to defaults. Tap Save to apply them on the server."))
XrayConfigModel.resetToDefaults()
}
showQuestionDrawer(qsTr("Reset settings?"), qsTr("All XRay settings will be restored to defaults."),
qsTr("Reset"), qsTr("Cancel"), yesButtonFunction, function() {

View File

@@ -123,8 +123,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("TTI")
hintText: qsTr("Transmission time interval (ms). Valid range: 10100.")
placeholderText: XrayConfigModel.mkcpDefaultTti()
subtitleText: qsTr("Range 10100, default %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
textField.text: mkcpTti
textField.maximumLength: 3
textField.validator: RegularExpressionValidator { regularExpression: /^(|\d{1,2}|100)$/ }
@@ -143,8 +142,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("uplinkCapacity")
hintText: qsTr("Uplink capacity (MB/s). Maximum: 2147483647.")
placeholderText: XrayConfigModel.mkcpDefaultUplinkCapacity()
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
textField.text: mkcpUplinkCapacity
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -163,8 +161,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("downlinkCapacity")
hintText: qsTr("Downlink capacity (MB/s). Maximum: 2147483647.")
placeholderText: XrayConfigModel.mkcpDefaultDownlinkCapacity()
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
textField.text: mkcpDownlinkCapacity
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -183,8 +180,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("readBufferSize")
hintText: qsTr("Read buffer size (MB). Range: 12147483647.")
placeholderText: XrayConfigModel.mkcpDefaultReadBufferSize()
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
textField.text: mkcpReadBufferSize
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -203,8 +199,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("writeBufferSize")
hintText: qsTr("Write buffer size (MB). Range: 12147483647.")
placeholderText: XrayConfigModel.mkcpDefaultWriteBufferSize()
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
textField.text: mkcpWriteBufferSize
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -248,7 +243,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xhttpMode
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpModeOptions()
@@ -297,7 +291,6 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Host")
placeholderText: XrayConfigModel.xhttpHostDefault()
textField.text: xhttpHost
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._:,-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpHost)
@@ -329,6 +322,50 @@ PageType {
}
}
DropDownType {
id: headersDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
text: xhttpHeadersTemplate
descriptionText: qsTr("Headers template")
headerText: qsTr("Headers template")
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpHeadersTemplateOptions()
for (var i = 0; i < opts.length; i++) {
append({name: opts[i]})
}
}
}
clickedFunction: function () {
xhttpHeadersTemplate = selectedText
headersDropDown.text = selectedText
headersDropDown.closeTriggered()
}
Component.onCompleted: {
for (var i = 0; i < model.count; i++) {
if (model.get(i).name === xhttpHeadersTemplate) {
selectedIndex = i;
break
}
}
}
}
Connections {
target: XrayConfigModel
function onDataChanged() {
headersDropDown.text = xhttpHeadersTemplate
}
}
}
DropDownType {
id: uplinkMethodDropDown
fitContent: true
@@ -342,7 +379,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xhttpUplinkMethod
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpUplinkMethodOptions()
@@ -423,7 +459,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xhttpSessionPlacement
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpSessionPlacementOptions()
@@ -455,20 +490,47 @@ PageType {
}
}
TextFieldWithHeaderType {
DropDownType {
id: sessionKeyDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
text: xhttpSessionKey
descriptionText: qsTr("SessionKey")
headerText: qsTr("SessionKey")
textField.text: xhttpSessionKey
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpSessionKey)
textField.onEditingFinished: {
var v = textField.text.trim()
if (v !== xhttpSessionKey) xhttpSessionKey = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpSessionKeyOptions()
for (var i = 0; i < opts.length; i++) {
append({name: opts[i]})
}
}
}
clickedFunction: function () {
xhttpSessionKey = selectedText
sessionKeyDropDown.text = selectedText
sessionKeyDropDown.closeTriggered()
}
Component.onCompleted: {
for (var i = 0; i < model.count; i++) {
if (model.get(i).name === xhttpSessionKey) {
selectedIndex = i;
break
}
}
}
}
Connections {
target: XrayConfigModel
function onDataChanged() {
sessionKeyDropDown.text = xhttpSessionKey
}
}
}
@@ -485,7 +547,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xhttpSeqPlacement
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpSeqPlacementOptions()
@@ -542,12 +603,11 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: xhttpUplinkDataPlacement
descriptionText: qsTr("Header/Cookie apply only in Packet-up mode")
descriptionText: qsTr("UplinkDataPlacement")
headerText: qsTr("UplinkDataPlacement")
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xhttpUplinkDataPlacement
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xhttpUplinkDataPlacementOptions()
@@ -613,8 +673,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("UplinkChunkSize")
hintText: qsTr("Uplink chunk size in bytes. Maximum: 2147483647. 0 = off.")
placeholderText: XrayConfigModel.xhttpUplinkChunkSizeDefault()
subtitleText: qsTr("≥ 0 (0 = off)")
textField.text: xhttpUplinkChunkSize
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -633,7 +692,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("scMaxBufferedPosts")
hintText: qsTr("Max buffered POSTs. Range: 02147483647.")
subtitleText: qsTr("≥ 0")
textField.text: xhttpScMaxBufferedPosts
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
@@ -661,8 +720,6 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScMaxEachPostBytesMin
maxValue: xhttpScMaxEachPostBytesMax
minPlaceholder: XrayConfigModel.scMaxEachPostBytesMinDefault()
maxPlaceholder: XrayConfigModel.scMaxEachPostBytesMaxDefault()
onMinChanged: function(val) { xhttpScMaxEachPostBytesMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScMaxEachPostBytesMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -683,8 +740,6 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScStreamUpServerSecsMin
maxValue: xhttpScStreamUpServerSecsMax
minPlaceholder: XrayConfigModel.scStreamUpServerSecsMinDefault()
maxPlaceholder: XrayConfigModel.scStreamUpServerSecsMaxDefault()
onMinChanged: function(val) { xhttpScStreamUpServerSecsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScStreamUpServerSecsMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -705,8 +760,6 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScMinPostsIntervalMsMin
maxValue: xhttpScMinPostsIntervalMsMax
minPlaceholder: XrayConfigModel.scMinPostsIntervalMsMinDefault()
maxPlaceholder: XrayConfigModel.scMinPostsIntervalMsMaxDefault()
onMinChanged: function(val) { xhttpScMinPostsIntervalMsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScMinPostsIntervalMsMax = val; root.editDirty = false }
onEdited: root.editDirty = true

View File

@@ -63,8 +63,6 @@ PageType {
Layout.rightMargin: 16
minValue: xPaddingBytesMin
maxValue: xPaddingBytesMax
minPlaceholder: XrayConfigModel.xPaddingBytesMinDefault()
maxPlaceholder: XrayConfigModel.xPaddingBytesMaxDefault()
onMinChanged: function(val) { xPaddingBytesMin = val; root.editDirty = false }
onMaxChanged: function(val) { xPaddingBytesMax = val; root.editDirty = false }
onEdited: root.editDirty = true

View File

@@ -79,7 +79,6 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 16
headerText: qsTr("xPaddingKey")
placeholderText: XrayConfigModel.xPaddingKeyDefault()
textField.text: xPaddingKey
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xPaddingKey)
@@ -97,7 +96,6 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("xPaddingHeader")
placeholderText: XrayConfigModel.xPaddingHeaderDefault()
textField.text: xPaddingHeader
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xPaddingHeader)
@@ -122,7 +120,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xPaddingPlacement
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xPaddingPlacementOptions()
@@ -167,7 +164,6 @@ PageType {
drawerParent: root
listView: ListViewWithRadioButtonType {
rootWidth: root.width
currentValue: xPaddingMethod
model: ListModel {
Component.onCompleted: {
var opts = XrayConfigModel.xPaddingMethodOptions()

View File

@@ -93,8 +93,6 @@ PageType {
Layout.rightMargin: 16
minValue: xmuxMaxConcurrencyMin
maxValue: xmuxMaxConcurrencyMax
minPlaceholder: "0"
maxPlaceholder: "0"
onMinChanged: function(val) { xmuxMaxConcurrencyMin = val; root.editDirty = false }
onMaxChanged: function(val) { xmuxMaxConcurrencyMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -116,8 +114,6 @@ PageType {
Layout.rightMargin: 16
minValue: xmuxMaxConnectionsMin
maxValue: xmuxMaxConnectionsMax
minPlaceholder: "0"
maxPlaceholder: "0"
onMinChanged: function(val) { xmuxMaxConnectionsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xmuxMaxConnectionsMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -139,8 +135,6 @@ PageType {
Layout.rightMargin: 16
minValue: xmuxCMaxReuseTimesMin
maxValue: xmuxCMaxReuseTimesMax
minPlaceholder: "0"
maxPlaceholder: "0"
onMinChanged: function(val) { xmuxCMaxReuseTimesMin = val; root.editDirty = false }
onMaxChanged: function(val) { xmuxCMaxReuseTimesMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -162,8 +156,6 @@ PageType {
Layout.rightMargin: 16
minValue: xmuxHMaxRequestTimesMin
maxValue: xmuxHMaxRequestTimesMax
minPlaceholder: "0"
maxPlaceholder: "0"
onMinChanged: function(val) { xmuxHMaxRequestTimesMin = val; root.editDirty = false }
onMaxChanged: function(val) { xmuxHMaxRequestTimesMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -185,8 +177,6 @@ PageType {
Layout.rightMargin: 16
minValue: xmuxHMaxReusableSecsMin
maxValue: xmuxHMaxReusableSecsMax
minPlaceholder: "0"
maxPlaceholder: "0"
onMinChanged: function(val) { xmuxHMaxReusableSecsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xmuxHMaxReusableSecsMax = val; root.editDirty = false }
onEdited: root.editDirty = true
@@ -198,7 +188,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 16
headerText: qsTr("hKeepAlivePeriod")
hintText: qsTr("HTTP keep-alive period. Integer, may be negative.")
subtitleText: qsTr("Integer, may be negative")
textField.text: xmuxHKeepAlivePeriod
textField.maximumLength: 11
textField.validator: RegularExpressionValidator { regularExpression: /^-?\d*$/ }

View File

@@ -562,6 +562,22 @@ PageType {
font.pixelSize: 13
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
visible: secret !== ""
onClicked: {
ExportController.generateQrFromString(tmeLink())
PageController.goToShareConnectionPage(
qsTr("Telegram connection link"),
qsTr("MTProxy connection link"),
"", "", "")
}
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
@@ -614,7 +630,7 @@ PageType {
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: {
ExportController.generateQrFromStringRaw(tgLink())
ExportController.generateQrFromString(tgLink())
PageController.goToShareConnectionPage(
qsTr("Telegram connection link"),
qsTr("MTProxy connection link"),
@@ -861,7 +877,7 @@ PageType {
}
function mtProxyShareQr(link) {
ExportController.generateQrFromStringRaw(link)
ExportController.generateQrFromString(link)
PageController.goToShareConnectionPage(qsTr("Telegram connection link"),
qsTr("MTProxy connection link"), "", "", "")
}
@@ -1446,6 +1462,15 @@ PageType {
font.pixelSize: 13
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: settingsRoot.mtProxyShareQr(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36

View File

@@ -562,6 +562,22 @@ PageType {
font.pixelSize: 13
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
visible: secret !== ""
onClicked: {
ExportController.generateQrFromString(tmeLink())
PageController.goToShareConnectionPage(
qsTr("Telegram connection link"),
qsTr("Telemt connection link"),
"", "", "")
}
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
@@ -614,7 +630,7 @@ PageType {
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: {
ExportController.generateQrFromStringRaw(tgLink())
ExportController.generateQrFromString(tgLink())
PageController.goToShareConnectionPage(
qsTr("Telegram connection link"),
qsTr("Telemt connection link"),
@@ -861,7 +877,7 @@ PageType {
}
function telemtShareQr(link) {
ExportController.generateQrFromStringRaw(link)
ExportController.generateQrFromString(link)
PageController.goToShareConnectionPage(qsTr("Telegram connection link"),
qsTr("Telemt connection link"), "", "", "")
}
@@ -1446,6 +1462,15 @@ PageType {
font.pixelSize: 13
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/qr-code.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: settingsRoot.telemtShareQr(settingsRoot.telemtTmeLinkForAdditional(modelData))
}
ImageButtonType {
implicitWidth: 36
implicitHeight: 36
@@ -1524,13 +1549,121 @@ PageType {
Layout.bottomMargin: 8
}
LabelTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.bottomMargin: 4
text: qsTr("Worker mode")
}
ButtonGroup {
id: workerModeGroup
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 4
spacing: 0
visible: transportMode !== "faketls"
HorizontalRadioButton {
Layout.fillWidth: true
text: qsTr("Auto")
ButtonGroup.group: workerModeGroup
checked: workersMode === "auto"
onClicked: { workersMode = "auto"; TelemtConfigModel.setWorkersMode("auto") }
}
HorizontalRadioButton {
Layout.fillWidth: true
text: qsTr("Manual")
ButtonGroup.group: workerModeGroup
checked: workersMode === "manual"
onClicked: { workersMode = "manual"; TelemtConfigModel.setWorkersMode("manual") }
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 8
visible: transportMode === "faketls"
text: qsTr("Workers are set to 0 automatically for FakeTLS mode.")
color: AmneziaStyle.color.mutedGray
font.pixelSize: 12
wrapMode: Text.WordWrap
}
TextFieldWithHeaderType {
id: workersTextField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: workersMode === "manual" && transportMode !== "faketls"
headerText: qsTr("Workers count")
textField.placeholderText: "2"
textField.text: workers
textField.maximumLength: 2
textField.inputMethodHints: Qt.ImhDigitsOnly
textField.validator: IntValidator {
bottom: 0
top: TelemtConfigModel.maxWorkers()
}
textField.onTextChanged: {
var cur = workersTextField.textField.text
if (cur === "") {
return
}
var n = parseInt(cur, 10)
var maxW = TelemtConfigModel.maxWorkers()
if (isNaN(n) || n < 0) {
n = 0
}
if (n > maxW) {
n = maxW
}
var clamped = String(n)
if (clamped !== cur) {
textField.text = clamped
textField.cursorPosition = clamped.length
}
}
textField.onEditingFinished: {
var v = workersTextField.textField.text
if (v !== "") {
var m = parseInt(v, 10)
var maxW2 = TelemtConfigModel.maxWorkers()
if (isNaN(m) || m < 0) {
m = 0
}
if (m > maxW2) {
m = maxW2
}
v = String(m)
textField.text = v
}
if (v !== workers) {
workers = v
TelemtConfigModel.setWorkers(workers)
}
}
}
DividerType {
Layout.fillWidth: true
Layout.bottomMargin: 8
}
SwitcherType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 4
text: qsTr("Set public IP manually")
descriptionText: qsTr("By default the proxy auto-detects its public IP. Enable to override it manually, e.g. when the server is behind NAT / Docker bridge")
text: qsTr("Server is behind NAT / Docker bridge")
descriptionText: qsTr("Enable if your server is not directly accessible from the internet, e.g. Docker or private network")
checked: natEnabled
onToggled: function () {
if (checked !== natEnabled) {
@@ -1540,6 +1673,41 @@ PageType {
}
}
TextFieldWithHeaderType {
id: natInternalIpTextField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: natEnabled
headerText: qsTr("Internal IP")
textField.placeholderText: "172.17.0.2"
textField.text: natInternalIp
textField.maximumLength: 15
textField.validator: RegularExpressionValidator {
regularExpression: root.natIpv4InputFormat
}
textField.onTextChanged: {
if (root.natIpv4FieldShowInvalidError(textField.text)) {
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
} else {
natInternalIpTextField.errorText = ""
}
}
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (!TelemtConfigModel.isValidOptionalIpv4(textField.text)) {
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
return
}
natInternalIpTextField.errorText = ""
if (textField.text !== natInternalIp) {
natInternalIp = textField.text
TelemtConfigModel.setNatInternalIp(natInternalIp)
}
}
}
TextFieldWithHeaderType {
id: natExternalIpTextField
Layout.fillWidth: true
@@ -1547,7 +1715,7 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: natEnabled
headerText: qsTr("Public IP")
headerText: qsTr("External IP")
textField.placeholderText: "1.2.3.4"
textField.text: natExternalIp
textField.maximumLength: 15
@@ -1744,6 +1912,7 @@ PageType {
publicHostTextField.errorText = ""
tagTextField.errorText = ""
tlsDomainTextField.errorText = ""
natInternalIpTextField.errorText = ""
natExternalIpTextField.errorText = ""
portTextField.errorText = ""
@@ -1779,9 +1948,13 @@ PageType {
errorLines.push(bullet + tlsErr)
}
var natIpErr = qsTr("Enter a valid IPv4 address")
if (!TelemtConfigModel.isValidOptionalIpv4(natInternalIpTextField.textField.text)) {
natInternalIpTextField.errorText = natIpErr
errorLines.push(bullet + qsTr("NAT internal IP: enter a valid IPv4 address"))
}
if (!TelemtConfigModel.isValidOptionalIpv4(natExternalIpTextField.textField.text)) {
natExternalIpTextField.errorText = natIpErr
errorLines.push(bullet + qsTr("Public IP: enter a valid IPv4 address"))
errorLines.push(bullet + qsTr("NAT external IP: enter a valid IPv4 address"))
}
if (errorLines.length > 0) {
PageController.showErrorMessage(errorLines.join("\n"))
@@ -1796,7 +1969,15 @@ PageType {
: tlsDomainTextField.textField.text
TelemtConfigModel.setTlsDomain(domainValue)
if (transportMode === "faketls") {
workers = "0"
TelemtConfigModel.setWorkers("0")
} else {
TelemtConfigModel.setWorkersMode(workersMode)
TelemtConfigModel.setWorkers(workers)
}
TelemtConfigModel.setNatEnabled(natEnabled)
TelemtConfigModel.setNatInternalIp(natInternalIpTextField.textField.text)
TelemtConfigModel.setNatExternalIp(natExternalIpTextField.textField.text)
previousPort = port

View File

@@ -19,7 +19,6 @@ PageType {
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
property bool isOutdatedAwgContainer: ServersUiController.isProcessedContainerOutdatedAwg()
BackButtonType {
id: backButton
@@ -51,25 +50,11 @@ PageType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: root.isOutdatedAwgContainer ? 16 : 32
Layout.bottomMargin: 32
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
}
WarningType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: root.isOutdatedAwgContainer
iconPath: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
textColor: AmneziaStyle.color.goldenApricot
textString: qsTr("AmneziaWG 2.0 is outdated and does not include the latest security improvements, but it will continue to work. Moving to AmneziaWG 3.0 by deploying a new container on the server is recommended for stronger protocol security")
}
}
model: root.isUnsupportedContainer ? null : ProtocolsModel

Some files were not shown because too many files have changed in this diff Show More