Ikev2 support

This commit is contained in:
pokamest 2021-10-04 19:07:49 +03:00
parent a5bcf1a02d
commit fa151cd320
27 changed files with 626 additions and 90 deletions

View file

@ -19,6 +19,7 @@ include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri")
HEADERS += \
../ipc/ipc.h \
configurators/cloak_configurator.h \
configurators/ikev2_configurator.h \
configurators/shadowsocks_configurator.h \
configurators/ssh_configurator.h \
configurators/vpn_configurator.h \
@ -34,6 +35,7 @@ HEADERS += \
debug.h \
defines.h \
managementserver.h \
protocols/ikev2_vpn_protocol.h \
protocols/openvpnovercloakprotocol.h \
protocols/protocols_defs.h \
protocols/shadowsocksvpnprotocol.h \
@ -73,6 +75,7 @@ HEADERS += \
SOURCES += \
configurators/cloak_configurator.cpp \
configurators/ikev2_configurator.cpp \
configurators/shadowsocks_configurator.cpp \
configurators/ssh_configurator.cpp \
configurators/vpn_configurator.cpp \
@ -87,6 +90,7 @@ SOURCES += \
debug.cpp \
main.cpp \
managementserver.cpp \
protocols/ikev2_vpn_protocol.cpp \
protocols/openvpnovercloakprotocol.cpp \
protocols/protocols_defs.cpp \
protocols/shadowsocksvpnprotocol.cpp \

View file

@ -0,0 +1,67 @@
#include "ikev2_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QDebug>
#include <QTemporaryFile>
#include <QJsonDocument>
#include "sftpdefs.h"
#include "core/server_defs.h"
#include "containers/containers_defs.h"
#include "core/scripts_registry.h"
#include "utils.h"
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
{
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
connData.clientId = Utils::getRandomString(16);
connData.password = Utils::getRandomString(16);
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "\
"-s \"O=IKEv2 VPN,CN=%1\" "\
"-k rsa -g 3072 -v 120 "\
"-d sql:/etc/ipsec.d -t \",,\" "\
"--keyUsage digitalSignature,keyEncipherment "\
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId);
ErrorCode e = ServerController::runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
.arg(connData.password)
.arg(connData.clientId)
.arg(certFileName);
e = ServerController::runContainerScript(credentials, container, scriptExportCert);
connData.cert = ServerController::getTextFileFromContainer(container, credentials, certFileName, &e);
qDebug() << "Ikev2Configurator::ConnectionData cert size:" << connData.cert.size();
return connData;
}
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode && *errorCode) {
return "";
}
QJsonObject config;
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.cert.toBase64());
config[config_key::password] = connData.password;
return QJsonDocument(config).toJson();
}

View file

@ -0,0 +1,30 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "core/defs.h"
#include "core/servercontroller.h"
class Ikev2Configurator
{
public:
struct ConnectionData {
QByteArray cert; // p12 client cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
static QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
private:
static ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
};
#endif // IKEV2_CONFIGURATOR_H

View file

@ -248,10 +248,6 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config)
{
// TODO replace DNS if it already set
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (m_settings().routeMode() != Settings::VpnAllSites) {
config.replace("redirect-gateway def1 bypass-dhcp", "");
}
@ -277,9 +273,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config)
QString OpenVpnConfigurator::processConfigWithExportSettings(QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if(!config.contains("redirect-gateway def1 bypass-dhcp")) {
config.append("redirect-gateway def1 bypass-dhcp\n");
}
@ -308,5 +301,5 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
QStringList scriptList {script_import, script_sign};
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
return ServerController::runScript(ServerController::sshParams(credentials), script);
return ServerController::runScript(credentials, script);
}

View file

@ -3,6 +3,7 @@
#include "cloak_configurator.h"
#include "shadowsocks_configurator.h"
#include "wireguard_configurator.h"
#include "ikev2_configurator.h"
#include <QFile>
#include <QJsonObject>
@ -10,6 +11,11 @@
#include "containers/containers_defs.h"
Settings &VpnConfigurator::m_settings()
{
static Settings s;
return s;
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode)
@ -27,6 +33,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
case Protocol::WireGuard:
return WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
case Protocol::Ikev2:
return Ikev2Configurator::genIkev2Config(credentials, container, containerConfig, errorCode);
default:
return "";
}
@ -34,6 +43,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer container, Protocol proto, QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (proto == Protocol::OpenVpn) {
return OpenVpnConfigurator::processConfigWithLocalSettings(config);
}
@ -42,6 +54,9 @@ QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer containe
QString VpnConfigurator::processConfigWithExportSettings(DockerContainer container, Protocol proto, QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (proto == Protocol::OpenVpn) {
return OpenVpnConfigurator::processConfigWithExportSettings(config);
}

View file

@ -7,6 +7,7 @@
#include "settings.h"
#include "core/servercontroller.h"
// Retrieve connection settings from server
class VpnConfigurator
{
public:
@ -21,6 +22,7 @@ public:
static void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
static Settings &m_settings();
};
#endif // VPN_CONFIGURATOR_H

View file

@ -130,7 +130,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
e = ServerController::runScript(ServerController::sshParams(credentials),
e = ServerController::runScript(credentials,
ServerController::replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'",
ServerController::genVarsForScript(credentials, container)));

View file

@ -42,6 +42,9 @@ QVector<amnezia::Protocol> ContainerProps::protocolsForContainer(amnezia::Docker
case DockerContainer::Cloak:
return { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak };
case DockerContainer::Ipsec:
return { Protocol::Ikev2, Protocol::L2tp };
case DockerContainer::Dns:
return { };
@ -69,6 +72,8 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"},
{DockerContainer::Cloak, "OpenVpn over Cloak"},
{DockerContainer::WireGuard, "WireGuard"},
{DockerContainer::Ipsec, QObject::tr("IPsec container")},
{DockerContainer::TorWebSite, QObject::tr("Web site in TOR network")},
{DockerContainer::Dns, QObject::tr("DNS Service")},
{DockerContainer::FileShare, QObject::tr("SMB file sharing service")},
@ -84,6 +89,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols "
"configured with traffic masking by Cloak plugin")},
{DockerContainer::WireGuard, QObject::tr("WireGuard container")},
{DockerContainer::Ipsec, QObject::tr("IPsec container")},
{DockerContainer::TorWebSite, QObject::tr("Web site in TOR network")},
{DockerContainer::Dns, QObject::tr("DNS Service")},
{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")},
@ -99,15 +106,29 @@ amnezia::ServiceType ContainerProps::containerService(DockerContainer c)
case DockerContainer::Cloak : return ServiceType::Vpn;
case DockerContainer::ShadowSocks : return ServiceType::Vpn;
case DockerContainer::WireGuard : return ServiceType::Vpn;
case DockerContainer::Ipsec : return ServiceType::Vpn;
case DockerContainer::TorWebSite : return ServiceType::Other;
case DockerContainer::Dns : return ServiceType::Other;
case DockerContainer::FileShare : return ServiceType::Other;
case DockerContainer::Sftp : return ServiceType::Other;
default: return ServiceType::Other;
}
}
Protocol ContainerProps::defaultProtocol(DockerContainer c)
{
return static_cast<Protocol>(c);
switch (c) {
case DockerContainer::None : return Protocol::Any;
case DockerContainer::OpenVpn : return Protocol::OpenVpn;
case DockerContainer::Cloak : return Protocol::Cloak;
case DockerContainer::ShadowSocks : return Protocol::ShadowSocks;
case DockerContainer::WireGuard : return Protocol::WireGuard;
case DockerContainer::Ipsec : return Protocol::Ikev2;
case DockerContainer::TorWebSite : return Protocol::TorWebSite;
case DockerContainer::Dns : return Protocol::Dns;
case DockerContainer::FileShare : return Protocol::FileShare;
case DockerContainer::Sftp : return Protocol::Sftp;
}
}

View file

@ -18,6 +18,7 @@ enum DockerContainer {
ShadowSocks,
Cloak,
WireGuard,
Ipsec,
//non-vpn
TorWebSite,

View file

@ -74,6 +74,7 @@ const char key_openvpn_config_path[] = "openvpn_config_path";
const char key_shadowsocks_config_data[] = "shadowsocks_config_data";
const char key_cloak_config_data[] = "cloak_config_data";
const char key_wireguard_config_data[] = "wireguard_config_data";
const char key_ikev2_config_data[] = "ikev2_config_data";
}

View file

@ -11,6 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::FileShare: return QLatin1String("file_share");

View file

@ -24,11 +24,11 @@
using namespace QSsh;
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script,
ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
{
SshConnection *client = connectToHost(sshParams);
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() == SshConnection::State::Connecting) {
qDebug() << "ServerController::runScript aborted, connectToHost in progress";
return ErrorCode::SshTimeoutError;
@ -121,6 +121,26 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
return ErrorCode::NoError;
}
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials,
DockerContainer container, QString script,
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut,
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr)
{
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
if (e) return e;
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
e = runScript(credentials,
replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
// QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
// runScript(credentials,
// replaceVars(remover, genVarsForScript(credentials, container)));
return e;
}
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &file, const QString &path,
QSsh::SftpOverwriteMode overwriteMode)
@ -136,20 +156,20 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
};
if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) {
e = runScript(sshParams(credentials),
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
if (e) return e;
}
else if (overwriteMode == QSsh::SftpOverwriteMode::SftpAppendToExisting) {
e = runScript(sshParams(credentials),
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
if (e) return e;
e = runScript(sshParams(credentials),
e = runScript(credentials,
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
@ -162,23 +182,23 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return ErrorCode::ServerContainerMissingError;
}
runScript(sshParams(credentials),
runScript(credentials,
replaceVars(QString("sudo shred %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
runScript(sshParams(credentials),
runScript(credentials,
replaceVars(QString("sudo rm %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
return e;
}
QString ServerController::getTextFileFromContainer(DockerContainer container,
QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
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 \"xxd -p \'%2\'\"").
arg(ContainerProps::containerToString(container)).arg(path);
qDebug().noquote() << "Copy file from container\n" << script;
@ -186,14 +206,14 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState());
return QString();
return {};
}
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
if (errorCode) *errorCode = ErrorCode::SshRemoteProcessCreationError;
return QString();
return {};
}
QEventLoop wait;
@ -221,7 +241,7 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
}
if (errorCode) *errorCode = ErrorCode::NoError;
return proc->readAllStandardOutput();
return QByteArray::fromHex(proc->readAllStandardOutput());
}
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
@ -352,13 +372,13 @@ SshConnectionParameters ServerController::sshParams(const ServerCredentials &cre
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
{
return runScript(sshParams(credentials),
return runScript(credentials,
amnezia::scriptData(SharedScriptType::remove_all_containers));
}
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
{
return runScript(sshParams(credentials),
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
genVarsForScript(credentials, container)));
}
@ -482,7 +502,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
stdOut += data + "\n";
};
ErrorCode e = runScript(sshParams(credentials),
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr);
@ -495,7 +515,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
// create folder on host
return runScript(sshParams(credentials),
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
genVarsForScript(credentials, container)));
}
@ -515,7 +535,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
// stdOut += data + "\n";
// };
return runScript(sshParams(credentials),
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
genVarsForScript(credentials, container, config)));
}
@ -530,7 +550,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
stdOut += data + "\n";
};
ErrorCode e = runScript(sshParams(credentials),
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
@ -553,11 +573,12 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
};
ErrorCode e = runScript(sshParams(credentials),
ErrorCode e = runContainerScript(credentials, container,
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
VpnConfigurator::updateContainerConfigAfterInstallation(container, config, stdOut);
return e;
@ -576,7 +597,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
"/opt/amnezia/start.sh");
if (e) return e;
return runScript(sshParams(credentials),
return runScript(credentials,
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"",
genVarsForScript(credentials, container, config)));
}
@ -634,6 +655,25 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }});
// IPsec vars
vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}});
vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}});
vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}});
vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}});
vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}});
vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}});
vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}});
vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}});
vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}});
vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}});
vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}});
// Sftp vars
vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Protocol::Sftp))) }});
vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }});
@ -661,7 +701,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
stdOut += data + "\n";
};
ErrorCode e = runScript(sshParams(credentials),
ErrorCode e = runScript(credentials,
amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
if (errorCode) *errorCode = e;
@ -731,7 +771,7 @@ void ServerController::disconnectFromHost(const ServerCredentials &credentials)
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
return runScript(sshParams(credentials),
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
genVarsForScript(credentials)));
}

View file

@ -46,14 +46,18 @@ public:
const ServerCredentials &credentials, const QString &file, const QString &path,
QSsh::SftpOverwriteMode overwriteMode = QSsh::SftpOverwriteMode::SftpOverwriteExisting);
static QString getTextFileFromContainer(DockerContainer container,
static QByteArray getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
static ErrorCode setupServerFirewall(const ServerCredentials &credentials);
static QString replaceVars(const QString &script, const Vars &vars);
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script,
static ErrorCode runScript(const ServerCredentials &credentials, QString script,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
static ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);

View file

@ -0,0 +1,57 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QRegularExpression>
#include <QTcpSocket>
#include <QThread>
#include "debug.h"
#include "ikev2_vpn_protocol.h"
#include "utils.h"
Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
//m_configFile.setFileTemplate(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
readIkev2Configuration(configuration);
}
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
Ikev2Protocol::stop();
QThread::msleep(200);
}
void Ikev2Protocol::stop()
{
#ifndef Q_OS_IOS
#endif
}
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(config::key_ikev2_config_data).toObject();
}
ErrorCode Ikev2Protocol::start()
{
#ifndef Q_OS_IOS
QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8());
qDebug() << "Ikev2Protocol::start()" << cert;
QTemporaryFile certFile;
certFile.open();
certFile.write(cert);
certFile.close();
return ErrorCode::NoError;
#endif
}

View file

@ -0,0 +1,32 @@
#ifndef IPSEC_PROTOCOL_H
#define IPSEC_PROTOCOL_H
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QTimer>
#include "vpnprotocol.h"
#include "core/ipcclient.h"
class Ikev2Protocol : public VpnProtocol
{
Q_OBJECT
public:
explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~Ikev2Protocol() override;
ErrorCode start() override;
void stop() override;
private:
void readIkev2Configuration(const QJsonObject &configuration);
private:
QJsonObject m_config;
};
#endif // IPSEC_PROTOCOL_H

View file

@ -57,7 +57,7 @@ void OpenVpnProtocol::stop()
}
}
ErrorCode OpenVpnProtocol::checkAndSetupTapDriver()
ErrorCode OpenVpnProtocol::prepare()
{
if (!IpcClient::Interface()) {
return ErrorCode::AmneziaServiceConnectionFailed;

View file

@ -21,7 +21,7 @@ public:
ErrorCode start() override;
void stop() override;
ErrorCode checkAndSetupTapDriver();
ErrorCode prepare() override;
static QString defaultConfigFileName();
static QString defaultConfigPath();

View file

@ -16,6 +16,7 @@ constexpr char port[] = "port";
constexpr char local_port[] = "local_port";
constexpr char description[] = "description";
constexpr char cert[] = "cert";
constexpr char containers[] = "containers";
@ -23,7 +24,6 @@ constexpr char container[] = "container";
constexpr char defaultContainer[] = "defaultContainer";
constexpr char protocols[] = "protocols";
//constexpr char protocol[] = "protocol";
constexpr char remote[] = "remote";
constexpr char transport_proto[] = "transport_proto";

View file

@ -4,6 +4,13 @@
#include "vpnprotocol.h"
#include "core/errorstrings.h"
#include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h"
#include "openvpnovercloakprotocol.h"
#include "wireguardprotocol.h"
#include "ikev2_vpn_protocol.h"
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent)
: QObject(parent),
m_connectionState(ConnectionState::Unknown),
@ -88,6 +95,19 @@ QString VpnProtocol::vpnGateway() const
return m_vpnGateway;
}
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration)
{
switch (container) {
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
default: return nullptr;
}
}
QString VpnProtocol::routeGateway() const
{
return m_routeGateway;

View file

@ -6,6 +6,8 @@
#include <QJsonObject>
#include "core/defs.h"
#include "containers/containers_defs.h"
using namespace amnezia;
class QTimer;
@ -23,6 +25,7 @@ public:
static QString textConnectionState(ConnectionState connectionState);
virtual ErrorCode prepare() { return ErrorCode::NoError; }
virtual bool isConnected() const;
virtual bool isDisconnected() const;
@ -37,6 +40,8 @@ public:
QString routeGateway() const;
QString vpnGateway() const;
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void connectionStateChanged(VpnProtocol::ConnectionState state);

View file

@ -122,5 +122,9 @@
<file>server_scripts/sftp/run_container.sh</file>
<file>ui/qml/Pages/Protocols/PageProtoSftp.qml</file>
<file>ui/qml/Pages/Protocols/PageProtoTorWebSite.qml</file>
<file>server_scripts/ipsec/configure_container.sh</file>
<file>server_scripts/ipsec/Dockerfile</file>
<file>server_scripts/ipsec/run_container.sh</file>
<file>server_scripts/ipsec/start.sh</file>
</qresource>
</RCC>

View file

@ -0,0 +1,4 @@
FROM amneziavpn/ipsec-server:latest
RUN mkdir -p /opt/amnezia
LABEL maintainer="AmneziaVPN"

View file

@ -0,0 +1,257 @@
#!/bin/bash
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
if [ ! -e /dev/ppp ]; then
cat <<'EOF'
Warning: /dev/ppp is missing, and IPsec/L2TP mode may not work. Please use
IKEv2 (https://git.io/ikev2docker) or IPsec/XAuth mode to connect.
EOF
fi
NET_IFACE=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$')
[ -z "$NET_IFACE" ] && NET_IFACE=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -Po '(?<=dev )(\S+)')
[ -z "$NET_IFACE" ] && NET_IFACE=eth0
mkdir -p /opt/src
mkdir -p /opt/amnezia/ikev2/clients
# Create IPsec config
cat > /etc/ipsec.conf <<EOF
version 2.0
config setup
virtual-private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:!$IPSEC_VPN_L2TP_NET,%v4:!$IPSEC_VPN_XAUTH_NET
uniqueids=no
conn shared
left=%defaultroute
leftid=$SERVER_IP_ADDRESS
right=%any
encapsulation=yes
authby=secret
pfs=no
rekey=no
keyingtries=5
dpddelay=30
dpdtimeout=120
dpdaction=clear
ikev2=never
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp1024,aes128-sha1;modp1024
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
ikelifetime=24h
salifetime=24h
sha2-truncbug=$IPSEC_VPN_SHA2_TRUNCBUG
EOF
if [ "$IPSEC_VPN_DISABLE_L2TP" != "yes" ]; then
cat >> /etc/ipsec.conf <<'EOF'
conn l2tp-psk
auto=add
leftprotoport=17/1701
rightprotoport=17/%any
type=transport
also=shared
EOF
fi
if [ "$IPSEC_VPN_DISABLE_XAUTH" != "yes" ]; then
cat >> /etc/ipsec.conf <<EOF
conn xauth-psk
auto=add
leftsubnet=0.0.0.0/0
rightaddresspool=$IPSEC_VPN_XAUTH_POOL
modecfgdns=$PRIMARY_DNS,$SECONDARY_DNS
leftxauthserver=yes
rightxauthclient=yes
leftmodecfgserver=yes
rightmodecfgclient=yes
modecfgpull=yes
cisco-unity=yes
also=shared
EOF
fi
cat >> /etc/ipsec.conf <<'EOF'
include /etc/ipsec.d/*.conf
EOF
if uname -r | grep -qi 'coreos'; then
sed -i '/phase2alg/s/,aes256-sha2_512//' /etc/ipsec.conf
fi
if grep -qs ike-frag /etc/ipsec.d/ikev2.conf; then
sed -i 's/^[[:space:]]\+ike-frag=/ fragmentation=/' /etc/ipsec.d/ikev2.conf
fi
# Create xl2tpd config
cat > /etc/xl2tpd/xl2tpd.conf <<EOF
[global]
port = 1701
[lns default]
ip range = $IPSEC_VPN_L2TP_POOL
local ip = $IPSEC_VPN_L2TP_LOCAL
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tpd
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
EOF
# Set xl2tpd options
cat > /etc/ppp/options.xl2tpd <<EOF
+mschap-v2
ipcp-accept-local
ipcp-accept-remote
noccp
auth
mtu 1280
mru 1280
proxyarp
lcp-echo-failure 4
lcp-echo-interval 30
connect-delay 5000
ms-dns $PRIMARY_DNS
ms-dns $SECONDARY_DNS
EOF
# Update sysctl settings
syt='/sbin/sysctl -e -q -w'
$syt kernel.msgmnb=65536 2>/dev/null
$syt kernel.msgmax=65536 2>/dev/null
$syt net.ipv4.ip_forward=1 2>/dev/null
$syt net.ipv4.conf.all.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.rp_filter=0 2>/dev/null
$syt net.ipv4.conf.default.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.rp_filter=0 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.send_redirects=0" 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.rp_filter=0" 2>/dev/null
# Create IPTables rules
ipi='iptables -I INPUT'
ipf='iptables -I FORWARD'
ipp='iptables -t nat -I POSTROUTING'
res='RELATED,ESTABLISHED'
if ! iptables -t nat -C POSTROUTING -s "$IPSEC_VPN_L2TP_NET" -o "$NET_IFACE" -j MASQUERADE 2>/dev/null; then
$ipi 1 -p udp --dport 1701 -m policy --dir in --pol none -j DROP
$ipi 2 -m conntrack --ctstate INVALID -j DROP
$ipi 3 -m conntrack --ctstate "$res" -j ACCEPT
$ipi 4 -p udp -m multiport --dports 500,4500 -j ACCEPT
$ipi 5 -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT
$ipi 6 -p udp --dport 1701 -j DROP
$ipf 1 -m conntrack --ctstate INVALID -j DROP
$ipf 2 -i "$NET_IFACE" -o ppp+ -m conntrack --ctstate "$res" -j ACCEPT
$ipf 3 -i ppp+ -o "$NET_IFACE" -j ACCEPT
$ipf 4 -i ppp+ -o ppp+ -j ACCEPT
$ipf 5 -i "$NET_IFACE" -d "$IPSEC_VPN_XAUTH_NET" -m conntrack --ctstate "$res" -j ACCEPT
$ipf 6 -s "$IPSEC_VPN_XAUTH_NET" -o "$NET_IFACE" -j ACCEPT
$ipf 7 -s "$IPSEC_VPN_XAUTH_NET" -o ppp+ -j ACCEPT
if [ "$IPSEC_VPN_VPN_ANDROID_MTU_FIX" = "yes" ]; then
# Client-to-client traffic is allowed by default. To *disallow* such traffic,
# uncomment below and restart the Docker container.
$ipf 2 -i ppp+ -o ppp+ -s "$IPSEC_VPN_L2TP_NET" -d "$IPSEC_VPN_L2TP_NET" -j DROP
$ipf 3 -s "$IPSEC_VPN_XAUTH_NET" -d "$IPSEC_VPN_XAUTH_NET" -j DROP
$ipf 4 -i ppp+ -d "$IPSEC_VPN_XAUTH_NET" -j DROP
$ipf 5 -s "$IPSEC_VPN_XAUTH_NET" -o ppp+ -j DROP
fi
iptables -A FORWARD -j DROP
$ipp -s "$IPSEC_VPN_XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE
$ipp -s "$IPSEC_VPN_L2TP_NET" -o "$NET_IFACE" -j MASQUERADE
fi
if [ "$IPSEC_VPN_VPN_ANDROID_MTU_FIX" = "yes" ]; then
echo "Applying fix for Android MTU/MSS issues..."
iptables -t mangle -A FORWARD -m policy --pol ipsec --dir in \
-p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \
-j TCPMSS --set-mss 1360
iptables -t mangle -A FORWARD -m policy --pol ipsec --dir out \
-p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \
-j TCPMSS --set-mss 1360
fi
# Update file attributes
touch /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd
chmod 600 /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd
echo
echo "Starting IPsec service..."
mkdir -p /run/pluto /var/run/pluto
rm -f /run/pluto/pluto.pid /var/run/pluto/pluto.pid
ipsec initnss >/dev/null
ipsec pluto --config /etc/ipsec.conf
# Start xl2tpd
mkdir -p /var/run/xl2tpd
rm -f /var/run/xl2tpd.pid
/usr/sbin/xl2tpd -c /etc/xl2tpd/xl2tpd.conf
################# IKEV2 ##################
if [ "$IPSEC_VPN_DISABLE_IKEV2" != "yes" ]; then
printf "y\n\nN\n" | certutil -z <(head -c 1024 /dev/urandom) \
-S -x -n "IKEv2 VPN CA" \
-s "O=IKEv2 VPN,CN=IKEv2 VPN CA" \
-k rsa -g 3072 -v 120 \
-d sql:/etc/ipsec.d -t "CT,," -2
certutil -z <(head -c 1024 /dev/urandom) \
-S -c "IKEv2 VPN CA" -n "$SERVER_IP_ADDRESS" \
-s "O=IKEv2 VPN,CN=$SERVER_IP_ADDRESS" \
-k rsa -g 3072 -v 120 \
-d sql:/etc/ipsec.d -t ",," \
--keyUsage digitalSignature,keyEncipherment \
--extKeyUsage serverAuth \
--extSAN "ip:$SERVER_IP_ADDRESS,dns:$SERVER_IP_ADDRESS"
cat > /etc/ipsec.d/ikev2.conf <<EOF
conn ikev2-cp
left=%defaultroute
leftcert=$SERVER_IP_ADDRESS
leftid=$SERVER_IP_ADDRESS
leftsendcert=always
leftsubnet=0.0.0.0/0
leftrsasigkey=%cert
right=%any
rightid=%fromcert
rightaddresspool=192.168.43.10-192.168.43.250
rightca=%same
rightrsasigkey=%cert
narrowing=yes
dpddelay=30
dpdtimeout=120
dpdaction=clear
auto=add
ikev2=insist
rekey=no
pfs=no
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2
ikelifetime=24h
salifetime=24h
encapsulation=yes
modecfgdns=$PRIMARY_DNS,$SECONDARY_DNS
EOF
ipsec auto --add ikev2-cp
else
ipsec auto --delete ikev2-cp
fi # if [ "$IPSEC_VPN_DISABLE_IKEV2" != "yes" ]

View file

@ -0,0 +1,7 @@
sudo docker run \
--restart=always \
-p 500:500/udp \
-p 4500:4500/udp \
-d --privileged \
--name $CONTAINER_NAME $CONTAINER_NAME

View file

View file

@ -1,6 +1,6 @@
[Interface]
Address = 10.8.1.2/32
DNS = 1.1.1.1, 1.0.0.1
DNS = $PRIMARY_DNS, $SECONDARY_DNS
PrivateKey = $WIREGUARD_CLIENT_PRIVATE_KEY
[Peer]

View file

@ -133,10 +133,7 @@ ErrorCode VpnConnection::lastError() const
QMap<Protocol, QString> VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig)
{
QMap<Protocol, QString> configs;
for (Protocol proto: { Protocol::OpenVpn,
Protocol::ShadowSocks,
Protocol::Cloak,
Protocol::WireGuard}) {
for (Protocol proto: ProtocolProps::allProtocols()) {
QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString();
@ -242,6 +239,13 @@ ErrorCode VpnConnection::createVpnConfiguration(int serverIndex,
m_vpnConfiguration.insert(config::key_wireguard_config_data, wgConfigData);
}
if (container == DockerContainer::Ipsec) {
QString ikev2ConfigData = createVpnConfigurationForProto(
serverIndex, credentials, container, containerConfig, Protocol::Ikev2, &errorCode);
m_vpnConfiguration.insert(config::key_ikev2_config_data, ikev2ConfigData);
}
//qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson();
return ErrorCode::NoError;
}
@ -261,63 +265,29 @@ ErrorCode VpnConnection::connectToVpn(int serverIndex,
m_vpnProtocol.reset();
}
if (container == DockerContainer::None || container == DockerContainer::OpenVpn) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig);
ErrorCode e = createVpnConfiguration(serverIndex, credentials, container, containerConfig);
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
m_vpnProtocol.reset(new OpenVpnProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
}
else if (container == DockerContainer::ShadowSocks) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::ShadowSocks, containerConfig);
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
#ifndef Q_OS_ANDROID
m_vpnProtocol.reset(VpnProtocol::factory(container, containerConfig));
if (!m_vpnProtocol) {
return ErrorCode::InternalError;
}
m_vpnProtocol.reset(new ShadowSocksVpnProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
}
else if (container == DockerContainer::Cloak) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::Cloak, containerConfig);
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
m_vpnProtocol->prepare();
m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
}
else if (container == DockerContainer::WireGuard) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::WireGuard, containerConfig);
if (e) {
emit connectionStateChanged(VpnProtocol::Error);
return e;
}
#ifdef Q_OS_ANDROID
#else
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(Protocol::WireGuard, m_vpnConfiguration);
androidVpnProtocol->initialize();
m_vpnProtocol.reset(androidVpnProtocol);
#else
m_vpnProtocol.reset(new WireguardProtocol(m_vpnConfiguration));
#endif
}
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));