mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
Feat: Add MtProxy (Telegram)
This commit is contained in:
@@ -36,6 +36,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
|
||||
@@ -111,6 +112,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
|
||||
@@ -201,12 +203,14 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ void CoreController::initModels()
|
||||
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
|
||||
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
|
||||
|
||||
m_mtProxyConfigModel = new MtProxyConfigModel(this);
|
||||
setQmlContextProperty("MtProxyConfigModel", m_mtProxyConfigModel);
|
||||
|
||||
m_clientManagementModel = new ClientManagementModel(this);
|
||||
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
|
||||
|
||||
@@ -170,7 +173,7 @@ void CoreController::initControllers()
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel,
|
||||
#endif
|
||||
m_sftpConfigModel, m_socks5ConfigModel, this);
|
||||
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, this);
|
||||
setQmlContextProperty("InstallController", m_installUiController);
|
||||
|
||||
m_importController = new ImportUiController(m_importCoreController, this);
|
||||
@@ -203,6 +206,10 @@ void CoreController::initControllers()
|
||||
m_systemController = new SystemController(this);
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
m_networkReachabilityController = new NetworkReachabilityController(this);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
|
||||
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
|
||||
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
#include "ui/controllers/networkReachabilityController.h"
|
||||
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/selfhosted/usersController.h"
|
||||
@@ -69,6 +70,8 @@
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/services/mtProxyConfigModel.h"
|
||||
|
||||
#include "ui/models/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
@@ -158,6 +161,7 @@ private:
|
||||
ServersUiController* m_serversUiController;
|
||||
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
|
||||
SystemController* m_systemController;
|
||||
NetworkReachabilityController* m_networkReachabilityController;
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
@@ -210,6 +214,7 @@ private:
|
||||
#endif
|
||||
SftpConfigModel* m_sftpConfigModel;
|
||||
Socks5ProxyConfigModel* m_socks5ConfigModel;
|
||||
MtProxyConfigModel* m_mtProxyConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "core/installers/openvpnInstaller.h"
|
||||
#include "core/installers/sftpInstaller.h"
|
||||
#include "core/installers/socks5Installer.h"
|
||||
#include "core/installers/mtProxyInstaller.h"
|
||||
#include "core/installers/torInstaller.h"
|
||||
#include "core/installers/wireguardInstaller.h"
|
||||
#include "core/installers/xrayInstaller.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "core/utils/utilities.h"
|
||||
@@ -54,6 +56,21 @@ using namespace ProtocolUtils;
|
||||
namespace
|
||||
{
|
||||
Logger logger("InstallController");
|
||||
|
||||
bool dockerDaemonContainerMissing(const QString &out, const QString &containerDockerName)
|
||||
{
|
||||
if (!out.contains(QLatin1String("Error response from daemon"), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
if (out.contains(QLatin1String("No such container"), Qt::CaseInsensitive)
|
||||
&& out.contains(containerDockerName, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
if (out.size() < 700 && out.contains(QLatin1String("is not running"), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -133,6 +150,11 @@ ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer co
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
SshSession sshSession(this);
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
@@ -154,6 +176,9 @@ ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer co
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
clearCachedProfile(serverIndex, container);
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
||||
}
|
||||
@@ -372,9 +397,22 @@ ErrorCode InstallController::configureContainerWorker(const ServerCredentials &c
|
||||
sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), baseVars),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (e != ErrorCode::NoError) {
|
||||
return e;
|
||||
}
|
||||
|
||||
if (dockerDaemonContainerMissing(stdOut, ContainerUtils::containerToString(container))) {
|
||||
qDebug() << "configureContainerWorker: Docker daemon reports container missing/stopped, output:" << stdOut;
|
||||
return ErrorCode::ServerContainerMissingError;
|
||||
}
|
||||
|
||||
updateContainerConfigAfterInstallation(container, config, stdOut);
|
||||
|
||||
return e;
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
|
||||
@@ -527,6 +565,32 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
||||
}
|
||||
}
|
||||
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
|
||||
const auto *newMt = newConfig.getMtProxyProtocolConfig();
|
||||
if (oldMt && newMt) {
|
||||
const QString oldPort =
|
||||
oldMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : oldMt->port;
|
||||
const QString newPort =
|
||||
newMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : newMt->port;
|
||||
if (oldPort != newPort) {
|
||||
return true;
|
||||
}
|
||||
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
|
||||
protocols::mtProxy::transportModeStandard)
|
||||
: oldMt->transportMode;
|
||||
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
|
||||
protocols::mtProxy::transportModeStandard)
|
||||
: newMt->transportMode;
|
||||
if (oldTransport != newTransport) {
|
||||
return true;
|
||||
}
|
||||
if (oldMt->tlsDomain != newMt->tlsDomain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Socks5Proxy) {
|
||||
return true;
|
||||
}
|
||||
@@ -772,6 +836,7 @@ QScopedPointer<InstallerBase> InstallController::createInstaller(DockerContainer
|
||||
case DockerContainer::TorWebSite: return QScopedPointer<InstallerBase>(new TorInstaller(this));
|
||||
case DockerContainer::Sftp: return QScopedPointer<InstallerBase>(new SftpInstaller(this));
|
||||
case DockerContainer::Socks5Proxy: return QScopedPointer<InstallerBase>(new Socks5Installer(this));
|
||||
case DockerContainer::MtProxy: return QScopedPointer<InstallerBase>(new MtProxyInstaller(this));
|
||||
default: return QScopedPointer<InstallerBase>(new InstallerBase(this));
|
||||
}
|
||||
}
|
||||
@@ -810,6 +875,13 @@ bool InstallController::isUpdateDockerContainerRequired(DockerContainer containe
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (container == DockerContainer::MtProxy) {
|
||||
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
|
||||
const auto *newMt = newConfig.getMtProxyProtocolConfig();
|
||||
if (!oldMt || !newMt) {
|
||||
return true;
|
||||
}
|
||||
return !oldMt->equalsDockerDeploymentSettings(*newMt);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1093,6 +1165,31 @@ void InstallController::updateContainerConfigAfterInstallation(DockerContainer c
|
||||
onion.replace("\n", "");
|
||||
torProtocolConfig->serverConfig.site = onion;
|
||||
}
|
||||
} else if (container == DockerContainer::MtProxy) {
|
||||
if (auto* mtProxyConfig = containerConfig.getMtProxyProtocolConfig()) {
|
||||
qDebug() << "amnezia mtproxy" << stdOut;
|
||||
|
||||
static const QRegularExpression reSecret(
|
||||
QStringLiteral(R"(\[\*\]\s+Secret:\s+([0-9a-fA-F]{32}))"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const QRegularExpression reTgLink(QStringLiteral(R"(\[\*\]\s+tg://\s+link:\s+(tg://proxy\?[^\s]+))"));
|
||||
static const QRegularExpression reTmeLink(
|
||||
QStringLiteral(R"(\[\*\]\s+t\.me\s+link:\s+(https://t\.me/proxy\?[^\s]+))"));
|
||||
|
||||
const QRegularExpressionMatch mSecret = reSecret.match(stdOut);
|
||||
const QRegularExpressionMatch mTgLink = reTgLink.match(stdOut);
|
||||
const QRegularExpressionMatch mTmeLink = reTmeLink.match(stdOut);
|
||||
|
||||
if (mSecret.hasMatch()) {
|
||||
mtProxyConfig->secret = mSecret.captured(1);
|
||||
}
|
||||
if (mTgLink.hasMatch()) {
|
||||
mtProxyConfig->tgLink = mTgLink.captured(1);
|
||||
}
|
||||
if (mTmeLink.hasMatch()) {
|
||||
mtProxyConfig->tmeLink = mTmeLink.captured(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
client/core/diagnostics/containerDiagnostics.h
Normal file
16
client/core/diagnostics/containerDiagnostics.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef CONTAINERDIAGNOSTICS_H
|
||||
#define CONTAINERDIAGNOSTICS_H
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
struct ContainerDiagnostics
|
||||
{
|
||||
bool available = false;
|
||||
bool portReachable = false;
|
||||
|
||||
virtual ~ContainerDiagnostics() = default;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // CONTAINERDIAGNOSTICS_H
|
||||
18
client/core/diagnostics/mtProxyDiagnostics.h
Normal file
18
client/core/diagnostics/mtProxyDiagnostics.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef MTPROXYDIAGNOSTICS_H
|
||||
#define MTPROXYDIAGNOSTICS_H
|
||||
|
||||
#include "containerDiagnostics.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace amnezia {
|
||||
struct MtProxyDiagnostics : ContainerDiagnostics {
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // MTPROXYDIAGNOSTICS_H
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "core/models/protocols/sftpProtocolConfig.h"
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/torProtocolConfig.h"
|
||||
|
||||
@@ -91,6 +92,12 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
config.protocolConfig = socks5Config;
|
||||
break;
|
||||
}
|
||||
case Proto::MtProxy: {
|
||||
MtProxyProtocolConfig mtConfig;
|
||||
mtConfig.port = portStr;
|
||||
config.protocolConfig = mtConfig;
|
||||
break;
|
||||
}
|
||||
case Proto::Ikev2: {
|
||||
Ikev2ProtocolConfig ikev2Config;
|
||||
config.protocolConfig = ikev2Config;
|
||||
|
||||
82
client/core/installers/mtProxyInstaller.cpp
Normal file
82
client/core/installers/mtProxyInstaller.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "mtProxyInstaller.h"
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxySecretPath("/data/secret");
|
||||
}
|
||||
|
||||
MtProxyInstaller::MtProxyInstaller(QObject *parent)
|
||||
: InstallerBase(parent) {
|
||||
}
|
||||
|
||||
ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession, ContainerConfig &config) {
|
||||
if (container != DockerContainer::MtProxy || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode jsonErr = ErrorCode::NoError;
|
||||
const QByteArray jsonRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyClientJsonPath), jsonErr);
|
||||
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
QJsonObject merged = mt->toJson();
|
||||
const QJsonObject snap = doc.object();
|
||||
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
|
||||
merged.insert(it.key(), it.value());
|
||||
}
|
||||
*mt = MtProxyProtocolConfig::fromJson(merged);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (hex32.match(sec).hasMatch()) {
|
||||
mt->secret = sec;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, const ContainerConfig &config) {
|
||||
const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return;
|
||||
}
|
||||
const QByteArray payload = QJsonDocument(mt->toJson()).toJson(QJsonDocument::Compact);
|
||||
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
|
||||
QString(kMtProxyClientJsonUploadPath));
|
||||
if (err != ErrorCode::NoError) {
|
||||
qWarning() << "MtProxyInstaller::uploadClientSettingsSnapshot failed" << err;
|
||||
}
|
||||
}
|
||||
20
client/core/installers/mtProxyInstaller.h
Normal file
20
client/core/installers/mtProxyInstaller.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef MTPROXYINSTALLER_H
|
||||
#define MTPROXYINSTALLER_H
|
||||
|
||||
#include "installerBase.h"
|
||||
|
||||
class MtProxyInstaller : public InstallerBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MtProxyInstaller(QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode
|
||||
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
|
||||
SshSession *sshSession, amnezia::ContainerConfig &config) override;
|
||||
|
||||
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &config);
|
||||
};
|
||||
|
||||
#endif // MTPROXYINSTALLER_H
|
||||
@@ -113,6 +113,16 @@ const Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig()
|
||||
return protocolConfig.as<Socks5ProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig()
|
||||
{
|
||||
return protocolConfig.as<MtProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
const MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig() const
|
||||
{
|
||||
return protocolConfig.as<MtProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig()
|
||||
{
|
||||
return protocolConfig.as<Ikev2ProtocolConfig>();
|
||||
|
||||
@@ -57,6 +57,9 @@ struct ContainerConfig {
|
||||
Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig();
|
||||
const Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig() const;
|
||||
|
||||
MtProxyProtocolConfig* getMtProxyProtocolConfig();
|
||||
const MtProxyProtocolConfig* getMtProxyProtocolConfig() const;
|
||||
|
||||
Ikev2ProtocolConfig* getIkev2ProtocolConfig();
|
||||
const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/dnsProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -38,6 +39,8 @@ Proto ProtocolConfig::type() const
|
||||
return Proto::TorWebSite;
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return Proto::Dns;
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return Proto::MtProxy;
|
||||
}
|
||||
return Proto::Unknown;
|
||||
}, data);
|
||||
@@ -65,6 +68,8 @@ QString ProtocolConfig::port() const
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return arg.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : arg.port;
|
||||
}
|
||||
return QString();
|
||||
}, data);
|
||||
@@ -88,6 +93,8 @@ QString ProtocolConfig::transportProto() const
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return QStringLiteral("tcp");
|
||||
}
|
||||
return QString();
|
||||
}, data);
|
||||
@@ -299,6 +306,8 @@ ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type)
|
||||
return ProtocolConfig{TorProtocolConfig::fromJson(json)};
|
||||
case Proto::Dns:
|
||||
return ProtocolConfig{DnsProtocolConfig::fromJson(json)};
|
||||
case Proto::MtProxy:
|
||||
return ProtocolConfig{MtProxyProtocolConfig::fromJson(json)};
|
||||
default:
|
||||
return ProtocolConfig{AwgProtocolConfig{}};
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/torProtocolConfig.h"
|
||||
#include "core/models/protocols/dnsProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -36,6 +37,7 @@ struct ProtocolConfig {
|
||||
XrayProtocolConfig,
|
||||
SftpProtocolConfig,
|
||||
Socks5ProxyProtocolConfig,
|
||||
MtProxyProtocolConfig,
|
||||
Ikev2ProtocolConfig,
|
||||
TorProtocolConfig,
|
||||
DnsProtocolConfig
|
||||
|
||||
147
client/core/models/protocols/mtProxyProtocolConfig.cpp
Normal file
147
client/core/models/protocols/mtProxyProtocolConfig.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "mtProxyProtocolConfig.h"
|
||||
|
||||
#include "../../../core/utils/protocolEnum.h"
|
||||
#include "../../../core/protocols/protocolUtils.h"
|
||||
#include "../../../core/utils/constants/configKeys.h"
|
||||
#include "../../../core/utils/constants/protocolConstants.h"
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
QJsonObject MtProxyProtocolConfig::toJson() const {
|
||||
QJsonObject obj;
|
||||
|
||||
if (!port.isEmpty()) {
|
||||
obj[configKey::port] = port;
|
||||
}
|
||||
if (!secret.isEmpty()) {
|
||||
obj[protocols::mtProxy::secretKey] = secret;
|
||||
}
|
||||
if (!tag.isEmpty()) {
|
||||
obj[protocols::mtProxy::tagKey] = tag;
|
||||
}
|
||||
if (!tgLink.isEmpty()) {
|
||||
obj[protocols::mtProxy::tgLinkKey] = tgLink;
|
||||
}
|
||||
if (!tmeLink.isEmpty()) {
|
||||
obj[protocols::mtProxy::tmeLinkKey] = tmeLink;
|
||||
}
|
||||
obj[protocols::mtProxy::isEnabledKey] = isEnabled;
|
||||
if (!publicHost.isEmpty()) {
|
||||
obj[protocols::mtProxy::publicHostKey] = publicHost;
|
||||
}
|
||||
if (!transportMode.isEmpty()) {
|
||||
obj[protocols::mtProxy::transportModeKey] = transportMode;
|
||||
}
|
||||
if (!tlsDomain.isEmpty()) {
|
||||
obj[protocols::mtProxy::tlsDomainKey] = tlsDomain;
|
||||
}
|
||||
if (!additionalSecrets.isEmpty()) {
|
||||
obj[protocols::mtProxy::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
|
||||
}
|
||||
if (!workersMode.isEmpty()) {
|
||||
obj[protocols::mtProxy::workersModeKey] = workersMode;
|
||||
}
|
||||
if (!workers.isEmpty()) {
|
||||
obj[protocols::mtProxy::workersKey] = workers;
|
||||
}
|
||||
obj[protocols::mtProxy::natEnabledKey] = natEnabled;
|
||||
if (!natInternalIp.isEmpty()) {
|
||||
obj[protocols::mtProxy::natInternalIpKey] = natInternalIp;
|
||||
}
|
||||
if (!natExternalIp.isEmpty()) {
|
||||
obj[protocols::mtProxy::natExternalIpKey] = natExternalIp;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig MtProxyProtocolConfig::fromJson(const QJsonObject &json) {
|
||||
MtProxyProtocolConfig config;
|
||||
|
||||
config.port = json.value(configKey::port).toString();
|
||||
config.secret = json.value(protocols::mtProxy::secretKey).toString();
|
||||
config.tag = json.value(protocols::mtProxy::tagKey).toString();
|
||||
config.tgLink = json.value(protocols::mtProxy::tgLinkKey).toString();
|
||||
config.tmeLink = json.value(protocols::mtProxy::tmeLinkKey).toString();
|
||||
config.isEnabled = json.value(protocols::mtProxy::isEnabledKey).toBool(true);
|
||||
config.publicHost = json.value(protocols::mtProxy::publicHostKey).toString();
|
||||
config.transportMode = json.value(protocols::mtProxy::transportModeKey).toString();
|
||||
config.tlsDomain = json.value(protocols::mtProxy::tlsDomainKey).toString();
|
||||
for (const auto &v: json.value(protocols::mtProxy::additionalSecretsKey).toArray()) {
|
||||
const QString s = v.toString();
|
||||
if (!s.isEmpty()) {
|
||||
config.additionalSecrets.append(s);
|
||||
}
|
||||
}
|
||||
config.workersMode = json.value(protocols::mtProxy::workersModeKey).toString();
|
||||
config.workers = json.value(protocols::mtProxy::workersKey).toString();
|
||||
config.natEnabled = json.value(protocols::mtProxy::natEnabledKey).toBool(false);
|
||||
config.natInternalIp = json.value(protocols::mtProxy::natInternalIpKey).toString();
|
||||
config.natExternalIp = json.value(protocols::mtProxy::natExternalIpKey).toString();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
bool MtProxyProtocolConfig::equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const {
|
||||
const auto normPort = [](const QString &p) {
|
||||
return p.isEmpty() ? QString(protocols::mtProxy::defaultPort) : p;
|
||||
};
|
||||
const auto normTransport = [](const QString &t) {
|
||||
return t.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : t;
|
||||
};
|
||||
const auto normWorkersMode = [](const QString &m) {
|
||||
return m.isEmpty() ? QString(protocols::mtProxy::workersModeAuto) : m;
|
||||
};
|
||||
|
||||
if (normPort(port) != normPort(other.port)) {
|
||||
return false;
|
||||
}
|
||||
if (normTransport(transportMode) != normTransport(other.transportMode)) {
|
||||
return false;
|
||||
}
|
||||
if (tlsDomain != other.tlsDomain) {
|
||||
return false;
|
||||
}
|
||||
if (secret != other.secret) {
|
||||
return false;
|
||||
}
|
||||
if (tag != other.tag) {
|
||||
return false;
|
||||
}
|
||||
if (publicHost != other.publicHost) {
|
||||
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;
|
||||
}
|
||||
if (isEnabled != other.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList aa = additionalSecrets;
|
||||
QStringList bb = other.additionalSecrets;
|
||||
aa.removeAll(QString());
|
||||
bb.removeAll(QString());
|
||||
std::sort(aa.begin(), aa.end());
|
||||
std::sort(bb.begin(), bb.end());
|
||||
return aa == bb;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
38
client/core/models/protocols/mtProxyProtocolConfig.h
Normal file
38
client/core/models/protocols/mtProxyProtocolConfig.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef MTPROXYPROTOCOLCONFIG_H
|
||||
#define MTPROXYPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
struct MtProxyProtocolConfig {
|
||||
QString port;
|
||||
QString secret;
|
||||
QString tag;
|
||||
QString tgLink;
|
||||
QString tmeLink;
|
||||
bool isEnabled = true;
|
||||
QString publicHost;
|
||||
QString transportMode;
|
||||
QString tlsDomain;
|
||||
QStringList additionalSecrets;
|
||||
QString workersMode;
|
||||
QString workers;
|
||||
bool natEnabled = false;
|
||||
QString natInternalIp;
|
||||
QString natExternalIp;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static MtProxyProtocolConfig fromJson(const QJsonObject &json);
|
||||
|
||||
// Port, transport, TLS, secrets, NAT, workers, isEnabled, additionalSecrets (order-independent).
|
||||
// Ignores tgLink / tmeLink (derived / display).
|
||||
bool equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // MTPROXYPROTOCOLCONFIG_H
|
||||
@@ -68,7 +68,9 @@ QMap<Proto, QString> ProtocolUtils::protocolHumanNames()
|
||||
{ Proto::TorWebSite, "Website in Tor network" },
|
||||
{ Proto::Dns, "DNS Service" },
|
||||
{ Proto::Sftp, QObject::tr("SFTP service") },
|
||||
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
|
||||
{ Proto::MtProxy, QObject::tr("MTProxy (Telegram)") },
|
||||
};
|
||||
}
|
||||
|
||||
QMap<Proto, QString> ProtocolUtils::protocolDescriptions()
|
||||
@@ -92,6 +94,7 @@ ServiceType ProtocolUtils::protocolService(Proto p)
|
||||
case Proto::Dns: return ServiceType::Other;
|
||||
case Proto::Sftp: return ServiceType::Other;
|
||||
case Proto::Socks5Proxy: return ServiceType::Other;
|
||||
case Proto::MtProxy: return ServiceType::Other;
|
||||
default: return ServiceType::Other;
|
||||
}
|
||||
}
|
||||
@@ -104,6 +107,7 @@ int ProtocolUtils::getPortForInstall(Proto p)
|
||||
case OpenVpn:
|
||||
case Socks5Proxy:
|
||||
return QRandomGenerator::global()->bounded(30000, 50000);
|
||||
case MtProxy:
|
||||
default:
|
||||
return defaultPort(p);
|
||||
}
|
||||
@@ -123,6 +127,7 @@ int ProtocolUtils::defaultPort(Proto p)
|
||||
case Proto::Dns: return 53;
|
||||
case Proto::Sftp: return 222;
|
||||
case Proto::Socks5Proxy: return 38080;
|
||||
case Proto::MtProxy: return QString(protocols::mtProxy::defaultPort).toInt();
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -141,6 +146,7 @@ bool ProtocolUtils::defaultPortChangeable(Proto p)
|
||||
case Proto::Dns: return false;
|
||||
case Proto::Sftp: return true;
|
||||
case Proto::Socks5Proxy: return true;
|
||||
case Proto::MtProxy: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -161,6 +167,7 @@ TransportProto ProtocolUtils::defaultTransportProto(Proto p)
|
||||
case Proto::Dns: return TransportProto::Udp;
|
||||
case Proto::Sftp: return TransportProto::Tcp;
|
||||
case Proto::Socks5Proxy: return TransportProto::Tcp;
|
||||
case Proto::MtProxy: return TransportProto::Tcp;
|
||||
default: return TransportProto::Udp;
|
||||
}
|
||||
}
|
||||
@@ -180,6 +187,7 @@ bool ProtocolUtils::defaultTransportProtoChangeable(Proto p)
|
||||
case Proto::Dns: return false;
|
||||
case Proto::Sftp: return false;
|
||||
case Proto::Socks5Proxy: return false;
|
||||
case Proto::MtProxy: return false;
|
||||
default: return false;
|
||||
}
|
||||
return false;
|
||||
@@ -208,4 +216,3 @@ QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfi
|
||||
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ namespace amnezia
|
||||
constexpr QLatin1String xray("xray");
|
||||
constexpr QLatin1String ssxray("ssxray");
|
||||
constexpr QLatin1String socks5proxy("socks5proxy");
|
||||
constexpr QLatin1String mtproxy("mtproxy");
|
||||
|
||||
constexpr QLatin1String splitTunnelSites("splitTunnelSites");
|
||||
constexpr QLatin1String splitTunnelType("splitTunnelType");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
namespace protocols
|
||||
{
|
||||
|
||||
@@ -174,9 +175,37 @@ namespace amnezia
|
||||
constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg";
|
||||
}
|
||||
|
||||
namespace mtProxy
|
||||
{
|
||||
constexpr char secretKey[] = "mtproxy_secret";
|
||||
constexpr char tagKey[] = "mtproxy_tag";
|
||||
constexpr char tgLinkKey[] = "mtproxy_tg_link";
|
||||
constexpr char tmeLinkKey[] = "mtproxy_tme_link";
|
||||
constexpr char isEnabledKey[] = "mtproxy_is_enabled";
|
||||
constexpr char publicHostKey[] = "mtproxy_public_host";
|
||||
constexpr char transportModeKey[] = "mtproxy_transport_mode";
|
||||
constexpr char tlsDomainKey[] = "mtproxy_tls_domain";
|
||||
constexpr char additionalSecretsKey[] = "mtproxy_additional_secrets";
|
||||
constexpr char workersKey[] = "mtproxy_workers";
|
||||
constexpr char workersModeKey[] = "mtproxy_workers_mode";
|
||||
constexpr char natEnabledKey[] = "mtproxy_nat_enabled";
|
||||
constexpr char natInternalIpKey[] = "mtproxy_nat_internal_ip";
|
||||
constexpr char natExternalIpKey[] = "mtproxy_nat_external_ip";
|
||||
|
||||
constexpr char transportModeStandard[] = "standard";
|
||||
constexpr char transportModeFakeTLS[] = "faketls";
|
||||
|
||||
constexpr char workersModeAuto[] = "auto";
|
||||
constexpr char workersModeManual[] = "manual";
|
||||
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultWorkers[] = "2";
|
||||
constexpr int maxWorkers = 32;
|
||||
constexpr int botTagHexLength = 32;
|
||||
constexpr char defaultTlsDomain[] = "googletagmanager.com";
|
||||
}
|
||||
|
||||
} // namespace protocols
|
||||
}
|
||||
|
||||
#endif // PROTOCOLCONSTANTS_H
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace amnezia
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp,
|
||||
Socks5Proxy
|
||||
Socks5Proxy,
|
||||
MtProxy,
|
||||
};
|
||||
Q_ENUM_NS(DockerContainer)
|
||||
} // namespace ContainerEnumNS
|
||||
|
||||
@@ -72,7 +72,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
|
||||
{ DockerContainer::MtProxy, QObject::tr("MTProxy (Telegram)") },
|
||||
};
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
@@ -102,7 +104,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
|
||||
{ DockerContainer::Socks5Proxy,
|
||||
QObject::tr("") } };
|
||||
QObject::tr("") },
|
||||
{ DockerContainer::MtProxy,
|
||||
QObject::tr("Telegram MTProto proxy server") },
|
||||
};
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
@@ -172,7 +177,12 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
|
||||
{ DockerContainer::MtProxy,
|
||||
QObject::tr("Telegram MTProto proxy server. "
|
||||
"Allows Telegram clients to connect through your server "
|
||||
"using the MTProto protocol. Supports FakeTLS mode for "
|
||||
"bypassing DPI-based blocking.") },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,6 +207,7 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
case DockerContainer::Dns: return Proto::Dns;
|
||||
case DockerContainer::Sftp: return Proto::Sftp;
|
||||
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
|
||||
case DockerContainer::MtProxy: return Proto::MtProxy;
|
||||
default: return Proto::Unknown;
|
||||
}
|
||||
}
|
||||
@@ -224,6 +235,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
case DockerContainer::MtProxy: return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -237,7 +249,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
return false;
|
||||
case DockerContainer::MtProxy: return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -256,6 +268,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
case DockerContainer::MtProxy: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
@@ -318,6 +331,7 @@ bool ContainerUtils::isShareable(DockerContainer container)
|
||||
case DockerContainer::Dns: return false;
|
||||
case DockerContainer::Sftp: return false;
|
||||
case DockerContainer::Socks5Proxy: return false;
|
||||
case DockerContainer::MtProxy: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
@@ -346,8 +360,9 @@ int ContainerUtils::installPageOrder(DockerContainer container)
|
||||
case DockerContainer::Xray: return 3;
|
||||
case DockerContainer::Ipsec: return 7;
|
||||
case DockerContainer::SSXray: return 8;
|
||||
case DockerContainer::MtProxy:
|
||||
return 20;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace amnezia
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp,
|
||||
Socks5Proxy
|
||||
Socks5Proxy,
|
||||
MtProxy,
|
||||
};
|
||||
Q_ENUM_NS(Proto)
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
@@ -20,6 +19,7 @@
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "core/models/protocols/sftpProtocolConfig.h"
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
@@ -38,6 +38,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
case DockerContainer::Dns: return QLatin1String("dns");
|
||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
|
||||
case DockerContainer::MtProxy: return QLatin1String("mtproxy");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
@@ -285,6 +286,55 @@ amnezia::ScriptVars amnezia::genSocks5ProxyVars(const ContainerConfig &container
|
||||
return vars;
|
||||
}
|
||||
|
||||
amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConfig) {
|
||||
ScriptVars vars;
|
||||
|
||||
if (auto *mtProxyProtocolConfig = containerConfig.getMtProxyProtocolConfig()) {
|
||||
const MtProxyProtocolConfig &c = *mtProxyProtocolConfig;
|
||||
|
||||
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
|
||||
vars.append({{"$MTPROXY_SECRET", c.secret}});
|
||||
vars.append({{"$MTPROXY_TAG", c.tag}});
|
||||
vars.append({{"$MTPROXY_TRANSPORT_MODE",
|
||||
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
|
||||
: c.transportMode}});
|
||||
|
||||
QString tlsDomain = c.tlsDomain;
|
||||
if (tlsDomain.isEmpty()) {
|
||||
tlsDomain = QString(protocols::mtProxy::defaultTlsDomain);
|
||||
}
|
||||
vars.append({{"$MTPROXY_TLS_DOMAIN", tlsDomain}});
|
||||
vars.append({{"$MTPROXY_PUBLIC_HOST", c.publicHost}});
|
||||
|
||||
QStringList additionalList;
|
||||
for (const QString &s: c.additionalSecrets) {
|
||||
if (!s.isEmpty()) {
|
||||
additionalList << s;
|
||||
}
|
||||
}
|
||||
vars.append({{"$MTPROXY_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(','))}});
|
||||
|
||||
const QString workersMode = c.workersMode.isEmpty() ? QString(protocols::mtProxy::workersModeAuto)
|
||||
: c.workersMode;
|
||||
QString workers;
|
||||
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual)) {
|
||||
workers = c.workers.isEmpty() ? QString(protocols::mtProxy::defaultWorkers) : c.workers;
|
||||
} else {
|
||||
const QString transportMode =
|
||||
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : c.transportMode;
|
||||
workers = (transportMode == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) ? QStringLiteral("0")
|
||||
: QStringLiteral("2");
|
||||
}
|
||||
vars.append({{"$MTPROXY_WORKERS", workers}});
|
||||
|
||||
vars.append({{"$MTPROXY_NAT_ENABLED", c.natEnabled ? QStringLiteral("1") : QStringLiteral("0")}});
|
||||
vars.append({{"$MTPROXY_NAT_INTERNAL_IP", c.natInternalIp}});
|
||||
vars.append({{"$MTPROXY_NAT_EXTERNAL_IP", c.natExternalIp}});
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig)
|
||||
{
|
||||
ScriptVars vars;
|
||||
@@ -309,6 +359,9 @@ amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer contain
|
||||
case Proto::Socks5Proxy:
|
||||
vars.append(genSocks5ProxyVars(containerConfig));
|
||||
break;
|
||||
case Proto::MtProxy:
|
||||
vars.append(genMtProxyVars(containerConfig));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ ScriptVars genWireGuardVars(const ContainerConfig &containerConfig);
|
||||
ScriptVars genAwgVars(const ContainerConfig &containerConfig);
|
||||
ScriptVars genSftpVars(const ContainerConfig &containerConfig);
|
||||
ScriptVars genSocks5ProxyVars(const ContainerConfig &containerConfig);
|
||||
ScriptVars genMtProxyVars(const ContainerConfig &containerConfig);
|
||||
|
||||
ScriptVars genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace libssh {
|
||||
QEventLoop wait;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
||||
watcher.setFuture(future);
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
int connectionResult = watcher.result();
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace libssh {
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &Client::writeToChannelFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
return watcher.result();
|
||||
}
|
||||
@@ -284,7 +284,7 @@ namespace libssh {
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
closeScpSession();
|
||||
return watcher.result();
|
||||
|
||||
@@ -103,8 +103,8 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString runner =
|
||||
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy;
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
|
||||
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||
|
||||
9
client/server_scripts/mtproxy/Dockerfile
Normal file
9
client/server_scripts/mtproxy/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM amneziavpn/mtproxy:latest
|
||||
|
||||
RUN mkdir -p /opt/amnezia /data
|
||||
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
|
||||
chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
VOLUME /data
|
||||
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
|
||||
CMD [""]
|
||||
60
client/server_scripts/mtproxy/configure_container.sh
Normal file
60
client/server_scripts/mtproxy/configure_container.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Download Telegram config files
|
||||
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
|
||||
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
|
||||
|
||||
# Determine secret: env var -> saved file -> generate new
|
||||
if [ -n "$MTPROXY_SECRET" ]; then
|
||||
SECRET="$MTPROXY_SECRET"
|
||||
elif [ -f /data/secret ]; then
|
||||
SECRET=$(cat /data/secret)
|
||||
else
|
||||
SECRET=$(openssl rand -hex 16)
|
||||
fi
|
||||
|
||||
# Validate: must be exactly 32 hex chars
|
||||
echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}$' || SECRET=$(openssl rand -hex 16)
|
||||
|
||||
# Persist secret for start.sh restarts
|
||||
echo "$SECRET" > /data/secret
|
||||
|
||||
# Detect external IP
|
||||
IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
|
||||
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
|
||||
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://icanhazip.com 2>/dev/null)
|
||||
|
||||
# Use custom public host/domain if provided, otherwise fall back to detected IP
|
||||
if [ -n "$MTPROXY_PUBLIC_HOST" ]; then
|
||||
LINK_HOST="$MTPROXY_PUBLIC_HOST"
|
||||
else
|
||||
LINK_HOST="$IP"
|
||||
fi
|
||||
|
||||
PORT=$MTPROXY_PORT
|
||||
|
||||
# Transport mode is substituted by replaceVars — plain variable, no curly braces
|
||||
TRANSPORT_MODE=$MTPROXY_TRANSPORT_MODE
|
||||
|
||||
PADDED_SECRET="dd${SECRET}"
|
||||
|
||||
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
|
||||
DOMAIN_HEX=$(echo -n "$MTPROXY_TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')
|
||||
FAKETLS_SECRET="ee${SECRET}${DOMAIN_HEX}"
|
||||
else
|
||||
FAKETLS_SECRET=""
|
||||
fi
|
||||
|
||||
# Active link secret depends on transport mode
|
||||
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$FAKETLS_SECRET" ]; then
|
||||
LINK_SECRET="$FAKETLS_SECRET"
|
||||
else
|
||||
LINK_SECRET="$PADDED_SECRET"
|
||||
fi
|
||||
|
||||
# Output stable markers — parsed by updateContainerConfigAfterInstallation()
|
||||
echo "[*] MTProxy configuration"
|
||||
echo "[*] Secret: ${SECRET}"
|
||||
echo "[*] FakeTLS: ${FAKETLS_SECRET}"
|
||||
echo "[*] tg:// link: tg://proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"
|
||||
echo "[*] t.me link: https://t.me/proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"
|
||||
9
client/server_scripts/mtproxy/run_container.sh
Normal file
9
client/server_scripts/mtproxy/run_container.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
# Run container
|
||||
sudo docker run -d \
|
||||
--log-driver none \
|
||||
--restart always \
|
||||
-p $MTPROXY_PORT:$MTPROXY_PORT/tcp \
|
||||
-v amnezia-mtproxy-data:/data \
|
||||
--name $CONTAINER_NAME \
|
||||
$CONTAINER_NAME
|
||||
|
||||
71
client/server_scripts/mtproxy/start.sh
Normal file
71
client/server_scripts/mtproxy/start.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Container startup"
|
||||
|
||||
# Read persisted secret
|
||||
SECRET=""
|
||||
if [ -f /data/secret ]; then
|
||||
SECRET=$(cat /data/secret)
|
||||
fi
|
||||
|
||||
if [ -z "$SECRET" ]; then
|
||||
echo "ERROR: /data/secret not found — run configure_container first"
|
||||
tail -f /dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build tag argument
|
||||
TAG_ARG=""
|
||||
if [ -n "$MTPROXY_TAG" ]; then
|
||||
TAG_ARG="-P $MTPROXY_TAG"
|
||||
fi
|
||||
|
||||
# Build domain argument for FakeTLS mode
|
||||
DOMAIN_ARG=""
|
||||
if [ "$MTPROXY_TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
|
||||
DOMAIN_ARG="--domain $MTPROXY_TLS_DOMAIN"
|
||||
fi
|
||||
|
||||
WORKERS=$MTPROXY_WORKERS
|
||||
STATS_PORT=2398
|
||||
LISTEN_PORT=$MTPROXY_PORT
|
||||
|
||||
NAT_FLAG=""
|
||||
NAT_VALUE=""
|
||||
if [ "$MTPROXY_NAT_ENABLED" = "1" ] && [ -n "$MTPROXY_NAT_INTERNAL_IP" ] && [ -n "$MTPROXY_NAT_EXTERNAL_IP" ]; then
|
||||
NAT_FLAG="--nat-info"
|
||||
NAT_VALUE="$MTPROXY_NAT_INTERNAL_IP:$MTPROXY_NAT_EXTERNAL_IP"
|
||||
else
|
||||
INTERNAL_IP=$(hostname -i 2>/dev/null | awk '{print $1}')
|
||||
EXTERNAL_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
|
||||
[ -z "$EXTERNAL_IP" ] && EXTERNAL_IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
|
||||
|
||||
if [ -n "$INTERNAL_IP" ] && [ -n "$EXTERNAL_IP" ] && [ "$INTERNAL_IP" != "$EXTERNAL_IP" ]; then
|
||||
NAT_FLAG="--nat-info"
|
||||
NAT_VALUE="${INTERNAL_IP}:${EXTERNAL_IP}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build additional secrets arguments
|
||||
ADDITIONAL_SECRETS_ARG=""
|
||||
if [ -n "$MTPROXY_ADDITIONAL_SECRETS" ]; then
|
||||
for S in $(echo "$MTPROXY_ADDITIONAL_SECRETS" | tr ',' ' '); do
|
||||
ADDITIONAL_SECRETS_ARG="$ADDITIONAL_SECRETS_ARG -S $S"
|
||||
done
|
||||
fi
|
||||
|
||||
# Start proxy (foreground)
|
||||
exec mtproto-proxy \
|
||||
-u root \
|
||||
-p ${STATS_PORT} \
|
||||
-H ${LISTEN_PORT} \
|
||||
-S ${SECRET} \
|
||||
${ADDITIONAL_SECRETS_ARG} \
|
||||
--aes-pwd /data/proxy-secret \
|
||||
-M ${WORKERS} \
|
||||
-C 60000 \
|
||||
--allow-skip-dh \
|
||||
${NAT_FLAG:+${NAT_FLAG} ${NAT_VALUE}} \
|
||||
${TAG_ARG} \
|
||||
${DOMAIN_ARG} \
|
||||
/data/proxy-multi.conf
|
||||
46
client/ui/controllers/networkReachabilityController.cpp
Normal file
46
client/ui/controllers/networkReachabilityController.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "networkReachabilityController.h"
|
||||
|
||||
#include <QNetworkInformation>
|
||||
|
||||
namespace {
|
||||
|
||||
bool reachabilityAllowsRemoteOperations(QNetworkInformation::Reachability r) {
|
||||
using R = QNetworkInformation::Reachability;
|
||||
// Unknown: no backend or not yet determined — do not block UI.
|
||||
return r == R::Online || r == R::Unknown;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NetworkReachabilityController::NetworkReachabilityController(QObject *parent) : QObject(parent) {
|
||||
attachToNetworkInformation();
|
||||
}
|
||||
|
||||
bool NetworkReachabilityController::hasInternetAccess() const {
|
||||
return m_hasInternetAccess;
|
||||
}
|
||||
|
||||
void NetworkReachabilityController::attachToNetworkInformation() {
|
||||
if (!QNetworkInformation::loadDefaultBackend()) {
|
||||
return;
|
||||
}
|
||||
QNetworkInformation *ni = QNetworkInformation::instance();
|
||||
if (!ni) {
|
||||
return;
|
||||
}
|
||||
const bool initial = reachabilityAllowsRemoteOperations(ni->reachability());
|
||||
const bool previous = m_hasInternetAccess;
|
||||
m_hasInternetAccess = initial;
|
||||
if (previous != m_hasInternetAccess) {
|
||||
emit hasInternetAccessChanged();
|
||||
}
|
||||
connect(ni, &QNetworkInformation::reachabilityChanged, this,
|
||||
[this](QNetworkInformation::Reachability r) {
|
||||
const bool ok = reachabilityAllowsRemoteOperations(r);
|
||||
if (ok == m_hasInternetAccess) {
|
||||
return;
|
||||
}
|
||||
m_hasInternetAccess = ok;
|
||||
emit hasInternetAccessChanged();
|
||||
});
|
||||
}
|
||||
30
client/ui/controllers/networkReachabilityController.h
Normal file
30
client/ui/controllers/networkReachabilityController.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef NETWORKREACHABILITYCONTROLLER_H
|
||||
#define NETWORKREACHABILITYCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
// Exposes QNetworkInformation to QML for UI that must not run remote operations offline.
|
||||
// Note: mozilla/networkwatcher.h has NetworkWatcher::getReachability() using the same API,
|
||||
// but networkwatcher.cpp is not linked into the desktop client (only the service process).
|
||||
|
||||
class NetworkReachabilityController final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool hasInternetAccess READ hasInternetAccess NOTIFY hasInternetAccessChanged)
|
||||
|
||||
public:
|
||||
explicit NetworkReachabilityController(QObject *parent = nullptr);
|
||||
|
||||
bool hasInternetAccess() const;
|
||||
|
||||
signals:
|
||||
|
||||
void hasInternetAccessChanged();
|
||||
|
||||
private:
|
||||
void attachToNetworkInformation();
|
||||
|
||||
bool m_hasInternetAccess = true;
|
||||
};
|
||||
|
||||
#endif // NETWORKREACHABILITYCONTROLLER_H
|
||||
@@ -50,6 +50,7 @@ namespace PageLoader
|
||||
PageServiceTorWebsiteSettings,
|
||||
PageServiceDnsSettings,
|
||||
PageServiceSocksProxySettings,
|
||||
PageServiceMtProxySettings,
|
||||
|
||||
PageSetupWizardStart,
|
||||
PageSetupWizardCredentials,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "exportUiController.h"
|
||||
|
||||
#include "../systemController.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
|
||||
ExportUiController::ExportUiController(ExportController* exportController, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -51,6 +52,14 @@ void ExportUiController::generateXrayConfig(int serverIndex, const QString &clie
|
||||
applyExportResult(result);
|
||||
}
|
||||
|
||||
void ExportUiController::generateQrFromString(const QString &text)
|
||||
{
|
||||
clearPreviousConfig();
|
||||
m_config = text;
|
||||
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(text.toUtf8());
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
QString ExportUiController::getConfig()
|
||||
{
|
||||
return m_config;
|
||||
|
||||
@@ -23,6 +23,7 @@ public slots:
|
||||
void generateWireGuardConfig(int serverIndex, const QString &clientName);
|
||||
void generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName);
|
||||
void generateXrayConfig(int serverIndex, const QString &clientName);
|
||||
void generateQrFromString(const QString &text);
|
||||
|
||||
QString getConfig();
|
||||
QString getNativeConfigString();
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#include <QEventLoop>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
@@ -69,6 +72,7 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
#endif
|
||||
SftpConfigModel *sftpConfigModel,
|
||||
Socks5ProxyConfigModel *socks5ConfigModel,
|
||||
MtProxyConfigModel* mtConfigModel,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_installController(installController),
|
||||
@@ -85,7 +89,8 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_ikev2ConfigModel(ikev2ConfigModel),
|
||||
#endif
|
||||
m_sftpConfigModel(sftpConfigModel),
|
||||
m_socks5ConfigModel(socks5ConfigModel)
|
||||
m_socks5ConfigModel(socks5ConfigModel),
|
||||
m_mtProxyConfigModel(mtConfigModel)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
@@ -202,7 +207,7 @@ void InstallUiController::scanServerForInstalledContainers(int serverIndex)
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateContainer(int serverIndex, int containerIndex, int protocolIndex)
|
||||
void InstallUiController::updateContainer(int serverIndex, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
@@ -241,6 +246,10 @@ void InstallUiController::updateContainer(int serverIndex, int containerIndex, i
|
||||
containerConfig.protocolConfig = m_socks5ConfigModel->getProtocolConfig();
|
||||
break;
|
||||
}
|
||||
case Proto::MtProxy: {
|
||||
containerConfig.protocolConfig = m_mtProxyConfigModel->getProtocolConfig();
|
||||
break;
|
||||
}
|
||||
#ifdef Q_OS_WINDOWS
|
||||
case Proto::Ikev2: {
|
||||
containerConfig.protocolConfig = m_ikev2ConfigModel->getProtocolConfig();
|
||||
@@ -252,6 +261,45 @@ void InstallUiController::updateContainer(int serverIndex, int containerIndex, i
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverIndex, container);
|
||||
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
emit serverIsBusy(true);
|
||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||
[this, watcher, serverIndex, container, closePage]() {
|
||||
const ErrorCode errorCode = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit serverIsBusy(false);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
const ContainerConfig updatedConfig =
|
||||
m_serversController->getContainerConfig(serverIndex, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
|
||||
const auto defaultContainer =
|
||||
m_serversController->getServerConfig(serverIndex).defaultContainer();
|
||||
if ((serverIndex == m_serversController->getDefaultServerIndex())
|
||||
&& (container == defaultContainer)) {
|
||||
emit currentContainerUpdated();
|
||||
} else {
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
}
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
|
||||
ContainerConfig newConfigCopy = containerConfig;
|
||||
ContainerConfig oldConfigCopy = oldContainerConfig;
|
||||
InstallController *installController = m_installController;
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverIndex, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateContainer(serverIndex, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverIndex, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -262,7 +310,7 @@ void InstallUiController::updateContainer(int serverIndex, int containerIndex, i
|
||||
if ((serverIndex == m_serversController->getDefaultServerIndex()) && (container == defaultContainer)) {
|
||||
emit currentContainerUpdated();
|
||||
} else {
|
||||
emit updateContainerFinished(tr("Settings updated successfully"));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -271,6 +319,148 @@ void InstallUiController::updateContainer(int serverIndex, int containerIndex, i
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::setContainerEnabled(int serverIndex, int containerIndex, bool enabled) {
|
||||
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
const ServerCredentials credentials = m_serversController->getServerCredentials(serverIndex);
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
|
||||
emit serverIsBusy(true);
|
||||
SshSession sshSession(this);
|
||||
const QString script = enabled
|
||||
? QString("sudo docker start %1").arg(containerName)
|
||||
: QString("sudo docker stop %1").arg(containerName);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script);
|
||||
emit serverIsBusy(false);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig currentConfig = m_serversController->getContainerConfig(serverIndex, container);
|
||||
if (auto *mtConfig = currentConfig.getMtProxyProtocolConfig()) {
|
||||
mtConfig->isEnabled = enabled;
|
||||
m_serversController->updateContainerConfig(serverIndex, container, currentConfig);
|
||||
m_protocolModel->updateModel(currentConfig);
|
||||
}
|
||||
emit setContainerEnabledFinished(enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::refreshContainerStatus(int serverIndex, int containerIndex) {
|
||||
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
const ServerCredentials credentials = m_serversController->getServerCredentials(serverIndex);
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
SshSession sshSession(this);
|
||||
const QString script = QString(
|
||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||
.arg(containerName);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit containerStatusRefreshed(3);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString status = stdOut.trimmed();
|
||||
if (status == "running") {
|
||||
emit containerStatusRefreshed(1);
|
||||
} else if (status == "not_found" || status.isEmpty()) {
|
||||
emit containerStatusRefreshed(0);
|
||||
} else if (status == "exited" || status == "created" || status == "paused") {
|
||||
emit containerStatusRefreshed(2);
|
||||
} else {
|
||||
emit containerStatusRefreshed(3);
|
||||
}
|
||||
}
|
||||
|
||||
void InstallUiController::refreshContainerDiagnostics(int serverIndex, int containerIndex, int port) {
|
||||
const ServerCredentials credentials = m_serversController->getServerCredentials(serverIndex);
|
||||
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
|
||||
const QString script =
|
||||
QString(
|
||||
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
|
||||
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
|
||||
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
|
||||
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
|
||||
"echo \"PORT_OK=${PORT_OK}\"; "
|
||||
"echo \"TG_OK=${TG_OK}\"; "
|
||||
"echo \"CLIENTS=${CLIENTS:-0}\"; "
|
||||
"echo \"CONF_TIME=${CONF_TIME}\"; "
|
||||
"echo \"STATS=http://localhost:2398/stats\";")
|
||||
.arg(containerName)
|
||||
.arg(port);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
SshSession sshSession(this);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
|
||||
return;
|
||||
}
|
||||
|
||||
bool portReachable = false;
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
|
||||
for (const QString &line: stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith("PORT_OK=")) {
|
||||
portReachable = line.mid(8).trimmed() == "yes";
|
||||
} else if (line.startsWith("TG_OK=")) {
|
||||
upstreamReachable = line.mid(6).trimmed() == "yes";
|
||||
} else if (line.startsWith("CLIENTS=")) {
|
||||
clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith("CONF_TIME=")) {
|
||||
lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith("STATS=")) {
|
||||
statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
emit containerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh,
|
||||
statsEndpoint);
|
||||
}
|
||||
|
||||
void InstallUiController::fetchContainerSecret(int serverIndex, int containerIndex) {
|
||||
const ServerCredentials credentials = m_serversController->getServerCredentials(serverIndex);
|
||||
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
SshSession sshSession(this);
|
||||
const QString path = QStringLiteral("/data/secret");
|
||||
const QString cmd =
|
||||
QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit containerSecretFetched(QString());
|
||||
return;
|
||||
}
|
||||
|
||||
const QString secret = stdOut.trimmed();
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
emit containerSecretFetched(hex32.match(secret).hasMatch() ? secret : QString());
|
||||
}
|
||||
|
||||
void InstallUiController::rebootServer(int serverIndex)
|
||||
{
|
||||
QString serverName = m_serversController->getServerConfig(serverIndex).displayName();
|
||||
@@ -484,10 +674,10 @@ void InstallUiController::updateProtocolConfigModel(int serverIndex, int contain
|
||||
case Proto::TorWebSite: updateIfPresent(m_torConfigModel, containerConfig.getTorProtocolConfig()); break;
|
||||
case Proto::Sftp: updateIfPresent(m_sftpConfigModel, containerConfig.getSftpProtocolConfig()); break;
|
||||
case Proto::Socks5Proxy: updateIfPresent(m_socks5ConfigModel, containerConfig.getSocks5ProxyProtocolConfig()); break;
|
||||
case Proto::MtProxy: updateIfPresent(m_mtProxyConfigModel, containerConfig.getMtProxyProtocolConfig()); break;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
case Proto::Ikev2: updateIfPresent(m_ikev2ConfigModel, containerConfig.getIkev2ProtocolConfig()); break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "ui/models/services/torConfigModel.h"
|
||||
#include "core/models/protocols/sftpProtocolConfig.h"
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "ui/models/services/mtProxyConfigModel.h"
|
||||
|
||||
class InstallUiController : public QObject
|
||||
{
|
||||
@@ -48,6 +49,7 @@ public:
|
||||
#endif
|
||||
SftpConfigModel* sftpConfigModel,
|
||||
Socks5ProxyConfigModel* socks5ConfigModel,
|
||||
MtProxyConfigModel* mtConfigModel,
|
||||
QObject *parent = nullptr);
|
||||
~InstallUiController();
|
||||
|
||||
@@ -58,12 +60,16 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(int serverIndex);
|
||||
|
||||
void updateContainer(int serverIndex, int containerIndex, int protocolIndex);
|
||||
void updateContainer(int serverIndex, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(int serverIndex);
|
||||
void rebootServer(int serverIndex);
|
||||
void removeAllContainers(int serverIndex);
|
||||
void removeContainer(int serverIndex, int containerIndex);
|
||||
void setContainerEnabled(int serverIndex, int containerIndex, bool enabled);
|
||||
void refreshContainerStatus(int serverIndex, int containerIndex);
|
||||
void refreshContainerDiagnostics(int serverIndex, int containerIndex, int port);
|
||||
void fetchContainerSecret(int serverIndex, int containerIndex);
|
||||
|
||||
void clearCachedProfile(int serverIndex, int containerIndex);
|
||||
|
||||
@@ -94,7 +100,7 @@ signals:
|
||||
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
|
||||
void installServerFinished(const QString &finishMessage);
|
||||
|
||||
void updateContainerFinished(const QString &message);
|
||||
void updateContainerFinished(const QString &message, bool closePage);
|
||||
|
||||
void scanServerFinished(bool isInstalledContainerFound);
|
||||
|
||||
@@ -102,6 +108,11 @@ signals:
|
||||
void removeServerFinished(const QString &finishedMessage);
|
||||
void removeAllContainersFinished(const QString &finishedMessage);
|
||||
void removeContainerFinished(const QString &finishedMessage);
|
||||
void setContainerEnabledFinished(bool enabled);
|
||||
void containerStatusRefreshed(int status);
|
||||
void containerDiagnosticsRefreshed(bool portReachable, bool upstreamReachable, int clientsConnected,
|
||||
const QString &lastConfigRefresh, const QString &statsEndpoint);
|
||||
void containerSecretFetched(const QString &secret);
|
||||
|
||||
void installationErrorOccurred(ErrorCode errorCode);
|
||||
void wrongInstallationUser(const QString &message);
|
||||
@@ -140,6 +151,7 @@ private:
|
||||
#endif
|
||||
SftpConfigModel* m_sftpConfigModel;
|
||||
Socks5ProxyConfigModel* m_socks5ConfigModel;
|
||||
MtProxyConfigModel* m_mtProxyConfigModel;
|
||||
|
||||
ServerCredentials m_processedServerCredentials;
|
||||
|
||||
|
||||
@@ -482,6 +482,8 @@ QStringList ServersUiController::getAllInstalledServicesName(int serverIndex) co
|
||||
servicesName.append("TOR");
|
||||
} else if (container == DockerContainer::Socks5Proxy) {
|
||||
servicesName.append("SOCKS5");
|
||||
} else if (container == DockerContainer::MtProxy) {
|
||||
servicesName.append("MTProxy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsSftpRole: return container == DockerContainer::Sftp;
|
||||
case IsTorWebsiteRole: return container == DockerContainer::TorWebSite;
|
||||
case IsSocks5ProxyRole: return container == DockerContainer::Socks5Proxy;
|
||||
case IsMtProxyRole: return container == DockerContainer::MtProxy;
|
||||
case InstallPageOrderRole: return ContainerUtils::installPageOrder(container);
|
||||
}
|
||||
|
||||
@@ -184,5 +185,6 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsSftpRole] = "isSftp";
|
||||
roles[IsTorWebsiteRole] = "isTorWebsite";
|
||||
roles[IsSocks5ProxyRole] = "isSocks5Proxy";
|
||||
roles[IsMtProxyRole] = "isMtProxy";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ public:
|
||||
IsDnsRole,
|
||||
IsSftpRole,
|
||||
IsTorWebsiteRole,
|
||||
IsSocks5ProxyRole
|
||||
IsSocks5ProxyRole,
|
||||
IsMtProxyRole,
|
||||
};
|
||||
|
||||
Q_INVOKABLE void openContainerSettings(int containerIndex);
|
||||
|
||||
@@ -42,6 +42,7 @@ QHash<int, QByteArray> ProtocolsModel::roleNames() const
|
||||
roles[IsSftpRole] = "isSftp";
|
||||
roles[IsIpsecRole] = "isIpsec";
|
||||
roles[IsSocks5ProxyRole] = "isSocks5Proxy";
|
||||
roles[IsMtProxyRole] = "isMtProxy";
|
||||
|
||||
return roles;
|
||||
}
|
||||
@@ -71,6 +72,7 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
|
||||
case IsSftpRole: return proto == Proto::Sftp;
|
||||
case IsIpsecRole: return proto == Proto::Ikev2;
|
||||
case IsSocks5ProxyRole: return proto == Proto::Socks5Proxy;
|
||||
case IsMtProxyRole: return proto == Proto::MtProxy;
|
||||
case RawConfigRole:
|
||||
return getRawConfig();
|
||||
case IsClientProtocolExistsRole:
|
||||
@@ -124,6 +126,7 @@ PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const
|
||||
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
|
||||
case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings;
|
||||
case Proto::Socks5Proxy: return PageLoader::PageEnum::PageServiceSocksProxySettings;
|
||||
case Proto::MtProxy: return PageLoader::PageEnum::PageServiceMtProxySettings;
|
||||
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ public:
|
||||
IsXrayRole,
|
||||
IsSftpRole,
|
||||
IsIpsecRole,
|
||||
IsSocks5ProxyRole
|
||||
IsSocks5ProxyRole,
|
||||
IsMtProxyRole,
|
||||
};
|
||||
|
||||
explicit ProtocolsModel(QObject *parent = nullptr);
|
||||
|
||||
714
client/ui/models/services/mtProxyConfigModel.cpp
Normal file
714
client/ui/models/services/mtProxyConfigModel.cpp
Normal file
@@ -0,0 +1,714 @@
|
||||
#include "mtProxyConfigModel.h"
|
||||
|
||||
#include "ui/models/utils/mtproxy_public_host_input.h"
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QtGlobal>
|
||||
#include <qqml.h>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
MtProxyConfigModel::MtProxyConfigModel(QObject *parent) : QAbstractListModel(parent) {
|
||||
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
|
||||
}
|
||||
|
||||
int MtProxyConfigModel::rowCount(const QModelIndex &parent) const {
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
if (!index.isValid() || index.row() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Roles::PortRole: {
|
||||
m_protocolConfig.port = value.toString();
|
||||
break;
|
||||
}
|
||||
case Roles::SecretRole: {
|
||||
m_protocolConfig.secret = value.toString();
|
||||
break;
|
||||
}
|
||||
case Roles::TagRole: {
|
||||
const QString tag = sanitizeMtProxyTagFieldText(value.toString());
|
||||
if (!isValidMtProxyTag(tag)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.tag = tag;
|
||||
break;
|
||||
}
|
||||
case Roles::IsEnabledRole: {
|
||||
m_protocolConfig.isEnabled = value.toBool();
|
||||
break;
|
||||
}
|
||||
case Roles::PublicHostRole: {
|
||||
const QString h = value.toString().trimmed();
|
||||
if (!isValidPublicHost(h)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.publicHost = h;
|
||||
break;
|
||||
}
|
||||
case Roles::TransportModeRole: {
|
||||
m_protocolConfig.transportMode = value.toString();
|
||||
break;
|
||||
}
|
||||
case Roles::TlsDomainRole: {
|
||||
const QString d = value.toString().trimmed();
|
||||
if (!isValidFakeTlsDomain(d)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.tlsDomain = d;
|
||||
break;
|
||||
}
|
||||
case Roles::AdditionalSecretsRole: {
|
||||
m_protocolConfig.additionalSecrets = value.toStringList();
|
||||
break;
|
||||
}
|
||||
case Roles::WorkersModeRole: {
|
||||
m_protocolConfig.workersMode = value.toString();
|
||||
break;
|
||||
}
|
||||
case Roles::WorkersRole: {
|
||||
m_protocolConfig.workers = value.toString();
|
||||
break;
|
||||
}
|
||||
case Roles::NatEnabledRole: {
|
||||
m_protocolConfig.natEnabled = value.toBool();
|
||||
break;
|
||||
}
|
||||
case Roles::NatInternalIpRole: {
|
||||
const QString ip = value.toString().trimmed();
|
||||
if (!isValidOptionalIpv4(ip)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.natInternalIp = ip;
|
||||
break;
|
||||
}
|
||||
case Roles::NatExternalIpRole: {
|
||||
const QString ip = value.toString().trimmed();
|
||||
if (!isValidOptionalIpv4(ip)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.natExternalIp = ip;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index, index, QList{role});
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant MtProxyConfigModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid() || index.row() != 0) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Roles::PortRole: {
|
||||
return m_protocolConfig.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : m_protocolConfig.port;
|
||||
}
|
||||
case Roles::SecretRole: {
|
||||
return m_protocolConfig.secret;
|
||||
}
|
||||
case Roles::TagRole: {
|
||||
return m_protocolConfig.tag;
|
||||
}
|
||||
case Roles::TgLinkRole: {
|
||||
return m_protocolConfig.tgLink;
|
||||
}
|
||||
case Roles::TmeLinkRole: {
|
||||
return m_protocolConfig.tmeLink;
|
||||
}
|
||||
case Roles::IsEnabledRole: {
|
||||
return m_protocolConfig.isEnabled;
|
||||
}
|
||||
case Roles::PublicHostRole: {
|
||||
return m_protocolConfig.publicHost.isEmpty()
|
||||
? m_fullConfig.value(configKey::hostName).toString()
|
||||
: m_protocolConfig.publicHost;
|
||||
}
|
||||
case Roles::TransportModeRole: {
|
||||
return m_protocolConfig.transportMode.isEmpty()
|
||||
? QString(protocols::mtProxy::transportModeStandard)
|
||||
: m_protocolConfig.transportMode;
|
||||
}
|
||||
case Roles::TlsDomainRole: {
|
||||
return m_protocolConfig.tlsDomain;
|
||||
}
|
||||
case Roles::AdditionalSecretsRole: {
|
||||
return m_protocolConfig.additionalSecrets;
|
||||
}
|
||||
case Roles::WorkersModeRole: {
|
||||
return m_protocolConfig.workersMode.isEmpty()
|
||||
? QString(protocols::mtProxy::workersModeAuto)
|
||||
: m_protocolConfig.workersMode;
|
||||
}
|
||||
case Roles::WorkersRole: {
|
||||
return m_protocolConfig.workers.isEmpty() ? QString(protocols::mtProxy::defaultWorkers)
|
||||
: m_protocolConfig.workers;
|
||||
}
|
||||
case Roles::NatEnabledRole: {
|
||||
return m_protocolConfig.natEnabled;
|
||||
}
|
||||
case Roles::NatInternalIpRole: {
|
||||
return m_protocolConfig.natInternalIp;
|
||||
}
|
||||
case Roles::NatExternalIpRole: {
|
||||
return m_protocolConfig.natExternalIp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::updateModel(amnezia::DockerContainer container,
|
||||
const amnezia::MtProxyProtocolConfig &protocolConfig) {
|
||||
beginResetModel();
|
||||
m_container = container;
|
||||
m_protocolConfig = protocolConfig;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::updateModel(const QJsonObject &config) {
|
||||
beginResetModel();
|
||||
|
||||
m_fullConfig = config;
|
||||
m_protocolConfig = MtProxyProtocolConfig::fromJson(config.value(configKey::mtproxy).toObject());
|
||||
if (m_protocolConfig.port.isEmpty()) m_protocolConfig.port = protocols::mtProxy::defaultPort;
|
||||
if (m_protocolConfig.transportMode.isEmpty()) m_protocolConfig.transportMode = protocols::mtProxy::transportModeStandard;
|
||||
if (m_protocolConfig.workersMode.isEmpty()) m_protocolConfig.workersMode = protocols::mtProxy::workersModeAuto;
|
||||
if (m_protocolConfig.workers.isEmpty()) m_protocolConfig.workers = protocols::mtProxy::defaultWorkers;
|
||||
{
|
||||
QString tagIn = sanitizeMtProxyTagFieldText(m_protocolConfig.tag);
|
||||
if (!isValidMtProxyTag(tagIn)) {
|
||||
tagIn.clear();
|
||||
}
|
||||
m_protocolConfig.tag = tagIn;
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QJsonObject MtProxyConfigModel::getConfig() {
|
||||
m_fullConfig.insert(configKey::mtproxy, m_protocolConfig.toJson());
|
||||
return m_fullConfig;
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::generateSecret() {
|
||||
// Generate 16 random bytes = 32 hex chars
|
||||
QString secret;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
quint32 byte = QRandomGenerator::global()->bounded(256);
|
||||
secret += QString("%1").arg(byte, 2, 16, QChar('0'));
|
||||
}
|
||||
|
||||
m_protocolConfig.secret = secret;
|
||||
emit dataChanged(index(0), index(0), QList<int>{SecretRole});
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setSecret(const QString &secret) {
|
||||
if (secret.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), secret, SecretRole);
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::validateAndSetSecret(const QString &rawSecret) {
|
||||
if (!QRegularExpression("^[0-9a-fA-F]{32}$").match(rawSecret).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
setData(index(0), rawSecret, SecretRole);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setPort(const QString &port) {
|
||||
setData(index(0), port, PortRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setTag(const QString &tag) {
|
||||
setData(index(0), tag, TagRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setPublicHost(const QString &host) {
|
||||
const QString t = host.trimmed();
|
||||
if (!isValidPublicHost(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, PublicHostRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setTransportMode(const QString &mode) {
|
||||
setData(index(0), mode, TransportModeRole);
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::getTransportMode() const {
|
||||
return m_protocolConfig.transportMode.isEmpty()
|
||||
? QString(protocols::mtProxy::transportModeStandard)
|
||||
: m_protocolConfig.transportMode;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::getTlsDomain() const {
|
||||
return m_protocolConfig.tlsDomain.isEmpty()
|
||||
? QString(protocols::mtProxy::defaultTlsDomain)
|
||||
: m_protocolConfig.tlsDomain;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::getPublicHost() const {
|
||||
return m_protocolConfig.publicHost;
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setTlsDomain(const QString &domain) {
|
||||
const QString t = domain.trimmed();
|
||||
if (!isValidFakeTlsDomain(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, TlsDomainRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setWorkersMode(const QString &mode) {
|
||||
setData(index(0), mode, WorkersModeRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setWorkers(const QString &workers) {
|
||||
setData(index(0), workers, WorkersRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setNatEnabled(bool enabled) {
|
||||
setData(index(0), enabled, NatEnabledRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setNatInternalIp(const QString &ip) {
|
||||
const QString t = ip.trimmed();
|
||||
if (!isValidOptionalIpv4(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, NatInternalIpRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setNatExternalIp(const QString &ip) {
|
||||
const QString t = ip.trimmed();
|
||||
if (!isValidOptionalIpv4(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, NatExternalIpRole);
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::addAdditionalSecret() {
|
||||
QString newSecret;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
quint32 byte = QRandomGenerator::global()->bounded(256);
|
||||
newSecret += QString("%1").arg(byte, 2, 16, QChar('0'));
|
||||
}
|
||||
|
||||
m_protocolConfig.additionalSecrets.append(newSecret);
|
||||
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::removeAdditionalSecret(int idx) {
|
||||
if (idx < 0 || idx >= m_protocolConfig.additionalSecrets.size()) {
|
||||
return;
|
||||
}
|
||||
m_protocolConfig.additionalSecrets.removeAt(idx);
|
||||
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
|
||||
}
|
||||
|
||||
QVariantList MtProxyConfigModel::additionalSecretsList() const {
|
||||
QVariantList out;
|
||||
out.reserve(m_protocolConfig.additionalSecrets.size());
|
||||
for (const auto &s: m_protocolConfig.additionalSecrets) {
|
||||
if (!s.isEmpty()) {
|
||||
out.append(s);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void MtProxyConfigModel::setEnabled(bool enabled) {
|
||||
m_protocolConfig.isEnabled = enabled;
|
||||
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::generateQrCode(const QString &text) {
|
||||
if (text.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
auto qr = qrCodeUtils::generateQrCode(text.toUtf8());
|
||||
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::defaultTlsDomain() const {
|
||||
return protocols::mtProxy::defaultTlsDomain;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::defaultPort() const {
|
||||
return protocols::mtProxy::defaultPort;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::defaultWorkers() const {
|
||||
return protocols::mtProxy::defaultWorkers;
|
||||
}
|
||||
|
||||
int MtProxyConfigModel::maxWorkers() const {
|
||||
return protocols::mtProxy::maxWorkers;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::transportModeStandard() const {
|
||||
return protocols::mtProxy::transportModeStandard;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::transportModeFakeTLS() const {
|
||||
return protocols::mtProxy::transportModeFakeTLS;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::workersModeAuto() const {
|
||||
return protocols::mtProxy::workersModeAuto;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::workersModeManual() const {
|
||||
return protocols::mtProxy::workersModeManual;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isValidPublicHost(const QString &host) const {
|
||||
const QString t = host.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv4Protocol) {
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return NetworkUtilities::domainRegExp().exactMatch(t);
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isPublicHostInputAllowed(const QString &text) const {
|
||||
return mtproxyPublicHostInputAllowed(text);
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isPublicHostTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (isValidPublicHost(t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
|
||||
if (onlyDigitDot.match(t).hasMatch()) {
|
||||
if (t.endsWith(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
const QStringList parts = t.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||
if (parts.size() < 4) {
|
||||
return true;
|
||||
}
|
||||
for (const QString &part: parts) {
|
||||
if (part.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (t.contains(QLatin1Char(':'))) {
|
||||
if (t.contains(QLatin1String(":::"))) {
|
||||
return false;
|
||||
}
|
||||
if (t.endsWith(QLatin1Char(':'))) {
|
||||
return true;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
return false;
|
||||
}
|
||||
if (!t.contains(QLatin1String("::")) && t.count(QLatin1Char(':')) < 7 && !t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isValidMtProxyTag(const QString &tag) const {
|
||||
if (tag.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression re(
|
||||
QStringLiteral("^([0-9a-fA-F]{%1})$").arg(protocols::mtProxy::botTagHexLength));
|
||||
return re.match(tag).hasMatch();
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isMtProxyTagTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression hexOnly(QStringLiteral(R"(^[0-9a-fA-F]*$)"));
|
||||
if (!hexOnly.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return t.size() < protocols::mtProxy::botTagHexLength;
|
||||
}
|
||||
|
||||
int MtProxyConfigModel::mtProxyBotTagHexLength() const {
|
||||
return protocols::mtProxy::botTagHexLength;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isValidFakeTlsDomain(const QString &domain) const {
|
||||
const QString t = domain.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress addr;
|
||||
if (addr.setAddress(t)) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
QRegExp re(NetworkUtilities::domainRegExp());
|
||||
re.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
if (!re.exactMatch(t)) {
|
||||
return false;
|
||||
}
|
||||
// ee + 32 hex (base secret) + hex(UTF-8 domain); keep headroom under typical client limits.
|
||||
if (t.toUtf8().size() > 111) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::clipboardText() const {
|
||||
if (QClipboard *c = QGuiApplication::clipboard()) {
|
||||
return c->text();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
||||
const QString t = normalizeFakeTlsDomainInput(input);
|
||||
QString out;
|
||||
out.reserve(t.size());
|
||||
for (const QChar &c: t) {
|
||||
const ushort u = c.unicode();
|
||||
const bool letter = (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z');
|
||||
const bool digit = (u >= '0' && u <= '9');
|
||||
if (letter || digit || u == '.' || u == '-') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
if (out.size() > 253) {
|
||||
out.truncate(253);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isFakeTlsDomainInputAllowed(const QString &text) const {
|
||||
if (text.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression re(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||
return re.match(text).hasMatch();
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizePublicHostFieldText(const QString &input) const {
|
||||
QString out;
|
||||
const int cap = qMin(input.size(), 253);
|
||||
out.reserve(cap);
|
||||
for (const QChar &c: input) {
|
||||
if (out.size() >= 253) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '.' || u == ':' ||
|
||||
u == '-') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizePortFieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 5));
|
||||
for (const QChar &c: input) {
|
||||
const ushort u = c.unicode();
|
||||
if (u >= '0' && u <= '9' && out.size() < 5) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) const {
|
||||
QString trimmed = input.trimmed();
|
||||
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||
trimmed = trimmed.mid(2).trimmed();
|
||||
}
|
||||
// Prefer a contiguous 32-hex run (paste from bot message with extra text).
|
||||
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
||||
const QRegularExpressionMatch m = runHex.match(trimmed);
|
||||
if (m.hasMatch()) {
|
||||
return m.captured(1);
|
||||
}
|
||||
const int cap = protocols::mtProxy::botTagHexLength;
|
||||
QString out;
|
||||
out.reserve(qMin(trimmed.size(), cap));
|
||||
for (const QChar &c: trimmed) {
|
||||
if (out.size() >= cap) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'f') || (u >= 'A' && u <= 'F')) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeWorkersFieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 3));
|
||||
for (const QChar &c: input) {
|
||||
const ushort u = c.unicode();
|
||||
if (u >= '0' && u <= '9' && out.size() < 3) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 15));
|
||||
for (const QChar &c: input) {
|
||||
if (out.size() >= 15) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= '0' && u <= '9') || u == '.') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::normalizeFakeTlsDomainInput(const QString &input) const {
|
||||
QString t = input.trimmed();
|
||||
if (t.startsWith(QLatin1String("https://"), Qt::CaseInsensitive)) {
|
||||
t = t.mid(8);
|
||||
} else if (t.startsWith(QLatin1String("http://"), Qt::CaseInsensitive)) {
|
||||
t = t.mid(7);
|
||||
}
|
||||
if (const int slash = t.indexOf(QLatin1Char('/')); slash >= 0) {
|
||||
t = t.left(slash);
|
||||
}
|
||||
if (const int at = t.indexOf(QLatin1Char('@')); at >= 0) {
|
||||
t = t.mid(at + 1);
|
||||
}
|
||||
if (const int colon = t.indexOf(QLatin1Char(':')); colon >= 0) {
|
||||
t = t.left(colon);
|
||||
}
|
||||
if (t.startsWith(QLatin1String("www."), Qt::CaseInsensitive)) {
|
||||
const QString rest = t.mid(4);
|
||||
if (rest.contains(QLatin1Char('.'))) {
|
||||
t = rest;
|
||||
}
|
||||
}
|
||||
return t.trimmed();
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isFakeTlsDomainTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (isValidFakeTlsDomain(t)) {
|
||||
return false;
|
||||
}
|
||||
if (t.contains(QLatin1Char('/')) || t.contains(QLatin1Char(':')) || t.contains(QLatin1Char('@'))
|
||||
|| t.contains(QLatin1Char(' '))) {
|
||||
return false;
|
||||
}
|
||||
if (t.contains(QLatin1String(".."))) {
|
||||
return false;
|
||||
}
|
||||
if (!t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
if (t.endsWith(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression legalPartial(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||
if (!legalPartial.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MtProxyConfigModel::isValidOptionalIpv4(const QString &ip) const {
|
||||
const QString t = ip.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MtProxyConfigModel::roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
roles[PortRole] = "port";
|
||||
roles[SecretRole] = "secret";
|
||||
roles[TagRole] = "tag";
|
||||
roles[TgLinkRole] = "tgLink";
|
||||
roles[TmeLinkRole] = "tmeLink";
|
||||
roles[IsEnabledRole] = "isEnabled";
|
||||
roles[PublicHostRole] = "publicHost";
|
||||
roles[TransportModeRole] = "transportMode";
|
||||
roles[TlsDomainRole] = "tlsDomain";
|
||||
roles[AdditionalSecretsRole] = "additionalSecrets";
|
||||
roles[WorkersModeRole] = "workersMode";
|
||||
roles[WorkersRole] = "workers";
|
||||
roles[NatEnabledRole] = "natEnabled";
|
||||
roles[NatInternalIpRole] = "natInternalIp";
|
||||
roles[NatExternalIpRole] = "natExternalIp";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
amnezia::MtProxyProtocolConfig MtProxyConfigModel::getProtocolConfig() {
|
||||
return m_protocolConfig;
|
||||
}
|
||||
156
client/ui/models/services/mtProxyConfigModel.h
Normal file
156
client/ui/models/services/mtProxyConfigModel.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef MTPROXYCONFIGMODEL_H
|
||||
#define MTPROXYCONFIGMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
class MtProxyConfigModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
PortRole = Qt::UserRole + 1,
|
||||
SecretRole,
|
||||
TagRole,
|
||||
TgLinkRole,
|
||||
TmeLinkRole,
|
||||
IsEnabledRole,
|
||||
PublicHostRole,
|
||||
TransportModeRole,
|
||||
TlsDomainRole,
|
||||
AdditionalSecretsRole,
|
||||
WorkersModeRole,
|
||||
WorkersRole,
|
||||
NatEnabledRole,
|
||||
NatInternalIpRole,
|
||||
NatExternalIpRole
|
||||
};
|
||||
|
||||
explicit MtProxyConfigModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void updateModel(amnezia::DockerContainer container, const amnezia::MtProxyProtocolConfig &protocolConfig);
|
||||
|
||||
void updateModel(const QJsonObject &config);
|
||||
|
||||
QJsonObject getConfig();
|
||||
|
||||
amnezia::MtProxyProtocolConfig getProtocolConfig();
|
||||
|
||||
Q_INVOKABLE void generateSecret();
|
||||
|
||||
Q_INVOKABLE void setSecret(const QString &secret);
|
||||
|
||||
Q_INVOKABLE bool validateAndSetSecret(const QString &rawSecret);
|
||||
|
||||
Q_INVOKABLE void setPort(const QString &port);
|
||||
|
||||
Q_INVOKABLE void setTag(const QString &tag);
|
||||
|
||||
Q_INVOKABLE void setPublicHost(const QString &host);
|
||||
|
||||
Q_INVOKABLE void setTransportMode(const QString &mode);
|
||||
|
||||
Q_INVOKABLE QString getTransportMode() const;
|
||||
|
||||
Q_INVOKABLE QString getTlsDomain() const;
|
||||
|
||||
Q_INVOKABLE QString getPublicHost() const;
|
||||
|
||||
Q_INVOKABLE void setTlsDomain(const QString &domain);
|
||||
|
||||
Q_INVOKABLE void setWorkersMode(const QString &mode);
|
||||
|
||||
Q_INVOKABLE void setWorkers(const QString &workers);
|
||||
|
||||
Q_INVOKABLE void setNatEnabled(bool enabled);
|
||||
|
||||
Q_INVOKABLE void setNatInternalIp(const QString &ip);
|
||||
|
||||
Q_INVOKABLE void setNatExternalIp(const QString &ip);
|
||||
|
||||
Q_INVOKABLE void addAdditionalSecret();
|
||||
|
||||
Q_INVOKABLE void removeAdditionalSecret(int idx);
|
||||
/// Current `mtproxy_additional_secrets` list from in-memory config (for QML snapshot vs. unsaved adds).
|
||||
Q_INVOKABLE QVariantList additionalSecretsList() const;
|
||||
|
||||
Q_INVOKABLE QString generateQrCode(const QString &text);
|
||||
|
||||
Q_INVOKABLE void setEnabled(bool enabled);
|
||||
|
||||
Q_INVOKABLE QString defaultTlsDomain() const;
|
||||
|
||||
Q_INVOKABLE QString defaultPort() const;
|
||||
|
||||
Q_INVOKABLE QString defaultWorkers() const;
|
||||
|
||||
Q_INVOKABLE int maxWorkers() const;
|
||||
|
||||
Q_INVOKABLE QString transportModeStandard() const;
|
||||
|
||||
Q_INVOKABLE QString transportModeFakeTLS() const;
|
||||
|
||||
Q_INVOKABLE QString workersModeAuto() const;
|
||||
|
||||
Q_INVOKABLE QString workersModeManual() const;
|
||||
|
||||
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
|
||||
|
||||
Q_INVOKABLE bool isPublicHostInputAllowed(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isPublicHostTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isValidMtProxyTag(const QString &tag) const;
|
||||
|
||||
Q_INVOKABLE bool isMtProxyTagTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE int mtProxyBotTagHexLength() const;
|
||||
|
||||
Q_INVOKABLE bool isValidFakeTlsDomain(const QString &domain) const;
|
||||
|
||||
Q_INVOKABLE QString normalizeFakeTlsDomainInput(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeFakeTlsDomainFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE QString clipboardText() const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeWorkersFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isValidOptionalIpv4(const QString &ip) const;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
amnezia::DockerContainer m_container;
|
||||
QJsonObject m_fullConfig;
|
||||
amnezia::MtProxyProtocolConfig m_protocolConfig;
|
||||
};
|
||||
|
||||
#endif // MTPROXYCONFIGMODEL_H
|
||||
127
client/ui/models/utils/mtproxy_public_host_input.cpp
Normal file
127
client/ui/models/utils/mtproxy_public_host_input.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "mtproxy_public_host_input.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace {
|
||||
|
||||
bool ipv4OctetTokenOk(const QString &s) {
|
||||
static const QRegularExpression re(QStringLiteral(R"(^\d{1,3}$)"));
|
||||
if (!re.match(s).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
bool ok = false;
|
||||
const int n = s.toInt(&ok);
|
||||
return ok && n >= 0 && n <= 255;
|
||||
}
|
||||
|
||||
// Reject labels like "312edweqwe" (digits >255 then letters).
|
||||
bool labelHasInvalidOctetLikePrefixBeforeLetters(const QString &label) {
|
||||
static const QRegularExpression re(QStringLiteral(R"(^(\d+)([a-zA-Z].*)$)"));
|
||||
const QRegularExpressionMatch m = re.match(label);
|
||||
if (!m.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
const QString digits = m.captured(1);
|
||||
if (digits.length() > 3) {
|
||||
return true;
|
||||
}
|
||||
bool ok = false;
|
||||
const int n = digits.toInt(&ok);
|
||||
if (!ok) {
|
||||
return true;
|
||||
}
|
||||
if (n > 255) {
|
||||
return true;
|
||||
}
|
||||
// Do not restrict n≤255 + letters here (e.g. "123mlkjh.example.com"); four-segment IPv4+junk is handled below.
|
||||
return false;
|
||||
}
|
||||
|
||||
// "123.123wqqweqweqweqwe" — first label is a real octet, second looks like an octet glued to letters (not "123.45").
|
||||
bool looksLikeTwoSegmentOctetThenDigitLetterGlue(const QString &text) {
|
||||
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||
if (parts.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
if (!ipv4OctetTokenOk(parts.at(0))) {
|
||||
return false;
|
||||
}
|
||||
const QString &p1 = parts.at(1);
|
||||
static const QRegularExpression digitThenLetter(QStringLiteral(R"(^\d+[a-zA-Z])"));
|
||||
if (!digitThenLetter.match(p1).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return !ipv4OctetTokenOk(p1);
|
||||
}
|
||||
|
||||
// "a.b.c.djunk" where first three parts are pure octets and last part has digits then letters (e.g. "123wdqweqweqwe").
|
||||
bool looksLikeFourOctetIpv4WithGarbageInLastSegment(const QString &text) {
|
||||
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||
if (parts.size() != 4) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!ipv4OctetTokenOk(parts.at(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static const QRegularExpression digitThenLetter(QStringLiteral(R"(^\d+[a-zA-Z])"));
|
||||
return digitThenLetter.match(parts.at(3)).hasMatch();
|
||||
}
|
||||
|
||||
bool hostLabelsRejectBrokenDigitLetterMix(const QString &text) {
|
||||
if (looksLikeTwoSegmentOctetThenDigitLetterGlue(text)) {
|
||||
return false;
|
||||
}
|
||||
if (looksLikeFourOctetIpv4WithGarbageInLastSegment(text)) {
|
||||
return false;
|
||||
}
|
||||
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||
for (const QString &part: parts) {
|
||||
if (labelHasInvalidOctetLikePrefixBeforeLetters(part)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool mtproxyPublicHostInputAllowed(const QString &text) {
|
||||
if (text.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression allowed(QStringLiteral(R"(^[a-zA-Z0-9.:\-]*$)"));
|
||||
if (!allowed.match(text).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyDigits.match(text).hasMatch() && text.length() > 3) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
|
||||
if (!text.isEmpty() && onlyDigitDot.match(text).hasMatch()) {
|
||||
static const QRegularExpression ipv4Partial(QStringLiteral(R"(^(\d{1,3}\.){0,3}\d{0,3}$)"));
|
||||
return ipv4Partial.match(text).hasMatch();
|
||||
}
|
||||
if (text.contains(QLatin1Char(':'))) {
|
||||
static const QRegularExpression ipv6Chars(QStringLiteral(R"(^[0-9a-fA-F:.]*$)"));
|
||||
if (!ipv6Chars.match(text).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
if (text.size() > 45) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!hostLabelsRejectBrokenDigitLetterMix(text)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PublicHostInputValidator::PublicHostInputValidator(QObject *parent) : QValidator(parent) {}
|
||||
|
||||
QValidator::State PublicHostInputValidator::validate(QString &input, int &pos) const {
|
||||
Q_UNUSED(pos)
|
||||
return mtproxyPublicHostInputAllowed(input) ? Acceptable : Invalid;
|
||||
}
|
||||
20
client/ui/models/utils/mtproxy_public_host_input.h
Normal file
20
client/ui/models/utils/mtproxy_public_host_input.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef MTPROXY_PUBLIC_HOST_INPUT_H
|
||||
#define MTPROXY_PUBLIC_HOST_INPUT_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <QValidator>
|
||||
|
||||
/// Shared rules for public host field (IPv4 dotted partial, IPv6 hex, FQDN ASCII).
|
||||
bool mtproxyPublicHostInputAllowed(const QString &text);
|
||||
|
||||
class PublicHostInputValidator : public QValidator {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PublicHostInputValidator(QObject *parent = nullptr);
|
||||
|
||||
QValidator::State validate(QString &input, int &pos) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -45,6 +45,9 @@ ListViewType {
|
||||
PageController.goToPage(PageEnum.PageProtocolRaw)
|
||||
} else if (isDns) {
|
||||
PageController.goToPage(PageEnum.PageServiceDnsSettings)
|
||||
} else if (isMtProxy) {
|
||||
MtProxyConfigModel.updateModel(config)
|
||||
PageController.goToPage(PageEnum.PageServiceMtProxySettings)
|
||||
} else {
|
||||
InstallController.updateProtocols(ServersUiController.processedIndex, containerIndex)
|
||||
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
|
||||
|
||||
@@ -31,6 +31,9 @@ ListViewType {
|
||||
|
||||
function triggerCurrentItem() {
|
||||
var item = root.itemAtIndex(selectedIndex)
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
item.selectable.clicked()
|
||||
}
|
||||
|
||||
|
||||
1885
client/ui/qml/Pages2/PageServiceMtProxySettings.qml
Normal file
1885
client/ui/qml/Pages2/PageServiceMtProxySettings.qml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -132,9 +132,11 @@ PageType {
|
||||
onInstallationErrorOccurred(message)
|
||||
}
|
||||
|
||||
function onUpdateContainerFinished(message) {
|
||||
function onUpdateContainerFinished(message, closePage) {
|
||||
PageController.showNotificationMessage(message)
|
||||
PageController.closePage()
|
||||
if (closePage) {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
|
||||
function onCachedProfileCleared(message) {
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<file>Pages2/PageProtocolWireGuardSettings.qml</file>
|
||||
<file>Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>Pages2/PageServiceDnsSettings.qml</file>
|
||||
<file>Pages2/PageServiceMtProxySettings.qml</file>
|
||||
<file>Pages2/PageServiceSftpSettings.qml</file>
|
||||
<file>Pages2/PageServiceSocksProxySettings.qml</file>
|
||||
<file>Pages2/PageServiceTorWebsiteSettings.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user