diff --git a/client/client.pro b/client/client.pro index 1c027a04..b4b87af4 100644 --- a/client/client.pro +++ b/client/client.pro @@ -14,6 +14,7 @@ include("3rd/QRCodeGenerator/QRCodeGenerator.pri") HEADERS += \ ../ipc/ipc.h \ configurators/cloak_configurator.h \ + configurators/shadowsocks_configurator.h \ core/defs.h \ core/errorstrings.h \ core/ipcclient.h \ @@ -39,6 +40,7 @@ HEADERS += \ SOURCES += \ configurators/cloak_configurator.cpp \ + configurators/shadowsocks_configurator.cpp \ core/ipcclient.cpp \ configurators/openvpn_configurator.cpp \ core/scripts_registry.cpp \ diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp index a95987bf..8f31be26 100644 --- a/client/configurators/cloak_configurator.cpp +++ b/client/configurators/cloak_configurator.cpp @@ -6,8 +6,8 @@ #include "protocols/protocols_defs.h" -QJsonObject CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) +QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { ErrorCode e = ErrorCode::NoError; @@ -21,7 +21,7 @@ QJsonObject CloakConfigurator::genCloakConfig(const ServerCredentials &credentia if (e) { if (errorCode) *errorCode = e; - return QJsonObject(); + return ""; } QJsonObject config; @@ -30,14 +30,18 @@ QJsonObject CloakConfigurator::genCloakConfig(const ServerCredentials &credentia config.insert("EncryptionMethod", "aes-gcm"); config.insert("UID", cloakBypassUid); config.insert("PublicKey", cloakPublicKey); - config.insert("ServerName", amnezia::protocols::cloak::ckDefaultRedirSite); + config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS"); config.insert("NumConn", 4); config.insert("BrowserSig", "chrome"); config.insert("StreamTimeout", 300); - // Amnezia field - config.insert("Remote", credentials.hostName); + // transfer params to protocol runner + config.insert(config_key::transport_proto, "$OPENVPN_TRANSPORT_PROTO"); + config.insert(config_key::remote, credentials.hostName); - qDebug().noquote() << QJsonDocument(config).toJson(); - return config; + QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(), + ServerController::genVarsForScript(credentials, container, containerConfig)); + + // qDebug().noquote() << textCfg; + return textCfg; } diff --git a/client/configurators/cloak_configurator.h b/client/configurators/cloak_configurator.h index 8e298d57..562b9917 100644 --- a/client/configurators/cloak_configurator.h +++ b/client/configurators/cloak_configurator.h @@ -11,8 +11,8 @@ class CloakConfigurator { public: - static QJsonObject genCloakConfig(const ServerCredentials &credentials, DockerContainer container, - ErrorCode *errorCode = nullptr); + static QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); }; #endif // CLOAK_CONFIGURATOR_H diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 19ae5fb2..4d29c5eb 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -192,35 +192,16 @@ Settings &OpenVpnConfigurator::m_settings() } QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { -// QFile configTemplFile; -// if (proto == Protocol::OpenVpn) -// configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn"); -// else if (proto == Protocol::ShadowSocks) { -// configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn"); -// } - -// configTemplFile.open(QIODevice::ReadOnly); -// QString config = configTemplFile.readAll(); - - QString config = amnezia::scriptData(ProtocolScriptType::openvpn_template, container); + QString config = ServerController::replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), + ServerController::genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { return ""; } - if (container == DockerContainer::OpenVpn) - config.replace("$PROTO", "udp"); - else if (container == DockerContainer::ShadowSocksOverOpenVpn) { - config.replace("$PROTO", "tcp"); - config.replace("$LOCAL_PROXY_PORT", amnezia::protocols::shadowsocks::ssLocalProxyPort); - } - else if (container == DockerContainer::OpenVpnOverCloak) { - config.replace("$PROTO", "tcp"); - } - config.replace("$PRIMARY_DNS", m_settings().primaryDns()); config.replace("$SECONDARY_DNS", m_settings().secondaryDns()); @@ -229,11 +210,11 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia } config.replace("$REMOTE_HOST", connData.host); - config.replace("$REMOTE_PORT", amnezia::protocols::openvpn::openvpnDefaultPort); - config.replace("$CA_CERT", connData.caCert); - config.replace("$CLIENT_CERT", connData.clientCert); - config.replace("$PRIV_KEY", connData.privKey); - config.replace("$TA_KEY", connData.taKey); + config.replace("$REMOTE_PORT", amnezia::protocols::openvpn::defaultPort); + config.replace("$OPENVPN_CA_CERT", connData.caCert); + config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert); + config.replace("$OPENVPN_PRIV_KEY", connData.privKey); + config.replace("$OPENVPN_TA_KEY", connData.taKey); #ifdef Q_OS_MAC config.replace("block-outside-dns", ""); @@ -281,13 +262,13 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " "easyrsa import-req %2/%3.req %3\"") - .arg(amnezia::server::getContainerName(container)) + .arg(amnezia::containerToString(container)) .arg(amnezia::protocols::openvpn::clientsDirPath) .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " "easyrsa sign-req client %2\"") - .arg(amnezia::server::getContainerName(container)) + .arg(amnezia::containerToString(container)) .arg(clientId); QStringList scriptList {script_import, script_sign}; diff --git a/client/configurators/openvpn_configurator.h b/client/configurators/openvpn_configurator.h index db9f1ab8..a40effef 100644 --- a/client/configurators/openvpn_configurator.h +++ b/client/configurators/openvpn_configurator.h @@ -23,7 +23,7 @@ public: }; static QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, - ErrorCode *errorCode = nullptr); + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); static QString convertOpenSShKey(const QString &key); diff --git a/client/configurators/shadowsocks_configurator.cpp b/client/configurators/shadowsocks_configurator.cpp new file mode 100644 index 00000000..5703a972 --- /dev/null +++ b/client/configurators/shadowsocks_configurator.cpp @@ -0,0 +1,37 @@ +#include "shadowsocks_configurator.h" + +#include +#include +#include + +#include "protocols/protocols_defs.h" + +QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +{ + ErrorCode e = ErrorCode::NoError; + + QString ssKey = ServerController::getTextFileFromContainer(container, credentials, + amnezia::protocols::shadowsocks::ssKeyPath, &e); + ssKey.replace("\n", ""); + + if (e) { + if (errorCode) *errorCode = e; + return ""; + } + + QJsonObject config; + config.insert("server", credentials.hostName); + config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT"); + config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT"); + config.insert("password", ssKey); + config.insert("timeout", 60); + config.insert("method", "$SHADOWSOCKS_CIPHER"); + + + QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(), + ServerController::genVarsForScript(credentials, container, containerConfig)); + + qDebug().noquote() << textCfg; + return textCfg; +} diff --git a/client/configurators/shadowsocks_configurator.h b/client/configurators/shadowsocks_configurator.h new file mode 100644 index 00000000..4445b074 --- /dev/null +++ b/client/configurators/shadowsocks_configurator.h @@ -0,0 +1,18 @@ +#ifndef SHADOWSOCKS_CONFIGURATOR_H +#define SHADOWSOCKS_CONFIGURATOR_H + +#include + +#include "core/defs.h" +#include "settings.h" +#include "core/servercontroller.h" + +class ShadowSocksConfigurator +{ +public: + + static QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); +}; + +#endif // SHADOWSOCKS_CONFIGURATOR_H diff --git a/client/core/defs.h b/client/core/defs.h index b3249257..cce2f5a5 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -5,44 +5,6 @@ #include namespace amnezia { -Q_NAMESPACE - -enum class Protocol { - Any, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard -}; -Q_ENUM_NS(Protocol) - -inline Protocol protoFromString(QString proto){ - auto&& metaEnum = QMetaEnum::fromType(); - return static_cast(metaEnum.keyToValue(proto.toStdString().c_str())); -} - -inline QString protoToString(Protocol proto){ - return QVariant::fromValue(proto).toString(); -} - - -enum class DockerContainer { - None, - OpenVpn, - ShadowSocksOverOpenVpn, - OpenVpnOverCloak, - WireGuard -}; -Q_ENUM_NS(DockerContainer) - -inline DockerContainer containerFromString(QString container){ - auto&& metaEnum = QMetaEnum::fromType(); - return static_cast(metaEnum.keyToValue(container.toStdString().c_str())); -} - -inline QString containerToString(DockerContainer container){ - return QVariant::fromValue(container).toString(); -} //static DockerContainer containerForProto(Protocol proto) //{ @@ -78,6 +40,7 @@ enum ErrorCode // Server errors ServerCheckFailed, ServerPortAlreadyAllocatedError, + ServerContainerMissingError, // Ssh connection errors SshSocketError, SshTimeoutError, SshProtocolError, diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 93b8d1c8..f8be1c50 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -9,7 +9,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) switch (container) { case DockerContainer::OpenVpn: return QLatin1String("openvpn"); case DockerContainer::OpenVpnOverCloak: return QLatin1String("openvpn_cloak"); - case DockerContainer::ShadowSocksOverOpenVpn: return QLatin1String("openvpn_shadowsocks"); + case DockerContainer::OpenVpnOverShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); default: return ""; } @@ -32,6 +32,7 @@ QString amnezia::scriptName(ProtocolScriptType type) { switch (type) { case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile"); + case ProtocolScriptType::run_container: return QLatin1String("run_container.sh"); case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh"); case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); @@ -46,7 +47,11 @@ QString amnezia::scriptData(amnezia::SharedScriptType type) qDebug() << "Error opening script" << fileName; return ""; } - return file.readAll(); + QByteArray ba = file.readAll(); + if (ba.isEmpty()) { + qDebug() << "Error, script is empty" << fileName; + } + return ba; } QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container) diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index f5d2c951..88db5068 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -3,6 +3,7 @@ #include #include "core/defs.h" +#include "protocols/protocols_defs.h"" namespace amnezia { @@ -19,6 +20,7 @@ enum SharedScriptType { enum ProtocolScriptType { // Protocol scripts dockerfile, + run_container, configure_container, container_startup, openvpn_template diff --git a/client/core/server_defs.cpp b/client/core/server_defs.cpp index a2f64133..64ca1c99 100644 --- a/client/core/server_defs.cpp +++ b/client/core/server_defs.cpp @@ -1,16 +1,16 @@ #include "server_defs.h" -QString amnezia::server::getContainerName(amnezia::DockerContainer container) -{ - switch (container) { - case(DockerContainer::OpenVpn): return "amnezia-openvpn"; - case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak"; - case(DockerContainer::ShadowSocksOverOpenVpn): return "amnezia-shadowsocks"; - default: return ""; - } -} +//QString amnezia::containerToString(amnezia::DockerContainer container) +//{ +// switch (container) { +// case(DockerContainer::OpenVpn): return "amnezia-openvpn"; +// case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak"; +// case(DockerContainer::OpenVpnOverShadowSocks): return "amnezia-shadowsocks"; +// default: return ""; +// } +//} QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container) { - return "/opt/amnezia/" + getContainerName(container); + return "/opt/amnezia/" + containerToString(container); } diff --git a/client/core/server_defs.h b/client/core/server_defs.h index 3e767cbd..f70d5ebb 100644 --- a/client/core/server_defs.h +++ b/client/core/server_defs.h @@ -2,11 +2,11 @@ #define SERVER_DEFS_H #include -#include "core/defs.h" +#include "protocols/protocols_defs.h"" namespace amnezia { namespace server { -QString getContainerName(amnezia::DockerContainer container); +//QString getContainerName(amnezia::DockerContainer container); QString getDockerfileFolder(amnezia::DockerContainer container); } diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index e161f91c..16492d24 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -119,41 +119,27 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, return ErrorCode::NoError; } -ErrorCode ServerController::installDocker(const ServerCredentials &credentials) -{ - // Setup openvpn part - QString scriptData = amnezia::scriptData(SharedScriptType::install_docker); - if (scriptData.isEmpty()) return ErrorCode::InternalError; - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { - stdOut += data + "\n"; - - if (data.contains("Automatically restart Docker daemon?")) { - proc->write("yes\n"); - } - }; - auto cbReadStdErr = [&](const QString &data, QSharedPointer ) { - stdOut += data + "\n"; - }; - - return runScript(sshParams(credentials), - replaceVars(scriptData, genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)), - cbReadStdOut, cbReadStdErr); -} - ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path) { ErrorCode e; QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); - uploadFileToHost(credentials, file.toUtf8(), tmpFileName); + e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); + if (e) return e; + + QString stdOut; + auto cbReadStd = [&](const QString &data, QSharedPointer ) { + stdOut += data + "\n"; + }; e = runScript(sshParams(credentials), replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container))); + genVarsForScript(credentials, container)), cbReadStd, cbReadStd); if (e) return e; + if (stdOut.contains("Error: No such container:")) { + return ErrorCode::ServerContainerMissingError; + } runScript(sshParams(credentials), replaceVars(QString("sudo shred %1").arg(tmpFileName), @@ -164,52 +150,6 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, genVarsForScript(credentials, container))); return e; - - // QString script = QString("sudo docker exec -i %1 sh -c \"echo \'%2\' > %3\""). -// arg(amnezia::server::getContainerName(container)).arg(file).arg(path); - -// qDebug().noquote() << "uploadTextFileToContainer\n" << script; - -// SshConnection *client = connectToHost(sshParams(credentials)); -// if (client->state() != SshConnection::State::Connected) { -// return fromSshConnectionErrorCode(client->errorState()); -// } - -// QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); - -// if (!proc) { -// qCritical() << "Failed to create SshRemoteProcess, breaking."; -// return ErrorCode::SshRemoteProcessCreationError; -// } - -// QEventLoop wait; -// int exitStatus = -1; - -//// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){ -//// qDebug() << "uploadTextFileToContainer started"; -//// }); - -// QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){ -// //qDebug() << "Remote process exited with status" << status; -// exitStatus = status; -// wait.quit(); -// }); - -// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ -// qDebug().noquote() << proc->readAllStandardOutput(); -// }); - -// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ -// qDebug().noquote() << proc->readAllStandardError(); -// }); - -// proc->start(); - -// if (exitStatus < 0) { -// wait.exec(); -// } - -// return fromSshProcessExitStatus(exitStatus); } QString ServerController::getTextFileFromContainer(DockerContainer container, @@ -218,7 +158,7 @@ QString ServerController::getTextFileFromContainer(DockerContainer container, if (errorCode) *errorCode = ErrorCode::NoError; QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\""). - arg(amnezia::server::getContainerName(container)).arg(path); + arg(amnezia::containerToString(container)).arg(path); qDebug().noquote() << "Copy file from container\n" << script; @@ -402,77 +342,151 @@ ErrorCode ServerController::removeContainer(const ServerCredentials &credentials ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { - ErrorCode e = runScript(sshParams(credentials), - replaceVars(amnezia::scriptData(SharedScriptType::install_docker), - genVarsForScript(credentials))); + qDebug().noquote() << "ServerController::setupContainer" << containerToString(container); + qDebug().noquote() << QJsonDocument(config).toJson(); + ErrorCode e = ErrorCode::NoError; +// e = runScript(sshParams(credentials), +// replaceVars(amnezia::scriptData(SharedScriptType::install_docker), +// genVarsForScript(credentials))); + +// if (e) return e; + + + e = prepareHostWorker(credentials, container, config); if (e) return e; + removeContainer(credentials, container); + + e = buildContainerWorker(credentials, container, config); + if (e) return e; + + e = runContainerWorker(credentials, container, config); + if (e) return e; + + e = configureContainerWorker(credentials, container, config); + if (e) return e; + + return startupContainerWorker(credentials, container, config); + + + + +// if (container == DockerContainer::OpenVpn) { +// return setupOpenVpnServer(credentials, config); +// } +// else if (container == DockerContainer::OpenVpnOverShadowSocks) { +// return setupShadowSocksServer(credentials, config); +// } +// else if (container == DockerContainer::OpenVpnOverCloak) { +// return setupOpenVpnOverCloakServer(credentials, config); +// } + +// return ErrorCode::NoError; +} + +ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &oldConfig, const QJsonObject &newConfig) +{ + if (isReinstallContainerRequred(container, oldConfig, newConfig)) { + return setupContainer(credentials, container, newConfig); + } + else { + ErrorCode e = configureContainerWorker(credentials, container, newConfig); + if (e) return e; + + return startupContainerWorker(credentials, container, newConfig); + } +} + +bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) +{ if (container == DockerContainer::OpenVpn) { - return ErrorCode::NoError; - //return setupOpenVpnServer(credentials, config); - } - else if (container == DockerContainer::ShadowSocksOverOpenVpn) { - return setupShadowSocksServer(credentials, config); - } - else if (container == DockerContainer::OpenVpnOverCloak) { - return setupOpenVpnOverCloakServer(credentials, config); + const QJsonObject &oldProtoConfig = oldConfig[config_key::openvpn].toObject(); + const QJsonObject &newProtoConfig = newConfig[config_key::openvpn].toObject(); + + if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) != + newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) + return true; + + if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) != + newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) + return true; } - return ErrorCode::NoError; + if (container == DockerContainer::OpenVpnOverCloak) { + const QJsonObject &oldProtoConfig = oldConfig[config_key::cloak].toObject(); + const QJsonObject &newProtoConfig = newConfig[config_key::cloak].toObject(); + + if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) != + newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) + return true; + } + + return false; } -ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config) -{ - return ErrorCode::NotImplementedError; +//ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config) +//{ +// return ErrorCode::NotImplementedError; +//} -// QString scriptData; -// QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh"; -// QFile file(scriptFileName); -// if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError; +//ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config) +//{ +// ErrorCode e; +// DockerContainer container = DockerContainer::OpenVpnOverCloak; -// scriptData = file.readAll(); -// if (scriptData.isEmpty()) return ErrorCode::InternalError; - -// QString stdOut; -// auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; - -// if (data.contains("Automatically restart Docker daemon?")) { -// proc->write("yes\n"); -// } -// }; -// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; -// }; - -// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::OpenVpn), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr); +// e = prepareHostWorker(credentials, container, config); // if (e) return e; -// QApplication::processEvents(); -// if (stdOut.contains("port is already allocated")) return ErrorCode::ServerPortAlreadyAllocatedError; -// if (stdOut.contains("Error response from daemon")) return ErrorCode::ServerCheckFailed; +// removeContainer(credentials, container); -// return checkOpenVpnServer(DockerContainer::OpenVpn, credentials); +// e = buildContainerWorker(credentials, container, config); +// if (e) return e; + +// e = runContainerWorker(credentials, container, config); +// if (e) return e; + +// e = configureContainerWorker(credentials, container, config); +// if (e) return e; + +// return startupContainerWorker(credentials, container, config); +//} + +ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { + stdOut += data + "\n"; + + if (data.contains("Automatically restart Docker daemon?")) { + proc->write("yes\n"); + } + }; + auto cbReadStdErr = [&](const QString &data, QSharedPointer ) { + stdOut += data + "\n"; + }; + + return runScript(sshParams(credentials), + replaceVars(amnezia::scriptData(SharedScriptType::install_docker), + genVarsForScript(credentials, container)), + cbReadStdOut, cbReadStdErr); } -ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config) +ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { - ErrorCode e; - DockerContainer container = DockerContainer::OpenVpnOverCloak; - // create folder on host - e = runScript(sshParams(credentials), + return runScript(sshParams(credentials), replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); - if (e) return e; +} - uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, DockerContainer::OpenVpnOverCloak).toUtf8(), +ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +{ + ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); - - QString scriptData = amnezia::scriptData(SharedScriptType::build_container); - if (scriptData.isEmpty()) return ErrorCode::InternalError; + if (e) return e; // QString stdOut; // auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { @@ -482,91 +496,118 @@ ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials // stdOut += data + "\n"; // }; - e = runScript(sshParams(credentials), - replaceVars(scriptData, - genVarsForScript(credentials, container))); - if (e) return e; - - - runScript(sshParams(credentials), - replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, DockerContainer::OpenVpnOverCloak), - genVarsForScript(credentials, container))); - if (e) return e; - - uploadTextFileToContainer(DockerContainer::OpenVpnOverCloak, credentials, - replaceVars(amnezia::scriptData(ProtocolScriptType::container_startup, DockerContainer::OpenVpnOverCloak), - genVarsForScript(credentials, container)), - "/opt/amnezia/start.sh"); - - runScript(sshParams(credentials), - replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", - genVarsForScript(credentials, container))); - if (e) return e; - - return e; + return runScript(sshParams(credentials), + replaceVars(amnezia::scriptData(SharedScriptType::build_container), + genVarsForScript(credentials, container, config))); } -ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config) +ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { - return ErrorCode::NotImplementedError; -// // Setup openvpn part -// QString scriptData; -// QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh"; -// QFile file(scriptFileName); -// if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError; - -// scriptData = file.readAll(); -// if (scriptData.isEmpty()) return ErrorCode::InternalError; - -// QString stdOut; -// auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; - -// if (data.contains("Automatically restart Docker daemon?")) { -// proc->write("yes\n"); -// } -// }; -// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; -// }; - -// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr); -// if (e) return e; - -// // Create ss config -// QJsonObject ssConfig; -// ssConfig.insert("server", "0.0.0.0"); -// ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort()); -// ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort()); -// ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex())); -// ssConfig.insert("timeout", 60); -// ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption()); -// QString configData = QJsonDocument(ssConfig).toJson(); -// QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json"; - -// configData.replace("\"", "\\\""); -// //qDebug().noquote() << configData; - -// uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath); - -// // Start ss -// QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\""). -// arg(amnezia::server::getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath); - -// e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script); -// return e; + return runScript(sshParams(credentials), + replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), + genVarsForScript(credentials, container, config))); } +ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +{ + return runScript(sshParams(credentials), + replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), + genVarsForScript(credentials, container, config))); +} + +ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +{ + ErrorCode e = uploadTextFileToContainer(container, credentials, + replaceVars(amnezia::scriptData(ProtocolScriptType::container_startup, container), + genVarsForScript(credentials, container, config)), + "/opt/amnezia/start.sh"); + if (e) return e; + + return runScript(sshParams(credentials), + replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", + genVarsForScript(credentials, container, config))); +} + +//ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config) +//{ +// return ErrorCode::NotImplementedError; +//// // Setup openvpn part +//// QString scriptData; +//// QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh"; +//// QFile file(scriptFileName); +//// if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError; + +//// scriptData = file.readAll(); +//// if (scriptData.isEmpty()) return ErrorCode::InternalError; + +//// QString stdOut; +//// auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { +//// stdOut += data + "\n"; + +//// if (data.contains("Automatically restart Docker daemon?")) { +//// proc->write("yes\n"); +//// } +//// }; +//// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { +//// stdOut += data + "\n"; +//// }; + +//// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr); +//// if (e) return e; + +//// // Create ss config +//// QJsonObject ssConfig; +//// ssConfig.insert("server", "0.0.0.0"); +//// ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort()); +//// ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort()); +//// ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex())); +//// ssConfig.insert("timeout", 60); +//// ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption()); +//// QString configData = QJsonDocument(ssConfig).toJson(); +//// QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json"; + +//// configData.replace("\"", "\\\""); +//// //qDebug().noquote() << configData; + +//// uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath); + +//// // Start ss +//// QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\""). +//// arg(amnezia::containerToString(DockerContainer::ShadowSocks)).arg(sSConfigPath); + +//// e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script); +// // return e; +//} + ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { + const QJsonObject &openvpnConfig = config.value(config_key::openvpn).toObject(); + const QJsonObject &cloakConfig = config.value(config_key::cloak).toObject(); + const QJsonObject &ssConfig = config.value(config_key::shadowsocks).toObject(); + // + Vars vars; - vars.append({{"$VPN_SUBNET_IP", nonEmpty(config.value(config_key::subnet_address).toString(), amnezia::protocols::vpnDefaultSubnetAddress) }}); - vars.append({{"$VPN_SUBNET_MASK_VAL", nonEmpty(config.value(config_key::subnet_mask_val).toString(), amnezia::protocols::vpnDefaultSubnetMaskVal) }}); - vars.append({{"$VPN_SUBNET_MASK", nonEmpty(config.value(config_key::subnet_mask).toString(), amnezia::protocols::vpnDefaultSubnetMask) }}); + // OpenVPN vars + vars.append({{"$VPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(amnezia::protocols::vpnDefaultSubnetAddress) }}); + vars.append({{"$VPN_SUBNET_MASK_VAL", openvpnConfig.value(config_key::subnet_mask_val).toString(amnezia::protocols::vpnDefaultSubnetMaskVal) }}); + vars.append({{"$VPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(amnezia::protocols::vpnDefaultSubnetMask) }}); - vars.append({{"$CONTAINER_NAME", amnezia::server::getContainerName(container)}}); - vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::server::getContainerName(container)}}); + vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(amnezia::protocols::openvpn::defaultPort) }}); + vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(amnezia::protocols::openvpn::defaultTransportProto) }}); + + bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(amnezia::protocols::openvpn::defaultNcpDisable); + vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }}); + vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(amnezia::protocols::openvpn::defaultCipher) }}); + vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(amnezia::protocols::openvpn::defaultHash) }}); + + // ShadowSocks vars + vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(amnezia::protocols::shadowsocks::defaultPort) }}); + vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(amnezia::protocols::shadowsocks::defaultLocalProxyPort) }}); + vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(amnezia::protocols::shadowsocks::defaultCipher) }}); + + vars.append({{"$CONTAINER_NAME", amnezia::containerToString(container)}}); + vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::containerToString(container)}}); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { @@ -576,22 +617,19 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName"; } - // - const QJsonObject &openvpnConfig = config.value(config_key::openvpn).toObject(); - const QJsonObject &cloakConfig = config.value(config_key::cloak).toObject(); - const QJsonObject &ssConfig = config.value(config_key::shadowsocks).toObject(); + if (container == DockerContainer::OpenVpn) { - vars.append({{"$DOCKER_PORT", nonEmpty(config.value(config_key::port).toString(), protocols::openvpn::openvpnDefaultPort) }}); + vars.append({{"$DOCKER_PORT", config.value(config_key::port).toString(protocols::openvpn::defaultPort) }}); } else if (container == DockerContainer::OpenVpnOverCloak) { - vars.append({{"$DOCKER_PORT", nonEmpty(config.value(config_key::port).toString(), protocols::cloak::ckDefaultPort) }}); + vars.append({{"$DOCKER_PORT", config.value(config_key::port).toString(protocols::cloak::defaultPort) }}); - vars.append({{"$FAKE_WEB_SITE_ADDRESS", nonEmpty(cloakConfig.value(config_key::site).toString(), protocols::cloak::ckDefaultRedirSite) }}); + vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }}); } - else if (container == DockerContainer::ShadowSocksOverOpenVpn) { - vars.append({{"$DOCKER_PORT", nonEmpty(config.value(config_key::port).toString(), protocols::shadowsocks::ssDefaultPort) }}); + else if (container == DockerContainer::OpenVpnOverShadowSocks) { + vars.append({{"$DOCKER_PORT", config.value(config_key::port).toString(protocols::shadowsocks::defaultPort) }}); } return vars; @@ -686,7 +724,7 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars) { QString s = script; for (const QPair &var : vars) { - //qDebug() << "Replacing" << var << vars.value(var); + //qDebug() << "Replacing" << var.first << var.second; s.replace(var.first, var.second); } return s; diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index ebfd01f1..126e423a 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -6,6 +6,8 @@ #include "sshconnection.h" #include "sshremoteprocess.h" #include "defs.h" +#include "protocols/protocols_defs.h"" + using namespace amnezia; @@ -26,6 +28,10 @@ public: static ErrorCode removeAllContainers(const ServerCredentials &credentials); static ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); static ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + static ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &oldConfig, const QJsonObject &newConfig = QJsonObject()); + + static bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials); @@ -51,11 +57,18 @@ public: private: static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); - static ErrorCode installDocker(const ServerCredentials &credentials); + static ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); + + //static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); + //static ErrorCode setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); + // static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); + + static ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + static ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + static ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + static ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + static ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); - static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); - static ErrorCode setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); - static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject()); }; #endif // SERVERCONTROLLER_H diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index c220267d..14f57377 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -37,9 +37,13 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_cloakCfgFile.close(); QStringList args = QStringList() << "-c" << m_cloakCfgFile.fileName() - << "-s" << m_cloakConfig.value("Remote").toString() - << "-p" << amnezia::protocols::cloak::ckDefaultPort - << "-l" << amnezia::protocols::openvpn::openvpnDefaultPort; + << "-s" << m_cloakConfig.value(config_key::remote).toString() + << "-p" << amnezia::protocols::cloak::defaultPort + << "-l" << amnezia::protocols::openvpn::defaultPort; + + if (m_cloakConfig.value(config_key::transport_proto).toString() == protocols::UDP) { + args << "-u"; + } qDebug().noquote() << "OpenVpnOverCloakProtocol::start()" << cloakExecPath() << args.join(" "); diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 68e0f64e..87644859 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -4,78 +4,148 @@ #include namespace amnezia { - -inline QString nonEmpty(const QString &val, const QString &deflt) { return val.isEmpty() ? deflt : val; } - - namespace config_key { // Json config strings -const char hostName[] = "hostName"; -const char userName[] = "userName"; -const char password[] = "password"; -const char port[] = "port"; -const char description[] = "description"; +constexpr char hostName[] = "hostName"; +constexpr char userName[] = "userName"; +constexpr char password[] = "password"; +constexpr char port[] = "port"; +constexpr char local_port[] = "local_port"; + +constexpr char description[] = "description"; -const char containers[] = "containers"; -const char container[] = "container"; -const char defaultContainer[] = "defaultContainer"; +constexpr char containers[] = "containers"; +constexpr char container[] = "container"; +constexpr char defaultContainer[] = "defaultContainer"; -const char protocols[] = "protocols"; -const char protocol[] = "protocol"; +constexpr char protocols[] = "protocols"; +//constexpr char protocol[] = "protocol"; -const char transport_protocol[] = "transport_protocol"; -const char cipher[] = "cipher"; -const char hash[] = "hash"; +constexpr char remote[] = "remote"; +constexpr char transport_proto[] = "transport_proto"; +constexpr char cipher[] = "cipher"; +constexpr char hash[] = "hash"; +constexpr char ncp_disable[] = "ncp_disable"; -const char site[] = "site"; +constexpr char site[] = "site"; +constexpr char block_outside_dns[] = "block_outside_dns"; -const char subnet_address[] = "subnet_address"; -const char subnet_mask[] = "subnet_mask"; -const char subnet_mask_val[] = "subnet_mask_val"; +constexpr char subnet_address[] = "subnet_address"; +constexpr char subnet_mask[] = "subnet_mask"; +constexpr char subnet_mask_val[] = "subnet_mask_val"; -const char openvpn[] = "openvpn"; -const char shadowsocks[] = "shadowsocks"; -const char cloak[] = "cloak"; +// proto config keys +constexpr char last_config[] = "last_config"; +constexpr char openvpn[] = "openvpn"; +constexpr char shadowsocks[] = "shadowsocks"; +constexpr char cloak[] = "cloak"; +constexpr char wireguard[] = "wireguard"; + +// containers config keys +constexpr char amnezia_openvpn[] = "amnezia-openvpn"; +constexpr char amnezia_shadowsocks[] = "amnezia-shadowsocks"; +constexpr char amnezia_openvpn_cloak[] = "amnezia-openvpn-cloak"; +constexpr char amnezia_wireguard[] = "amnezia-wireguard"; } namespace protocols { -const char vpnDefaultSubnetAddress[] = "10.8.0.0"; -const char vpnDefaultSubnetMask[] = "255.255.255.0"; -const char vpnDefaultSubnetMaskVal[] = "24"; +constexpr char vpnDefaultSubnetAddress[] = "10.8.0.0"; +constexpr char vpnDefaultSubnetMask[] = "255.255.255.0"; +constexpr char vpnDefaultSubnetMaskVal[] = "24"; + +constexpr char UDP[] = "udp"; // case sens +constexpr char TCP[] = "tcp"; namespace openvpn { -const char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; -const char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; -const char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; -const char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; -const char openvpnDefaultPort[] = "1194"; -const char openvpnDefaultProto[] = "UDP"; -const char openvpnDefaultCipher[] = "AES-256-GCM"; -const char openvpnDefaultHash[] = "SHA512"; -const bool blockOutsideDNS = true; +constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; +constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; +constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; +constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; +constexpr char defaultPort[] = "1194"; +constexpr char defaultTransportProto[] = amnezia::protocols::UDP; +constexpr char defaultCipher[] = "AES-256-GCM"; +constexpr char defaultHash[] = "SHA512"; +constexpr bool defaultBlockOutsideDns = true; +constexpr bool defaultNcpDisable = false; +constexpr char ncpDisableString[] = "ncp-disable"; + } namespace shadowsocks { -const char ssDefaultPort[] = "6789"; -const char ssLocalProxyPort[] = "8585"; -const char ssDefaultCipher[] = "chacha20-ietf-poly1305"; +constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; +constexpr char defaultPort[] = "6789"; +constexpr char defaultLocalProxyPort[] = "8585"; +constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; } namespace cloak { -const char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; -const char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; -const char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; -const char ckDefaultPort[] = "443"; -const char ckDefaultRedirSite[] = "mail.ru"; +constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; +constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; +constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; +constexpr char defaultPort[] = "443"; +constexpr char defaultRedirSite[] = "mail.ru"; } } // namespace protocols + +enum class Protocol { + Any, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard +}; + +inline Protocol protoFromString(QString proto){ + if (proto == config_key::openvpn) return Protocol::OpenVpn; + if (proto == config_key::cloak) return Protocol::Cloak; + if (proto == config_key::shadowsocks) return Protocol::ShadowSocks; + if (proto == config_key::wireguard) return Protocol::WireGuard; + return Protocol::Any; +} + +inline QString protoToString(Protocol proto){ + switch (proto) { + case(Protocol::OpenVpn): return config_key::openvpn; + case(Protocol::Cloak): return config_key::cloak; + case(Protocol::ShadowSocks): return config_key::shadowsocks; + case(Protocol::WireGuard): return config_key::wireguard; + default: return ""; + } +} + +enum class DockerContainer { + None, + OpenVpn, + OpenVpnOverShadowSocks, + OpenVpnOverCloak, + WireGuard +}; + +inline DockerContainer containerFromString(const QString &container){ + if (container == config_key::amnezia_openvpn) return DockerContainer::OpenVpn; + if (container == config_key::amnezia_openvpn_cloak) return DockerContainer::OpenVpnOverCloak; + if (container == config_key::amnezia_shadowsocks) return DockerContainer::OpenVpnOverShadowSocks; + if (container == config_key::amnezia_wireguard) return DockerContainer::WireGuard; + return DockerContainer::None; +} + +inline QString containerToString(DockerContainer container){ + switch (container) { + case(DockerContainer::OpenVpn): return config_key::amnezia_openvpn; + case(DockerContainer::OpenVpnOverCloak): return config_key::amnezia_openvpn_cloak; + case(DockerContainer::OpenVpnOverShadowSocks): return config_key::amnezia_shadowsocks; + case(DockerContainer::WireGuard): return config_key::amnezia_wireguard; + default: return ""; + } +} + } // namespace amnezia #endif // PROTOCOLS_DEFS_H diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 0c07bdbb..24faf6d2 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -96,18 +96,6 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() #endif } -QJsonObject ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container) -{ - QJsonObject ssConfig; - ssConfig.insert("server", credentials.hostName); - ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssDefaultPort); - ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssLocalProxyPort); - ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex())); - ssConfig.insert("timeout", 60); - ssConfig.insert("method", amnezia::protocols::shadowsocks::ssDefaultCipher); - return ssConfig; -} - void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration) { m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data).toObject(); diff --git a/client/protocols/shadowsocksvpnprotocol.h b/client/protocols/shadowsocksvpnprotocol.h index 061b5381..8a2ae4b1 100644 --- a/client/protocols/shadowsocksvpnprotocol.h +++ b/client/protocols/shadowsocksvpnprotocol.h @@ -3,6 +3,7 @@ #include "openvpnprotocol.h" #include "QProcess" +#include "protocols/protocols_defs.h"" class ShadowSocksVpnProtocol : public OpenVpnProtocol { @@ -13,8 +14,6 @@ public: ErrorCode start() override; void stop() override; - static QJsonObject genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container = DockerContainer::ShadowSocksOverOpenVpn); - protected: void readShadowSocksConfiguration(const QJsonObject &configuration); diff --git a/client/resources.qrc b/client/resources.qrc index be575644..60d3aaeb 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -34,11 +34,7 @@ images/server_settings.png images/share.png server_scripts/remove_container.sh - server_scripts/setup_openvpn_server.sh - server_scripts/template_openvpn.ovpn images/background_connected.png - server_scripts/setup_shadowsocks_server.sh - server_scripts/template_shadowsocks.ovpn server_scripts/setup_host_firewall.sh images/reload.png server_scripts/openvpn_cloak/Dockerfile @@ -54,5 +50,16 @@ images/plus.png server_scripts/check_connection.sh server_scripts/remove_all_containers.sh + server_scripts/openvpn_cloak/run_container.sh + server_scripts/openvpn/configure_container.sh + server_scripts/openvpn/run_container.sh + server_scripts/openvpn/template.ovpn + server_scripts/openvpn/Dockerfile + server_scripts/openvpn/start.sh + server_scripts/openvpn_shadowsocks/configure_container.sh + server_scripts/openvpn_shadowsocks/Dockerfile + server_scripts/openvpn_shadowsocks/run_container.sh + server_scripts/openvpn_shadowsocks/start.sh + server_scripts/openvpn_shadowsocks/template.ovpn diff --git a/client/server_scripts/openvpn/Dockerfile b/client/server_scripts/openvpn/Dockerfile new file mode 100644 index 00000000..e438e6e5 --- /dev/null +++ b/client/server_scripts/openvpn/Dockerfile @@ -0,0 +1,49 @@ +FROM alpine:latest + +LABEL maintainer="AmneziaVPN" + +#Install required packages +RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools +RUN apk --update upgrade --no-cache + +ENV EASYRSA_BATCH 1 +ENV PATH="/usr/share/easy-rsa:${PATH}" + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = hybla \n\ + # for low-latency network, use cubic instead \n\ + # net.ipv4.tcp_congestion_control = cubic \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] +CMD [ "" ] diff --git a/client/server_scripts/openvpn/configure_container.sh b/client/server_scripts/openvpn/configure_container.sh new file mode 100644 index 00000000..1be0902c --- /dev/null +++ b/client/server_scripts/openvpn/configure_container.sh @@ -0,0 +1,27 @@ +sudo docker exec -i $CONTAINER_NAME bash -c '\ +echo -e "\ +port $OPENVPN_PORT \\n\ +proto $OPENVPN_TRANSPORT_PROTO \\n\ +dev tun \\n\ +ca /opt/amnezia/openvpn/ca.crt \\n\ +cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\ +key /opt/amnezia/openvpn/AmneziaReq.key \\n\ +dh /opt/amnezia/openvpn/dh.pem \\n\ +server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\ +ifconfig-pool-persist ipp.txt \\n\ +duplicate-cn \\n\ +keepalive 10 120 \\n\ +$OPENVPN_NCP_DISABLE \\n\ +cipher $OPENVPN_CIPHER \\n\ +data-ciphers $OPENVPN_CIPHER \\n\ +auth $OPENVPN_HASH \\n\ +user nobody \\n\ +group nobody \\n\ +persist-key \\n\ +persist-tun \\n\ +status openvpn-status.log \\n\ +verb 1 \\n\ +tls-server \\n\ +tls-version-min 1.2 \\n\ +tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf' + diff --git a/client/server_scripts/openvpn/run_container.sh b/client/server_scripts/openvpn/run_container.sh new file mode 100644 index 00000000..b3216482 --- /dev/null +++ b/client/server_scripts/openvpn/run_container.sh @@ -0,0 +1,18 @@ +# Run container +sudo docker run -d --restart always --cap-add=NET_ADMIN -p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO --name $CONTAINER_NAME $CONTAINER_NAME + +# Create tun device if not exist +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi' + +# Prevent to route packets outside of the container in case if server behind of the NAT +sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" + +# OpenVPN config +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \ +cd /opt/amnezia/openvpn && easyrsa init-pki; \ +cd /opt/amnezia/openvpn && easyrsa gen-dh; \ +cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ +cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\ +cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\ +cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn' + diff --git a/client/server_scripts/openvpn/start.sh b/client/server_scripts/openvpn/start.sh new file mode 100644 index 00000000..696ce73f --- /dev/null +++ b/client/server_scripts/openvpn/start.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts + +echo "Container startup" + +if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi + +# Allow traffic on the TUN interface. +iptables -A INPUT -i tun0 -j ACCEPT +iptables -A FORWARD -i tun0 -j ACCEPT +iptables -A OUTPUT -o tun0 -j ACCEPT + +# Allow forwarding traffic only from the VPN. +iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT +iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT + +iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE + +# kill daemons in case of restart +killall -KILL openvpn + +# start daemons if configured +if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi + +tail -f /dev/null diff --git a/client/server_scripts/template_openvpn.ovpn b/client/server_scripts/openvpn/template.ovpn similarity index 68% rename from client/server_scripts/template_openvpn.ovpn rename to client/server_scripts/openvpn/template.ovpn index 6b4c4430..1d1d9d5d 100644 --- a/client/server_scripts/template_openvpn.ovpn +++ b/client/server_scripts/openvpn/template.ovpn @@ -1,12 +1,13 @@ client dev tun -proto $PROTO +proto $OPENVPN_TRANSPORT_PROTO resolv-retry infinite nobind persist-key persist-tun -cipher AES-256-GCM -auth SHA512 +$OPENVPN_NCP_DISABLE +cipher $OPENVPN_CIPHER +auth $OPENVPN_HASH verb 3 tls-client tls-version-min 1.2 @@ -21,14 +22,14 @@ block-outside-dns remote $REMOTE_HOST $REMOTE_PORT -$CA_CERT +$OPENVPN_CA_CERT -$CLIENT_CERT +$OPENVPN_CLIENT_CERT -$PRIV_KEY +$OPENVPN_PRIV_KEY -$TA_KEY +$OPENVPN_TA_KEY diff --git a/client/server_scripts/openvpn_cloak/configure_container.sh b/client/server_scripts/openvpn_cloak/configure_container.sh index d36e8c81..ff521284 100644 --- a/client/server_scripts/openvpn_cloak/configure_container.sh +++ b/client/server_scripts/openvpn_cloak/configure_container.sh @@ -1,35 +1,7 @@ -# CONTAINER_NAME=... this var will be set in ServerController -# Don't run commands in background like sh -c "openvpn &" -# SERVER_PORT=443 - -#sudo docker stop $CONTAINER_NAME -#sudo docker rm -f $CONTAINER_NAME -#sudo docker pull amneziavpn/openvpn-cloak:latest -#sudo docker run -d --restart always --cap-add=NET_ADMIN -p $DOCKER_PORT:443/tcp --name $CONTAINER_NAME amneziavpn/openvpn-cloak:latest - -sudo docker stop $CONTAINER_NAME -sudo docker rm -f $CONTAINER_NAME -sudo docker run -d --restart always --cap-add=NET_ADMIN -p $DOCKER_PORT:443/tcp --name $CONTAINER_NAME $CONTAINER_NAME - -# Create tun device if not exist -sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi' - -# Prevent to route packets outside of the container in case if server behind of the NAT -sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" - -# OpenVPN config -sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \ -cd /opt/amnezia/openvpn && easyrsa init-pki; \ -cd /opt/amnezia/openvpn && easyrsa gen-dh; \ -cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ -cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\ -cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\ -cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn' - sudo docker exec -i $CONTAINER_NAME bash -c '\ echo -e "\ -port 1194 \\n\ -proto tcp \\n\ +port $OPENVPN_PORT \\n\ +proto $OPENVPN_TRANSPORT_PROTO \\n\ dev tun \\n\ ca /opt/amnezia/openvpn/ca.crt \\n\ cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\ @@ -39,9 +11,10 @@ server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\ ifconfig-pool-persist ipp.txt \\n\ duplicate-cn \\n\ keepalive 10 120 \\n\ -cipher AES-256-GCM \\n\ -ncp-ciphers AES-256-GCM:AES-256-CBC \\n\ -auth SHA512 \\n\ +$OPENVPN_NCP_DISABLE \\n\ +cipher $OPENVPN_CIPHER \\n\ +data-ciphers $OPENVPN_CIPHER \\n\ +auth $OPENVPN_HASH \\n\ user nobody \\n\ group nobody \\n\ persist-key \\n\ @@ -52,8 +25,6 @@ tls-server \\n\ tls-version-min 1.2 \\n\ tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf' -#sudo docker exec -d $CONTAINER_NAME sh -c "openvpn --config /opt/amnezia/openvpn/server.conf" - # Cloak config sudo docker exec -i $CONTAINER_NAME bash -c '\ mkdir -p /opt/amnezia/cloak; \ @@ -66,8 +37,8 @@ echo $CLOAK_PRIVATE_KEY > /opt/amnezia/cloak/cloak_private.key; \ echo -e "{\\n\ \"ProxyBook\": {\\n\ \"openvpn\": [\\n\ - \"tcp\",\\n\ - \"localhost:1194\"\\n\ + \"$OPENVPN_TRANSPORT_PROTO\",\\n\ + \"localhost:$OPENVPN_PORT\"\\n\ ]\\n\ },\\n\ \"BypassUID\": [\\n\ @@ -79,6 +50,4 @@ echo -e "{\\n\ \"AdminUID\": \"$CLOAK_ADMIN_UID\",\\n\ \"DatabasePath\": \"userinfo.db\",\\n\ \"StreamTimeout\": 300\\n\ -}" >>/opt/amnezia/cloak/ck-config.json' - -#sudo docker exec -d $CONTAINER_NAME sh -c "/usr/bin/ck-server -c /opt/amnezia/cloak/ck-config.json" +}" >/opt/amnezia/cloak/ck-config.json' diff --git a/client/server_scripts/openvpn_cloak/run_container.sh b/client/server_scripts/openvpn_cloak/run_container.sh new file mode 100644 index 00000000..be3dbad5 --- /dev/null +++ b/client/server_scripts/openvpn_cloak/run_container.sh @@ -0,0 +1,17 @@ +# Run container +sudo docker run -d --restart always --cap-add=NET_ADMIN -p $DOCKER_PORT:443/tcp --name $CONTAINER_NAME $CONTAINER_NAME + +# Create tun device if not exist +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi' + +# Prevent to route packets outside of the container in case if server behind of the NAT +sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" + +# OpenVPN config +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \ +cd /opt/amnezia/openvpn && easyrsa init-pki; \ +cd /opt/amnezia/openvpn && easyrsa gen-dh; \ +cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ +cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\ +cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\ +cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn' diff --git a/client/server_scripts/openvpn_cloak/start.sh b/client/server_scripts/openvpn_cloak/start.sh index 8cde6d6f..711733ba 100644 --- a/client/server_scripts/openvpn_cloak/start.sh +++ b/client/server_scripts/openvpn_cloak/start.sh @@ -2,7 +2,7 @@ # This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts -echo "Container Startup start" +echo "Container startup" if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi @@ -17,6 +17,11 @@ iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE +# kill daemons in case of restart +killall -KILL openvpn +killall -KILL ck-server + +# start daemons if configured if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi if [ -f /opt/amnezia/cloak/ck-config.json ]; then (ck-server -c /opt/amnezia/cloak/ck-config.json &); fi diff --git a/client/server_scripts/openvpn_cloak/template.ovpn b/client/server_scripts/openvpn_cloak/template.ovpn index 2f9fb4fa..98e0e09f 100644 --- a/client/server_scripts/openvpn_cloak/template.ovpn +++ b/client/server_scripts/openvpn_cloak/template.ovpn @@ -1,12 +1,13 @@ client dev tun -proto $PROTO +proto $OPENVPN_TRANSPORT_PROTO resolv-retry infinite nobind persist-key persist-tun -cipher AES-256-GCM -auth SHA512 +$OPENVPN_NCP_DISABLE +cipher $OPENVPN_CIPHER +auth $OPENVPN_HASH verb 3 tls-client tls-version-min 1.2 @@ -22,14 +23,14 @@ route $REMOTE_HOST 255.255.255.255 net_gateway remote 127.0.0.1 1194 -$CA_CERT +$OPENVPN_CA_CERT -$CLIENT_CERT +$OPENVPN_CLIENT_CERT -$PRIV_KEY +$OPENVPN_PRIV_KEY -$TA_KEY +$OPENVPN_TA_KEY diff --git a/client/server_scripts/openvpn_shadowsocks/Dockerfile b/client/server_scripts/openvpn_shadowsocks/Dockerfile new file mode 100644 index 00000000..e5cdea08 --- /dev/null +++ b/client/server_scripts/openvpn_shadowsocks/Dockerfile @@ -0,0 +1,53 @@ +FROM alpine:latest + +LABEL maintainer="AmneziaVPN" + +#Install required packages +RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz +RUN apk --update upgrade --no-cache + +ENV EASYRSA_BATCH 1 +ENV PATH="/usr/share/easy-rsa:${PATH}" + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.10.9/shadowsocks-v1.10.9.x86_64-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz +RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/ +RUN chmod a+x /usr/bin/ssserver + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = hybla \n\ + # for low-latency network, use cubic instead \n\ + # net.ipv4.tcp_congestion_control = cubic \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] +CMD [ "" ] diff --git a/client/server_scripts/openvpn_shadowsocks/configure_container.sh b/client/server_scripts/openvpn_shadowsocks/configure_container.sh new file mode 100644 index 00000000..5f05427a --- /dev/null +++ b/client/server_scripts/openvpn_shadowsocks/configure_container.sh @@ -0,0 +1,40 @@ +sudo docker exec -i $CONTAINER_NAME bash -c '\ +echo -e "\ +port $OPENVPN_PORT \\n\ +proto tcp \\n\ +dev tun \\n\ +ca /opt/amnezia/openvpn/ca.crt \\n\ +cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\ +key /opt/amnezia/openvpn/AmneziaReq.key \\n\ +dh /opt/amnezia/openvpn/dh.pem \\n\ +server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\ +ifconfig-pool-persist ipp.txt \\n\ +duplicate-cn \\n\ +keepalive 10 120 \\n\ +$OPENVPN_NCP_DISABLE \\n\ +cipher $OPENVPN_CIPHER \\n\ +data-ciphers $OPENVPN_CIPHER \\n\ +auth $OPENVPN_HASH \\n\ +user nobody \\n\ +group nobody \\n\ +persist-key \\n\ +persist-tun \\n\ +status openvpn-status.log \\n\ +verb 1 \\n\ +tls-server \\n\ +tls-version-min 1.2 \\n\ +tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf' + +# Cloak config +sudo docker exec -i $CONTAINER_NAME bash -c '\ +mkdir -p /opt/amnezia/shadowsocks; \ +cd /opt/amnezia/shadowsocks || exit 1; \ +SHADOWSOCKS_PASSWORD=$(openssl rand -base64 32 | tr "=" "A" | tr "+" "A" | tr "/" "A") && echo $SHADOWSOCKS_PASSWORD > /opt/amnezia/shadowsocks/shadowsocks.key; \ +echo -e "{\\n\ + \"local_port\": 8585,\\n\ + \"method\": \"$SHADOWSOCKS_CIPHER\",\\n\ + \"password\": \"$SHADOWSOCKS_PASSWORD\",\\n\ + \"server\": \"0.0.0.0\",\\n\ + \"server_port\": $SHADOWSOCKS_SERVER_PORT,\\n\ + \"timeout\": 60\\n\ +}" >/opt/amnezia/shadowsocks/ss-config.json' diff --git a/client/server_scripts/openvpn_shadowsocks/run_container.sh b/client/server_scripts/openvpn_shadowsocks/run_container.sh new file mode 100644 index 00000000..7e9363d0 --- /dev/null +++ b/client/server_scripts/openvpn_shadowsocks/run_container.sh @@ -0,0 +1,17 @@ +# Run container +sudo docker run -d --restart always --cap-add=NET_ADMIN -p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp --name $CONTAINER_NAME $CONTAINER_NAME + +# Create tun device if not exist +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi' + +# Prevent to route packets outside of the container in case if server behind of the NAT +sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" + +# OpenVPN config +sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \ +cd /opt/amnezia/openvpn && easyrsa init-pki; \ +cd /opt/amnezia/openvpn && easyrsa gen-dh; \ +cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\ +cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\ +cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\ +cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn' diff --git a/client/server_scripts/openvpn_shadowsocks/start.sh b/client/server_scripts/openvpn_shadowsocks/start.sh new file mode 100644 index 00000000..8ec252be --- /dev/null +++ b/client/server_scripts/openvpn_shadowsocks/start.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts + +echo "Container startup" + +if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi + +# Allow traffic on the TUN interface. +iptables -A INPUT -i tun0 -j ACCEPT +iptables -A FORWARD -i tun0 -j ACCEPT +iptables -A OUTPUT -o tun0 -j ACCEPT + +# Allow forwarding traffic only from the VPN. +iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT +iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT + +iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE + +# kill daemons in case of restart +killall -KILL openvpn +killall -KILL ssserver + +# start daemons if configured +if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi +if [ -f /opt/amnezia/shadowsocks/ss-config.json ]; then (ssserver -c /opt/amnezia/shadowsocks/ss-config.json &); fi +tail -f /dev/null diff --git a/client/server_scripts/template_shadowsocks.ovpn b/client/server_scripts/openvpn_shadowsocks/template.ovpn similarity index 67% rename from client/server_scripts/template_shadowsocks.ovpn rename to client/server_scripts/openvpn_shadowsocks/template.ovpn index 730d544f..5164c22d 100644 --- a/client/server_scripts/template_shadowsocks.ovpn +++ b/client/server_scripts/openvpn_shadowsocks/template.ovpn @@ -1,12 +1,13 @@ client dev tun -proto $PROTO +proto tcp resolv-retry infinite nobind persist-key persist-tun -cipher AES-256-GCM -auth SHA512 +$OPENVPN_NCP_DISABLE +cipher $OPENVPN_CIPHER +auth $OPENVPN_HASH verb 3 tls-client tls-version-min 1.2 @@ -18,19 +19,19 @@ dhcp-option DNS $PRIMARY_DNS dhcp-option DNS $SECONDARY_DNS block-outside-dns -socks-proxy 127.0.0.1 $LOCAL_PROXY_PORT +socks-proxy 127.0.0.1 $SHADOWSOCKS_LOCAL_PORT route $REMOTE_HOST 255.255.255.255 net_gateway remote $REMOTE_HOST $REMOTE_PORT -$CA_CERT +$OPENVPN_CA_CERT -$CLIENT_CERT +$OPENVPN_CLIENT_CERT -$PRIV_KEY +$OPENVPN_PRIV_KEY -$TA_KEY +$OPENVPN_TA_KEY diff --git a/client/server_scripts/remove_all_containers.sh b/client/server_scripts/remove_all_containers.sh index 745aabb0..bae7cc3d 100644 --- a/client/server_scripts/remove_all_containers.sh +++ b/client/server_scripts/remove_all_containers.sh @@ -1,3 +1,3 @@ sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker stop sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker rm -sudo docker images -a | grep amnezia | awk '{print $3}' | xargs docker rmi +sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi diff --git a/client/server_scripts/setup_openvpn_server.sh b/client/server_scripts/setup_openvpn_server.sh deleted file mode 100644 index 0a64c749..00000000 --- a/client/server_scripts/setup_openvpn_server.sh +++ /dev/null @@ -1,28 +0,0 @@ -# CONTAINER_NAME=... this var will be set in ServerController -# Don't run commands in background like sh -c "openvpn &" - -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi -sudo iptables -P FORWARD ACCEPT - -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm update -y -q -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm install -y -q curl -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then sudo export DEBIAN_FRONTEND=noninteractive; sudo $pm_apt install -y -q docker.io; else sudo $pm_yum install -y -q docker; fi -sudo systemctl start docker - -sudo docker stop $CONTAINER_NAME -sudo docker rm -f $CONTAINER_NAME -sudo docker pull amneziavpn/openvpn:latest -sudo docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/udp --name $CONTAINER_NAME amneziavpn/openvpn:latest - -# Prevent to route packets outside of the container in case if server behind of the NAT -sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" - -sudo docker exec -i $CONTAINER_NAME sh -c "mkdir -p /opt/amneziavpn_data/clients" -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki" -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh" - -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/dh.pem /etc/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req MyReq nopass << EOF2 yes EOF2" -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3" -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4" -sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/ca.crt pki/issued/MyReq.crt pki/private/MyReq.key ta.key /etc/openvpn" -sudo docker exec -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf" diff --git a/client/server_scripts/setup_shadowsocks_server.sh b/client/server_scripts/setup_shadowsocks_server.sh deleted file mode 100644 index c4ee4177..00000000 --- a/client/server_scripts/setup_shadowsocks_server.sh +++ /dev/null @@ -1,29 +0,0 @@ -# CONTAINER_NAME=... this var will be set in ServerController -# Don't run commands in background like sh -c "openvpn &" - -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi -sudo iptables -P FORWARD ACCEPT - -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm update -y -q -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm install -y -q curl -pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then sudo export DEBIAN_FRONTEND=noninteractive; sudo $pm_apt install -y -q docker.io; else sudo $pm_yum install -y -q docker; fi -sudo systemctl start docker - -sudo docker stop $CONTAINER_NAME -sudo docker rm -f $CONTAINER_NAME -sudo docker pull amneziavpn/shadowsocks:latest -sudo docker run -d --restart always --cap-add=NET_ADMIN -p 6789:6789/tcp --name $CONTAINER_NAME amneziavpn/shadowsocks:latest - -# Prevent to route packets outside of the container in case if server behind of the NAT -sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" - -# OpenVpn -sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amneziavpn_data/clients;\ -cd /opt/amneziavpn_data && easyrsa init-pki;\ -cd /opt/amneziavpn_data && easyrsa gen-dh;\ -cd /opt/amneziavpn_data && cp pki/dh.pem /etc/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req MyReq nopass << EOF2 yes EOF2;\ -cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3;\ -cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4;\ -cd /opt/amneziavpn_data && cp pki/ca.crt pki/issued/MyReq.crt pki/private/MyReq.key ta.key /etc/openvpn' - -sudo docker exec -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf" diff --git a/client/settings.cpp b/client/settings.cpp index 8198ac6d..c38ee5e2 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -88,33 +88,112 @@ QString Settings::defaultContainerName(int serverIndex) const else return name; } +QMap Settings::containers(int serverIndex) const +{ + const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray(); + + QMap containersMap; + for (const QJsonValue &val : containers) { + containersMap.insert(containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); + } + + return containersMap; +} + +void Settings::setContainers(int serverIndex, const QMap &containers) +{ + QJsonObject s = server(serverIndex); + QJsonArray c; + for (const QJsonObject &o: containers) { + c.append(o); + } + s.insert(config_key::containers, c); + editServer(serverIndex, s); +} + + QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container) { if (container == DockerContainer::None) return QJsonObject(); - - const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray(); - for (const QJsonValue &val : containers) { - if (val.toObject().value(config_key::container).toString() == containerToString(container)) { - return val.toObject(); - } - } - return QJsonObject(); + return containers(serverIndex).value(container); } +//QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container) +//{ +// if (container == DockerContainer::None) return QJsonObject(); + +// const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray(); +// for (const QJsonValue &val : containers) { +// if (val.toObject().value(config_key::container).toString() == containerToString(container)) { +// return val.toObject(); +// } +// } +// return QJsonObject(); +//} + +void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config) +{ + if (container == DockerContainer::None) return; + + auto c = containers(serverIndex); + c[container] = config; + c[container][config_key::container] = containerToString(container); + setContainers(serverIndex, c); +} + +void Settings::removeContainerConfig(int serverIndex, DockerContainer container) +{ + if (container == DockerContainer::None) return; + + auto c = containers(serverIndex); + c.remove(container); + setContainers(serverIndex, c); +} + +//void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config) +//{ +// if (container == DockerContainer::None) return; + +// QJsonObject s = server(serverIndex); +// QJsonArray c = s.value(config_key::containers).toArray(); +// for (int i = c.size() - 1; i >= 0; i--) { +// if (c.at(i).toObject().value(config_key::container).toString() == containerToString(container)) { +// c.removeAt(i); +// } +// } + +// c.append(config); +// s.insert(config_key::containers, c); +// editServer(serverIndex, s); +//} + QJsonObject Settings::protocolConfig(int serverIndex, DockerContainer container, Protocol proto) { const QJsonObject &c = containerConfig(serverIndex, container); + return c.value(protoToString(proto)).toObject(); +} - switch (proto) { - case Protocol::OpenVpn: - return c.value(config_key::openvpn).toObject(); - case Protocol::ShadowSocks: - return c.value(config_key::shadowsocks).toObject(); - case Protocol::Cloak: - return c.value(config_key::cloak).toObject(); - default: - return QJsonObject(); +void Settings::setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config) +{ + QJsonObject c = containerConfig(serverIndex, container); + c.insert(protoToString(proto), config); + + setContainerConfig(serverIndex, container, c); +} + +void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto) +{ + if (proto == Protocol::Any) { + for (Protocol p: { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak, Protocol::WireGuard}) { + clearLastConnectionConfig(serverIndex, container, p); + } + return; } + + QJsonObject c = protocolConfig(serverIndex, container, proto); + c.remove(config_key::last_config); + setProtocolConfig(serverIndex, container, proto, c); + qDebug() << "Settings::clearLastConnectionConfig for" << protoToString(proto); } bool Settings::haveAuthData() const @@ -127,7 +206,6 @@ bool Settings::haveAuthData() const QString Settings::nextAvailableServerName() const { int i = 0; - //bool found = false; bool nameExist = false; do { diff --git a/client/settings.h b/client/settings.h index e6c21372..3a6c0e2c 100644 --- a/client/settings.h +++ b/client/settings.h @@ -10,6 +10,7 @@ #include #include "core/defs.h" +#include "protocols/protocols_defs.h" using namespace amnezia; @@ -57,8 +58,17 @@ public: DockerContainer defaultContainer(int serverIndex) const; QString defaultContainerName(int serverIndex) const; + QMap containers(int serverIndex) const; + void setContainers(int serverIndex, const QMap &containers); + QJsonObject containerConfig(int serverIndex, DockerContainer container); + void setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config); + void removeContainerConfig(int serverIndex, DockerContainer container); + QJsonObject protocolConfig(int serverIndex, DockerContainer container, Protocol proto); + void setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config); + + void clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto = Protocol::Any); bool haveAuthData() const; QString nextAvailableServerName() const; diff --git a/client/ui/mainwindow.cpp b/client/ui/mainwindow.cpp index 93a53642..972feb0b 100644 --- a/client/ui/mainwindow.cpp +++ b/client/ui/mainwindow.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -115,11 +116,14 @@ MainWindow::MainWindow(QWidget *parent) : m_ipAddressValidator.setRegExp(Utils::ipAddressRegExp()); m_ipAddressPortValidator.setRegExp(Utils::ipAddressPortRegExp()); + m_ipNetwok24Validator.setRegExp(Utils::ipNetwork24RegExp()); ui->lineEdit_new_server_ip->setValidator(&m_ipAddressPortValidator); ui->lineEdit_network_settings_dns1->setValidator(&m_ipAddressValidator); ui->lineEdit_network_settings_dns2->setValidator(&m_ipAddressValidator); + ui->lineEdit_proto_openvpn_subnet->setValidator(&m_ipNetwok24Validator); + ui->toolBox_share_connection->removeItem(ui->toolBox_share_connection->indexOf(ui->page_share_shadowsocks)); ui->page_share_shadowsocks->setVisible(false); } @@ -148,40 +152,28 @@ void MainWindow::goToPage(Page page, bool reset, bool slide) qDebug() << "goToPage" << page; if (reset) { if (page == Page::ServerSettings) { - updateSettings(); - - ui->label_server_settings_wait_info->hide(); - ui->label_server_settings_wait_info->clear(); - - QJsonObject server = m_settings.server(selectedServerIndex); - QString port = server.value(config_key::port).toString(); - ui->label_server_settings_server->setText(QString("%1@%2%3%4") - .arg(server.value(config_key::userName).toString()) - .arg(server.value(config_key::hostName).toString()) - .arg(port.isEmpty() ? "" : ":") - .arg(port)); - ui->lineEdit_server_settings_description->setText(server.value(config_key::description).toString()); + updateServerPage(); } if (page == Page::ShareConnection) { - QJsonObject ssConfig = ShadowSocksVpnProtocol::genShadowSocksConfig(m_settings.defaultServerCredentials()); +// QJsonObject ssConfig = ShadowSocksVpnProtocol::genShadowSocksConfig(m_settings.defaultServerCredentials()); - QString ssString = QString("%1:%2@%3:%4") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(QString::number(ssConfig.value("server_port").toInt())); +// QString ssString = QString("%1:%2@%3:%4") +// .arg(ssConfig.value("method").toString()) +// .arg(ssConfig.value("password").toString()) +// .arg(ssConfig.value("server").toString()) +// .arg(QString::number(ssConfig.value("server_port").toInt())); - ssString = "ss://" + ssString.toUtf8().toBase64(); - ui->lineEdit_share_ss_string->setText(ssString); - updateQRCodeImage(ssString, ui->label_share_ss_qr_code); +// ssString = "ss://" + ssString.toUtf8().toBase64(); +// ui->lineEdit_share_ss_string->setText(ssString); +// updateQRCodeImage(ssString, ui->label_share_ss_qr_code); - ui->label_share_ss_server->setText(ssConfig.value("server").toString()); - ui->label_share_ss_port->setText(QString::number(ssConfig.value("server_port").toInt())); - ui->label_share_ss_method->setText(ssConfig.value("method").toString()); - ui->label_share_ss_password->setText(ssConfig.value("password").toString()); +// ui->label_share_ss_server->setText(ssConfig.value("server").toString()); +// ui->label_share_ss_port->setText(QString::number(ssConfig.value("server_port").toInt())); +// ui->label_share_ss_method->setText(ssConfig.value("method").toString()); +// ui->label_share_ss_password->setText(ssConfig.value("password").toString()); } if (page == Page::ServersList) { - updateServersPage(); + updateServersListPage(); } if (page == Page::Start) { ui->label_new_server_wait_info->hide(); @@ -200,14 +192,26 @@ void MainWindow::goToPage(Page page, bool reset, bool slide) ui->pushButton_new_server_settings_openvpn->setChecked(true); ui->pushButton_new_server_settings_openvpn->setChecked(false); - ui->lineEdit_new_server_cloak_port->setText(amnezia::protocols::cloak::ckDefaultPort); - ui->lineEdit_new_server_cloak_site->setText(amnezia::protocols::cloak::ckDefaultRedirSite); + ui->lineEdit_new_server_cloak_port->setText(amnezia::protocols::cloak::defaultPort); + ui->lineEdit_new_server_cloak_site->setText(amnezia::protocols::cloak::defaultRedirSite); - ui->lineEdit_new_server_ss_port->setText(amnezia::protocols::shadowsocks::ssDefaultPort); - ui->comboBox_new_server_ss_cipher->setCurrentText(amnezia::protocols::shadowsocks::ssDefaultCipher); + ui->lineEdit_new_server_ss_port->setText(amnezia::protocols::shadowsocks::defaultPort); + ui->comboBox_new_server_ss_cipher->setCurrentText(amnezia::protocols::shadowsocks::defaultCipher); - ui->lineEdit_new_server_openvpn_port->setText(amnezia::protocols::openvpn::openvpnDefaultPort); - ui->comboBox_new_server_openvpn_proto->setCurrentText(amnezia::protocols::openvpn::openvpnDefaultProto); + ui->lineEdit_new_server_openvpn_port->setText(amnezia::protocols::openvpn::defaultPort); + ui->comboBox_new_server_openvpn_proto->setCurrentText(amnezia::protocols::openvpn::defaultTransportProto); + } + if (page == Page::ServerVpnProtocols) { + updateProtocolsPage(); + } + if (page == Page::AppSettings) { + updateAppSettingsPage(); + } + if (page == Page::Sites) { + updateSitesPage(); + } + if (page == Page::Vpn) { + updateVpnPage(); } ui->pushButton_new_server_connect_key->setChecked(false); @@ -424,23 +428,23 @@ void MainWindow::onPushButtonNewServerConnectConfigure(bool) { QJsonObject cloakConfig { { config_key::port, ui->lineEdit_new_server_cloak_port->text() }, - { config_key::container, amnezia::server::getContainerName(DockerContainer::OpenVpnOverCloak) }, + { config_key::container, amnezia::containerToString(DockerContainer::OpenVpnOverCloak) }, { config_key::cloak, QJsonObject { { config_key::site, ui->lineEdit_new_server_cloak_site->text() }} } }; QJsonObject ssConfig { { config_key::port, ui->lineEdit_new_server_ss_port->text() }, - { config_key::container, amnezia::server::getContainerName(DockerContainer::ShadowSocksOverOpenVpn) }, + { config_key::container, amnezia::containerToString(DockerContainer::OpenVpnOverShadowSocks) }, { config_key::shadowsocks, QJsonObject { { config_key::cipher, ui->comboBox_new_server_ss_cipher->currentText() }} } }; QJsonObject openVpnConfig { { config_key::port, ui->lineEdit_new_server_openvpn_port->text() }, - { config_key::container, amnezia::server::getContainerName(DockerContainer::OpenVpn) }, + { config_key::container, amnezia::containerToString(DockerContainer::OpenVpn) }, { config_key::openvpn, QJsonObject { - { config_key::transport_protocol, ui->comboBox_new_server_openvpn_proto->currentText() }} + { config_key::transport_proto, ui->comboBox_new_server_openvpn_proto->currentText() }} } }; @@ -454,20 +458,20 @@ void MainWindow::onPushButtonNewServerConnectConfigure(bool) if (ui->checkBox_new_server_ss->isChecked()) { containerConfigs.append(ssConfig); - containers.append(DockerContainer::ShadowSocksOverOpenVpn); + containers.append(DockerContainer::OpenVpnOverShadowSocks); } if (ui->checkBox_new_server_openvpn->isChecked()) { containerConfigs.append(openVpnConfig); - containers.append(DockerContainer::ShadowSocksOverOpenVpn); + containers.append(DockerContainer::OpenVpnOverShadowSocks); } - bool ok = true; -// bool ok = installServer(installCredentials, containers, configs, -// ui->page_new_server, -// ui->progressBar_new_server_connection, -// ui->pushButton_new_server_connect, -// ui->label_new_server_wait_info); +// bool ok = true; + bool ok = installServer(installCredentials, containers, containerConfigs, + ui->page_new_server_2, + ui->progressBar_new_server_connection, + ui->pushButton_new_server_connect, + ui->label_new_server_wait_info); if (ok) { QJsonObject server; @@ -587,6 +591,69 @@ bool MainWindow::installServer(ServerCredentials credentials, return true; } +ErrorCode MainWindow::doInstallAction(const std::function &action, QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info) +{ + progress->show(); + if (page) page->setEnabled(false); + if (button) button->setVisible(false); + + if (info) info->setVisible(true); + if (info) info->setText(tr("Please wait, configuring process may take up to 5 minutes")); + + + QTimer timer; + connect(&timer, &QTimer::timeout, [progress](){ + progress->setValue(progress->value() + 1); + }); + + progress->setValue(0); + timer.start(1000); + + ErrorCode e = action(); + qDebug() << "doInstallAction finished with code" << e; + + if (e) { + if (page) page->setEnabled(true); + if (button) button->setVisible(true); + if (info) info->setVisible(false); + + QMessageBox::warning(this, APPLICATION_NAME, + tr("Error occurred while configuring server.") + "\n" + + errorString(e)); + + progress->hide(); + return e; + } + + // just ui progressbar tweak + timer.stop(); + + int remaining_val = progress->maximum() - progress->value(); + + if (remaining_val > 0) { + QTimer timer1; + QEventLoop loop1; + + connect(&timer1, &QTimer::timeout, [&](){ + progress->setValue(progress->value() + 1); + if (progress->value() >= progress->maximum()) { + loop1.quit(); + } + }); + + timer1.start(5); + loop1.exec(); + } + + + progress->hide(); + if (button) button->show(); + if (page) page->setEnabled(true); + if (info) info->setText(tr("Operation finished")); + + return ErrorCode::NoError; +} + void MainWindow::onPushButtonReinstallServer(bool) { // onDisconnect(); @@ -606,7 +673,7 @@ void MainWindow::onPushButtonClearServer(bool) onDisconnect(); } - ErrorCode e = ServerController::removeContainer(m_settings.serverCredentials(selectedServerIndex), DockerContainer::None); + ErrorCode e = ServerController::removeAllContainers(m_settings.serverCredentials(selectedServerIndex)); ServerController::disconnectFromHost(m_settings.serverCredentials(selectedServerIndex)); if (e) { QMessageBox::warning(this, APPLICATION_NAME, @@ -630,9 +697,10 @@ void MainWindow::onPushButtonForgetServer(bool) onDisconnect(); } m_settings.removeServer(selectedServerIndex); + m_settings.setDefaultServer(0); closePage(); - updateServersPage(); + updateServersListPage(); } void MainWindow::onBytesChanged(quint64 receivedData, quint64 sentData) @@ -877,7 +945,7 @@ void MainWindow::setupUiConnections() QJsonObject server = m_settings.server(selectedServerIndex); server.insert(config_key::description, newText); m_settings.editServer(selectedServerIndex, server); - updateServersPage(); + updateServersListPage(); }); connect(ui->lineEdit_server_settings_description, &QLineEdit::returnPressed, this, [this](){ @@ -890,32 +958,128 @@ void MainWindow::setupUiConnections() void MainWindow::setupProtocolsPageConnections() { QJsonObject openvpnConfig; + + // default buttons + QList containers { + DockerContainer::OpenVpn, + DockerContainer::OpenVpnOverShadowSocks, + DockerContainer::OpenVpnOverCloak + }; + + // default buttons + QList defaultButtons { + ui->pushButton_proto_openvpn_cont_default, + ui->pushButton_proto_ss_openvpn_cont_default, + ui->pushButton_proto_cloak_openvpn_cont_default + }; + + for (int i = 0; i < containers.size(); ++i) { + connect(defaultButtons.at(i), &QPushButton::clicked, this, [this, containers, i](){ + m_settings.setDefaultContainer(selectedServerIndex, containers.at(i)); + updateProtocolsPage(); + }); + } + + // install buttons + QList installButtons { + ui->pushButton_proto_openvpn_cont_install, + ui->pushButton_proto_ss_openvpn_cont_install, + ui->pushButton_proto_cloak_openvpn_cont_install + }; + + for (int i = 0; i < containers.size(); ++i) { + QPushButton *button = installButtons.at(i); + DockerContainer container = containers.at(i); + + connect(button, &QPushButton::clicked, this, [this, container, button](bool checked){ + if (checked) { + ErrorCode e = doInstallAction([this, container](){ + return ServerController::setupContainer(m_settings.serverCredentials(selectedServerIndex), container); + }, + ui->page_server_protocols, ui->progressBar_protocols_container_reinstall, + nullptr, nullptr); + + if (!e) { + m_settings.setContainerConfig(selectedServerIndex, container, QJsonObject()); + } + } + else { + button->setEnabled(false); + ErrorCode e = ServerController::removeContainer(m_settings.serverCredentials(selectedServerIndex), container); + m_settings.removeContainerConfig(selectedServerIndex, container); + button->setEnabled(true); + } + + updateProtocolsPage(); + }); + } + + + // settings buttons + + // settings openvpn container connect(ui->pushButton_proto_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ - //updateOpenVpnPage(m_settings.server(selectedServerIndex).value()); + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpn, Protocol::OpenVpn)); + selectedDockerContainer = DockerContainer::OpenVpn; goToPage(Page::OpenVpnSettings); }); + + // settings shadowsocks container connect(ui->pushButton_proto_ss_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpnOverShadowSocks, Protocol::OpenVpn), + DockerContainer::OpenVpnOverShadowSocks); + selectedDockerContainer = DockerContainer::OpenVpnOverShadowSocks; goToPage(Page::OpenVpnSettings); }); + connect(ui->pushButton_proto_ss_openvpn_cont_ss_config, &QPushButton::clicked, this, [this](){ + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpnOverShadowSocks, Protocol::ShadowSocks)); + selectedDockerContainer = DockerContainer::OpenVpnOverShadowSocks; + goToPage(Page::ShadowSocksSettings); + }); + + // settings cloak container connect(ui->pushButton_proto_cloak_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpnOverCloak, Protocol::OpenVpn)); + selectedDockerContainer = DockerContainer::OpenVpnOverCloak; goToPage(Page::OpenVpnSettings); }); - - connect(ui->pushButton_proto_cloak_openvpn_cont_default, &QPushButton::clicked, this, [this](){ - m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::OpenVpnOverCloak); - updateSettings(); + connect(ui->pushButton_proto_cloak_openvpn_cont_ss_config, &QPushButton::clicked, this, [this](){ + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpnOverCloak, Protocol::ShadowSocks)); + selectedDockerContainer = DockerContainer::OpenVpnOverCloak; + goToPage(Page::ShadowSocksSettings); + }); + connect(ui->pushButton_proto_cloak_openvpn_cont_cloak_config, &QPushButton::clicked, this, [this](){ + updateOpenVpnPage(m_settings.protocolConfig(selectedServerIndex, DockerContainer::OpenVpnOverCloak, Protocol::Cloak)); + selectedDockerContainer = DockerContainer::OpenVpnOverCloak; + goToPage(Page::CloakSettings); }); - connect(ui->pushButton_proto_ss_openvpn_cont_default, &QPushButton::clicked, this, [this](){ - m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::ShadowSocksOverOpenVpn); - updateSettings(); + /// + // Protocols pages + connect(ui->checkBox_proto_openvpn_auto_encryption, &QCheckBox::stateChanged, this, [this](){ + ui->comboBox_proto_openvpn_cipher->setDisabled(ui->checkBox_proto_openvpn_auto_encryption->isChecked()); + ui->comboBox_proto_openvpn_hash->setDisabled(ui->checkBox_proto_openvpn_auto_encryption->isChecked()); }); - connect(ui->pushButton_proto_openvpn_cont_default, &QPushButton::clicked, this, [this](){ - m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::OpenVpn); - updateSettings(); - }); + connect(ui->pushButton_proto_openvpn_save, &QPushButton::clicked, this, [this](){ + QJsonObject protocolConfig = m_settings.protocolConfig(selectedServerIndex, selectedDockerContainer, Protocol::OpenVpn); + protocolConfig = getOpenVpnConfigFromPage(protocolConfig); + QJsonObject containerConfig = m_settings.containerConfig(selectedServerIndex, selectedDockerContainer); + QJsonObject newContainerConfig = containerConfig; + newContainerConfig.insert(config_key::openvpn, protocolConfig); + + ErrorCode e = doInstallAction([this, containerConfig, newContainerConfig](){ + return ServerController::updateContainer(m_settings.serverCredentials(selectedServerIndex), selectedDockerContainer, containerConfig, newContainerConfig); + }, + ui->page_proto_openvpn, ui->progressBar_proto_openvpn_reset, + ui->pushButton_proto_openvpn_save, ui->label_proto_openvpn_info); + + if (!e) { + m_settings.setContainerConfig(selectedServerIndex, selectedDockerContainer, newContainerConfig); + m_settings.clearLastConnectionConfig(selectedServerIndex, selectedDockerContainer); + } + }); } void MainWindow::setupNewServerPageConnections() @@ -1019,15 +1183,23 @@ void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) } void MainWindow::onConnect() +{ + + int serverIndex = m_settings.defaultServerIndex(); + ServerCredentials credentials = m_settings.serverCredentials(serverIndex); + DockerContainer container = m_settings.defaultContainer(serverIndex); + const QJsonObject &containerConfig = m_settings.containerConfig(serverIndex, container); + + onConnectWorker(serverIndex, credentials, container, containerConfig); +} + +void MainWindow::onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) { ui->label_error_text->clear(); ui->pushButton_connect->setChecked(true); qApp->processEvents(); - // TODO: Call connectToVpn with restricted server account - ServerCredentials credentials = m_settings.defaultServerCredentials(); - - ErrorCode errorCode = m_vpnConnection->connectToVpn(credentials); + ErrorCode errorCode = m_vpnConnection->connectToVpn(serverIndex, credentials, container, containerConfig); if (errorCode) { //ui->pushButton_connect->setChecked(false); QMessageBox::critical(this, APPLICATION_NAME, errorString(errorCode)); @@ -1121,34 +1293,56 @@ void MainWindow::onPushButtonDeleteCustomSiteClicked(const QString &siteToDelete } void MainWindow::updateSettings() +{ + +} + +void MainWindow::updateSitesPage() +{ + ui->listWidget_sites->clear(); + for (const QString &site : m_settings.customSites()) { + makeSitesListItem(ui->listWidget_sites, site); + } +} + +void MainWindow::updateVpnPage() { ui->radioButton_mode_selected_sites->setChecked(m_settings.customRouting()); ui->pushButton_vpn_add_site->setEnabled(m_settings.customRouting()); +} +void MainWindow::updateAppSettingsPage() +{ ui->checkBox_autostart->setChecked(Autostart::isAutostart()); ui->checkBox_autoconnect->setChecked(m_settings.isAutoConnect()); ui->lineEdit_network_settings_dns1->setText(m_settings.primaryDns()); ui->lineEdit_network_settings_dns2->setText(m_settings.secondaryDns()); +} - ui->listWidget_sites->clear(); - for(const QString &site : m_settings.customSites()) { - makeSitesListItem(ui->listWidget_sites, site); - } +void MainWindow::updateServerPage() +{ + ui->label_server_settings_wait_info->hide(); + ui->label_server_settings_wait_info->clear(); + QJsonObject server = m_settings.server(selectedServerIndex); + QString port = server.value(config_key::port).toString(); + ui->label_server_settings_server->setText(QString("%1@%2%3%4") + .arg(server.value(config_key::userName).toString()) + .arg(server.value(config_key::hostName).toString()) + .arg(port.isEmpty() ? "" : ":") + .arg(port)); + ui->lineEdit_server_settings_description->setText(server.value(config_key::description).toString()); - QJsonObject selectedServer = m_settings.server(selectedServerIndex); QString selectedContainerName = m_settings.defaultContainerName(selectedServerIndex); ui->label_server_settings_current_vpn_protocol->setText(tr("Protocol: ") + selectedContainerName); - qDebug() << "DefaultContainer(selectedServerIndex)" << selectedServerIndex << m_settings.defaultContainer(selectedServerIndex); - ui->pushButton_proto_cloak_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::OpenVpnOverCloak); - ui->pushButton_proto_ss_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::ShadowSocksOverOpenVpn); - ui->pushButton_proto_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::OpenVpn); + //qDebug() << "DefaultContainer(selectedServerIndex)" << selectedServerIndex << containerToString(m_settings.defaultContainer(selectedServerIndex)); + } -void MainWindow::updateServersPage() +void MainWindow::updateServersListPage() { ui->listWidget_servers->clear(); const QJsonArray &servers = m_settings.serversArray(); @@ -1161,6 +1355,34 @@ void MainWindow::updateServersPage() ui->listWidget_servers->setUpdatesEnabled(true); } +void MainWindow::updateProtocolsPage() +{ + ui->progressBar_protocols_container_reinstall->hide(); + + auto containers = m_settings.containers(selectedServerIndex); + + DockerContainer defaultContainer = m_settings.defaultContainer(selectedServerIndex); + ui->pushButton_proto_cloak_openvpn_cont_default->setChecked(defaultContainer == DockerContainer::OpenVpnOverCloak); + ui->pushButton_proto_ss_openvpn_cont_default->setChecked(defaultContainer == DockerContainer::OpenVpnOverShadowSocks); + ui->pushButton_proto_openvpn_cont_default->setChecked(defaultContainer == DockerContainer::OpenVpn); + + ui->pushButton_proto_cloak_openvpn_cont_default->setVisible(containers.contains(DockerContainer::OpenVpnOverCloak)); + ui->pushButton_proto_ss_openvpn_cont_default->setVisible(containers.contains(DockerContainer::OpenVpnOverShadowSocks)); + ui->pushButton_proto_openvpn_cont_default->setVisible(containers.contains(DockerContainer::OpenVpn)); + + ui->pushButton_proto_cloak_openvpn_cont_share->setVisible(containers.contains(DockerContainer::OpenVpnOverCloak)); + ui->pushButton_proto_ss_openvpn_cont_share->setVisible(containers.contains(DockerContainer::OpenVpnOverShadowSocks)); + ui->pushButton_proto_openvpn_cont_share->setVisible(containers.contains(DockerContainer::OpenVpn)); + + ui->pushButton_proto_cloak_openvpn_cont_install->setChecked(containers.contains(DockerContainer::OpenVpnOverCloak)); + ui->pushButton_proto_ss_openvpn_cont_install->setChecked(containers.contains(DockerContainer::OpenVpnOverShadowSocks)); + ui->pushButton_proto_openvpn_cont_install->setChecked(containers.contains(DockerContainer::OpenVpn)); + + ui->frame_openvpn_ss_cloak_settings->setVisible(containers.contains(DockerContainer::OpenVpnOverCloak)); + ui->frame_openvpn_ss_settings->setVisible(containers.contains(DockerContainer::OpenVpnOverShadowSocks)); + ui->frame_openvpn_settings->setVisible(containers.contains(DockerContainer::OpenVpn)); +} + void MainWindow::updateShareCodePage() { // QJsonObject o; @@ -1175,22 +1397,37 @@ void MainWindow::updateShareCodePage() //qDebug() << "Share code" << QJsonDocument(o).toJson(); } -void MainWindow::updateOpenVpnPage(const QJsonObject &openvpnConfig) -{ - ui->lineEdit_proto_openvpn_subnet->setText(nonEmpty(openvpnConfig.value(config_key::subnet_address).toString(), - protocols::vpnDefaultSubnetAddress)); +void MainWindow::updateOpenVpnPage(const QJsonObject &openvpnConfig, DockerContainer container) +{ + ui->radioButton_proto_openvpn_udp->setEnabled(true); + ui->radioButton_proto_openvpn_tcp->setEnabled(true); - QString trasnsport = nonEmpty(openvpnConfig.value(config_key::transport_protocol).toString(), - protocols::openvpn::openvpnDefaultProto); + ui->lineEdit_proto_openvpn_subnet->setText(openvpnConfig.value(config_key::subnet_address). + toString(protocols::vpnDefaultSubnetAddress)); - ui->radioButton_proto_openvpn_udp->setChecked(trasnsport == protocols::openvpn::openvpnDefaultProto); - ui->radioButton_proto_openvpn_tcp->setChecked(trasnsport != protocols::openvpn::openvpnDefaultProto); + QString trasnsport = openvpnConfig.value(config_key::transport_proto). + toString(protocols::openvpn::defaultTransportProto); - ui->comboBox_proto_openvpn_cipher->setCurrentText(nonEmpty(openvpnConfig.value(config_key::cipher).toString(), - protocols::openvpn::openvpnDefaultCipher)); + ui->radioButton_proto_openvpn_udp->setChecked(trasnsport == protocols::openvpn::defaultTransportProto); + ui->radioButton_proto_openvpn_tcp->setChecked(trasnsport != protocols::openvpn::defaultTransportProto); - ui->comboBox_proto_openvpn_cipher->setCurrentText(nonEmpty(openvpnConfig.value(config_key::hash).toString(), - protocols::openvpn::openvpnDefaultHash)); + ui->comboBox_proto_openvpn_cipher->setCurrentText(openvpnConfig.value(config_key::cipher). + toString(protocols::openvpn::defaultCipher)); + + ui->comboBox_proto_openvpn_hash->setCurrentText(openvpnConfig.value(config_key::hash). + toString(protocols::openvpn::defaultHash)); + + bool blockOutsideDns = openvpnConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); + ui->checkBox_proto_openvpn_block_dns->setChecked(blockOutsideDns); + + bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); + ui->checkBox_proto_openvpn_auto_encryption->setChecked(!isNcpDisabled); + + if (container == DockerContainer::OpenVpnOverShadowSocks) { + ui->radioButton_proto_openvpn_udp->setEnabled(false); + ui->radioButton_proto_openvpn_tcp->setEnabled(false); + ui->radioButton_proto_openvpn_tcp->setChecked(true); + } } void MainWindow::makeSitesListItem(QListWidget *listWidget, const QString &address) @@ -1232,7 +1469,7 @@ void MainWindow::makeServersListItem(QListWidget *listWidget, const QJsonObject connect(widget->ui->pushButton_default, &QPushButton::clicked, this, [this, index](){ m_settings.setDefaultServer(index); updateSettings(); - updateServersPage(); + updateServersListPage(); }); connect(widget->ui->pushButton_share, &QPushButton::clicked, this, [this, index](){ @@ -1275,3 +1512,15 @@ void MainWindow::updateQRCodeImage(const QString &text, QLabel *label) label->setPixmap(QPixmap::fromImage(encodeImage.scaledToWidth(label->width()))); } + +QJsonObject MainWindow::getOpenVpnConfigFromPage(QJsonObject oldConfig) +{ + oldConfig.insert(config_key::subnet_address, ui->lineEdit_proto_openvpn_subnet->text()); + oldConfig.insert(config_key::transport_proto, ui->radioButton_proto_openvpn_udp->isChecked() ? protocols::UDP : protocols::TCP); + oldConfig.insert(config_key::ncp_disable, ! ui->checkBox_proto_openvpn_auto_encryption->isChecked()); + oldConfig.insert(config_key::cipher, ui->comboBox_proto_openvpn_cipher->currentText()); + oldConfig.insert(config_key::hash, ui->comboBox_proto_openvpn_hash->currentText()); + oldConfig.insert(config_key::block_outside_dns, ui->checkBox_proto_openvpn_block_dns->isChecked()); + + return oldConfig; +} diff --git a/client/ui/mainwindow.h b/client/ui/mainwindow.h index 7079514b..c497280c 100644 --- a/client/ui/mainwindow.h +++ b/client/ui/mainwindow.h @@ -68,6 +68,7 @@ private slots: void onTrayActivated(QSystemTrayIcon::ActivationReason reason); void onConnect(); + void onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void onDisconnect(); @@ -81,6 +82,8 @@ private: bool installServer(ServerCredentials credentials, QList containers, QJsonArray configs, QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info); + ErrorCode doInstallAction(const std::function &action, QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info); + void setupTray(); void setTrayIcon(const QString &iconPath); @@ -89,15 +92,23 @@ private: void setupNewServerPageConnections(); void updateSettings(); - void updateServersPage(); + + void updateSitesPage(); + void updateVpnPage(); + void updateAppSettingsPage(); + void updateServerPage(); + void updateServersListPage(); + void updateProtocolsPage(); void updateShareCodePage(); - void updateOpenVpnPage(const QJsonObject &openvpnConfig); + void updateOpenVpnPage(const QJsonObject &openvpnConfig, DockerContainer container = DockerContainer::None); void makeSitesListItem(QListWidget* listWidget, const QString &address); void makeServersListItem(QListWidget* listWidget, const QJsonObject &server, bool isDefault, int index); void updateQRCodeImage(const QString &text, QLabel *label); + QJsonObject getOpenVpnConfigFromPage(QJsonObject oldConfig); + private: Ui::MainWindow *ui; VpnConnection* m_vpnConnection; @@ -111,6 +122,7 @@ private: QRegExpValidator m_ipAddressValidator; QRegExpValidator m_ipAddressPortValidator; + QRegExpValidator m_ipNetwok24Validator; CQR_Encode m_qrEncode; @@ -129,6 +141,7 @@ private: QStack pagesStack; int selectedServerIndex = -1; // server index to use when proto settings page opened + DockerContainer selectedDockerContainer; // same ServerCredentials installCredentials; // used to save cred between pages new_server and new_server_2 }; diff --git a/client/ui/mainwindow.ui b/client/ui/mainwindow.ui index b0d8d774..16f9b7af 100644 --- a/client/ui/mainwindow.ui +++ b/client/ui/mainwindow.ui @@ -287,7 +287,7 @@ QPushButton:hover { - 12 + 14 @@ -3530,6 +3530,9 @@ border: none; + + QLayout::SetMinAndMaxSize + @@ -3539,62 +3542,6 @@ border: none; - - - - - 36 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -padding: 0px; -image: url(:/images/connect_button_connected.png); -margin: 0px; - - - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -image: url(:/images/share.png); -padding: 0px; -margin: 0px; - - - - - - @@ -3626,6 +3573,76 @@ QPushButton:!checked { image: url(:/images/uncheck.png); } + + + + + + + true + + + false + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/connect_button_connected.png); +} +QPushButton:!checked { + image: url(:/images/connect_button_disconnected.png); +} @@ -3642,7 +3659,7 @@ QPushButton:!checked { - + @@ -3689,6 +3706,9 @@ QPushButton:!checked { + + QLayout::SetMinAndMaxSize + @@ -3698,62 +3718,6 @@ QPushButton:!checked { - - - - - 36 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -padding: 0px; -image: url(:/images/connect_button_disconnected.png); -margin: 0px; - - - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -image: url(:/images/share.png); -padding: 0px; -margin: 0px; - - - - - - @@ -3797,10 +3761,77 @@ QPushButton:!checked { + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/connect_button_connected.png); +} +QPushButton:!checked { + image: url(:/images/connect_button_disconnected.png); +} + + + + + + + true + + + - + @@ -3840,6 +3871,9 @@ QPushButton:!checked { + + QLayout::SetMinAndMaxSize + @@ -3849,62 +3883,6 @@ QPushButton:!checked { - - - - - 36 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -padding: 0px; -image: url(:/images/connect_button_connected.png); -margin: 0px; - - - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - PointingHandCursor - - - background: transparent; -image: url(:/images/share.png); -padding: 0px; -margin: 0px; - - - - - - @@ -3948,10 +3926,77 @@ QPushButton:!checked { + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/connect_button_connected.png); +} +QPushButton:!checked { + image: url(:/images/connect_button_disconnected.png); +} + + + + + + + true + + + - + @@ -3985,6 +4030,50 @@ QPushButton:!checked { + + + + 40 + 580 + 300 + 40 + + + + QProgressBar{ +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} + +QProgressBar::chunk { +background: rgba(255, 255, 255, 0.15); +border-radius: 4px 0px 0px 4px; + +} + + + + 24 + + + Qt::AlignCenter + + + true + + + Configuring... + + @@ -4106,8 +4195,8 @@ QToolBox::tab:hover { 0 0 - 100 - 30 + 360 + 500 @@ -4234,8 +4323,8 @@ background: #282932; 0 0 - 100 - 30 + 360 + 500 @@ -4484,7 +4573,7 @@ color: #100A44; true - + 10 @@ -4532,7 +4621,7 @@ QPushButton:hover { - + true @@ -4551,67 +4640,11 @@ QPushButton:hover { true - - - - 30 - 150 - 321 - 91 - - - - QGroupBox{ - border: 1px solid lightgray; - border-radius: 2px; - margin-top: 7ex; -} - -QGroupBox::title { - font-size: 16px; - font-style: normal; - font-weight: normal; - color: #181922; - - subcontrol-origin: margin; - subcontrol-position: top left; -} - - - Network protocol - - - - - 10 - 30 - 171 - 19 - - - - UDP - - - - - - 10 - 60 - 171 - 19 - - - - TCP - - - 30 - 290 + 330 151 31 @@ -4662,14 +4695,14 @@ QGroupBox::title { - + true 30 - 260 + 300 151 21 @@ -4685,7 +4718,7 @@ QGroupBox::title { 200 - 290 + 330 151 31 @@ -4736,14 +4769,14 @@ QGroupBox::title { - + true 200 - 260 + 300 151 21 @@ -4759,7 +4792,7 @@ QGroupBox::title { 30 - 350 + 390 321 21 @@ -4771,6 +4804,206 @@ QGroupBox::title { false + + + + 40 + 530 + 300 + 40 + + + + PointingHandCursor + + + QPushButton { +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} +QPushButton:hover { +background: #211966; +} + + + Save and restart VPN + + + + + true + + + + 30 + 140 + 151 + 21 + + + + Network protocol + + + true + + + + + + 30 + 170 + 321 + 71 + + + + QFrame{ + border: 1px solid lightgray; + border-radius: 2px; + margin-top: 0px; +} + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 40 + 171 + 19 + + + + TCP + + + + + + 10 + 10 + 171 + 19 + + + + UDP + + + + + + + 30 + 270 + 321 + 21 + + + + Auto-negotiate encryption + + + false + + + + + + 40 + 530 + 301 + 40 + + + + QProgressBar{ +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} + +QProgressBar::chunk { +background: rgba(255, 255, 255, 0.15); +border-radius: 4px 0px 0px 4px; + +} + + + + 24 + + + Qt::AlignCenter + + + true + + + Configuring... + + + + + true + + + + 40 + 570 + 301 + 41 + + + + + + + Qt::AlignCenter + + + true + + + progressBar_proto_openvpn_reset + label_38 + pushButton_back_from_openvpn_settings + lineEdit_proto_openvpn_subnet + label_98 + comboBox_proto_openvpn_cipher + label_99 + comboBox_proto_openvpn_hash + label_97 + checkBox_proto_openvpn_block_dns + pushButton_proto_openvpn_save + label_100 + frame_3 + checkBox_proto_openvpn_auto_encryption + label_proto_openvpn_info @@ -4792,7 +5025,7 @@ color: #100A44; - OpenVPN Settings + ShadowSocks Settings Qt::AlignCenter @@ -4833,6 +5066,170 @@ QPushButton:hover { + + + + 190 + 80 + 151 + 31 + + + + + chacha20-poly1305 + + + + + aes-256-gcm + + + + + aes-128-gcm + + + + + aes-256-cfb + + + + + aes-128-cfb + + + + + chacha20 + + + + + chacha20-ietf + + + + + + + 40 + 530 + 301 + 40 + + + + QProgressBar{ +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} + +QProgressBar::chunk { +background: rgba(255, 255, 255, 0.15); +border-radius: 4px 0px 0px 4px; + +} + + + + 24 + + + Qt::AlignCenter + + + true + + + Configuring... + + + + + + 40 + 530 + 300 + 40 + + + + PointingHandCursor + + + QPushButton { +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} +QPushButton:hover { +background: #211966; +} + + + Save and restart VPN + + + + + true + + + + 30 + 80 + 151 + 31 + + + + Cipher + + + true + + + + + true + + + + 40 + 570 + 301 + 41 + + + + + + + Qt::AlignCenter + + + true + + @@ -4863,7 +5260,7 @@ color: #100A44; true - + 10 @@ -4895,6 +5292,155 @@ QPushButton:hover { + + + + 190 + 80 + 151 + 31 + + + + + chacha20-poly1305 + + + + + aes-256-gcm + + + + + aes-128-gcm + + + + + plain + + + + + + true + + + + 30 + 80 + 151 + 31 + + + + Cipher + + + true + + + + + + 40 + 530 + 301 + 40 + + + + QProgressBar{ +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} + +QProgressBar::chunk { +background: rgba(255, 255, 255, 0.15); +border-radius: 4px 0px 0px 4px; + +} + + + + 24 + + + Qt::AlignCenter + + + true + + + Configuring... + + + + + true + + + + 40 + 580 + 301 + 41 + + + + + + + Qt::AlignCenter + + + true + + + + + + 40 + 530 + 300 + 40 + + + + PointingHandCursor + + + QPushButton { +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} +QPushButton:hover { +background: #211966; +} + + + Save and restart VPN + + diff --git a/client/utils.h b/client/utils.h index dd981e21..84031707 100644 --- a/client/utils.h +++ b/client/utils.h @@ -27,6 +27,9 @@ public: static QRegExp ipAddressPortRegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); } + static QRegExp ipNetwork24RegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "0$"); } + static bool processIsRunning(const QString& fileName); static void killProcessByName(const QString &name); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 369888ec..0b222132 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "ipc.h" @@ -80,11 +81,78 @@ ErrorCode VpnConnection::lastError() const return m_vpnProtocol.data()->lastError(); } -ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credentials, DockerContainer container) +QMap VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) +{ + QMap configs; + for (Protocol proto: { Protocol::OpenVpn, + Protocol::ShadowSocks, + Protocol::Cloak, + Protocol::WireGuard}) { + + QString cfg = containerConfig.value(protoToString(proto)).toObject().value(config_key::last_config).toString(); + + if (!cfg.isEmpty()) configs.insert(proto, cfg); + } + return configs; +} + +QString VpnConnection::createVpnConfigurationForProto(int serverIndex, + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Protocol proto, + ErrorCode *errorCode) +{ + ErrorCode e = ErrorCode::NoError; + auto lastVpnConfig = getLastVpnConfig(containerConfig); + + QString configData; + if (lastVpnConfig.contains(proto)) { + configData = lastVpnConfig.value(proto); + qDebug() << "VpnConnection::createVpnConfiguration using saved config for " << protoToString(proto); + } + else { + if (proto == Protocol::OpenVpn) { + configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, + container, containerConfig, &e); + } + else if (proto == Protocol::Cloak) { + configData = CloakConfigurator::genCloakConfig(credentials, + container, containerConfig, &e); + } + else if (proto == Protocol::ShadowSocks) { + configData = ShadowSocksConfigurator::genShadowSocksConfig(credentials, + container, containerConfig, &e); + } + + if (errorCode && e) { + *errorCode = e; + return ""; + } + + + if (serverIndex >= 0) { + QJsonObject protoObject = m_settings.protocolConfig(serverIndex, container, proto); + protoObject.insert(config_key::last_config, configData); + m_settings.setProtocolConfig(serverIndex, container, proto, protoObject); + } + } + + if (errorCode) *errorCode = e; + return configData; +} + +ErrorCode VpnConnection::createVpnConfiguration(int serverIndex, + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) { ErrorCode errorCode = ErrorCode::NoError; - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocksOverOpenVpn || container == DockerContainer::OpenVpnOverCloak) { - QString openVpnConfigData = OpenVpnConfigurator::genOpenVpnConfig(credentials, container, &errorCode); + + if (container == DockerContainer::OpenVpn || + container == DockerContainer::OpenVpnOverShadowSocks || + container == DockerContainer::OpenVpnOverCloak) { + + QString openVpnConfigData = + createVpnConfigurationForProto( + serverIndex, credentials, container, containerConfig, Protocol::OpenVpn, &errorCode); + + m_vpnConfiguration.insert(config::key_openvpn_config_data, openVpnConfigData); if (errorCode) { return errorCode; @@ -101,13 +169,21 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent } } - if (container == DockerContainer::ShadowSocksOverOpenVpn) { - QJsonObject ssConfigData = ShadowSocksVpnProtocol::genShadowSocksConfig(credentials); + if (container == DockerContainer::OpenVpnOverShadowSocks) { + QJsonObject ssConfigData = QJsonDocument::fromJson( + createVpnConfigurationForProto( + serverIndex, credentials, container, containerConfig, Protocol::ShadowSocks, &errorCode).toUtf8()). + object(); + m_vpnConfiguration.insert(config::key_shadowsocks_config_data, ssConfigData); } if (container == DockerContainer::OpenVpnOverCloak) { - QJsonObject cloakConfigData = CloakConfigurator::genCloakConfig(credentials, DockerContainer::OpenVpnOverCloak, &errorCode); + QJsonObject cloakConfigData = QJsonDocument::fromJson( + createVpnConfigurationForProto( + serverIndex, credentials, container, containerConfig, Protocol::Cloak, &errorCode).toUtf8()). + object(); + m_vpnConfiguration.insert(config::key_cloak_config_data, cloakConfigData); } @@ -115,17 +191,9 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent return ErrorCode::NoError; } -ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, DockerContainer container) +ErrorCode VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) { - qDebug() << "connectToVpn, CustomRouting is" << m_settings.customRouting(); -// qDebug() << "Cred" << m_settings.serverCredentials().hostName << -// m_settings.serverCredentials().password; - //protocol = Protocol::ShadowSocks; - container = DockerContainer::OpenVpnOverCloak; - - // TODO: Try protocols one by one in case of Protocol::Any - // TODO: Implement some behavior in case if connection not stable - qDebug() << "Connect to VPN"; + qDebug() << "СonnectToVpn, CustomRouting is" << m_settings.customRouting(); emit connectionStateChanged(VpnProtocol::ConnectionState::Connecting); @@ -133,13 +201,10 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Dock disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); m_vpnProtocol->stop(); m_vpnProtocol.reset(); - //m_vpnProtocol->deleteLater(); } - //qApp->processEvents(); - if (container == DockerContainer::None || container == DockerContainer::OpenVpn) { - ErrorCode e = createVpnConfiguration(credentials, DockerContainer::OpenVpn); + ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig); if (e) { emit connectionStateChanged(VpnProtocol::ConnectionState::Error); return e; @@ -152,8 +217,8 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Dock return e; } } - else if (container == DockerContainer::ShadowSocksOverOpenVpn) { - ErrorCode e = createVpnConfiguration(credentials, DockerContainer::ShadowSocksOverOpenVpn); + else if (container == DockerContainer::OpenVpnOverShadowSocks) { + ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpnOverShadowSocks, containerConfig); if (e) { emit connectionStateChanged(VpnProtocol::ConnectionState::Error); return e; @@ -167,7 +232,7 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Dock } } else if (container == DockerContainer::OpenVpnOverCloak) { - ErrorCode e = createVpnConfiguration(credentials, DockerContainer::OpenVpnOverCloak); + ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpnOverCloak, containerConfig); if (e) { emit connectionStateChanged(VpnProtocol::ConnectionState::Error); return e; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 9d7d5c94..532bac63 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -24,9 +24,16 @@ public: static QString bytesPerSecToText(quint64 bytes); ErrorCode lastError() const; - ErrorCode createVpnConfiguration(const ServerCredentials &credentials, DockerContainer container); - ErrorCode connectToVpn(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None); + static QMap getLastVpnConfig(const QJsonObject &containerConfig); + QString createVpnConfigurationForProto(int serverIndex, + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Protocol proto, + ErrorCode *errorCode = nullptr); + + ErrorCode createVpnConfiguration(int serverIndex, + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + + ErrorCode connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); bool isConnected() const;