shadowsocks impl

This commit is contained in:
pokamest 2021-01-15 23:36:35 +03:00
parent 95cbb07cbb
commit ec9ae0ef4f
20 changed files with 380 additions and 117 deletions

View file

@ -12,6 +12,12 @@ enum class Protocol {
WireGuard WireGuard
}; };
enum class DockerContainer {
OpenVpn,
ShadowSocks,
WireGuard
};
struct ServerCredentials struct ServerCredentials
{ {
QString hostName; QString hostName;

View file

@ -144,46 +144,77 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
return connData; return connData;
} }
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode) OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{ {
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName; connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) { if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
*errorCode = ErrorCode::EasyRsaExecutableMissing; if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing;
return connData; return connData;
} }
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId); QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
ErrorCode e = ServerController::uploadTextFileToContainer(credentials, connData.request, reqFileName);
if (e) { DockerContainer container;
*errorCode = e; if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn;
else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks;
else {
if (errorCode) *errorCode = ErrorCode::InternalError;
return connData; return connData;
} }
ServerController::signCert(credentials, connData.clientId); ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
connData.caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath(), &e);
connData.clientCert = ServerController::getTextFileFromContainer(credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
if (e) { if (e) {
*errorCode = e; if (errorCode) *errorCode = e;
return connData; return connData;
} }
connData.taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath(), &e); e = ServerController::signCert(container, credentials, connData.clientId);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::caCertPath(), &e);
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
}
return connData; return connData;
} }
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode) QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{ {
QFile configTemplFile(":/server_scripts/template.ovpn"); QFile configTemplFile;
if (proto == Protocol::OpenVpn)
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
else if (proto == Protocol::ShadowSocks) {
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
}
configTemplFile.open(QIODevice::ReadOnly); configTemplFile.open(QIODevice::ReadOnly);
QString config = configTemplFile.readAll(); QString config = configTemplFile.readAll();
ConnectionData connData = prepareOpenVpnConfig(credentials, errorCode); ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
if (proto == Protocol::OpenVpn)
config.replace("$PROTO", "udp"); config.replace("$PROTO", "udp");
else if (proto == Protocol::ShadowSocks) {
config.replace("$PROTO", "tcp");
config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort()));
}
config.replace("$REMOTE_HOST", connData.host); config.replace("$REMOTE_HOST", connData.host);
config.replace("$REMOTE_PORT", "1194"); config.replace("$REMOTE_PORT", "1194");
config.replace("$CA_CERT", connData.caCert); config.replace("$CA_CERT", connData.caCert);

View file

@ -22,7 +22,8 @@ public:
QString host; // host ip QString host; // host ip
}; };
static QString genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); static QString genOpenVpnConfig(const ServerCredentials &credentials, Protocol proto,
ErrorCode *errorCode = nullptr);
private: private:
static QString getRandomString(int len); static QString getRandomString(int len);
@ -34,7 +35,8 @@ private:
static ConnectionData createCertRequest(); static ConnectionData createCertRequest();
static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode = nullptr);
}; };

View file

@ -5,13 +5,25 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPointer> #include <QPointer>
#include <QTimer> #include <QTimer>
#include <QJsonObject>
#include <QJsonDocument>
#include "sshconnectionmanager.h" #include "sshconnectionmanager.h"
using namespace QSsh; using namespace QSsh;
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script) QString ServerController::getContainerName(DockerContainer container)
{
switch (container) {
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
default: return "";
}
}
ErrorCode ServerController::runScript(DockerContainer container,
const SshConnectionParameters &sshParams, QString script)
{ {
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
@ -26,7 +38,9 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
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++) {
const QString &line = lines.at(i); QString line = lines.at(i);
line.replace("$CONTAINER_NAME", getContainerName(container));
if (line.startsWith("#")) { if (line.startsWith("#")) {
continue; continue;
} }
@ -52,23 +66,22 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
wait.quit(); wait.quit();
}); });
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
// QString s = proc->readAllStandardOutput(); // QString s = proc->readAllStandardOutput();
// if (s != "." && !s.isEmpty()) { // if (s != "." && !s.isEmpty()) {
// qDebug().noquote() << s; // qDebug().noquote() << s;
// } // }
// }); // });
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
// QString s = proc->readAllStandardError(); // QString s = proc->readAllStandardError();
// if (s != "." && !s.isEmpty()) { // if (s != "." && !s.isEmpty()) {
// qDebug().noquote() << s; // qDebug().noquote() << s;
// } // }
// }); // });
proc->start(); proc->start();
if (i < lines.count()) {
if (i < lines.count() - 1) {
wait.exec(); wait.exec();
} }
@ -81,13 +94,13 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &credentials, ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
QString &file, const QString &path) const ServerCredentials &credentials, QString &file, const QString &path)
{ {
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
QString script = QString("docker exec -i amneziavpn sh -c \"echo \'%1\' > %2\""). QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
arg(file).arg(path); arg(getContainerName(container)).arg(file).arg(path);
qDebug().noquote() << script; qDebug().noquote() << script;
@ -116,25 +129,29 @@ ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &c
wait.quit(); wait.quit();
}); });
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
// qDebug().noquote() << proc->readAllStandardOutput(); qDebug().noquote() << proc->readAllStandardOutput();
// }); });
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
// qDebug().noquote() << proc->readAllStandardError(); qDebug().noquote() << proc->readAllStandardError();
// }); });
proc->start(); proc->start();
wait.exec(); wait.exec();
// if (proc->isRunning()) {
// wait.exec();
// }
return fromSshProcessExitStatus(exitStatus); return fromSshProcessExitStatus(exitStatus);
} }
QString ServerController::getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, QString ServerController::getTextFileFromContainer(DockerContainer container,
ErrorCode *errorCode) const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
{ {
QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\""). QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\"").
arg(path); arg(getContainerName(container)).arg(path);
qDebug().noquote() << "Copy file from container\n" << script; qDebug().noquote() << "Copy file from container\n" << script;
@ -162,6 +179,10 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred
proc->start(); proc->start();
wait.exec(); wait.exec();
// if (proc->isRunning()) {
// wait.exec();
// }
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) { if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus); if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus);
} }
@ -169,25 +190,28 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred
return proc->readAllStandardOutput(); return proc->readAllStandardOutput();
} }
ErrorCode ServerController::signCert(const ServerCredentials &credentials, QString clientId) ErrorCode ServerController::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
{ {
QString script_import = QString("docker exec -i amneziavpn bash -c \"cd /opt/amneziavpn_data && " QString script_import = QString("docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
"easyrsa import-req /opt/amneziavpn_data/clients/%1.req %1 &>/dev/null\"") "easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
.arg(clientId); .arg(getContainerName(container)).arg(clientId);
QString script_sign = QString("docker exec -i amneziavpn bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && " QString script_sign = QString("docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
"easyrsa sign-req client %1 &>/dev/null\"") "easyrsa sign-req client %2\"")
.arg(clientId); .arg(getContainerName(container)).arg(clientId);
QStringList script {script_import, script_sign}; QStringList script {script_import, script_sign};
return runScript(sshParams(credentials), script.join("\n")); return runScript(container, sshParams(credentials), script.join("\n"));
} }
ErrorCode ServerController::checkOpenVpnServer(const ServerCredentials &credentials) ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
{ {
QString caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath()); QString caCert = ServerController::getTextFileFromContainer(container,
QString taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath()); credentials, ServerController::caCertPath());
QString taKey = ServerController::getTextFileFromContainer(container,
credentials, ServerController::taKeyPath());
if (!caCert.isEmpty() && !taKey.isEmpty()) { if (!caCert.isEmpty() && !taKey.isEmpty()) {
return ErrorCode::NoError; return ErrorCode::NoError;
@ -209,15 +233,18 @@ ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
case(QSsh::SshAuthenticationError): return ErrorCode::SshAuthenticationError; case(QSsh::SshAuthenticationError): return ErrorCode::SshAuthenticationError;
case(QSsh::SshClosedByServerError): return ErrorCode::SshClosedByServerError; case(QSsh::SshClosedByServerError): return ErrorCode::SshClosedByServerError;
case(QSsh::SshInternalError): return ErrorCode::SshInternalError; case(QSsh::SshInternalError): return ErrorCode::SshInternalError;
default: return ErrorCode::SshInternalError;
} }
} }
ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus) ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
{ {
qDebug() << exitStatus;
switch (SshRemoteProcess::ExitStatus(exitStatus)) { switch (SshRemoteProcess::ExitStatus(exitStatus)) {
case(SshRemoteProcess::ExitStatus::NormalExit): return ErrorCode::NoError; case(SshRemoteProcess::ExitStatus::NormalExit): return ErrorCode::NoError;
case(SshRemoteProcess::ExitStatus::FailedToStart): return ErrorCode::FailedToStartRemoteProcessError; case(SshRemoteProcess::ExitStatus::FailedToStart): return ErrorCode::FailedToStartRemoteProcessError;
case(SshRemoteProcess::ExitStatus::CrashExit): return ErrorCode::RemoteProcessCrashError; case(SshRemoteProcess::ExitStatus::CrashExit): return ErrorCode::RemoteProcessCrashError;
default: return ErrorCode::SshInternalError;
} }
} }
@ -238,10 +265,24 @@ SshConnectionParameters ServerController::sshParams(const ServerCredentials &cre
ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto) ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto)
{ {
QString scriptFileName; QString scriptFileName;
DockerContainer container;
if (proto == Protocol::OpenVpn || proto == Protocol::Any) { ErrorCode errorCode;
scriptFileName = ":/server_scripts/remove_openvpn_server.sh"; if (proto == Protocol::Any) {
removeServer(credentials, Protocol::OpenVpn);
removeServer(credentials, Protocol::ShadowSocks);
return ErrorCode::NoError;
} }
else if (proto == Protocol::OpenVpn) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::OpenVpn;
}
else if (proto == Protocol::ShadowSocks) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::ShadowSocks;
}
else return ErrorCode::NotImplementedError;
QString scriptData; QString scriptData;
@ -251,7 +292,7 @@ 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(sshParams(credentials), scriptData); return runScript(container, sshParams(credentials), scriptData);
} }
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto) ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
@ -263,8 +304,10 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr
return setupShadowSocksServer(credentials); return setupShadowSocksServer(credentials);
} }
else if (proto == Protocol::Any) { else if (proto == Protocol::Any) {
return ErrorCode::NotImplementedError;
// TODO: run concurently // TODO: run concurently
return setupOpenVpnServer(credentials); // return setupOpenVpnServer(credentials);
//setupShadowSocksServer(credentials); //setupShadowSocksServer(credentials);
} }
@ -281,18 +324,49 @@ ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credenti
scriptData = file.readAll(); scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError; if (scriptData.isEmpty()) return ErrorCode::InternalError;
ErrorCode e = runScript(sshParams(credentials), scriptData); ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData);
if (e) return e; if (e) return e;
//return ok; return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
return checkOpenVpnServer(credentials);
} }
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials) ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
{ {
Q_UNUSED(credentials) // Setup openvpn part
QString scriptData;
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
QFile file(scriptFileName);
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
return ErrorCode::NotImplementedError; scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError;
ErrorCode e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), scriptData);
if (e) return e;
// Create ss config
QJsonObject ssConfig;
ssConfig.insert("server", "0.0.0.0");
ssConfig.insert("server_port", ssRemotePort());
ssConfig.insert("local_port", ssContainerPort());
ssConfig.insert("password", credentials.password);
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ssEncryption());
QString configData = QJsonDocument(ssConfig).toJson();
QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
qDebug().noquote() << configData;
configData.replace("\"", "\\\"");
qDebug().noquote() << configData;
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
// Start ss
QString script = QString("docker exec -i %1 sh -c \"ss-server -c %2 &\"").
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
return e;
} }
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams) SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)

View file

@ -22,21 +22,32 @@ public:
static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; } static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; }
static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; } 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);
static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto); static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto);
static ErrorCode checkOpenVpnServer(const ServerCredentials &credentials); static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
static ErrorCode uploadTextFileToContainer(const ServerCredentials &credentials, QString &file, const QString &path); static ErrorCode uploadTextFileToContainer(DockerContainer container,
static QString getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); const ServerCredentials &credentials, QString &file, const QString &path);
static ErrorCode signCert(const ServerCredentials &credentials, QString clientId); static QString getTextFileFromContainer(DockerContainer container,
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
private: private:
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script); static ErrorCode runScript(DockerContainer container,
const QSsh::SshConnectionParameters &sshParams, QString script);
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials); static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials); static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);

View file

@ -19,7 +19,8 @@ OpenVpnProtocol::OpenVpnProtocol(const QString& args, QObject* parent) :
OpenVpnProtocol::~OpenVpnProtocol() OpenVpnProtocol::~OpenVpnProtocol()
{ {
stop(); qDebug() << "OpenVpnProtocol::stop()";
OpenVpnProtocol::stop();
} }
void OpenVpnProtocol::onMessageReceived(const Message& message) void OpenVpnProtocol::onMessageReceived(const Message& message)
@ -120,7 +121,7 @@ ErrorCode OpenVpnProtocol::start()
m_requestFromUserToStop = false; m_requestFromUserToStop = false;
m_openVpnStateSigTermHandlerTimer.stop(); m_openVpnStateSigTermHandlerTimer.stop();
stop(); OpenVpnProtocol::stop();
if (communicator() && !communicator()->isConnected()) { if (communicator() && !communicator()->isConnected()) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed); setLastError(ErrorCode::AmneziaServiceConnectionFailed);

View file

@ -15,7 +15,7 @@ class OpenVpnProtocol : public VpnProtocol
public: public:
explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr); explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
~OpenVpnProtocol() override; virtual ~OpenVpnProtocol() override;
ErrorCode start() override; ErrorCode start() override;
void stop() override; void stop() override;

View file

@ -1,6 +1,68 @@
#include "shadowsocksvpnprotocol.h" #include "shadowsocksvpnprotocol.h"
#include "core/servercontroller.h"
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol() #include "communicator.h"
#include "debug.h"
#include "utils.h"
#include <QJsonDocument>
#include <QJsonObject>
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QString &args, QObject *parent):
OpenVpnProtocol(args, parent)
{ {
m_shadowSocksConfig = args;
}
ErrorCode ShadowSocksVpnProtocol::start()
{
qDebug() << "ShadowSocksVpnProtocol::start()";
QJsonObject config = QJsonDocument::fromJson(m_shadowSocksConfig.toUtf8()).object();
ssProcess.setProcessChannelMode(QProcess::MergedChannels);
ssProcess.setProgram(shadowSocksExecPath());
ssProcess.setArguments(QStringList() << "-s" << config.value("server").toString()
<< "-p" << QString::number(config.value("server_port").toInt())
<< "-l" << QString::number(config.value("local_port").toInt())
<< "-m" << config.value("method").toString()
<< "-k" << config.value("password").toString()
);
ssProcess.start();
ssProcess.waitForStarted();
if (ssProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
return OpenVpnProtocol::start();
}
else return ErrorCode::FailedToStartRemoteProcessError;
}
void ShadowSocksVpnProtocol::stop()
{
qDebug() << "ShadowSocksVpnProtocol::stop()";
ssProcess.kill();
}
QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
{
#ifdef Q_OS_WIN
return Utils::executable(QString("ss/ss-local"), true);
#else
return Utils::executable(QString("/ss-local"), true);
#endif
}
QString ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto)
{
QJsonObject ssConfig;
ssConfig.insert("server", credentials.hostName);
ssConfig.insert("server_port", ServerController::ssRemotePort());
ssConfig.insert("local_port", ServerController::ssContainerPort());
ssConfig.insert("password", credentials.password);
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ServerController::ssEncryption());
return QJsonDocument(ssConfig).toJson();
} }

View file

@ -2,11 +2,26 @@
#define SHADOWSOCKSVPNPROTOCOL_H #define SHADOWSOCKSVPNPROTOCOL_H
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
#include "QProcess"
class ShadowSocksVpnProtocol : public OpenVpnProtocol class ShadowSocksVpnProtocol : public OpenVpnProtocol
{ {
public: public:
ShadowSocksVpnProtocol(); ShadowSocksVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
ErrorCode start() override;
void stop() override;
static QString genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocks);
protected:
QString shadowSocksExecPath() const;
protected:
QString m_shadowSocksConfig;
private:
QProcess ssProcess;
}; };
#endif // SHADOWSOCKSVPNPROTOCOL_H #endif // SHADOWSOCKSVPNPROTOCOL_H

View file

@ -33,10 +33,11 @@
<file>images/line.png</file> <file>images/line.png</file>
<file>images/server_settings.png</file> <file>images/server_settings.png</file>
<file>images/share.png</file> <file>images/share.png</file>
<file>server_scripts/remove_openvpn_server.sh</file> <file>server_scripts/remove_container.sh</file>
<file>server_scripts/setup_openvpn_server.sh</file> <file>server_scripts/setup_openvpn_server.sh</file>
<file>server_scripts/template.ovpn</file> <file>server_scripts/template_openvpn.ovpn</file>
<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>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -0,0 +1,2 @@
docker stop $CONTAINER_NAME
docker rm -f $CONTAINER_NAME

View file

@ -1,2 +0,0 @@
sudo docker stop amneziavpn
sudo docker rm -f amneziavpn

View file

@ -1,24 +1,21 @@
#DOCKER_IMAGE="amneziavpn/openvpn:latest" #CONTAINER_NAME=... this var will be set in ServerController
#CONTAINER_NAME="amneziavpn"
#sudo apt update #apt update
sudo apt install -y docker.io curl apt install -y docker.io curl
sudo systemctl start docker systemctl start docker
sudo docker stop amneziavpn docker stop $CONTAINER_NAME
sudo docker rm -f amneziavpn docker rm -f $CONTAINER_NAME
sudo docker pull amneziavpn/openvpn:latest docker pull amneziavpn/openvpn:latest
sudo docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/udp --name amneziavpn amneziavpn/openvpn:latest docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/udp --name $CONTAINER_NAME amneziavpn/openvpn:latest
docker exec -i amneziavpn sh -c "mkdir -p /opt/amneziavpn_data/clients" docker exec -i $CONTAINER_NAME sh -c "mkdir -p /opt/amneziavpn_data/clients"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh"
#docker exec -i amneziavpn sh -c "cat /proc/sys/kernel/random/entropy_avail" 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"
docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && easyrsa init-pki" docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3"
docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh" docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4"
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"
docker exec -i amneziavpn 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" docker exec -i $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf &"
docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3"
docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4"
docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && cp pki/ca.crt pki/issued/MyReq.crt pki/private/MyReq.key ta.key /etc/openvpn"
docker exec -i amneziavpn sh -c "openvpn --config /etc/openvpn/server.conf &"

View file

@ -1,13 +1,21 @@
#DOCKER_IMAGE="amneziavpn/shadow-vpn:latest" #CONTAINER_NAME=... this var will be set in ServerController
#CONTAINER_NAME="shadow-vpn"
#sudo apt update #apt update
sudo apt install -y docker.io curl apt install -y docker.io curl
sudo systemctl start docker systemctl start docker
sudo docker stop shadow-vpn docker stop $CONTAINER_NAME
sudo docker rm -f shadow-vpn docker rm -f $CONTAINER_NAME
sudo docker pull amneziavpn/shadow-vpn:latest docker pull amneziavpn/shadowsocks:latest
sudo docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/tcp -p 6789:6789/tcp --name shadow-vpn amneziavpn/shadow-vpn:latest docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/tcp -p 6789:6789/tcp --name $CONTAINER_NAME amneziavpn/shadowsocks:latest
docker exec -i $CONTAINER_NAME sh -c "mkdir -p /opt/amneziavpn_data/clients"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh"
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"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4"
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"
docker exec -i $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf &"

View file

@ -14,6 +14,7 @@ key-direction 1
remote-cert-tls server remote-cert-tls server
remote $REMOTE_HOST $REMOTE_PORT remote $REMOTE_HOST $REMOTE_PORT
<ca> <ca>
$CA_CERT $CA_CERT
</ca> </ca>

View file

@ -0,0 +1,31 @@
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
socks-proxy 127.0.0.1 $LOCAL_PROXY_PORT
route $REMOTE_HOST 255.255.255.255 net_gateway
remote $REMOTE_HOST $REMOTE_PORT
<ca>
$CA_CERT
</ca>
<cert>
$CLIENT_CERT
</cert>
<key>
$PRIV_KEY
</key>
<tls-auth>
$TA_KEY
</tls-auth>

View file

@ -336,6 +336,8 @@ void MainWindow::onBytesChanged(quint64 receivedData, quint64 sentData)
void MainWindow::onConnectionStateChanged(VpnProtocol::ConnectionState state) void MainWindow::onConnectionStateChanged(VpnProtocol::ConnectionState state)
{ {
qDebug() << "MainWindow::onConnectionStateChanged" << VpnProtocol::textConnectionState(state);
bool pushButtonConnectEnabled = false; bool pushButtonConnectEnabled = false;
ui->label_state->setText(VpnProtocol::textConnectionState(state)); ui->label_state->setText(VpnProtocol::textConnectionState(state));
@ -541,6 +543,7 @@ void MainWindow::onConnect()
QMessageBox::critical(this, APPLICATION_NAME, errorString(errorCode)); QMessageBox::critical(this, APPLICATION_NAME, errorString(errorCode));
return; return;
} }
ui->pushButton_connect->setEnabled(false); ui->pushButton_connect->setEnabled(false);
} }

View file

@ -267,7 +267,7 @@ QStackedWidget QWidget {
</string> </string>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>2</number>
</property> </property>
<widget class="QWidget" name="page_start"> <widget class="QWidget" name="page_start">
<widget class="QLabel" name="label_23"> <widget class="QLabel" name="label_23">

View file

@ -6,6 +6,7 @@
#include <core/servercontroller.h> #include <core/servercontroller.h>
#include "protocols/openvpnprotocol.h" #include "protocols/openvpnprotocol.h"
#include "protocols/shadowsocksvpnprotocol.h"
#include "utils.h" #include "utils.h"
#include "vpnconnection.h" #include "vpnconnection.h"
@ -36,8 +37,8 @@ ErrorCode VpnConnection::lastError() const
ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, Protocol protocol) ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, Protocol protocol)
{ {
ErrorCode errorCode = ErrorCode::NoError; ErrorCode errorCode = ErrorCode::NoError;
if (protocol == Protocol::OpenVpn) { if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks) {
QString configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, &errorCode); QString configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode);
if (errorCode) { if (errorCode) {
return errorCode; return errorCode;
} }
@ -51,8 +52,7 @@ ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials,
return ErrorCode::FailedToSaveConfigData; return ErrorCode::FailedToSaveConfigData;
} }
else if (protocol == Protocol::ShadowSocks) { else {
// Request OpenVPN config and ShadowSocks
return ErrorCode::NotImplementedError; return ErrorCode::NotImplementedError;
} }
return ErrorCode::NotImplementedError; return ErrorCode::NotImplementedError;
@ -61,6 +61,8 @@ ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials,
ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Protocol protocol) ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Protocol protocol)
{ {
// protocol = Protocol::ShadowSocks;
// 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
qDebug() << "Connect to VPN"; qDebug() << "Connect to VPN";
@ -81,8 +83,18 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
} }
else if (protocol == Protocol::ShadowSocks) { else if (protocol == Protocol::ShadowSocks) {
ErrorCode e = requestVpnConfig(credentials, Protocol::ShadowSocks);
if (e) {
emit connectionStateChanged(VpnProtocol::ConnectionState::Error); emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
return ErrorCode::NotImplementedError; return e;
}
if (m_vpnProtocol) {
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
}
m_vpnProtocol.reset(new ShadowSocksVpnProtocol(ShadowSocksVpnProtocol::genShadowSocksConfig(credentials)));
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)));
@ -107,6 +119,12 @@ void VpnConnection::disconnectFromVpn()
m_vpnProtocol.data()->stop(); m_vpnProtocol.data()->stop();
} }
VpnProtocol::ConnectionState VpnConnection::connectionState()
{
if (!m_vpnProtocol) return VpnProtocol::ConnectionState::Disconnected;
return m_vpnProtocol->connectionState();
}
bool VpnConnection::onConnected() const bool VpnConnection::onConnected() const
{ {
if (!m_vpnProtocol.data()) { if (!m_vpnProtocol.data()) {

View file

@ -27,6 +27,8 @@ public:
bool onDisconnected() const; bool onDisconnected() const;
void disconnectFromVpn(); void disconnectFromVpn();
VpnProtocol::ConnectionState connectionState();
signals: signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void connectionStateChanged(VpnProtocol::ConnectionState state); void connectionStateChanged(VpnProtocol::ConnectionState state);