- no dockerhub
- trafic masking
This commit is contained in:
parent
059c6404ab
commit
85b6b06cc9
31 changed files with 1106 additions and 256 deletions
|
@ -13,14 +13,19 @@ include("3rd/QRCodeGenerator/QRCodeGenerator.pri")
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
../ipc/ipc.h \
|
../ipc/ipc.h \
|
||||||
|
configurators/cloak_configurator.h \
|
||||||
core/defs.h \
|
core/defs.h \
|
||||||
core/errorstrings.h \
|
core/errorstrings.h \
|
||||||
core/ipcclient.h \
|
core/ipcclient.h \
|
||||||
core/openvpnconfigurator.h \
|
configurators/openvpn_configurator.h \
|
||||||
|
core/scripts_registry.h \
|
||||||
|
core/server_defs.h \
|
||||||
core/servercontroller.h \
|
core/servercontroller.h \
|
||||||
debug.h \
|
debug.h \
|
||||||
defines.h \
|
defines.h \
|
||||||
managementserver.h \
|
managementserver.h \
|
||||||
|
protocols/openvpnovercloakprotocol.h \
|
||||||
|
protocols/protocols_defs.h \
|
||||||
protocols/shadowsocksvpnprotocol.h \
|
protocols/shadowsocksvpnprotocol.h \
|
||||||
settings.h \
|
settings.h \
|
||||||
ui/Controls/SlidingStackedWidget.h \
|
ui/Controls/SlidingStackedWidget.h \
|
||||||
|
@ -32,12 +37,16 @@ HEADERS += \
|
||||||
protocols/openvpnprotocol.h \
|
protocols/openvpnprotocol.h \
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
configurators/cloak_configurator.cpp \
|
||||||
core/ipcclient.cpp \
|
core/ipcclient.cpp \
|
||||||
core/openvpnconfigurator.cpp \
|
configurators/openvpn_configurator.cpp \
|
||||||
|
core/scripts_registry.cpp \
|
||||||
|
core/server_defs.cpp \
|
||||||
core/servercontroller.cpp \
|
core/servercontroller.cpp \
|
||||||
debug.cpp \
|
debug.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
managementserver.cpp \
|
managementserver.cpp \
|
||||||
|
protocols/openvpnovercloakprotocol.cpp \
|
||||||
protocols/shadowsocksvpnprotocol.cpp \
|
protocols/shadowsocksvpnprotocol.cpp \
|
||||||
settings.cpp \
|
settings.cpp \
|
||||||
ui/Controls/SlidingStackedWidget.cpp \
|
ui/Controls/SlidingStackedWidget.cpp \
|
||||||
|
|
45
client/configurators/cloak_configurator.cpp
Normal file
45
client/configurators/cloak_configurator.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#include "cloak_configurator.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
|
QJsonObject CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
||||||
|
Protocol proto, ErrorCode *errorCode)
|
||||||
|
{
|
||||||
|
ErrorCode e = ErrorCode::NoError;
|
||||||
|
|
||||||
|
DockerContainer container = amnezia::containerForProto(proto);
|
||||||
|
|
||||||
|
QString cloakPublicKey = ServerController::getTextFileFromContainer(container, credentials,
|
||||||
|
amnezia::protocols::cloak::ckPublicKeyPath(), &e);
|
||||||
|
cloakPublicKey.replace("\n", "");
|
||||||
|
|
||||||
|
QString cloakBypassUid = ServerController::getTextFileFromContainer(container, credentials,
|
||||||
|
amnezia::protocols::cloak::ckBypassUidKeyPath(), &e);
|
||||||
|
cloakBypassUid.replace("\n", "");
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
if (errorCode) *errorCode = e;
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject config;
|
||||||
|
config.insert("Transport", "direct");
|
||||||
|
config.insert("ProxyMethod", "openvpn");
|
||||||
|
config.insert("EncryptionMethod", "aes-gcm");
|
||||||
|
config.insert("UID", cloakBypassUid);
|
||||||
|
config.insert("PublicKey", cloakPublicKey);
|
||||||
|
config.insert("ServerName", amnezia::protocols::cloak::ckDefaultRedirSite());
|
||||||
|
config.insert("NumConn", 4);
|
||||||
|
config.insert("BrowserSig", "chrome");
|
||||||
|
config.insert("StreamTimeout", 300);
|
||||||
|
|
||||||
|
// Amnezia field
|
||||||
|
config.insert("Remote", credentials.hostName);
|
||||||
|
|
||||||
|
qDebug().noquote() << QJsonDocument(config).toJson();
|
||||||
|
return config;
|
||||||
|
}
|
18
client/configurators/cloak_configurator.h
Normal file
18
client/configurators/cloak_configurator.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef CLOAK_CONFIGURATOR_H
|
||||||
|
#define CLOAK_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "core/defs.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "core/servercontroller.h"
|
||||||
|
|
||||||
|
class CloakConfigurator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
static QJsonObject genCloakConfig(const ServerCredentials &credentials, Protocol proto,
|
||||||
|
ErrorCode *errorCode = nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CLOAK_CONFIGURATOR_H
|
|
@ -1,24 +1,15 @@
|
||||||
#include "openvpnconfigurator.h"
|
#include "openvpn_configurator.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QRandomGenerator>
|
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
#include <utils.h>
|
||||||
|
|
||||||
QString OpenVpnConfigurator::getRandomString(int len)
|
#include "core/server_defs.h"
|
||||||
{
|
#include "protocols/protocols_defs.h"
|
||||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
#include "core/scripts_registry.h"
|
||||||
|
|
||||||
QString randomString;
|
|
||||||
for(int i=0; i<len; ++i) {
|
|
||||||
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
|
||||||
QChar nextChar = possibleCharacters.at(index);
|
|
||||||
randomString.append(nextChar);
|
|
||||||
}
|
|
||||||
return randomString;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString OpenVpnConfigurator::getEasyRsaShPath()
|
QString OpenVpnConfigurator::getEasyRsaShPath()
|
||||||
{
|
{
|
||||||
|
@ -26,17 +17,10 @@ QString OpenVpnConfigurator::getEasyRsaShPath()
|
||||||
// easyrsa sh path should looks like
|
// easyrsa sh path should looks like
|
||||||
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
|
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
|
||||||
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
|
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
|
||||||
// easyRsaShPath.replace("C:\\", "/cygdrive/c/");
|
|
||||||
// easyRsaShPath.replace("\\", "/");
|
|
||||||
easyRsaShPath = "\"" + easyRsaShPath + "\"";
|
easyRsaShPath = "\"" + easyRsaShPath + "\"";
|
||||||
|
|
||||||
// easyRsaShPath = "\"/cygdrive/c/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa\"";
|
|
||||||
|
|
||||||
// easyRsaShPath = "\"C:\\Program Files (x86)\\AmneziaVPN\\easyrsa\\easyrsa\"";
|
|
||||||
qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath;
|
qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath;
|
||||||
|
|
||||||
return easyRsaShPath;
|
return easyRsaShPath;
|
||||||
// return "\"/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa\"";
|
|
||||||
#else
|
#else
|
||||||
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
|
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
|
||||||
#endif
|
#endif
|
||||||
|
@ -126,7 +110,7 @@ ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &client
|
||||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||||
{
|
{
|
||||||
OpenVpnConfigurator::ConnectionData connData;
|
OpenVpnConfigurator::ConnectionData connData;
|
||||||
connData.clientId = getRandomString(32);
|
connData.clientId = Utils::getRandomString(32);
|
||||||
|
|
||||||
QTemporaryDir dir;
|
QTemporaryDir dir;
|
||||||
// if (dir.isValid()) {
|
// if (dir.isValid()) {
|
||||||
|
@ -165,15 +149,11 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
|
QString reqFileName = QString("%1/%2.req").
|
||||||
|
arg(amnezia::protocols::openvpn::clientsDirPath()).
|
||||||
|
arg(connData.clientId);
|
||||||
|
|
||||||
DockerContainer container;
|
DockerContainer container = amnezia::containerForProto(proto);
|
||||||
if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn;
|
|
||||||
else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks;
|
|
||||||
else {
|
|
||||||
if (errorCode) *errorCode = ErrorCode::InternalError;
|
|
||||||
return connData;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||||
if (e) {
|
if (e) {
|
||||||
|
@ -181,20 +161,22 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = ServerController::signCert(container, credentials, connData.clientId);
|
e = signCert(container, credentials, connData.clientId);
|
||||||
if (e) {
|
if (e) {
|
||||||
if (errorCode) *errorCode = e;
|
if (errorCode) *errorCode = e;
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::caCertPath(), &e);
|
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath(), &e);
|
||||||
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
|
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials,
|
||||||
|
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath()).arg(connData.clientId), &e);
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
if (errorCode) *errorCode = e;
|
if (errorCode) *errorCode = e;
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
|
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath(), &e);
|
||||||
|
|
||||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||||
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
|
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
|
||||||
|
@ -214,23 +196,31 @@ Settings &OpenVpnConfigurator::m_settings()
|
||||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
|
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
|
||||||
Protocol proto, ErrorCode *errorCode)
|
Protocol proto, ErrorCode *errorCode)
|
||||||
{
|
{
|
||||||
QFile configTemplFile;
|
// QFile configTemplFile;
|
||||||
if (proto == Protocol::OpenVpn)
|
// if (proto == Protocol::OpenVpn)
|
||||||
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
|
// configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
|
||||||
else if (proto == Protocol::ShadowSocks) {
|
// else if (proto == Protocol::ShadowSocks) {
|
||||||
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
|
// configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
|
||||||
}
|
// }
|
||||||
|
|
||||||
configTemplFile.open(QIODevice::ReadOnly);
|
// configTemplFile.open(QIODevice::ReadOnly);
|
||||||
QString config = configTemplFile.readAll();
|
// QString config = configTemplFile.readAll();
|
||||||
|
|
||||||
|
QString config = amnezia::scriptData(ProtocolScriptType::openvpn_template, proto);
|
||||||
|
|
||||||
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
|
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
|
||||||
|
if (errorCode && *errorCode) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (proto == Protocol::OpenVpn)
|
if (proto == Protocol::OpenVpn)
|
||||||
config.replace("$PROTO", "udp");
|
config.replace("$PROTO", "udp");
|
||||||
else if (proto == Protocol::ShadowSocks) {
|
else if (proto == Protocol::ShadowSocks) {
|
||||||
config.replace("$PROTO", "tcp");
|
config.replace("$PROTO", "tcp");
|
||||||
config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort()));
|
config.replace("$LOCAL_PROXY_PORT", QString::number(amnezia::protocols::shadowsocks::ssContainerPort()));
|
||||||
|
}
|
||||||
|
else if (proto == Protocol::OpenVpnOverCloak) {
|
||||||
|
config.replace("$PROTO", "tcp");
|
||||||
}
|
}
|
||||||
|
|
||||||
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
|
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
|
||||||
|
@ -241,7 +231,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
||||||
}
|
}
|
||||||
|
|
||||||
config.replace("$REMOTE_HOST", connData.host);
|
config.replace("$REMOTE_HOST", connData.host);
|
||||||
config.replace("$REMOTE_PORT", "1194");
|
config.replace("$REMOTE_PORT", amnezia::protocols::openvpn::openvpnDefaultPort());
|
||||||
config.replace("$CA_CERT", connData.caCert);
|
config.replace("$CA_CERT", connData.caCert);
|
||||||
config.replace("$CLIENT_CERT", connData.clientCert);
|
config.replace("$CLIENT_CERT", connData.clientCert);
|
||||||
config.replace("$PRIV_KEY", connData.privKey);
|
config.replace("$PRIV_KEY", connData.privKey);
|
||||||
|
@ -287,3 +277,23 @@ QString OpenVpnConfigurator::convertOpenSShKey(const QString &key)
|
||||||
|
|
||||||
return tmp.readAll();
|
return tmp.readAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
|
||||||
|
const ServerCredentials &credentials, QString clientId)
|
||||||
|
{
|
||||||
|
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::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(clientId);
|
||||||
|
|
||||||
|
QStringList scriptList {script_import, script_sign};
|
||||||
|
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
|
||||||
|
|
||||||
|
return ServerController::runScript(ServerController::sshParams(credentials), script);
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
#ifndef OPENVPNCONFIGURATOR_H
|
#ifndef OPENVPN_CONFIGURATOR_H
|
||||||
#define OPENVPNCONFIGURATOR_H
|
#define OPENVPN_CONFIGURATOR_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
#include "defs.h"
|
#include "core/defs.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "servercontroller.h"
|
#include "core/servercontroller.h"
|
||||||
|
|
||||||
|
|
||||||
class OpenVpnConfigurator
|
class OpenVpnConfigurator
|
||||||
{
|
{
|
||||||
|
@ -28,8 +27,10 @@ public:
|
||||||
|
|
||||||
static QString convertOpenSShKey(const QString &key);
|
static QString convertOpenSShKey(const QString &key);
|
||||||
|
|
||||||
|
static ErrorCode signCert(DockerContainer container,
|
||||||
|
const ServerCredentials &credentials, QString clientId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QString getRandomString(int len);
|
|
||||||
static QString getEasyRsaShPath();
|
static QString getEasyRsaShPath();
|
||||||
|
|
||||||
static QProcessEnvironment prepareEnv();
|
static QProcessEnvironment prepareEnv();
|
||||||
|
@ -44,4 +45,4 @@ private:
|
||||||
static Settings &m_settings();
|
static Settings &m_settings();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPENVPNCONFIGURATOR_H
|
#endif // OPENVPN_CONFIGURATOR_H
|
|
@ -9,15 +9,31 @@ enum class Protocol {
|
||||||
Any,
|
Any,
|
||||||
OpenVpn,
|
OpenVpn,
|
||||||
ShadowSocks,
|
ShadowSocks,
|
||||||
|
OpenVpnOverCloak,
|
||||||
WireGuard
|
WireGuard
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DockerContainer {
|
enum class DockerContainer {
|
||||||
|
None,
|
||||||
OpenVpn,
|
OpenVpn,
|
||||||
ShadowSocks,
|
ShadowSocks,
|
||||||
|
OpenVpnOverCloak,
|
||||||
WireGuard
|
WireGuard
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DockerContainer containerForProto(Protocol proto)
|
||||||
|
{
|
||||||
|
Q_ASSERT(proto != Protocol::Any);
|
||||||
|
|
||||||
|
switch (proto) {
|
||||||
|
case Protocol::OpenVpn: return DockerContainer::OpenVpn;
|
||||||
|
case Protocol::OpenVpnOverCloak: return DockerContainer::OpenVpnOverCloak;
|
||||||
|
case Protocol::ShadowSocks: return DockerContainer::ShadowSocks;
|
||||||
|
case Protocol::WireGuard: return DockerContainer::WireGuard;
|
||||||
|
case Protocol::Any: return DockerContainer::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ServerCredentials
|
struct ServerCredentials
|
||||||
{
|
{
|
||||||
QString hostName;
|
QString hostName;
|
||||||
|
@ -48,6 +64,7 @@ enum ErrorCode
|
||||||
// Ssh remote process errors
|
// Ssh remote process errors
|
||||||
SshRemoteProcessCreationError,
|
SshRemoteProcessCreationError,
|
||||||
FailedToStartRemoteProcessError, RemoteProcessCrashError,
|
FailedToStartRemoteProcessError, RemoteProcessCrashError,
|
||||||
|
SshSftpError,
|
||||||
|
|
||||||
// Local errors
|
// Local errors
|
||||||
FailedToSaveConfigData,
|
FailedToSaveConfigData,
|
||||||
|
@ -59,6 +76,7 @@ enum ErrorCode
|
||||||
OpenVpnExecutableMissing,
|
OpenVpnExecutableMissing,
|
||||||
EasyRsaExecutableMissing,
|
EasyRsaExecutableMissing,
|
||||||
ShadowSocksExecutableMissing,
|
ShadowSocksExecutableMissing,
|
||||||
|
CloakExecutableMissing,
|
||||||
AmneziaServiceConnectionFailed,
|
AmneziaServiceConnectionFailed,
|
||||||
|
|
||||||
// VPN errors
|
// VPN errors
|
||||||
|
@ -67,7 +85,8 @@ enum ErrorCode
|
||||||
|
|
||||||
// 3rd party utils errors
|
// 3rd party utils errors
|
||||||
OpenVpnExecutableCrashed,
|
OpenVpnExecutableCrashed,
|
||||||
ShadowSocksExecutableCrashed
|
ShadowSocksExecutableCrashed,
|
||||||
|
CloakExecutableCrashed
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace config {
|
namespace config {
|
||||||
|
@ -75,10 +94,10 @@ namespace config {
|
||||||
static QString key_openvpn_config_data() { return "openvpn_config_data"; }
|
static QString key_openvpn_config_data() { return "openvpn_config_data"; }
|
||||||
static QString key_openvpn_config_path() { return "openvpn_config_path"; }
|
static QString key_openvpn_config_path() { return "openvpn_config_path"; }
|
||||||
static QString key_shadowsocks_config_data() { return "shadowsocks_config_data"; }
|
static QString key_shadowsocks_config_data() { return "shadowsocks_config_data"; }
|
||||||
|
static QString key_cloak_config_data() { return "cloak_config_data"; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|
||||||
#endif // DEFS_H
|
#endif // DEFS_H
|
||||||
|
|
60
client/core/scripts_registry.cpp
Normal file
60
client/core/scripts_registry.cpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#include "scripts_registry.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
QString amnezia::scriptFolder(amnezia::Protocol proto)
|
||||||
|
{
|
||||||
|
switch (proto) {
|
||||||
|
case Protocol::OpenVpn: return QLatin1String("openvpn");
|
||||||
|
case Protocol::OpenVpnOverCloak: return QLatin1String("openvpn_cloak");
|
||||||
|
case Protocol::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||||
|
case Protocol::WireGuard: return QLatin1String("wireguard");
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString amnezia::scriptName(SharedScriptType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
|
||||||
|
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
|
||||||
|
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
|
||||||
|
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString amnezia::scriptName(ProtocolScriptType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile");
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString amnezia::scriptData(amnezia::SharedScriptType type)
|
||||||
|
{
|
||||||
|
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
|
||||||
|
QFile file(fileName);
|
||||||
|
if (! file.open(QIODevice::ReadOnly)) {
|
||||||
|
qDebug() << "Error opening script" << fileName;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return file.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString amnezia::scriptData(amnezia::ProtocolScriptType type, amnezia::Protocol proto)
|
||||||
|
{
|
||||||
|
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(proto), amnezia::scriptName(type));
|
||||||
|
QFile file(fileName);
|
||||||
|
if (! file.open(QIODevice::ReadOnly)) {
|
||||||
|
qDebug() << "Error opening script" << fileName;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
QByteArray data = file.readAll();
|
||||||
|
data.replace("\r", "");
|
||||||
|
return data;
|
||||||
|
}
|
34
client/core/scripts_registry.h
Normal file
34
client/core/scripts_registry.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef SCRIPTS_REGISTRY_H
|
||||||
|
#define SCRIPTS_REGISTRY_H
|
||||||
|
|
||||||
|
#include <QLatin1String>
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
namespace amnezia {
|
||||||
|
|
||||||
|
enum SharedScriptType {
|
||||||
|
// General scripts
|
||||||
|
prepare_host,
|
||||||
|
install_docker,
|
||||||
|
build_container,
|
||||||
|
setup_host_firewall,
|
||||||
|
};
|
||||||
|
enum ProtocolScriptType {
|
||||||
|
// Protocol scripts
|
||||||
|
dockerfile,
|
||||||
|
configure_container,
|
||||||
|
container_startup,
|
||||||
|
openvpn_template
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
QString scriptFolder(Protocol proto);
|
||||||
|
|
||||||
|
QString scriptName(SharedScriptType type);
|
||||||
|
QString scriptName(ProtocolScriptType type);
|
||||||
|
|
||||||
|
QString scriptData(SharedScriptType type);
|
||||||
|
QString scriptData(ProtocolScriptType type, Protocol proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SCRIPTS_REGISTRY_H
|
16
client/core/server_defs.cpp
Normal file
16
client/core/server_defs.cpp
Normal file
|
@ -0,0 +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::ShadowSocks): return "amnezia-shadowsocks";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container)
|
||||||
|
{
|
||||||
|
return "/opt/amnezia/" + getContainerName(container);
|
||||||
|
}
|
19
client/core/server_defs.h
Normal file
19
client/core/server_defs.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef SERVER_DEFS_H
|
||||||
|
#define SERVER_DEFS_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
namespace amnezia {
|
||||||
|
namespace server {
|
||||||
|
QString getContainerName(amnezia::DockerContainer container);
|
||||||
|
QString getDockerfileFolder(amnezia::DockerContainer container);
|
||||||
|
|
||||||
|
static QString vpnDefaultSubnetIp() { return "10.8.0.0"; }
|
||||||
|
static QString vpnDefaultSubnetMask() { return "255.255.255.0"; }
|
||||||
|
static QString vpnDefaultSubnetMaskVal() { return "24"; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SERVER_DEFS_H
|
|
@ -9,24 +9,20 @@
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
|
#include "sftpchannel.h"
|
||||||
#include "sshconnectionmanager.h"
|
#include "sshconnectionmanager.h"
|
||||||
|
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
#include "server_defs.h"
|
||||||
|
#include "scripts_registry.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace QSsh;
|
using namespace QSsh;
|
||||||
|
|
||||||
QString ServerController::getContainerName(DockerContainer container)
|
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script,
|
||||||
{
|
|
||||||
switch (container) {
|
|
||||||
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
|
|
||||||
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode ServerController::runScript(const QHash<QString, QString> &vars,
|
|
||||||
const SshConnectionParameters &sshParams, QString script,
|
|
||||||
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
|
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
|
||||||
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
|
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
|
||||||
{
|
{
|
||||||
|
@ -39,21 +35,36 @@ ErrorCode ServerController::runScript(const QHash<QString, QString> &vars,
|
||||||
|
|
||||||
qDebug() << "Run script";
|
qDebug() << "Run script";
|
||||||
|
|
||||||
|
QString totalLine;
|
||||||
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
|
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
|
||||||
for (int i = 0; i < lines.count(); i++) {
|
for (int i = 0; i < lines.count(); i++) {
|
||||||
QString line = lines.at(i);
|
QString currentLine = lines.at(i);
|
||||||
|
QString nextLine;
|
||||||
|
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
|
||||||
|
|
||||||
for (const QString &var : vars.keys()) {
|
if (totalLine.isEmpty()) {
|
||||||
//qDebug() << "Replacing" << var << vars.value(var);
|
totalLine = currentLine;
|
||||||
line.replace(var, vars.value(var));
|
}
|
||||||
|
else {
|
||||||
|
totalLine = totalLine + "\n" + currentLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith("#")) {
|
QString lineToExec;
|
||||||
|
if (currentLine.endsWith("\\")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lineToExec = totalLine;
|
||||||
|
totalLine.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run collected line
|
||||||
|
if (totalLine.startsWith("#")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug().noquote() << "EXEC" << line;
|
qDebug().noquote() << "EXEC" << lineToExec;
|
||||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
|
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
|
||||||
|
|
||||||
if (!proc) {
|
if (!proc) {
|
||||||
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
||||||
|
@ -103,61 +114,106 @@ ErrorCode ServerController::runScript(const QHash<QString, QString> &vars,
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
ErrorCode ServerController::installDocker(const ServerCredentials &credentials)
|
||||||
const ServerCredentials &credentials, QString &file, const QString &path)
|
|
||||||
{
|
{
|
||||||
QString script = QString("sudo docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
|
// Setup openvpn part
|
||||||
arg(getContainerName(container)).arg(file).arg(path);
|
QString scriptData = amnezia::scriptData(SharedScriptType::install_docker);
|
||||||
|
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||||
|
|
||||||
// qDebug().noquote() << "uploadTextFileToContainer\n" << script;
|
QString stdOut;
|
||||||
|
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
|
stdOut += data + "\n";
|
||||||
|
|
||||||
SshConnection *client = connectToHost(sshParams(credentials));
|
if (data.contains("Automatically restart Docker daemon?")) {
|
||||||
if (client->state() != SshConnection::State::Connected) {
|
proc->write("yes\n");
|
||||||
return fromSshConnectionErrorCode(client->errorState());
|
}
|
||||||
}
|
};
|
||||||
|
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
|
||||||
|
stdOut += data + "\n";
|
||||||
|
};
|
||||||
|
|
||||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
|
return runScript(sshParams(credentials),
|
||||||
|
replaceVars(scriptData, genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)),
|
||||||
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
}
|
||||||
|
|
||||||
if (!proc) {
|
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||||
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
const ServerCredentials &credentials, const QString &file, const QString &path)
|
||||||
return ErrorCode::SshRemoteProcessCreationError;
|
{
|
||||||
}
|
ErrorCode e;
|
||||||
|
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||||
|
uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
|
||||||
|
|
||||||
QEventLoop wait;
|
e = runScript(sshParams(credentials),
|
||||||
int exitStatus = -1;
|
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||||
|
genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
|
if (e) return e;
|
||||||
// qDebug() << "uploadTextFileToContainer started";
|
|
||||||
|
runScript(sshParams(credentials),
|
||||||
|
replaceVars(QString("sudo shred %1").arg(tmpFileName),
|
||||||
|
genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
|
runScript(sshParams(credentials),
|
||||||
|
replaceVars(QString("sudo rm %1").arg(tmpFileName),
|
||||||
|
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<SshRemoteProcess> 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::closed, &wait, [&](int status){
|
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||||
//qDebug() << "Remote process exited with status" << status;
|
// qDebug().noquote() << proc->readAllStandardOutput();
|
||||||
exitStatus = status;
|
// });
|
||||||
wait.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||||
qDebug().noquote() << proc->readAllStandardOutput();
|
// qDebug().noquote() << proc->readAllStandardError();
|
||||||
});
|
// });
|
||||||
|
|
||||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
// proc->start();
|
||||||
qDebug().noquote() << proc->readAllStandardError();
|
|
||||||
});
|
|
||||||
|
|
||||||
proc->start();
|
// if (exitStatus < 0) {
|
||||||
|
// wait.exec();
|
||||||
|
// }
|
||||||
|
|
||||||
if (exitStatus < 0) {
|
// return fromSshProcessExitStatus(exitStatus);
|
||||||
wait.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
return fromSshProcessExitStatus(exitStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ServerController::getTextFileFromContainer(DockerContainer container,
|
QString ServerController::getTextFileFromContainer(DockerContainer container,
|
||||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
|
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
|
||||||
{
|
{
|
||||||
|
if (errorCode) *errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
|
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
|
||||||
arg(getContainerName(container)).arg(path);
|
arg(amnezia::server::getContainerName(container)).arg(path);
|
||||||
|
|
||||||
qDebug().noquote() << "Copy file from container\n" << script;
|
qDebug().noquote() << "Copy file from container\n" << script;
|
||||||
|
|
||||||
|
@ -201,28 +257,12 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
|
||||||
return proc->readAllStandardOutput();
|
return proc->readAllStandardOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::signCert(DockerContainer container,
|
|
||||||
const ServerCredentials &credentials, QString clientId)
|
|
||||||
{
|
|
||||||
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
|
|
||||||
"easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
|
|
||||||
.arg(getContainerName(container)).arg(clientId);
|
|
||||||
|
|
||||||
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
|
|
||||||
"easyrsa sign-req client %2\"")
|
|
||||||
.arg(getContainerName(container)).arg(clientId);
|
|
||||||
|
|
||||||
QStringList script {script_import, script_sign};
|
|
||||||
|
|
||||||
return runScript(genVarsForScript(credentials, container), sshParams(credentials), script.join("\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
|
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
QString caCert = ServerController::getTextFileFromContainer(container,
|
QString caCert = ServerController::getTextFileFromContainer(container,
|
||||||
credentials, ServerController::caCertPath());
|
credentials, amnezia::protocols::openvpn::caCertPath());
|
||||||
QString taKey = ServerController::getTextFileFromContainer(container,
|
QString taKey = ServerController::getTextFileFromContainer(container,
|
||||||
credentials, ServerController::taKeyPath());
|
credentials, amnezia::protocols::openvpn::taKeyPath());
|
||||||
|
|
||||||
if (!caCert.isEmpty() && !taKey.isEmpty()) {
|
if (!caCert.isEmpty() && !taKey.isEmpty()) {
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
|
@ -232,6 +272,68 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath)
|
||||||
|
{
|
||||||
|
SshConnection *client = connectToHost(sshParams(credentials));
|
||||||
|
if (client->state() != SshConnection::State::Connected) {
|
||||||
|
return fromSshConnectionErrorCode(client->errorState());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool err = false;
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QTimer timer;
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
timer.start(3000);
|
||||||
|
|
||||||
|
QSharedPointer<SftpChannel> sftp = client->createSftpChannel();
|
||||||
|
sftp->initialize();
|
||||||
|
|
||||||
|
QObject::connect(sftp.data(), &SftpChannel::initialized, &wait, [&](){
|
||||||
|
timer.stop();
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
QObject::connect(&timer, &QTimer::timeout, &wait, [&](){
|
||||||
|
err= true;
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (!sftp) {
|
||||||
|
qCritical() << "Failed to create SftpChannel, breaking.";
|
||||||
|
return ErrorCode::SshRemoteProcessCreationError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTemporaryFile localFile;
|
||||||
|
localFile.open();
|
||||||
|
localFile.write(data);
|
||||||
|
localFile.close();
|
||||||
|
|
||||||
|
auto job = sftp->uploadFile(localFile.fileName(), remotePath, QSsh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||||
|
QObject::connect(sftp.data(), &SftpChannel::finished, &wait, [&](QSsh::SftpJobId j, const QString &error){
|
||||||
|
if (job == j) {
|
||||||
|
qDebug() << "Sftp finished with status" << error;
|
||||||
|
wait.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(sftp.data(), &SftpChannel::channelError, &wait, [&](const QString &reason){
|
||||||
|
qDebug() << "Sftp finished with error" << reason;
|
||||||
|
err= true;
|
||||||
|
wait.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return ErrorCode::SshSftpError;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
|
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
|
||||||
{
|
{
|
||||||
switch (error) {
|
switch (error) {
|
||||||
|
@ -311,11 +413,16 @@ ErrorCode ServerController::removeServer(const ServerCredentials &credentials, P
|
||||||
scriptData = file.readAll();
|
scriptData = file.readAll();
|
||||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||||
|
|
||||||
return runScript(genVarsForScript(credentials, container), sshParams(credentials), scriptData);
|
return runScript(sshParams(credentials), replaceVars(scriptData, genVarsForScript(credentials, container)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
|
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
|
||||||
{
|
{
|
||||||
|
ErrorCode e = runScript(sshParams(credentials),
|
||||||
|
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
|
||||||
|
genVarsForScript(credentials)));
|
||||||
|
if (e) return e;
|
||||||
|
|
||||||
if (proto == Protocol::OpenVpn) {
|
if (proto == Protocol::OpenVpn) {
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
//return setupOpenVpnServer(credentials);
|
//return setupOpenVpnServer(credentials);
|
||||||
|
@ -326,9 +433,9 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr
|
||||||
else if (proto == Protocol::Any) {
|
else if (proto == Protocol::Any) {
|
||||||
//return ErrorCode::NotImplementedError;
|
//return ErrorCode::NotImplementedError;
|
||||||
|
|
||||||
// TODO: run concurently
|
|
||||||
//setupOpenVpnServer(credentials);
|
//setupOpenVpnServer(credentials);
|
||||||
return setupShadowSocksServer(credentials);
|
//return setupShadowSocksServer(credentials);
|
||||||
|
return setupOpenVpnOverCloakServer(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
|
@ -336,100 +443,177 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr
|
||||||
|
|
||||||
ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials)
|
ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
QString scriptData;
|
return ErrorCode::NotImplementedError;
|
||||||
QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh";
|
|
||||||
QFile file(scriptFileName);
|
|
||||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
|
||||||
|
|
||||||
scriptData = file.readAll();
|
// QString scriptData;
|
||||||
|
// QString scriptFileName = ":/server_scripts/setup_openvpn_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<QSsh::SshRemoteProcess> proc) {
|
||||||
|
// stdOut += data + "\n";
|
||||||
|
|
||||||
|
// if (data.contains("Automatically restart Docker daemon?")) {
|
||||||
|
// proc->write("yes\n");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
|
// stdOut += data + "\n";
|
||||||
|
// };
|
||||||
|
|
||||||
|
// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::OpenVpn), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials &credentials)
|
||||||
|
{
|
||||||
|
ErrorCode e;
|
||||||
|
DockerContainer container = DockerContainer::OpenVpnOverCloak;
|
||||||
|
|
||||||
|
// create folder on host
|
||||||
|
e = runScript(sshParams(credentials),
|
||||||
|
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
|
||||||
|
genVarsForScript(credentials, container)));
|
||||||
|
if (e) return e;
|
||||||
|
|
||||||
|
uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, Protocol::OpenVpnOverCloak).toUtf8(),
|
||||||
|
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
||||||
|
|
||||||
|
|
||||||
|
// Setup openvpn part
|
||||||
|
QString scriptData = amnezia::scriptData(SharedScriptType::build_container);
|
||||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||||
|
|
||||||
QString stdOut;
|
// QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
stdOut += data + "\n";
|
// stdOut += data + "\n";
|
||||||
|
// };
|
||||||
|
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
|
// stdOut += data + "\n";
|
||||||
|
// };
|
||||||
|
|
||||||
if (data.contains("Automatically restart Docker daemon?")) {
|
e = runScript(sshParams(credentials),
|
||||||
proc->write("yes\n");
|
replaceVars(scriptData,
|
||||||
}
|
genVarsForScript(credentials, container)));
|
||||||
};
|
|
||||||
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
|
||||||
stdOut += data + "\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::OpenVpn), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (e) return e;
|
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;
|
|
||||||
|
|
||||||
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
|
runScript(sshParams(credentials),
|
||||||
|
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, Protocol::OpenVpnOverCloak),
|
||||||
|
genVarsForScript(credentials, container)));
|
||||||
|
if (e) return e;
|
||||||
|
|
||||||
|
uploadTextFileToContainer(DockerContainer::OpenVpnOverCloak, credentials,
|
||||||
|
replaceVars(amnezia::scriptData(ProtocolScriptType::container_startup, Protocol::OpenVpnOverCloak),
|
||||||
|
genVarsForScript(credentials, container)),
|
||||||
|
"/opt/amnezia/start.sh");
|
||||||
|
|
||||||
|
// qDebug().noquote() << "AAAA"
|
||||||
|
// << amnezia::scriptData(ProtocolScriptType::container_startup, Protocol::OpenVpnOverCloak),
|
||||||
|
// replaceVars("/opt/amnezia/start.sh",
|
||||||
|
// genVarsForScript(credentials, container));
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
|
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
// Setup openvpn part
|
return ErrorCode::NotImplementedError;
|
||||||
QString scriptData;
|
// // Setup openvpn part
|
||||||
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
|
// QString scriptData;
|
||||||
QFile file(scriptFileName);
|
// QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
|
||||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
// QFile file(scriptFileName);
|
||||||
|
// if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||||
|
|
||||||
scriptData = file.readAll();
|
// scriptData = file.readAll();
|
||||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
// if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||||
|
|
||||||
QString stdOut;
|
// QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
stdOut += data + "\n";
|
// stdOut += data + "\n";
|
||||||
|
|
||||||
if (data.contains("Automatically restart Docker daemon?")) {
|
// if (data.contains("Automatically restart Docker daemon?")) {
|
||||||
proc->write("yes\n");
|
// proc->write("yes\n");
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||||
stdOut += data + "\n";
|
// stdOut += data + "\n";
|
||||||
};
|
// };
|
||||||
|
|
||||||
ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
||||||
if (e) return e;
|
// if (e) return e;
|
||||||
|
|
||||||
// Create ss config
|
// // Create ss config
|
||||||
QJsonObject ssConfig;
|
// QJsonObject ssConfig;
|
||||||
ssConfig.insert("server", "0.0.0.0");
|
// ssConfig.insert("server", "0.0.0.0");
|
||||||
ssConfig.insert("server_port", ssRemotePort());
|
// ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort());
|
||||||
ssConfig.insert("local_port", ssContainerPort());
|
// ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort());
|
||||||
ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
|
// ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
|
||||||
ssConfig.insert("timeout", 60);
|
// ssConfig.insert("timeout", 60);
|
||||||
ssConfig.insert("method", ssEncryption());
|
// ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption());
|
||||||
QString configData = QJsonDocument(ssConfig).toJson();
|
// QString configData = QJsonDocument(ssConfig).toJson();
|
||||||
QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
|
// QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
|
||||||
|
|
||||||
configData.replace("\"", "\\\"");
|
// configData.replace("\"", "\\\"");
|
||||||
//qDebug().noquote() << configData;
|
// //qDebug().noquote() << configData;
|
||||||
|
|
||||||
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
|
// uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
|
||||||
|
|
||||||
// Start ss
|
// // Start ss
|
||||||
QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\"").
|
// QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\"").
|
||||||
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
|
// arg(amnezia::server::getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
|
||||||
|
|
||||||
e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script);
|
// e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script);
|
||||||
return e;
|
// return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<QString, QString> ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container)
|
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
QHash<QString, QString> vars;
|
Vars vars;
|
||||||
|
|
||||||
vars.insert("$CONTAINER_NAME", getContainerName(container));
|
vars.append(qMakePair<QString, QString>("$VPN_SUBNET_IP", amnezia::server::vpnDefaultSubnetIp()));
|
||||||
|
vars.append(qMakePair<QString, QString>("$VPN_SUBNET_MASK_VAL", amnezia::server::vpnDefaultSubnetMaskVal()));
|
||||||
|
vars.append(qMakePair<QString, QString>("$VPN_SUBNET_MASK", amnezia::server::vpnDefaultSubnetMask()));
|
||||||
|
|
||||||
|
vars.append(qMakePair<QString, QString>("$CONTAINER_NAME", amnezia::server::getContainerName(container)));
|
||||||
|
vars.append(qMakePair<QString, QString>("$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::server::getContainerName(container)));
|
||||||
|
|
||||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.insert("$SERVER_IP_ADDRESS", serverIp);
|
vars.append(qMakePair<QString, QString>("$SERVER_IP_ADDRESS", serverIp));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
|
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
if (container == DockerContainer::OpenVpn) {
|
||||||
|
vars.append(qMakePair<QString, QString>("$SERVER_PORT", amnezia::protocols::openvpn::openvpnDefaultPort()));
|
||||||
|
}
|
||||||
|
else if (container == DockerContainer::OpenVpnOverCloak) {
|
||||||
|
vars.append(qMakePair<QString, QString>("$SERVER_PORT", amnezia::protocols::cloak::ckDefaultPort()));
|
||||||
|
vars.append(qMakePair<QString, QString>("$FAKE_WEB_SITE_ADDRESS", amnezia::protocols::cloak::ckDefaultRedirSite()));
|
||||||
|
}
|
||||||
|
else if (container == DockerContainer::ShadowSocks) {
|
||||||
|
vars.append(qMakePair<QString, QString>("$SERVER_PORT", "6789"));
|
||||||
|
}
|
||||||
|
|
||||||
return vars;
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,9 +672,17 @@ SshConnection *ServerController::connectToHost(const SshConnectionParameters &ss
|
||||||
|
|
||||||
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
QFile file(":/server_scripts/setup_firewall.sh");
|
return runScript(sshParams(credentials),
|
||||||
file.open(QIODevice::ReadOnly);
|
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
|
||||||
|
genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)));
|
||||||
QString script = file.readAll();
|
}
|
||||||
return runScript(genVarsForScript(credentials, DockerContainer::OpenVpn), sshParams(credentials), script);
|
|
||||||
|
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||||
|
{
|
||||||
|
QString s = script;
|
||||||
|
for (const QPair<QString, QString> &var : vars) {
|
||||||
|
//qDebug() << "Replacing" << var << vars.value(var);
|
||||||
|
s.replace(var.first, var.second);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,13 @@ class ServerController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
typedef QList<QPair<QString, QString>> Vars;
|
||||||
|
|
||||||
static ErrorCode fromSshConnectionErrorCode(QSsh::SshError error);
|
static ErrorCode fromSshConnectionErrorCode(QSsh::SshError error);
|
||||||
|
|
||||||
// QSsh exitCode and exitStatus are different things
|
// QSsh exitCode and exitStatus are different things
|
||||||
static ErrorCode fromSshProcessExitStatus(int exitStatus);
|
static ErrorCode fromSshProcessExitStatus(int exitStatus);
|
||||||
|
|
||||||
static QString caCertPath() { return "/opt/amneziavpn_data/pki/ca.crt"; }
|
|
||||||
static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; }
|
|
||||||
static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; }
|
|
||||||
|
|
||||||
static QString getContainerName(amnezia::DockerContainer container);
|
|
||||||
|
|
||||||
static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
|
static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
|
||||||
|
|
||||||
static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto);
|
static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto);
|
||||||
|
@ -31,33 +26,32 @@ public:
|
||||||
|
|
||||||
static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
|
static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
|
||||||
|
|
||||||
|
static ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath);
|
||||||
|
|
||||||
static ErrorCode uploadTextFileToContainer(DockerContainer container,
|
static ErrorCode uploadTextFileToContainer(DockerContainer container,
|
||||||
const ServerCredentials &credentials, QString &file, const QString &path);
|
const ServerCredentials &credentials, const QString &file, const QString &path);
|
||||||
|
|
||||||
static QString getTextFileFromContainer(DockerContainer container,
|
static QString getTextFileFromContainer(DockerContainer container,
|
||||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
|
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
|
||||||
|
|
||||||
static ErrorCode signCert(DockerContainer container,
|
|
||||||
const ServerCredentials &credentials, QString clientId);
|
|
||||||
|
|
||||||
static int ssRemotePort() { return 6789; } // TODO move to ShadowSocksDefs.h
|
|
||||||
static int ssContainerPort() { return 8585; } // TODO move to ShadowSocksDefs.h
|
|
||||||
static QString ssEncryption() { return "chacha20-ietf-poly1305"; } // TODO move to ShadowSocksDefs.h
|
|
||||||
|
|
||||||
static ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
static ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
||||||
private:
|
|
||||||
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
|
||||||
|
|
||||||
static ErrorCode runScript(const QHash<QString, QString> &vars,
|
static QString replaceVars(const QString &script, const Vars &vars);
|
||||||
const QSsh::SshConnectionParameters &sshParams, QString script,
|
|
||||||
|
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script,
|
||||||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
|
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
|
||||||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
|
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
|
||||||
|
|
||||||
|
static Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
||||||
|
|
||||||
|
static ErrorCode installDocker(const ServerCredentials &credentials);
|
||||||
|
|
||||||
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
|
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
|
||||||
|
static ErrorCode setupOpenVpnOverCloakServer(const ServerCredentials &credentials);
|
||||||
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);
|
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);
|
||||||
|
|
||||||
|
|
||||||
static QHash<QString, QString> genVarsForScript(const ServerCredentials &credentials, DockerContainer container);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SERVERCONTROLLER_H
|
#endif // SERVERCONTROLLER_H
|
||||||
|
|
105
client/protocols/openvpnovercloakprotocol.cpp
Normal file
105
client/protocols/openvpnovercloakprotocol.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include "openvpnovercloakprotocol.h"
|
||||||
|
#include "core/servercontroller.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
OpenVpnOverCloakProtocol::OpenVpnOverCloakProtocol(const QJsonObject &configuration, QObject *parent):
|
||||||
|
OpenVpnProtocol(configuration, parent)
|
||||||
|
{
|
||||||
|
readCloakConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol()
|
||||||
|
{
|
||||||
|
qDebug() << "OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol";
|
||||||
|
OpenVpnOverCloakProtocol::stop();
|
||||||
|
QThread::msleep(200);
|
||||||
|
m_ckProcess.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode OpenVpnOverCloakProtocol::start()
|
||||||
|
{
|
||||||
|
if (Utils::processIsRunning(Utils::executable("ck-client", false))) {
|
||||||
|
Utils::killProcessByName(Utils::executable("ck-client", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
m_cloakCfgFile.setAutoRemove(false);
|
||||||
|
#endif
|
||||||
|
m_cloakCfgFile.open();
|
||||||
|
m_cloakCfgFile.write(QJsonDocument(m_cloakConfig).toJson());
|
||||||
|
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();
|
||||||
|
|
||||||
|
qDebug().noquote() << "OpenVpnOverCloakProtocol::start()"
|
||||||
|
<< cloakExecPath() << args.join(" ");
|
||||||
|
|
||||||
|
m_ckProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
|
||||||
|
m_ckProcess.setProgram(cloakExecPath());
|
||||||
|
m_ckProcess.setArguments(args);
|
||||||
|
|
||||||
|
connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this](){
|
||||||
|
qDebug().noquote() << "ck-client:" << m_ckProcess.readAllStandardOutput();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&m_ckProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
|
||||||
|
qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
|
||||||
|
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
|
||||||
|
if (exitStatus != QProcess::NormalExit){
|
||||||
|
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
if (exitCode !=0 ){
|
||||||
|
emit protocolError(amnezia::ErrorCode::InternalError);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ckProcess.start();
|
||||||
|
m_ckProcess.waitForStarted();
|
||||||
|
|
||||||
|
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
|
||||||
|
setConnectionState(ConnectionState::Connecting);
|
||||||
|
|
||||||
|
return OpenVpnProtocol::start();
|
||||||
|
}
|
||||||
|
else return ErrorCode::CloakExecutableMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenVpnOverCloakProtocol::stop()
|
||||||
|
{
|
||||||
|
OpenVpnProtocol::stop();
|
||||||
|
|
||||||
|
qDebug() << "OpenVpnOverCloakProtocol::stop()";
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
Utils::signalCtrl(m_ckProcess.processId(), CTRL_C_EVENT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_ckProcess.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString OpenVpnOverCloakProtocol::cloakExecPath()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
return Utils::executable(QString("cloak/ck-client"), true);
|
||||||
|
#else
|
||||||
|
return Utils::executable(QString("/ck-client"), true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
|
||||||
|
{
|
||||||
|
m_cloakConfig = configuration.value(config::key_cloak_config_data()).toObject();
|
||||||
|
}
|
30
client/protocols/openvpnovercloakprotocol.h
Normal file
30
client/protocols/openvpnovercloakprotocol.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef OPENVPNOVERCLOAKPROTOCOL_H
|
||||||
|
#define OPENVPNOVERCLOAKPROTOCOL_H
|
||||||
|
|
||||||
|
#include "openvpnprotocol.h"
|
||||||
|
#include "QProcess"
|
||||||
|
|
||||||
|
class OpenVpnOverCloakProtocol : public OpenVpnProtocol
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenVpnOverCloakProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||||
|
virtual ~OpenVpnOverCloakProtocol() override;
|
||||||
|
|
||||||
|
ErrorCode start() override;
|
||||||
|
void stop() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void readCloakConfiguration(const QJsonObject &configuration);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QJsonObject m_cloakConfig;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString cloakExecPath();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QProcess m_ckProcess;
|
||||||
|
QTemporaryFile m_cloakCfgFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // OPENVPNOVERCLOAKPROTOCOL_H
|
36
client/protocols/protocols_defs.h
Normal file
36
client/protocols/protocols_defs.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef PROTOCOLS_DEFS_H
|
||||||
|
#define PROTOCOLS_DEFS_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace amnezia {
|
||||||
|
namespace protocols {
|
||||||
|
namespace openvpn {
|
||||||
|
static QString caCertPath() { return "/opt/amnezia/openvpn/pki/ca.crt"; }
|
||||||
|
static QString clientCertPath() { return "/opt/amnezia/openvpn/pki/issued"; }
|
||||||
|
static QString taKeyPath() { return "/opt/amnezia/openvpn/ta.key"; }
|
||||||
|
static QString clientsDirPath() { return "/opt/amnezia/openvpn/clients"; }
|
||||||
|
static QString openvpnDefaultPort() { return "1194"; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace shadowsocks {
|
||||||
|
static int ssRemotePort() { return 6789; }
|
||||||
|
static int ssContainerPort() { return 8585; }
|
||||||
|
static QString ssEncryption() { return "chacha20-ietf-poly1305"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace cloak {
|
||||||
|
static QString ckPublicKeyPath() { return "/opt/amnezia/cloak/cloak_public.key"; }
|
||||||
|
static QString ckBypassUidKeyPath() { return "/opt/amnezia/cloak/cloak_bypass_uid.key"; }
|
||||||
|
static QString ckAdminKeyPath() { return "/opt/amnezia/cloak/cloak_admin_uid.key"; }
|
||||||
|
static QString ckDefaultPort() { return "443"; }
|
||||||
|
static QString ckDefaultRedirSite() { return "mail.ru"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace protocols
|
||||||
|
} // namespace amnezia
|
||||||
|
|
||||||
|
#endif // PROTOCOLS_DEFS_H
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
@ -99,11 +100,11 @@ QJsonObject ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials
|
||||||
{
|
{
|
||||||
QJsonObject ssConfig;
|
QJsonObject ssConfig;
|
||||||
ssConfig.insert("server", credentials.hostName);
|
ssConfig.insert("server", credentials.hostName);
|
||||||
ssConfig.insert("server_port", ServerController::ssRemotePort());
|
ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort());
|
||||||
ssConfig.insert("local_port", ServerController::ssContainerPort());
|
ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort());
|
||||||
ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
|
ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
|
||||||
ssConfig.insert("timeout", 60);
|
ssConfig.insert("timeout", 60);
|
||||||
ssConfig.insert("method", ServerController::ssEncryption());
|
ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption());
|
||||||
return ssConfig;
|
return ssConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,14 @@
|
||||||
<file>images/background_connected.png</file>
|
<file>images/background_connected.png</file>
|
||||||
<file>server_scripts/setup_shadowsocks_server.sh</file>
|
<file>server_scripts/setup_shadowsocks_server.sh</file>
|
||||||
<file>server_scripts/template_shadowsocks.ovpn</file>
|
<file>server_scripts/template_shadowsocks.ovpn</file>
|
||||||
<file>server_scripts/setup_firewall.sh</file>
|
<file>server_scripts/setup_host_firewall.sh</file>
|
||||||
<file>images/reload.png</file>
|
<file>images/reload.png</file>
|
||||||
|
<file>server_scripts/openvpn_cloak/Dockerfile</file>
|
||||||
|
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
|
||||||
|
<file>server_scripts/openvpn_cloak/start.sh</file>
|
||||||
|
<file>server_scripts/openvpn_cloak/template.ovpn</file>
|
||||||
|
<file>server_scripts/install_docker.sh</file>
|
||||||
|
<file>server_scripts/build_container.sh</file>
|
||||||
|
<file>server_scripts/prepare_host.sh</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
1
client/server_scripts/build_container.sh
Normal file
1
client/server_scripts/build_container.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER
|
6
client/server_scripts/install_docker.sh
Normal file
6
client/server_scripts/install_docker.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
52
client/server_scripts/openvpn_cloak/Dockerfile
Normal file
52
client/server_scripts/openvpn_cloak/Dockerfile
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
RUN curl -L https://github.com/cbeuw/Cloak/releases/download/v2.5.3/ck-server-linux-amd64-v2.5.3 > /usr/bin/ck-server
|
||||||
|
RUN chmod a+x /usr/bin/ck-server
|
||||||
|
|
||||||
|
# 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 [ "" ]
|
84
client/server_scripts/openvpn_cloak/configure_container.sh
Normal file
84
client/server_scripts/openvpn_cloak/configure_container.sh
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# 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 $SERVER_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 $SERVER_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\
|
||||||
|
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\
|
||||||
|
cipher AES-256-GCM \\n\
|
||||||
|
ncp-ciphers AES-256-GCM:AES-256-CBC \\n\
|
||||||
|
auth SHA512 \\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'
|
||||||
|
|
||||||
|
#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; \
|
||||||
|
cd /opt/amnezia/cloak || exit 1; \
|
||||||
|
CLOAK_ADMIN_UID=$(ck-server -u) && echo $CLOAK_ADMIN_UID > /opt/amnezia/cloak/cloak_admin_uid.key; \
|
||||||
|
CLOAK_BYPASS_UID=$(ck-server -u) && echo $CLOAK_BYPASS_UID > /opt/amnezia/cloak/cloak_bypass_uid.key; \
|
||||||
|
IFS=, read CLOAK_PUBLIC_KEY CLOAK_PRIVATE_KEY <<<$(ck-server -k); \
|
||||||
|
echo $CLOAK_PUBLIC_KEY > /opt/amnezia/cloak/cloak_public.key; \
|
||||||
|
echo $CLOAK_PRIVATE_KEY > /opt/amnezia/cloak/cloak_private.key; \
|
||||||
|
echo -e "{\\n\
|
||||||
|
\"ProxyBook\": {\\n\
|
||||||
|
\"openvpn\": [\\n\
|
||||||
|
\"tcp\",\\n\
|
||||||
|
\"localhost:1194\"\\n\
|
||||||
|
]\\n\
|
||||||
|
},\\n\
|
||||||
|
\"BypassUID\": [\\n\
|
||||||
|
\"$CLOAK_BYPASS_UID\"\\n\
|
||||||
|
],\\n\
|
||||||
|
\"BindAddr\":[\":443\"],\\n\
|
||||||
|
\"RedirAddr\": \"$FAKE_WEB_SITE_ADDRESS\",\\n\
|
||||||
|
\"PrivateKey\": \"$CLOAK_PRIVATE_KEY\",\\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"
|
23
client/server_scripts/openvpn_cloak/start.sh
Normal file
23
client/server_scripts/openvpn_cloak/start.sh
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||||
|
|
||||||
|
echo "Container Startup start"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tail -f /dev/null
|
35
client/server_scripts/openvpn_cloak/template.ovpn
Normal file
35
client/server_scripts/openvpn_cloak/template.ovpn
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto $PROTO
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
cipher AES-256-GCM
|
||||||
|
auth SHA512
|
||||||
|
verb 3
|
||||||
|
tls-client
|
||||||
|
tls-version-min 1.2
|
||||||
|
key-direction 1
|
||||||
|
remote-cert-tls server
|
||||||
|
redirect-gateway def1 bypass-dhcp
|
||||||
|
|
||||||
|
dhcp-option DNS $PRIMARY_DNS
|
||||||
|
dhcp-option DNS $SECONDARY_DNS
|
||||||
|
block-outside-dns
|
||||||
|
|
||||||
|
route $REMOTE_HOST 255.255.255.255 net_gateway
|
||||||
|
remote 127.0.0.1 1194
|
||||||
|
|
||||||
|
<ca>
|
||||||
|
$CA_CERT
|
||||||
|
</ca>
|
||||||
|
<cert>
|
||||||
|
$CLIENT_CERT
|
||||||
|
</cert>
|
||||||
|
<key>
|
||||||
|
$PRIV_KEY
|
||||||
|
</key>
|
||||||
|
<tls-auth>
|
||||||
|
$TA_KEY
|
||||||
|
</tls-auth>
|
3
client/server_scripts/prepare_host.sh
Normal file
3
client/server_scripts/prepare_host.sh
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
CUR_USER=$(whoami);\
|
||||||
|
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
||||||
|
sudo chown $CUR_USER $DOCKERFILE_FOLDER
|
|
@ -1,24 +1,31 @@
|
||||||
sudo sysctl -w net.ipv4.ip_forward=1
|
sudo sysctl -w net.ipv4.ip_forward=1
|
||||||
sudo iptables -P FORWARD ACCEPT
|
|
||||||
sudo iptables -C INPUT -p icmp --icmp-type echo-request -j DROP || sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
|
sudo iptables -C INPUT -p icmp --icmp-type echo-request -j DROP || sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
|
||||||
|
|
||||||
|
#sudo iptables -P FORWARD ACCEPT
|
||||||
|
sudo iptables -A FORWARD -j DOCKER-USER
|
||||||
|
sudo iptables -A FORWARD -j DOCKER-ISOLATION-STAGE-1
|
||||||
|
sudo iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -o docker0 -j DOCKER
|
||||||
|
sudo iptables -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT
|
||||||
|
|
||||||
# Tuning network
|
# Tuning network
|
||||||
sudo sysctl fs.file-max=51200
|
sudo sysctl fs.file-max=51200; \
|
||||||
sudo sysctl net.core.rmem_max=67108864
|
sudo sysctl net.core.rmem_max=67108864; \
|
||||||
sudo sysctl net.core.wmem_max=67108864
|
sudo sysctl net.core.wmem_max=67108864; \
|
||||||
sudo sysctl net.core.netdev_max_backlog=250000
|
sudo sysctl net.core.netdev_max_backlog=250000; \
|
||||||
sudo sysctl net.core.somaxconn=4096
|
sudo sysctl net.core.somaxconn=4096; \
|
||||||
sudo sysctl net.ipv4.tcp_syncookies=1
|
sudo sysctl net.ipv4.tcp_syncookies=1; \
|
||||||
sudo sysctl net.ipv4.tcp_tw_reuse=1
|
sudo sysctl net.ipv4.tcp_tw_reuse=1; \
|
||||||
sudo sysctl net.ipv4.tcp_tw_recycle=0
|
sudo sysctl net.ipv4.tcp_tw_recycle=0; \
|
||||||
sudo sysctl net.ipv4.tcp_fin_timeout=30
|
sudo sysctl net.ipv4.tcp_fin_timeout=30; \
|
||||||
sudo sysctl net.ipv4.tcp_keepalive_time=1200
|
sudo sysctl net.ipv4.tcp_keepalive_time=1200; \
|
||||||
sudo sysctl net.ipv4.ip_local_port_range="10000 65000"
|
sudo sysctl net.ipv4.ip_local_port_range="10000 65000"; \
|
||||||
sudo sysctl net.ipv4.tcp_max_syn_backlog=8192
|
sudo sysctl net.ipv4.tcp_max_syn_backlog=8192; \
|
||||||
sudo sysctl net.ipv4.tcp_max_tw_buckets=5000
|
sudo sysctl net.ipv4.tcp_max_tw_buckets=5000; \
|
||||||
sudo sysctl net.ipv4.tcp_fastopen=3
|
sudo sysctl net.ipv4.tcp_fastopen=3; \
|
||||||
sudo sysctl net.ipv4.tcp_mem="25600 51200 102400"
|
sudo sysctl net.ipv4.tcp_mem="25600 51200 102400"; \
|
||||||
sudo sysctl net.ipv4.tcp_rmem="4096 87380 67108864"
|
sudo sysctl net.ipv4.tcp_rmem="4096 87380 67108864"; \
|
||||||
sudo sysctl net.ipv4.tcp_wmem="4096 65536 67108864"
|
sudo sysctl net.ipv4.tcp_wmem="4096 65536 67108864"; \
|
||||||
sudo sysctl net.ipv4.tcp_mtu_probing=1
|
sudo sysctl net.ipv4.tcp_mtu_probing=1; \
|
||||||
sudo sysctl net.ipv4.tcp_congestion_control=hybla
|
sudo sysctl net.ipv4.tcp_congestion_control=hybla
|
||||||
|
|
|
@ -12,17 +12,18 @@ sudo systemctl start docker
|
||||||
sudo docker stop $CONTAINER_NAME
|
sudo docker stop $CONTAINER_NAME
|
||||||
sudo docker rm -f $CONTAINER_NAME
|
sudo docker rm -f $CONTAINER_NAME
|
||||||
sudo docker pull amneziavpn/shadowsocks:latest
|
sudo docker pull amneziavpn/shadowsocks:latest
|
||||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/tcp -p 6789:6789/tcp --name $CONTAINER_NAME 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
|
# 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 "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"
|
# OpenVpn
|
||||||
sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki"
|
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amneziavpn_data/clients;\
|
||||||
sudo docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh"
|
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 -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"
|
sudo docker exec -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf"
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#include <protocols/shadowsocksvpnprotocol.h>
|
#include <protocols/shadowsocksvpnprotocol.h>
|
||||||
|
|
||||||
#include "core/errorstrings.h"
|
#include "core/errorstrings.h"
|
||||||
#include "core/openvpnconfigurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
#include "core/servercontroller.h"
|
#include "core/servercontroller.h"
|
||||||
#include "ui/qautostart.h"
|
#include "ui/qautostart.h"
|
||||||
|
|
||||||
|
@ -111,6 +111,9 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
ui->lineEdit_new_server_ip->setValidator(&m_ipAddressPortValidator);
|
ui->lineEdit_new_server_ip->setValidator(&m_ipAddressPortValidator);
|
||||||
ui->lineEdit_network_settings_dns1->setValidator(&m_ipAddressValidator);
|
ui->lineEdit_network_settings_dns1->setValidator(&m_ipAddressValidator);
|
||||||
ui->lineEdit_network_settings_dns2->setValidator(&m_ipAddressValidator);
|
ui->lineEdit_network_settings_dns2->setValidator(&m_ipAddressValidator);
|
||||||
|
|
||||||
|
ui->toolBox_share_connection->removeItem(ui->toolBox_share_connection->indexOf(ui->page_share_shadowsocks));
|
||||||
|
ui->page_share_shadowsocks->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
|
|
@ -1918,7 +1918,7 @@ background: #211966;
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Software version: 1.5.3 (19.03.2021)</string>
|
<string>Software version: 1.6.0 (31.03.2021)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -2659,8 +2659,8 @@ background: #282932;
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>360</width>
|
<width>100</width>
|
||||||
<height>500</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
|
|
|
@ -4,11 +4,24 @@
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QRandomGenerator>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
QString Utils::getRandomString(int len)
|
||||||
|
{
|
||||||
|
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||||
|
|
||||||
|
QString randomString;
|
||||||
|
for(int i=0; i<len; ++i) {
|
||||||
|
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
||||||
|
QChar nextChar = possibleCharacters.at(index);
|
||||||
|
randomString.append(nextChar);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
QString Utils::defaultVpnConfigFileName()
|
QString Utils::defaultVpnConfigFileName()
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
class Utils {
|
class Utils {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static QString getRandomString(int len);
|
||||||
|
|
||||||
static QString configPath();
|
static QString configPath();
|
||||||
static QString defaultVpnConfigFileName();
|
static QString defaultVpnConfigFileName();
|
||||||
static QString executable(const QString& baseName, bool absPath);
|
static QString executable(const QString& baseName, bool absPath);
|
||||||
|
|
|
@ -3,13 +3,16 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include <core/openvpnconfigurator.h>
|
#include <configurators/openvpn_configurator.h>
|
||||||
|
#include <configurators/cloak_configurator.h>
|
||||||
#include <core/servercontroller.h>
|
#include <core/servercontroller.h>
|
||||||
|
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
#include "core/ipcclient.h"
|
#include "core/ipcclient.h"
|
||||||
#include "protocols/openvpnprotocol.h"
|
#include "protocols/openvpnprotocol.h"
|
||||||
|
#include "protocols/openvpnovercloakprotocol.h"
|
||||||
#include "protocols/shadowsocksvpnprotocol.h"
|
#include "protocols/shadowsocksvpnprotocol.h"
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "vpnconnection.h"
|
#include "vpnconnection.h"
|
||||||
|
|
||||||
|
@ -80,7 +83,7 @@ ErrorCode VpnConnection::lastError() const
|
||||||
ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credentials, Protocol protocol)
|
ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credentials, Protocol protocol)
|
||||||
{
|
{
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks) {
|
if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks || protocol == Protocol::OpenVpnOverCloak) {
|
||||||
QString openVpnConfigData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode);
|
QString openVpnConfigData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode);
|
||||||
m_vpnConfiguration.insert(config::key_openvpn_config_data(), openVpnConfigData);
|
m_vpnConfiguration.insert(config::key_openvpn_config_data(), openVpnConfigData);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
|
@ -91,6 +94,7 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent
|
||||||
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)){
|
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)){
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
stream << openVpnConfigData << endl;
|
stream << openVpnConfigData << endl;
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return ErrorCode::FailedToSaveConfigData;
|
return ErrorCode::FailedToSaveConfigData;
|
||||||
|
@ -102,6 +106,11 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent
|
||||||
m_vpnConfiguration.insert(config::key_shadowsocks_config_data(), ssConfigData);
|
m_vpnConfiguration.insert(config::key_shadowsocks_config_data(), ssConfigData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (protocol == Protocol::OpenVpnOverCloak) {
|
||||||
|
QJsonObject cloakConfigData = CloakConfigurator::genCloakConfig(credentials, Protocol::OpenVpnOverCloak, &errorCode);
|
||||||
|
m_vpnConfiguration.insert(config::key_cloak_config_data(), cloakConfigData);
|
||||||
|
}
|
||||||
|
|
||||||
//qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson();
|
//qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson();
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +120,8 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot
|
||||||
qDebug() << "connectToVpn, CustomRouting is" << m_settings.customRouting();
|
qDebug() << "connectToVpn, CustomRouting is" << m_settings.customRouting();
|
||||||
// qDebug() << "Cred" << m_settings.serverCredentials().hostName <<
|
// qDebug() << "Cred" << m_settings.serverCredentials().hostName <<
|
||||||
// m_settings.serverCredentials().password;
|
// m_settings.serverCredentials().password;
|
||||||
protocol = Protocol::ShadowSocks;
|
//protocol = Protocol::ShadowSocks;
|
||||||
|
protocol = Protocol::OpenVpnOverCloak;
|
||||||
|
|
||||||
// TODO: Try protocols one by one in case of Protocol::Any
|
// TODO: Try protocols one by one in case of Protocol::Any
|
||||||
// TODO: Implement some behavior in case if connection not stable
|
// TODO: Implement some behavior in case if connection not stable
|
||||||
|
@ -156,6 +166,20 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (protocol == Protocol::OpenVpnOverCloak) {
|
||||||
|
ErrorCode e = createVpnConfiguration(credentials, Protocol::OpenVpnOverCloak);
|
||||||
|
if (e) {
|
||||||
|
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration));
|
||||||
|
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
|
||||||
|
if (e) {
|
||||||
|
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));
|
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue