From ec9ae0ef4f70b5b32ca1b2de19ef52c17fccbeca Mon Sep 17 00:00:00 2001 From: pokamest Date: Fri, 15 Jan 2021 23:36:35 +0300 Subject: [PATCH] shadowsocks impl --- client/core/defs.h | 6 + client/core/openvpnconfigurator.cpp | 61 ++++-- client/core/openvpnconfigurator.h | 6 +- client/core/servercontroller.cpp | 174 +++++++++++++----- client/core/servercontroller.h | 21 ++- client/protocols/openvpnprotocol.cpp | 5 +- client/protocols/openvpnprotocol.h | 2 +- client/protocols/shadowsocksvpnprotocol.cpp | 66 ++++++- client/protocols/shadowsocksvpnprotocol.h | 17 +- client/resources.qrc | 5 +- client/server_scripts/remove_container.sh | 2 + .../server_scripts/remove_openvpn_server.sh | 2 - client/server_scripts/setup_openvpn_server.sh | 35 ++-- .../setup_shadowsocks_server.sh | 26 ++- .../{template.ovpn => template_openvpn.ovpn} | 1 + .../server_scripts/template_shadowsocks.ovpn | 31 ++++ client/ui/mainwindow.cpp | 3 + client/ui/mainwindow.ui | 2 +- client/vpnconnection.cpp | 30 ++- client/vpnconnection.h | 2 + 20 files changed, 380 insertions(+), 117 deletions(-) create mode 100644 client/server_scripts/remove_container.sh delete mode 100644 client/server_scripts/remove_openvpn_server.sh rename client/server_scripts/{template.ovpn => template_openvpn.ovpn} (99%) create mode 100644 client/server_scripts/template_shadowsocks.ovpn diff --git a/client/core/defs.h b/client/core/defs.h index 99fc9e75..95bf8d16 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -12,6 +12,12 @@ enum class Protocol { WireGuard }; +enum class DockerContainer { + OpenVpn, + ShadowSocks, + WireGuard +}; + struct ServerCredentials { QString hostName; diff --git a/client/core/openvpnconfigurator.cpp b/client/core/openvpnconfigurator.cpp index ba76f306..9e18c028 100644 --- a/client/core/openvpnconfigurator.cpp +++ b/client/core/openvpnconfigurator.cpp @@ -144,46 +144,77 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() 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(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { - *errorCode = ErrorCode::EasyRsaExecutableMissing; + if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing; return connData; } QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId); - ErrorCode e = ServerController::uploadTextFileToContainer(credentials, connData.request, reqFileName); - if (e) { - *errorCode = e; + + DockerContainer container; + if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn; + else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks; + else { + if (errorCode) *errorCode = ErrorCode::InternalError; return connData; } - ServerController::signCert(credentials, connData.clientId); - - connData.caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath(), &e); - connData.clientCert = ServerController::getTextFileFromContainer(credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e); + ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { - *errorCode = e; + if (errorCode) *errorCode = e; 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; } -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); QString config = configTemplFile.readAll(); - ConnectionData connData = prepareOpenVpnConfig(credentials, errorCode); + ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode); + + if (proto == Protocol::OpenVpn) + config.replace("$PROTO", "udp"); + else if (proto == Protocol::ShadowSocks) { + config.replace("$PROTO", "tcp"); + config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort())); + } - config.replace("$PROTO", "udp"); config.replace("$REMOTE_HOST", connData.host); config.replace("$REMOTE_PORT", "1194"); config.replace("$CA_CERT", connData.caCert); diff --git a/client/core/openvpnconfigurator.h b/client/core/openvpnconfigurator.h index 9f9b060b..dc0d1ec0 100644 --- a/client/core/openvpnconfigurator.h +++ b/client/core/openvpnconfigurator.h @@ -22,7 +22,8 @@ public: 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: static QString getRandomString(int len); @@ -34,7 +35,8 @@ private: static ConnectionData createCertRequest(); - static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); + static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, + Protocol proto, ErrorCode *errorCode = nullptr); }; diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index aa3bb912..c559a24d 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -5,13 +5,25 @@ #include #include #include +#include +#include #include "sshconnectionmanager.h" 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")); @@ -26,7 +38,9 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, const QStringList &lines = script.split("\n", QString::SkipEmptyParts); 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("#")) { continue; } @@ -52,23 +66,22 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, wait.quit(); }); - // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ - // QString s = proc->readAllStandardOutput(); - // if (s != "." && !s.isEmpty()) { - // qDebug().noquote() << s; - // } - // }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ +// QString s = proc->readAllStandardOutput(); +// if (s != "." && !s.isEmpty()) { +// qDebug().noquote() << s; +// } +// }); - // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ - // QString s = proc->readAllStandardError(); - // if (s != "." && !s.isEmpty()) { - // qDebug().noquote() << s; - // } - // }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ +// QString s = proc->readAllStandardError(); +// if (s != "." && !s.isEmpty()) { +// qDebug().noquote() << s; +// } +// }); proc->start(); - - if (i < lines.count() - 1) { + if (i < lines.count()) { wait.exec(); } @@ -81,13 +94,13 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, return ErrorCode::NoError; } -ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &credentials, - QString &file, const QString &path) +ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, + const ServerCredentials &credentials, QString &file, const QString &path) { QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); - QString script = QString("docker exec -i amneziavpn sh -c \"echo \'%1\' > %2\""). - arg(file).arg(path); + QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\""). + arg(getContainerName(container)).arg(file).arg(path); qDebug().noquote() << script; @@ -116,25 +129,29 @@ ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &c wait.quit(); }); - // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ - // qDebug().noquote() << proc->readAllStandardOutput(); - // }); + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ + qDebug().noquote() << proc->readAllStandardOutput(); + }); - // QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ - // qDebug().noquote() << proc->readAllStandardError(); - // }); + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ + qDebug().noquote() << proc->readAllStandardError(); + }); proc->start(); wait.exec(); +// if (proc->isRunning()) { +// wait.exec(); +// } + return fromSshProcessExitStatus(exitStatus); } -QString ServerController::getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, - ErrorCode *errorCode) +QString ServerController::getTextFileFromContainer(DockerContainer container, + const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode) { - QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\""). - arg(path); + QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\""). + arg(getContainerName(container)).arg(path); qDebug().noquote() << "Copy file from container\n" << script; @@ -162,6 +179,10 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred proc->start(); wait.exec(); +// if (proc->isRunning()) { +// wait.exec(); +// } + if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) { if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus); } @@ -169,25 +190,28 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred 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 && " - "easyrsa import-req /opt/amneziavpn_data/clients/%1.req %1 &>/dev/null\"") - .arg(clientId); + QString script_import = QString("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("docker exec -i amneziavpn bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && " - "easyrsa sign-req client %1 &>/dev/null\"") - .arg(clientId); + QString script_sign = QString("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(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 taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath()); + QString caCert = ServerController::getTextFileFromContainer(container, + credentials, ServerController::caCertPath()); + QString taKey = ServerController::getTextFileFromContainer(container, + credentials, ServerController::taKeyPath()); if (!caCert.isEmpty() && !taKey.isEmpty()) { return ErrorCode::NoError; @@ -209,15 +233,18 @@ ErrorCode ServerController::fromSshConnectionErrorCode(SshError error) case(QSsh::SshAuthenticationError): return ErrorCode::SshAuthenticationError; case(QSsh::SshClosedByServerError): return ErrorCode::SshClosedByServerError; case(QSsh::SshInternalError): return ErrorCode::SshInternalError; + default: return ErrorCode::SshInternalError; } } ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus) { + qDebug() << exitStatus; switch (SshRemoteProcess::ExitStatus(exitStatus)) { case(SshRemoteProcess::ExitStatus::NormalExit): return ErrorCode::NoError; case(SshRemoteProcess::ExitStatus::FailedToStart): return ErrorCode::FailedToStartRemoteProcessError; 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) { QString scriptFileName; + DockerContainer container; - if (proto == Protocol::OpenVpn || proto == Protocol::Any) { - scriptFileName = ":/server_scripts/remove_openvpn_server.sh"; + ErrorCode errorCode; + 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; @@ -251,7 +292,7 @@ ErrorCode ServerController::removeServer(const ServerCredentials &credentials, P scriptData = file.readAll(); 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) @@ -263,8 +304,10 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr return setupShadowSocksServer(credentials); } else if (proto == Protocol::Any) { + return ErrorCode::NotImplementedError; + // TODO: run concurently - return setupOpenVpnServer(credentials); + // return setupOpenVpnServer(credentials); //setupShadowSocksServer(credentials); } @@ -281,18 +324,49 @@ ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credenti scriptData = file.readAll(); if (scriptData.isEmpty()) return ErrorCode::InternalError; - ErrorCode e = runScript(sshParams(credentials), scriptData); + ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData); if (e) return e; - //return ok; - return checkOpenVpnServer(credentials); + return checkOpenVpnServer(DockerContainer::OpenVpn, 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) diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index f4458791..ad2d7e22 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -22,21 +22,32 @@ public: 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 ErrorCode removeServer(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 QString getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); + static ErrorCode uploadTextFileToContainer(DockerContainer container, + 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: 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 setupShadowSocksServer(const ServerCredentials &credentials); diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index c310bd8b..c290e13d 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -19,7 +19,8 @@ OpenVpnProtocol::OpenVpnProtocol(const QString& args, QObject* parent) : OpenVpnProtocol::~OpenVpnProtocol() { - stop(); + qDebug() << "OpenVpnProtocol::stop()"; + OpenVpnProtocol::stop(); } void OpenVpnProtocol::onMessageReceived(const Message& message) @@ -120,7 +121,7 @@ ErrorCode OpenVpnProtocol::start() m_requestFromUserToStop = false; m_openVpnStateSigTermHandlerTimer.stop(); - stop(); + OpenVpnProtocol::stop(); if (communicator() && !communicator()->isConnected()) { setLastError(ErrorCode::AmneziaServiceConnectionFailed); diff --git a/client/protocols/openvpnprotocol.h b/client/protocols/openvpnprotocol.h index 16fe8bdc..84f7709d 100644 --- a/client/protocols/openvpnprotocol.h +++ b/client/protocols/openvpnprotocol.h @@ -15,7 +15,7 @@ class OpenVpnProtocol : public VpnProtocol public: explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr); - ~OpenVpnProtocol() override; + virtual ~OpenVpnProtocol() override; ErrorCode start() override; void stop() override; diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 5ab7fdb2..80b164a1 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -1,6 +1,68 @@ #include "shadowsocksvpnprotocol.h" +#include "core/servercontroller.h" -ShadowSocksVpnProtocol::ShadowSocksVpnProtocol() +#include "communicator.h" +#include "debug.h" +#include "utils.h" + +#include +#include + +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(); } diff --git a/client/protocols/shadowsocksvpnprotocol.h b/client/protocols/shadowsocksvpnprotocol.h index 37006f36..b6645ea1 100644 --- a/client/protocols/shadowsocksvpnprotocol.h +++ b/client/protocols/shadowsocksvpnprotocol.h @@ -2,11 +2,26 @@ #define SHADOWSOCKSVPNPROTOCOL_H #include "openvpnprotocol.h" +#include "QProcess" class ShadowSocksVpnProtocol : public OpenVpnProtocol { 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 diff --git a/client/resources.qrc b/client/resources.qrc index ec0b46a2..e212d15e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -33,10 +33,11 @@ images/line.png images/server_settings.png images/share.png - server_scripts/remove_openvpn_server.sh + server_scripts/remove_container.sh server_scripts/setup_openvpn_server.sh - server_scripts/template.ovpn + server_scripts/template_openvpn.ovpn images/background_connected.png server_scripts/setup_shadowsocks_server.sh + server_scripts/template_shadowsocks.ovpn diff --git a/client/server_scripts/remove_container.sh b/client/server_scripts/remove_container.sh new file mode 100644 index 00000000..e5dbd439 --- /dev/null +++ b/client/server_scripts/remove_container.sh @@ -0,0 +1,2 @@ +docker stop $CONTAINER_NAME +docker rm -f $CONTAINER_NAME diff --git a/client/server_scripts/remove_openvpn_server.sh b/client/server_scripts/remove_openvpn_server.sh deleted file mode 100644 index 3df28e07..00000000 --- a/client/server_scripts/remove_openvpn_server.sh +++ /dev/null @@ -1,2 +0,0 @@ -sudo docker stop amneziavpn -sudo docker rm -f amneziavpn diff --git a/client/server_scripts/setup_openvpn_server.sh b/client/server_scripts/setup_openvpn_server.sh index d9b30a15..ad05b708 100644 --- a/client/server_scripts/setup_openvpn_server.sh +++ b/client/server_scripts/setup_openvpn_server.sh @@ -1,24 +1,21 @@ -#DOCKER_IMAGE="amneziavpn/openvpn:latest" -#CONTAINER_NAME="amneziavpn" +#CONTAINER_NAME=... this var will be set in ServerController -#sudo apt update -sudo apt install -y docker.io curl -sudo systemctl start docker +#apt update +apt install -y docker.io curl +systemctl start docker -sudo docker stop amneziavpn -sudo docker rm -f amneziavpn -sudo 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 stop $CONTAINER_NAME +docker rm -f $CONTAINER_NAME +docker pull 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 amneziavpn sh -c "cd /opt/amneziavpn_data && easyrsa init-pki" -docker exec -i amneziavpn sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh" - -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 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 &" +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 &" diff --git a/client/server_scripts/setup_shadowsocks_server.sh b/client/server_scripts/setup_shadowsocks_server.sh index 46df107a..87705aba 100644 --- a/client/server_scripts/setup_shadowsocks_server.sh +++ b/client/server_scripts/setup_shadowsocks_server.sh @@ -1,13 +1,21 @@ -#DOCKER_IMAGE="amneziavpn/shadow-vpn:latest" -#CONTAINER_NAME="shadow-vpn" +#CONTAINER_NAME=... this var will be set in ServerController -#sudo apt update -sudo apt install -y docker.io curl -sudo systemctl start docker +#apt update +apt install -y docker.io curl +systemctl start docker -sudo docker stop shadow-vpn -sudo docker rm -f shadow-vpn -sudo docker pull amneziavpn/shadow-vpn: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 stop $CONTAINER_NAME +docker rm -f $CONTAINER_NAME +docker pull amneziavpn/shadowsocks: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 &" diff --git a/client/server_scripts/template.ovpn b/client/server_scripts/template_openvpn.ovpn similarity index 99% rename from client/server_scripts/template.ovpn rename to client/server_scripts/template_openvpn.ovpn index da6d43c2..ee014358 100644 --- a/client/server_scripts/template.ovpn +++ b/client/server_scripts/template_openvpn.ovpn @@ -14,6 +14,7 @@ key-direction 1 remote-cert-tls server remote $REMOTE_HOST $REMOTE_PORT + $CA_CERT diff --git a/client/server_scripts/template_shadowsocks.ovpn b/client/server_scripts/template_shadowsocks.ovpn new file mode 100644 index 00000000..c68cac7d --- /dev/null +++ b/client/server_scripts/template_shadowsocks.ovpn @@ -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_CERT + + +$CLIENT_CERT + + +$PRIV_KEY + + +$TA_KEY + diff --git a/client/ui/mainwindow.cpp b/client/ui/mainwindow.cpp index 718edeea..ecffc1a2 100644 --- a/client/ui/mainwindow.cpp +++ b/client/ui/mainwindow.cpp @@ -336,6 +336,8 @@ void MainWindow::onBytesChanged(quint64 receivedData, quint64 sentData) void MainWindow::onConnectionStateChanged(VpnProtocol::ConnectionState state) { + qDebug() << "MainWindow::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); + bool pushButtonConnectEnabled = false; ui->label_state->setText(VpnProtocol::textConnectionState(state)); @@ -541,6 +543,7 @@ void MainWindow::onConnect() QMessageBox::critical(this, APPLICATION_NAME, errorString(errorCode)); return; } + ui->pushButton_connect->setEnabled(false); } diff --git a/client/ui/mainwindow.ui b/client/ui/mainwindow.ui index ccba720b..7f481945 100644 --- a/client/ui/mainwindow.ui +++ b/client/ui/mainwindow.ui @@ -267,7 +267,7 @@ QStackedWidget QWidget { - 0 + 2 diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index ce2cb0fa..5da635bc 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -6,6 +6,7 @@ #include #include "protocols/openvpnprotocol.h" +#include "protocols/shadowsocksvpnprotocol.h" #include "utils.h" #include "vpnconnection.h" @@ -36,8 +37,8 @@ ErrorCode VpnConnection::lastError() const ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, Protocol protocol) { ErrorCode errorCode = ErrorCode::NoError; - if (protocol == Protocol::OpenVpn) { - QString configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, &errorCode); + if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks) { + QString configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode); if (errorCode) { return errorCode; } @@ -51,8 +52,7 @@ ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, return ErrorCode::FailedToSaveConfigData; } - else if (protocol == Protocol::ShadowSocks) { - // Request OpenVPN config and ShadowSocks + else { return ErrorCode::NotImplementedError; } return ErrorCode::NotImplementedError; @@ -61,6 +61,8 @@ ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Protocol protocol) { + // protocol = Protocol::ShadowSocks; + // TODO: Try protocols one by one in case of Protocol::Any // TODO: Implement some behavior in case if connection not stable qDebug() << "Connect to VPN"; @@ -81,8 +83,18 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); } else if (protocol == Protocol::ShadowSocks) { - emit connectionStateChanged(VpnProtocol::ConnectionState::Error); - return ErrorCode::NotImplementedError; + ErrorCode e = requestVpnConfig(credentials, Protocol::ShadowSocks); + if (e) { + emit connectionStateChanged(VpnProtocol::ConnectionState::Error); + 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))); @@ -107,6 +119,12 @@ void VpnConnection::disconnectFromVpn() m_vpnProtocol.data()->stop(); } +VpnProtocol::ConnectionState VpnConnection::connectionState() +{ + if (!m_vpnProtocol) return VpnProtocol::ConnectionState::Disconnected; + return m_vpnProtocol->connectionState(); +} + bool VpnConnection::onConnected() const { if (!m_vpnProtocol.data()) { diff --git a/client/vpnconnection.h b/client/vpnconnection.h index d4f13d65..3964c40e 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -27,6 +27,8 @@ public: bool onDisconnected() const; void disconnectFromVpn(); + VpnProtocol::ConnectionState connectionState(); + signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void connectionStateChanged(VpnProtocol::ConnectionState state);