Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bb2ef9e30 | |||
| df2a6dc278 | |||
| 835f767c3f | |||
| e3fb239de9 | |||
| de67f244da | |||
| d424bb24cf | |||
| 615bba69e5 | |||
| 7bba7a9eab | |||
| a5e9cea22f | |||
| f9affb083b | |||
| 85b6b06cc9 | |||
| 059c6404ab | |||
| 0d989a7fae | |||
| 7dfc002316 | |||
| 8d8d392e84 | |||
| c6e75d5f86 | |||
| 99bfd56ef4 | |||
| c2f6c7d939 | |||
| d831d68e73 | |||
| 84e4b776ac | |||
| f9e1b2c6dc | |||
| 54fca5bebc | |||
| c5ce417d79 | |||
| 6765142ebc | |||
| ca898a6759 | |||
| a2bb382652 | |||
| 02f966bc67 | |||
| 65acdc8c09 | |||
| 407ea77a9e | |||
| 3e4a9f54a7 | |||
| 6b77bd5f13 | |||
| fb6de25e5f | |||
| ffbe5107e2 | |||
| 40a5b2e3f3 | |||
| c683884868 | |||
| 65961d8d2e | |||
| 7dc1f1e225 | |||
| a47ab15aef | |||
| c74efdaa9b | |||
| 39224c7bf7 | |||
| 96aa3d409d | |||
| c63990f720 | |||
| ad643bf76e | |||
| c7ea4966fd | |||
| 8fd81be477 |
@@ -3,6 +3,7 @@
|
||||
macOSPackage/
|
||||
AmneziaVPN.dmg
|
||||
AmneziaVPN.exe
|
||||
AmneziaVPN_*.exe
|
||||
deploy/build/*
|
||||
winbuild.bat
|
||||
|
||||
|
||||
@@ -1,2 +1,43 @@
|
||||
# Amnezia
|
||||
# Amnezia VPN
|
||||
## _The best client for self-hosted VPN_
|
||||
|
||||
[](https://travis-ci.com/amnezia-vpn/desktop-client)
|
||||
|
||||
Amnezia is a VPN client with the key feature of deploying your own VPN server on you virtual server.
|
||||
|
||||
## Features
|
||||
- Very easy to use - enter your ip address, ssh login and password, and Amnezia client will automatically install VPN docker containers to your server and connect to VPN.
|
||||
- OpenVPN and OpenVPN over ShadowSocks protocols support.
|
||||
- Custom VPN routing mode support - add any sites to client to enable VPN only for them.
|
||||
- Windows and MacOS support.
|
||||
- Unsecure sharing connection profile for family use.
|
||||
|
||||
## Tech
|
||||
|
||||
AmneziaVPN uses a number of open source projects to work:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [ShadowSocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [EasyRSA](https://github.com/OpenVPN/easy-rsa) - part of OpenVPN
|
||||
- [CygWin](https://www.cygwin.com/) - only for Windiws, used for launching EasyRSA scripts
|
||||
- [QtSsh](https://github.com/jaredtao/QtSsh) - forked form Qt Creator
|
||||
- and more...
|
||||
|
||||
## Development
|
||||
|
||||
Want to contribute? Welcome!
|
||||
Use Qt Creator for fast developing.
|
||||
|
||||
### Building sources and deployment
|
||||
Easiest way to build your own executables - is to fork project and configure [Travis CI](https://travis-ci.com/)
|
||||
Or you can build sources manually using Qt Creator. Qt >= 14.2 supported.
|
||||
Look to the `build_macos.sh` and `build_windows.bat` scripts in `deploy` folder for details.
|
||||
|
||||
## License
|
||||
GPL v.3
|
||||
|
||||
## Contacts
|
||||
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - main support channel
|
||||
[https://amnezia.org](https://amnezia.org) - our website
|
||||
@@ -0,0 +1,129 @@
|
||||
|
||||
#if !defined(AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_)
|
||||
#define AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
#define QR_LEVEL_L 0
|
||||
#define QR_LEVEL_M 1
|
||||
#define QR_LEVEL_Q 2
|
||||
#define QR_LEVEL_H 3
|
||||
|
||||
//
|
||||
#define QR_MODE_NUMERAL 0
|
||||
#define QR_MODE_ALPHABET 1
|
||||
#define QR_MODE_8BIT 2
|
||||
#define QR_MODE_KANJI 3
|
||||
|
||||
//
|
||||
#define QR_VRESION_S 0
|
||||
#define QR_VRESION_M 1
|
||||
#define QR_VRESION_L 2
|
||||
|
||||
#define MAX_ALLCODEWORD 3706
|
||||
#define MAX_DATACODEWORD 2956
|
||||
#define MAX_CODEBLOCK 153
|
||||
#define MAX_MODULESIZE 177
|
||||
|
||||
#define QR_MARGIN 0
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
typedef struct tagRS_BLOCKINFO
|
||||
{
|
||||
int ncRSBlock;
|
||||
int ncAllCodeWord;
|
||||
int ncDataCodeWord;
|
||||
|
||||
} RS_BLOCKINFO, *LPRS_BLOCKINFO;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct tagQR_VERSIONINFO
|
||||
{
|
||||
int nVersionNo;
|
||||
int ncAllCodeWord;
|
||||
|
||||
int ncDataCodeWord[4];
|
||||
|
||||
int ncAlignPoint;
|
||||
int nAlignPoint[6];
|
||||
|
||||
RS_BLOCKINFO RS_BlockInfo1[4];
|
||||
RS_BLOCKINFO RS_BlockInfo2[4];
|
||||
|
||||
} QR_VERSIONINFO, *LPQR_VERSIONINFO;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class CQR_Encode
|
||||
{
|
||||
public:
|
||||
CQR_Encode();
|
||||
~CQR_Encode();
|
||||
|
||||
public:
|
||||
int m_nLevel;
|
||||
int m_nVersion;
|
||||
bool m_bAutoExtent;
|
||||
int m_nMaskingNo;
|
||||
|
||||
public:
|
||||
int m_nSymbleSize;
|
||||
unsigned char m_byModuleData[MAX_MODULESIZE][MAX_MODULESIZE]; // [x][y]
|
||||
|
||||
private:
|
||||
int m_ncDataCodeWordBit;
|
||||
unsigned char m_byDataCodeWord[MAX_DATACODEWORD];
|
||||
|
||||
int m_ncDataBlock;
|
||||
unsigned char m_byBlockMode[MAX_DATACODEWORD];
|
||||
int m_nBlockLength[MAX_DATACODEWORD];
|
||||
|
||||
int m_ncAllCodeWord;
|
||||
unsigned char m_byAllCodeWord[MAX_ALLCODEWORD];
|
||||
unsigned char m_byRSWork[MAX_CODEBLOCK];
|
||||
|
||||
public:
|
||||
bool EncodeData(int nLevel, int nVersion, bool bAutoExtent, int nMaskingNo, char* lpsSource, int ncSource = 0);
|
||||
|
||||
private:
|
||||
int GetEncodeVersion(int nVersion, char* lpsSource, int ncLength);
|
||||
bool EncodeSourceData(char* lpsSource, int ncLength, int nVerGroup);
|
||||
|
||||
int GetBitLength(unsigned char nMode, int ncData, int nVerGroup);
|
||||
|
||||
int SetBitStream(int nIndex, unsigned short wData, int ncData);
|
||||
|
||||
bool IsNumeralData(unsigned char c);
|
||||
bool IsAlphabetData(unsigned char c);
|
||||
bool IsKanjiData(unsigned char c1, unsigned char c2);
|
||||
|
||||
unsigned char AlphabetToBinaly(unsigned char c);
|
||||
unsigned short KanjiToBinaly(unsigned short wc);
|
||||
|
||||
void GetRSCodeWord(unsigned char * lpbyRSWork, int ncDataCodeWord, int ncRSCodeWord);
|
||||
|
||||
private:
|
||||
void FormatModule();
|
||||
|
||||
void SetFunctionModule();
|
||||
void SetFinderPattern(int x, int y);
|
||||
void SetAlignmentPattern(int x, int y);
|
||||
void SetVersionPattern();
|
||||
void SetCodeWordPattern();
|
||||
void SetMaskingPattern(int nPatternNo);
|
||||
void SetFormatInfoPattern(int nPatternNo);
|
||||
int CountPenalty();
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif // !defined(AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_)
|
||||
@@ -0,0 +1,5 @@
|
||||
HEADERS += \
|
||||
3rd/QRCodeGenerator/QRCodeGenerator.h \
|
||||
|
||||
SOURCES += \
|
||||
3rd/QRCodeGenerator/QRCodeGenerator.cpp \
|
||||
@@ -823,11 +823,13 @@ void SshConnectionPrivate::createPrivateKey()
|
||||
if (m_connParams.privateKeyFile.isEmpty())
|
||||
throw SshClientException(SshKeyFileError, tr("No private key file given."));
|
||||
QFile keyFile(m_connParams.privateKeyFile);
|
||||
if (!keyFile.open(QIODevice::ReadOnly)) {
|
||||
throw SshClientException(SshKeyFileError,
|
||||
tr("Private key file error: %1").arg(keyFile.errorString()));
|
||||
|
||||
if (keyFile.open(QIODevice::ReadOnly)) {
|
||||
m_sendFacility.createAuthenticationKey(keyFile.readAll());
|
||||
}
|
||||
else {
|
||||
m_sendFacility.createAuthenticationKey(m_connParams.privateKeyFile.toUtf8());
|
||||
}
|
||||
m_sendFacility.createAuthenticationKey(keyFile.readAll());
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSharedMemory>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||
* if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
// On Android and iOS since the library is not supported fallback to
|
||||
// standard QApplication behaviour by simply returning at this point.
|
||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
|
||||
// Add any unique user data
|
||||
if ( ! userData.isEmpty() )
|
||||
d->addAppData( userData );
|
||||
|
||||
// Generating an application ID used for identifying the shared memory
|
||||
// block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes
|
||||
// attempting to attach at the same time
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// By explicitly attaching it and then deleting it we make sure that the
|
||||
// memory is deleted even after the process has crashed on Unix.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
d->memory->attach();
|
||||
delete d->memory;
|
||||
#endif
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
|
||||
// Create a shared memory block
|
||||
if( d->memory->create( sizeof( InstancesInfo ) )){
|
||||
// Initialize the shared memory block
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
} else {
|
||||
if( d->memory->error() == QSharedMemory::AlreadyExists ){
|
||||
// Attempt to attach to the memory segment
|
||||
if( ! d->memory->attach() ){
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
abortSafely();
|
||||
}
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
||||
abortSafely();
|
||||
}
|
||||
} else {
|
||||
qCritical() << "SingleApplication: Unable to create block.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while( true ){
|
||||
// If the shared memory block's checksum is valid continue
|
||||
if( d->blockChecksum() == inst->checksum ) break;
|
||||
|
||||
// If more than 5s have elapsed, assume the primary instance crashed and
|
||||
// assume it's position
|
||||
if( time.elapsed() > 5000 ){
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again. The random sleep here
|
||||
// limits the probability of a collision between two racing apps and
|
||||
// allows the app to initialise faster
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if( inst->primary == false ){
|
||||
d->startPrimary();
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if( allowSecondary ){
|
||||
d->startSecondary();
|
||||
if( d->options & Mode::SecondaryNotification ){
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
||||
}
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
||||
|
||||
delete d;
|
||||
|
||||
::exit( EXIT_SUCCESS );
|
||||
}
|
||||
|
||||
SingleApplication::~SingleApplication()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
delete d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isPrimary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isSecondary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance
|
||||
* ids. It is reset when the first (primary) instance of your app starts and
|
||||
* only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleApplication::instanceId() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->instanceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary
|
||||
* instance. Especially useful when SingleApplication is coupled with OS.
|
||||
* specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplication::primaryPid() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplication::primaryUser() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplication::currentUser() const
|
||||
{
|
||||
return SingleApplicationPrivate::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfuly, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
// Nobody to connect to
|
||||
if( isPrimary() ) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
||||
return false;
|
||||
|
||||
d->socket->write( message );
|
||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
||||
d->socket->flush();
|
||||
return dataWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the shared memory block and exits with a failure.
|
||||
* This function halts program execution.
|
||||
*/
|
||||
void SingleApplication::abortSafely()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
QStringList SingleApplication::userData() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->appData();
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#ifndef SINGLE_APPLICATION_H
|
||||
#define SINGLE_APPLICATION_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#ifndef QAPPLICATION_CLASS
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#endif
|
||||
|
||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same
|
||||
* Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleApplication : public QAPPLICATION_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using app_t = QAPPLICATION_CLASS;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
* primary instance should be notified when a secondary instance had been
|
||||
* started.
|
||||
* @note Operating system can restrict the shared memory blocks to the same
|
||||
* user, in which case the User/System modes will have no effect and the
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
/**
|
||||
* @brief Intitializes a SingleApplication instance with argc command line
|
||||
* arguments in argv
|
||||
* @arg {int &} argc - Number of arguments in argv
|
||||
* @arg {const char *[]} argv - Supplied command line arguments
|
||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||
* if there is already a primary instance.
|
||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
||||
* User wide or System wide.
|
||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||
* @note argc and argv may be changed as Qt removes arguments that it
|
||||
* recognizes
|
||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
||||
~SingleApplication() override;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
* @param {int} timeout - Timeout for connecting
|
||||
* @returns {bool}
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( const QByteArray &message, int timeout = 100 );
|
||||
|
||||
/**
|
||||
* @brief Get the set user data.
|
||||
* @returns {QStringList}
|
||||
*/
|
||||
QStringList userData() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
|
||||
#endif // SINGLE_APPLICATION_H
|
||||
@@ -0,0 +1,15 @@
|
||||
QT += core network
|
||||
CONFIG += c++11
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/singleapplication.h \
|
||||
$$PWD/singleapplication_p.h
|
||||
SOURCES += $$PWD/singleapplication.cpp \
|
||||
$$PWD/singleapplication_p.cpp
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
win32 {
|
||||
msvc:LIBS += Advapi32.lib
|
||||
gcc:LIBS += -ladvapi32
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#include <QtCore/QRandomGenerator>
|
||||
#else
|
||||
#include <QtCore/QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||
: q_ptr( q_ptr )
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = 0;
|
||||
}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||
{
|
||||
if( socket != nullptr ){
|
||||
socket->close();
|
||||
delete socket;
|
||||
}
|
||||
|
||||
if( memory != nullptr ){
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if( server != nullptr ){
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
|
||||
delete memory;
|
||||
}
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::getUsername()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
wchar_t username[UNLEN + 1];
|
||||
// Specifies size of the buffer on input
|
||||
DWORD usernameLength = UNLEN + 1;
|
||||
if( GetUserNameW( username, &usernameLength ) )
|
||||
return QString::fromWCharArray( username );
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
|
||||
#else
|
||||
return qEnvironmentVariable( "USERNAME" );
|
||||
#endif
|
||||
#endif
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
uid_t uid = geteuid();
|
||||
struct passwd *pw = getpwuid( uid );
|
||||
if( pw )
|
||||
username = QString::fromLocal8Bit( pw->pw_name );
|
||||
if ( username.isEmpty() ){
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
|
||||
#else
|
||||
username = qEnvironmentVariable( "USER" );
|
||||
#endif
|
||||
}
|
||||
return username;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName()
|
||||
{
|
||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
||||
appData.addData( "SingleApplication", 17 );
|
||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
||||
|
||||
if ( ! appDataList.isEmpty() )
|
||||
appData.addData( appDataList.join( "" ).toUtf8() );
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
|
||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
||||
}
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
||||
#else
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
appData.addData( getUsername().toUtf8() );
|
||||
}
|
||||
|
||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
||||
// server naming requirements.
|
||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const
|
||||
{
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary()
|
||||
{
|
||||
// Reset the number of connections
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = QCoreApplication::applicationPid();
|
||||
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = 0;
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer( blockServerName );
|
||||
server = new QLocalServer();
|
||||
|
||||
// Restrict access to the socket according to the
|
||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
||||
} else {
|
||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
||||
}
|
||||
|
||||
server->listen( blockServerName );
|
||||
QObject::connect(
|
||||
server,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&SingleApplicationPrivate::slotConnectionEstablished
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary()
|
||||
{
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = inst->secondary;
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
||||
{
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already
|
||||
// connected.
|
||||
if( socket == nullptr ){
|
||||
socket = new QLocalSocket();
|
||||
}
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) return true;
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectedState ){
|
||||
|
||||
while( true ){
|
||||
randomSleep();
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectingState )
|
||||
socket->connectToServer( blockServerName );
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectingState ){
|
||||
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
|
||||
}
|
||||
|
||||
// If connected break out of the loop
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) break;
|
||||
|
||||
// If elapsed time since start is longer than the method timeout return
|
||||
if( time.elapsed() >= msecs ) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleApplication protocol
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
headerStream << static_cast <quint64>( initMsg.length() );
|
||||
|
||||
socket->write( header );
|
||||
socket->write( initMsg );
|
||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
||||
socket->flush();
|
||||
return result;
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
||||
#else
|
||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
||||
#endif
|
||||
return checksum;
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivate::primaryPid() const
|
||||
{
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::primaryUser() const
|
||||
{
|
||||
QByteArray username;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
username = inst->primaryUser;
|
||||
memory->unlock();
|
||||
|
||||
return QString::fromUtf8( username );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
||||
{
|
||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
|
||||
[nextConnSocket, this](){
|
||||
connectionMap.remove(nextConnSocket);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage){
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
case StageBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||
{
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream headerStream( sock );
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
||||
readInitMessageBody( sock );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
readStream >> instanceId;
|
||||
|
||||
// checksum
|
||||
quint16 msgChecksum = 0;
|
||||
readStream >> msgChecksum;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||
#else
|
||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||
#endif
|
||||
|
||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
||||
QLatin1String(latin1Name) == blockServerName &&
|
||||
msgChecksum == actualChecksum;
|
||||
|
||||
if( !isValid ){
|
||||
sock->close();
|
||||
return;
|
||||
}
|
||||
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if( connectionType == NewInstance ||
|
||||
( connectionType == SecondaryInstance &&
|
||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
||||
{
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0){
|
||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||
{
|
||||
if( closedSocket->bytesAvailable() > 0 )
|
||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::randomSleep()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
|
||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
||||
#else
|
||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
||||
{
|
||||
appDataList.push_back(data);
|
||||
}
|
||||
|
||||
QStringList SingleApplicationPrivate::appData() const
|
||||
{
|
||||
return appDataList;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#ifndef SINGLEAPPLICATION_P_H
|
||||
#define SINGLEAPPLICATION_P_H
|
||||
|
||||
#include <QtCore/QSharedMemory>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include "singleapplication.h"
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum; // Must be the last field
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
qint64 msgLen = 0;
|
||||
quint32 instanceId = 0;
|
||||
quint8 stage = 0;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
SecondaryInstance = 2,
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
||||
~SingleApplicationPrivate() override;
|
||||
|
||||
static QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock() const;
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
bool connectToPrimary( int msecs, ConnectionType connectionType );
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
static void randomSleep();
|
||||
void addAppData(const QString &data);
|
||||
QStringList appData() const;
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
QStringList appDataList;
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
||||
};
|
||||
|
||||
#endif // SINGLEAPPLICATION_P_H
|
||||
@@ -8,45 +8,65 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
include("3rd/QtSsh/src/ssh/ssh.pri")
|
||||
include("3rd/QtSsh/src/botan/botan.pri")
|
||||
include("3rd/SingleApplication/singleapplication.pri")
|
||||
include("3rd/QRCodeGenerator/QRCodeGenerator.pri")
|
||||
|
||||
HEADERS += \
|
||||
../ipc/ipc.h \
|
||||
configurators/cloak_configurator.h \
|
||||
configurators/shadowsocks_configurator.h \
|
||||
configurators/vpn_configurator.h \
|
||||
core/defs.h \
|
||||
core/errorstrings.h \
|
||||
core/ipcclient.h \
|
||||
core/openvpnconfigurator.h \
|
||||
configurators/openvpn_configurator.h \
|
||||
core/scripts_registry.h \
|
||||
core/server_defs.h \
|
||||
core/servercontroller.h \
|
||||
debug.h \
|
||||
defines.h \
|
||||
managementserver.h \
|
||||
protocols/openvpnovercloakprotocol.h \
|
||||
protocols/protocols_defs.h \
|
||||
protocols/shadowsocksvpnprotocol.h \
|
||||
runguard.h \
|
||||
settings.h \
|
||||
ui/Controls/SlidingStackedWidget.h \
|
||||
ui/mainwindow.h \
|
||||
ui/qautostart.h \
|
||||
ui/server_widget.h \
|
||||
utils.h \
|
||||
vpnconnection.h \
|
||||
protocols/vpnprotocol.h \
|
||||
protocols/openvpnprotocol.h \
|
||||
|
||||
SOURCES += \
|
||||
configurators/cloak_configurator.cpp \
|
||||
configurators/shadowsocks_configurator.cpp \
|
||||
configurators/vpn_configurator.cpp \
|
||||
core/errorstrings.cpp \
|
||||
core/ipcclient.cpp \
|
||||
core/openvpnconfigurator.cpp \
|
||||
configurators/openvpn_configurator.cpp \
|
||||
core/scripts_registry.cpp \
|
||||
core/server_defs.cpp \
|
||||
core/servercontroller.cpp \
|
||||
debug.cpp \
|
||||
main.cpp \
|
||||
managementserver.cpp \
|
||||
protocols/openvpnovercloakprotocol.cpp \
|
||||
protocols/protocols_defs.cpp \
|
||||
protocols/shadowsocksvpnprotocol.cpp \
|
||||
runguard.cpp \
|
||||
settings.cpp \
|
||||
ui/Controls/SlidingStackedWidget.cpp \
|
||||
ui/mainwindow.cpp \
|
||||
ui/qautostart.cpp \
|
||||
ui/server_widget.cpp \
|
||||
utils.cpp \
|
||||
vpnconnection.cpp \
|
||||
protocols/vpnprotocol.cpp \
|
||||
protocols/openvpnprotocol.cpp \
|
||||
|
||||
FORMS += ui/mainwindow.ui
|
||||
FORMS += ui/mainwindow.ui \
|
||||
ui/server_widget.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "cloak_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
QString cloakPublicKey = ServerController::getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::cloak::ckPublicKeyPath, &e);
|
||||
cloakPublicKey.replace("\n", "");
|
||||
|
||||
QString cloakBypassUid = ServerController::getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
|
||||
cloakBypassUid.replace("\n", "");
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config.insert("Transport", "direct");
|
||||
config.insert("ProxyMethod", "openvpn");
|
||||
config.insert("EncryptionMethod", "aes-gcm");
|
||||
config.insert("UID", cloakBypassUid);
|
||||
config.insert("PublicKey", cloakPublicKey);
|
||||
config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS");
|
||||
config.insert("NumConn", 4);
|
||||
config.insert("BrowserSig", "chrome");
|
||||
config.insert("StreamTimeout", 300);
|
||||
|
||||
// transfer params to protocol runner
|
||||
config.insert(config_key::transport_proto, "$OPENVPN_TRANSPORT_PROTO");
|
||||
config.insert(config_key::remote, credentials.hostName);
|
||||
|
||||
QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(),
|
||||
ServerController::genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
// qDebug().noquote() << textCfg;
|
||||
return textCfg;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef CLOAK_CONFIGURATOR_H
|
||||
#define CLOAK_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "settings.h"
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
class CloakConfigurator
|
||||
{
|
||||
public:
|
||||
|
||||
static QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // CLOAK_CONFIGURATOR_H
|
||||
@@ -1,23 +1,15 @@
|
||||
#include "openvpnconfigurator.h"
|
||||
#include "openvpn_configurator.h"
|
||||
#include <QApplication>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTemporaryDir>
|
||||
#include <QDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <utils.h>
|
||||
|
||||
QString OpenVpnConfigurator::getRandomString(int len)
|
||||
{
|
||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
|
||||
QString randomString;
|
||||
for(int i=0; i<len; ++i) {
|
||||
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
||||
QChar nextChar = possibleCharacters.at(index);
|
||||
randomString.append(nextChar);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
#include "core/server_defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
|
||||
QString OpenVpnConfigurator::getEasyRsaShPath()
|
||||
{
|
||||
@@ -25,12 +17,10 @@ QString OpenVpnConfigurator::getEasyRsaShPath()
|
||||
// easyrsa sh path should looks like
|
||||
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
|
||||
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
|
||||
easyRsaShPath.replace("C:\\", "");
|
||||
easyRsaShPath.replace("\\", "/");
|
||||
easyRsaShPath.prepend("/");
|
||||
easyRsaShPath = "\"" + easyRsaShPath + "\"";
|
||||
qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath;
|
||||
|
||||
//return "\"" + easyRsaShPath + "\"";
|
||||
return "\"/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa\"";
|
||||
return easyRsaShPath;
|
||||
#else
|
||||
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
|
||||
#endif
|
||||
@@ -42,14 +32,15 @@ QProcessEnvironment OpenVpnConfigurator::prepareEnv()
|
||||
QString pathEnvVar = env.value("PATH");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\bin;");
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\i386;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\x64;");
|
||||
#else
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
env.insert("PATH", pathEnvVar);
|
||||
//qDebug().noquote() << "ENV PATH" << pathEnvVar;
|
||||
return env;
|
||||
}
|
||||
|
||||
@@ -61,7 +52,9 @@ ErrorCode OpenVpnConfigurator::initPKI(const QString &path)
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProcessEnvironment(prepareEnv());
|
||||
p.setProgram("cmd.exe");
|
||||
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " init-pki"));
|
||||
p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " init-pki"));
|
||||
qDebug().noquote() << "EasyRsa tmp path" << path;
|
||||
qDebug().noquote() << "EasyRsa args" << p.nativeArguments();
|
||||
#else
|
||||
p.setProgram(getEasyRsaShPath());
|
||||
p.setArguments(QStringList() << "init-pki");
|
||||
@@ -69,9 +62,9 @@ ErrorCode OpenVpnConfigurator::initPKI(const QString &path)
|
||||
|
||||
p.setWorkingDirectory(path);
|
||||
|
||||
// QObject::connect(&p, &QProcess::channelReadyRead, [&](){
|
||||
// qDebug().noquote() << p.readAll();
|
||||
// });
|
||||
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
|
||||
qDebug().noquote() << "Init PKI" << p.readAll();
|
||||
});
|
||||
|
||||
p.start();
|
||||
p.waitForFinished();
|
||||
@@ -88,7 +81,8 @@ ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &client
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProcessEnvironment(prepareEnv());
|
||||
p.setProgram("cmd.exe");
|
||||
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass"));
|
||||
p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass"));
|
||||
qDebug().noquote() << "EasyRsa args" << p.nativeArguments();
|
||||
#else
|
||||
p.setArguments(QStringList() << "gen-req" << clientId << "nopass");
|
||||
p.setProgram(getEasyRsaShPath());
|
||||
@@ -98,7 +92,7 @@ ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &client
|
||||
|
||||
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
|
||||
QString data = p.readAll();
|
||||
//qDebug().noquote() << data;
|
||||
qDebug().noquote() << data;
|
||||
|
||||
if (data.contains("Common Name (eg: your user, host, or server name)")) {
|
||||
p.write("\n");
|
||||
@@ -116,7 +110,7 @@ ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &client
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData;
|
||||
connData.clientId = getRandomString(32);
|
||||
connData.clientId = Utils::getRandomString(32);
|
||||
|
||||
QTemporaryDir dir;
|
||||
// if (dir.isValid()) {
|
||||
@@ -145,7 +139,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode)
|
||||
DockerContainer container, ErrorCode *errorCode)
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||
connData.host = credentials.hostName;
|
||||
@@ -155,15 +149,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
|
||||
|
||||
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;
|
||||
}
|
||||
QString reqFileName = QString("%1/%2.req").
|
||||
arg(amnezia::protocols::openvpn::clientsDirPath).
|
||||
arg(connData.clientId);
|
||||
|
||||
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||
if (e) {
|
||||
@@ -171,27 +159,27 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
return connData;
|
||||
}
|
||||
|
||||
e = ServerController::signCert(container, credentials, connData.clientId);
|
||||
e = 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);
|
||||
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e);
|
||||
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials,
|
||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
|
||||
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e);
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
|
||||
}
|
||||
|
||||
ServerController::setupServerFirewall(credentials);
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
@@ -202,27 +190,32 @@ Settings &OpenVpnConfigurator::m_settings()
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode)
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
QFile configTemplFile;
|
||||
if (proto == Protocol::OpenVpn)
|
||||
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
|
||||
QString config = ServerController::replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||
ServerController::genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
||||
if (errorCode && *errorCode) {
|
||||
return "";
|
||||
}
|
||||
|
||||
configTemplFile.open(QIODevice::ReadOnly);
|
||||
QString config = configTemplFile.readAll();
|
||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
|
||||
#ifdef Q_OS_MAC
|
||||
config.replace("block-outside-dns", "");
|
||||
#endif
|
||||
|
||||
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()));
|
||||
}
|
||||
//qDebug().noquote() << config;
|
||||
return processConfigWithLocalSettings(config);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
@@ -230,13 +223,59 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
||||
config.replace("redirect-gateway def1 bypass-dhcp", "");
|
||||
}
|
||||
|
||||
config.replace("$REMOTE_HOST", connData.host);
|
||||
config.replace("$REMOTE_PORT", "1194");
|
||||
config.replace("$CA_CERT", connData.caCert);
|
||||
config.replace("$CLIENT_CERT", connData.clientCert);
|
||||
config.replace("$PRIV_KEY", connData.privKey);
|
||||
config.replace("$TA_KEY", connData.taKey);
|
||||
|
||||
//qDebug().noquote() << config;
|
||||
return config;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::convertOpenSShKey(const QString &key)
|
||||
{
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
QTemporaryFile tmp;
|
||||
#ifdef QT_DEBUG
|
||||
tmp.setAutoRemove(false);
|
||||
#endif
|
||||
tmp.open();
|
||||
tmp.write(key.toUtf8());
|
||||
tmp.close();
|
||||
|
||||
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProcessEnvironment(prepareEnv());
|
||||
p.setProgram("cmd.exe");
|
||||
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
|
||||
#else
|
||||
p.setProgram("ssh-keygen");
|
||||
p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName());
|
||||
#endif
|
||||
|
||||
p.start();
|
||||
p.waitForFinished();
|
||||
|
||||
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
|
||||
|
||||
tmp.open();
|
||||
|
||||
return tmp.readAll();
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId)
|
||||
{
|
||||
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
|
||||
"easyrsa import-req %2/%3.req %3\"")
|
||||
.arg(amnezia::containerToString(container))
|
||||
.arg(amnezia::protocols::openvpn::clientsDirPath)
|
||||
.arg(clientId);
|
||||
|
||||
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
|
||||
"easyrsa sign-req client %2\"")
|
||||
.arg(amnezia::containerToString(container))
|
||||
.arg(clientId);
|
||||
|
||||
QStringList scriptList {script_import, script_sign};
|
||||
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
|
||||
|
||||
return ServerController::runScript(ServerController::sshParams(credentials), script);
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
#ifndef OPENVPNCONFIGURATOR_H
|
||||
#define OPENVPNCONFIGURATOR_H
|
||||
#ifndef OPENVPN_CONFIGURATOR_H
|
||||
#define OPENVPN_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "defs.h"
|
||||
#include "core/defs.h"
|
||||
#include "settings.h"
|
||||
#include "servercontroller.h"
|
||||
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
class OpenVpnConfigurator
|
||||
{
|
||||
@@ -23,11 +22,17 @@ public:
|
||||
QString host; // host ip
|
||||
};
|
||||
|
||||
static QString genOpenVpnConfig(const ServerCredentials &credentials, Protocol proto,
|
||||
ErrorCode *errorCode = nullptr);
|
||||
static QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
|
||||
static QString processConfigWithLocalSettings(QString config);
|
||||
|
||||
static QString convertOpenSShKey(const QString &key);
|
||||
|
||||
static ErrorCode signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId);
|
||||
|
||||
private:
|
||||
static QString getRandomString(int len);
|
||||
static QString getEasyRsaShPath();
|
||||
|
||||
static QProcessEnvironment prepareEnv();
|
||||
@@ -37,9 +42,9 @@ private:
|
||||
static ConnectionData createCertRequest();
|
||||
|
||||
static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode = nullptr);
|
||||
DockerContainer container, ErrorCode *errorCode = nullptr);
|
||||
|
||||
static Settings &m_settings();
|
||||
};
|
||||
|
||||
#endif // OPENVPNCONFIGURATOR_H
|
||||
#endif // OPENVPN_CONFIGURATOR_H
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "shadowsocks_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
QString ssKey = ServerController::getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::shadowsocks::ssKeyPath, &e);
|
||||
ssKey.replace("\n", "");
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config.insert("server", credentials.hostName);
|
||||
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
|
||||
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
|
||||
config.insert("password", ssKey);
|
||||
config.insert("timeout", 60);
|
||||
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
||||
|
||||
|
||||
QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(),
|
||||
ServerController::genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
qDebug().noquote() << textCfg;
|
||||
return textCfg;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef SHADOWSOCKS_CONFIGURATOR_H
|
||||
#define SHADOWSOCKS_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "settings.h"
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
class ShadowSocksConfigurator
|
||||
{
|
||||
public:
|
||||
|
||||
static QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "vpn_configurator.h"
|
||||
#include "openvpn_configurator.h"
|
||||
#include "cloak_configurator.h"
|
||||
#include "shadowsocks_configurator.h"
|
||||
//#include "wireguard_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
|
||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode)
|
||||
{
|
||||
switch (proto) {
|
||||
case Protocol::OpenVpn:
|
||||
return OpenVpnConfigurator::genOpenVpnConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Protocol::ShadowSocks:
|
||||
return ShadowSocksConfigurator::genShadowSocksConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Protocol::Cloak:
|
||||
return CloakConfigurator::genCloakConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
// case Protocol::WireGuard:
|
||||
// return WireGuardConfigurator::genWireGuardConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef VPN_CONFIGURATOR_H
|
||||
#define VPN_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "settings.h"
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
class VpnConfigurator
|
||||
{
|
||||
public:
|
||||
|
||||
static QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // VPN_CONFIGURATOR_H
|
||||
@@ -1,29 +1,19 @@
|
||||
#ifndef DEFS_H
|
||||
#define DEFS_H
|
||||
|
||||
#include <QMetaEnum>
|
||||
#include <QObject>
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
enum class Protocol {
|
||||
Any,
|
||||
OpenVpn,
|
||||
ShadowSocks,
|
||||
WireGuard
|
||||
};
|
||||
|
||||
enum class DockerContainer {
|
||||
OpenVpn,
|
||||
ShadowSocks,
|
||||
WireGuard
|
||||
};
|
||||
|
||||
struct ServerCredentials
|
||||
{
|
||||
QString hostName;
|
||||
QString userName;
|
||||
QString password;
|
||||
int port = 22;
|
||||
|
||||
bool isValid() { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
|
||||
};
|
||||
|
||||
enum ErrorCode
|
||||
@@ -37,6 +27,7 @@ enum ErrorCode
|
||||
// Server errors
|
||||
ServerCheckFailed,
|
||||
ServerPortAlreadyAllocatedError,
|
||||
ServerContainerMissingError,
|
||||
|
||||
// Ssh connection errors
|
||||
SshSocketError, SshTimeoutError, SshProtocolError,
|
||||
@@ -46,6 +37,7 @@ enum ErrorCode
|
||||
// Ssh remote process errors
|
||||
SshRemoteProcessCreationError,
|
||||
FailedToStartRemoteProcessError, RemoteProcessCrashError,
|
||||
SshSftpError,
|
||||
|
||||
// Local errors
|
||||
FailedToSaveConfigData,
|
||||
@@ -56,22 +48,29 @@ enum ErrorCode
|
||||
// Distro errors
|
||||
OpenVpnExecutableMissing,
|
||||
EasyRsaExecutableMissing,
|
||||
ShadowSocksExecutableMissing,
|
||||
CloakExecutableMissing,
|
||||
AmneziaServiceConnectionFailed,
|
||||
|
||||
// VPN errors
|
||||
OpenVpnAdaptersInUseError,
|
||||
OpenVpnUnknownError
|
||||
OpenVpnUnknownError,
|
||||
|
||||
// 3rd party utils errors
|
||||
OpenVpnExecutableCrashed,
|
||||
ShadowSocksExecutableCrashed,
|
||||
CloakExecutableCrashed
|
||||
};
|
||||
|
||||
namespace config {
|
||||
// config keys
|
||||
static QString key_openvpn_config_data() { return "openvpn_config_data"; }
|
||||
static QString key_openvpn_config_path() { return "openvpn_config_path"; }
|
||||
static QString key_shadowsocks_config_data() { return "shadowsocks_config_data"; }
|
||||
const char key_openvpn_config_data[] = "openvpn_config_data";
|
||||
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";
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // DEFS_H
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "errorstrings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
QString errorString(ErrorCode code){
|
||||
switch (code) {
|
||||
|
||||
// General error codes
|
||||
case(NoError): return QObject::tr("No error");
|
||||
case(UnknownError): return QObject::tr("Unknown Error");
|
||||
case(NotImplementedError): return QObject::tr("Function not implemented");
|
||||
|
||||
// Server errors
|
||||
case(ServerCheckFailed): return QObject::tr("Server check failed");
|
||||
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
|
||||
|
||||
// Ssh connection errors
|
||||
case(SshSocketError): return QObject::tr("Ssh connection error");
|
||||
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
|
||||
case(SshProtocolError): return QObject::tr("Ssh protocol error");
|
||||
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
|
||||
case(SshKeyFileError): return QObject::tr("Ssh key file error");
|
||||
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
|
||||
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
|
||||
case(SshInternalError): return QObject::tr("Ssh internal error");
|
||||
|
||||
// Ssh remote process errors
|
||||
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
|
||||
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
|
||||
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
|
||||
|
||||
// Local errors
|
||||
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
|
||||
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
|
||||
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
|
||||
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
|
||||
|
||||
// Distro errors
|
||||
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
|
||||
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
|
||||
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
|
||||
|
||||
// VPN errors
|
||||
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
|
||||
|
||||
case(InternalError):
|
||||
default:
|
||||
return QObject::tr("Internal error");
|
||||
}
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const ErrorCode &e)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << "ErrorCode::" << int(e) << "(" << errorString(e) << ")";
|
||||
|
||||
return debug;
|
||||
}
|
||||
@@ -1,55 +1,14 @@
|
||||
#ifndef ERRORSTRINGS_H
|
||||
#define ERRORSTRINGS_H
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
static QString errorString(ErrorCode code){
|
||||
switch (code) {
|
||||
|
||||
// General error codes
|
||||
case(NoError): return QObject::tr("No error");
|
||||
case(UnknownError): return QObject::tr("Unknown Error");
|
||||
case(NotImplementedError): return QObject::tr("Function not implemented");
|
||||
|
||||
// Server errors
|
||||
case(ServerCheckFailed): return QObject::tr("Server check failed");
|
||||
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
|
||||
|
||||
// Ssh connection errors
|
||||
case(SshSocketError): return QObject::tr("Ssh connection error");
|
||||
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
|
||||
case(SshProtocolError): return QObject::tr("Ssh protocol error");
|
||||
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
|
||||
case(SshKeyFileError): return QObject::tr("Ssh key file error");
|
||||
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
|
||||
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
|
||||
case(SshInternalError): return QObject::tr("Ssh internal error");
|
||||
|
||||
// Ssh remote process errors
|
||||
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
|
||||
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
|
||||
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
|
||||
|
||||
// Local errors
|
||||
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
|
||||
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
|
||||
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
|
||||
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
|
||||
|
||||
// Distro errors
|
||||
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
|
||||
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
|
||||
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
|
||||
|
||||
// VPN errors
|
||||
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
|
||||
|
||||
case(InternalError):
|
||||
default:
|
||||
return QObject::tr("Internal error");
|
||||
}
|
||||
}
|
||||
QString errorString(ErrorCode code);
|
||||
|
||||
QDebug operator<<(QDebug debug, const ErrorCode &e);
|
||||
|
||||
#endif // ERRORSTRINGS_H
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "scripts_registry.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::OpenVpn: return QLatin1String("openvpn");
|
||||
case DockerContainer::OpenVpnOverCloak: return QLatin1String("openvpn_cloak");
|
||||
case DockerContainer::OpenVpnOverShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
QString amnezia::scriptName(SharedScriptType type)
|
||||
{
|
||||
switch (type) {
|
||||
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
|
||||
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
|
||||
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
|
||||
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
|
||||
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
|
||||
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
|
||||
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
|
||||
}
|
||||
}
|
||||
|
||||
QString amnezia::scriptName(ProtocolScriptType type)
|
||||
{
|
||||
switch (type) {
|
||||
case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile");
|
||||
case ProtocolScriptType::run_container: return QLatin1String("run_container.sh");
|
||||
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
|
||||
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
|
||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||
}
|
||||
}
|
||||
|
||||
QString amnezia::scriptData(amnezia::SharedScriptType type)
|
||||
{
|
||||
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
|
||||
QFile file(fileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Error opening script" << fileName;
|
||||
return "";
|
||||
}
|
||||
QByteArray ba = file.readAll();
|
||||
if (ba.isEmpty()) {
|
||||
qDebug() << "Error, script is empty" << fileName;
|
||||
}
|
||||
return ba;
|
||||
}
|
||||
|
||||
QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container)
|
||||
{
|
||||
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type));
|
||||
QFile file(fileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Error opening script" << fileName;
|
||||
return "";
|
||||
}
|
||||
QByteArray data = file.readAll();
|
||||
data.replace("\r", "");
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef SCRIPTS_REGISTRY_H
|
||||
#define SCRIPTS_REGISTRY_H
|
||||
|
||||
#include <QLatin1String>
|
||||
#include "core/defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
enum SharedScriptType {
|
||||
// General scripts
|
||||
prepare_host,
|
||||
install_docker,
|
||||
build_container,
|
||||
remove_container,
|
||||
remove_all_containers,
|
||||
setup_host_firewall,
|
||||
check_connection
|
||||
};
|
||||
enum ProtocolScriptType {
|
||||
// Protocol scripts
|
||||
dockerfile,
|
||||
run_container,
|
||||
configure_container,
|
||||
container_startup,
|
||||
openvpn_template
|
||||
};
|
||||
|
||||
|
||||
QString scriptFolder(DockerContainer container);
|
||||
|
||||
QString scriptName(SharedScriptType type);
|
||||
QString scriptName(ProtocolScriptType type);
|
||||
|
||||
QString scriptData(SharedScriptType type);
|
||||
QString scriptData(ProtocolScriptType type, DockerContainer container);
|
||||
}
|
||||
|
||||
#endif // SCRIPTS_REGISTRY_H
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "server_defs.h"
|
||||
|
||||
//QString amnezia::containerToString(amnezia::DockerContainer container)
|
||||
//{
|
||||
// switch (container) {
|
||||
// case(DockerContainer::OpenVpn): return "amnezia-openvpn";
|
||||
// case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak";
|
||||
// case(DockerContainer::OpenVpnOverShadowSocks): return "amnezia-shadowsocks";
|
||||
// default: return "";
|
||||
// }
|
||||
//}
|
||||
|
||||
QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container)
|
||||
{
|
||||
return "/opt/amnezia/" + containerToString(container);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef SERVER_DEFS_H
|
||||
#define SERVER_DEFS_H
|
||||
|
||||
#include <QObject>
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
namespace amnezia {
|
||||
namespace server {
|
||||
//QString getContainerName(amnezia::DockerContainer container);
|
||||
QString getDockerfileFolder(amnezia::DockerContainer container);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SERVER_DEFS_H
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "servercontroller.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QEventLoop>
|
||||
#include <QLoggingCategory>
|
||||
@@ -8,30 +9,30 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QApplication>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "sftpchannel.h"
|
||||
#include "sshconnectionmanager.h"
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
#include "server_defs.h"
|
||||
#include "scripts_registry.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
using namespace QSsh;
|
||||
|
||||
QString ServerController::getContainerName(DockerContainer container)
|
||||
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script,
|
||||
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
|
||||
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
|
||||
{
|
||||
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,
|
||||
const std::function<void(const QString &)> &cbReadStdOut,
|
||||
const std::function<void(const QString &)> &cbReadStdErr)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
|
||||
SshConnection *client = connectToHost(sshParams);
|
||||
if (client->state() != SshConnection::State::Connected) {
|
||||
if (client->state() == SshConnection::State::Connecting) {
|
||||
qDebug() << "ServerController::runScript aborted, connectToHost in progress";
|
||||
return ErrorCode::SshTimeoutError;
|
||||
}
|
||||
else if (client->state() != SshConnection::State::Connected) {
|
||||
qDebug() << "ServerController::runScript connectToHost error: " << fromSshConnectionErrorCode(client->errorState());
|
||||
return fromSshConnectionErrorCode(client->errorState());
|
||||
}
|
||||
|
||||
@@ -39,17 +40,36 @@ ErrorCode ServerController::runScript(DockerContainer container,
|
||||
|
||||
qDebug() << "Run script";
|
||||
|
||||
QString totalLine;
|
||||
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
|
||||
for (int i = 0; i < lines.count(); i++) {
|
||||
QString line = lines.at(i);
|
||||
line.replace("$CONTAINER_NAME", getContainerName(container));
|
||||
QString currentLine = lines.at(i);
|
||||
QString nextLine;
|
||||
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
|
||||
|
||||
if (line.startsWith("#")) {
|
||||
if (totalLine.isEmpty()) {
|
||||
totalLine = currentLine;
|
||||
}
|
||||
else {
|
||||
totalLine = totalLine + "\n" + currentLine;
|
||||
}
|
||||
|
||||
QString lineToExec;
|
||||
if (currentLine.endsWith("\\")) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
lineToExec = totalLine;
|
||||
totalLine.clear();
|
||||
}
|
||||
|
||||
// Run collected line
|
||||
if (totalLine.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug().noquote() << "EXEC" << line;
|
||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
|
||||
qDebug().noquote() << "EXEC" << lineToExec;
|
||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
|
||||
|
||||
if (!proc) {
|
||||
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
||||
@@ -74,7 +94,7 @@ ErrorCode ServerController::runScript(DockerContainer container,
|
||||
if (s != "." && !s.isEmpty()) {
|
||||
qDebug().noquote() << "stdout" << s;
|
||||
}
|
||||
if (cbReadStdOut) cbReadStdOut(s);
|
||||
if (cbReadStdOut) cbReadStdOut(s, proc);
|
||||
});
|
||||
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
|
||||
@@ -82,7 +102,7 @@ ErrorCode ServerController::runScript(DockerContainer container,
|
||||
if (s != "." && !s.isEmpty()) {
|
||||
qDebug().noquote() << "stderr" << s;
|
||||
}
|
||||
if (cbReadStdErr) cbReadStdErr(s);
|
||||
if (cbReadStdErr) cbReadStdErr(s, proc);
|
||||
});
|
||||
|
||||
proc->start();
|
||||
@@ -100,63 +120,45 @@ ErrorCode ServerController::runScript(DockerContainer container,
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString &file, const QString &path)
|
||||
const ServerCredentials &credentials, const QString &file, const QString &path)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
|
||||
if (e) return e;
|
||||
|
||||
QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
|
||||
arg(getContainerName(container)).arg(file).arg(path);
|
||||
QString stdOut;
|
||||
auto cbReadStd = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
|
||||
stdOut += data + "\n";
|
||||
};
|
||||
|
||||
qDebug().noquote() << script;
|
||||
e = runScript(sshParams(credentials),
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
|
||||
|
||||
SshConnection *client = connectToHost(sshParams(credentials));
|
||||
if (client->state() != SshConnection::State::Connected) {
|
||||
return fromSshConnectionErrorCode(client->errorState());
|
||||
if (e) return e;
|
||||
if (stdOut.contains("Error: No such container:")) {
|
||||
return ErrorCode::ServerContainerMissingError;
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
|
||||
runScript(sshParams(credentials),
|
||||
replaceVars(QString("sudo shred %1").arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)));
|
||||
|
||||
if (!proc) {
|
||||
qCritical() << "Failed to create SshRemoteProcess, breaking.";
|
||||
return ErrorCode::SshRemoteProcessCreationError;
|
||||
}
|
||||
runScript(sshParams(credentials),
|
||||
replaceVars(QString("sudo rm %1").arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)));
|
||||
|
||||
QEventLoop wait;
|
||||
int exitStatus = -1;
|
||||
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
|
||||
// qDebug() << "Command started";
|
||||
// });
|
||||
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
|
||||
//qDebug() << "Remote process exited with status" << status;
|
||||
exitStatus = status;
|
||||
wait.quit();
|
||||
});
|
||||
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||
qDebug().noquote() << proc->readAllStandardOutput();
|
||||
});
|
||||
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||
qDebug().noquote() << proc->readAllStandardError();
|
||||
});
|
||||
|
||||
proc->start();
|
||||
//wait.exec();
|
||||
|
||||
if (exitStatus < 0) {
|
||||
wait.exec();
|
||||
}
|
||||
|
||||
return fromSshProcessExitStatus(exitStatus);
|
||||
return e;
|
||||
}
|
||||
|
||||
QString ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
|
||||
{
|
||||
QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\"").
|
||||
arg(getContainerName(container)).arg(path);
|
||||
if (errorCode) *errorCode = ErrorCode::NoError;
|
||||
|
||||
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
|
||||
arg(amnezia::containerToString(container)).arg(path);
|
||||
|
||||
qDebug().noquote() << "Copy file from container\n" << script;
|
||||
|
||||
@@ -200,28 +202,12 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
return proc->readAllStandardOutput();
|
||||
}
|
||||
|
||||
ErrorCode ServerController::signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString 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 %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(container, sshParams(credentials), script.join("\n"));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
|
||||
{
|
||||
QString caCert = ServerController::getTextFileFromContainer(container,
|
||||
credentials, ServerController::caCertPath());
|
||||
credentials, amnezia::protocols::openvpn::caCertPath);
|
||||
QString taKey = ServerController::getTextFileFromContainer(container,
|
||||
credentials, ServerController::taKeyPath());
|
||||
credentials, amnezia::protocols::openvpn::taKeyPath);
|
||||
|
||||
if (!caCert.isEmpty() && !taKey.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
@@ -231,6 +217,68 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath)
|
||||
{
|
||||
SshConnection *client = connectToHost(sshParams(credentials));
|
||||
if (client->state() != SshConnection::State::Connected) {
|
||||
return fromSshConnectionErrorCode(client->errorState());
|
||||
}
|
||||
|
||||
bool err = false;
|
||||
|
||||
QEventLoop wait;
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
timer.start(3000);
|
||||
|
||||
QSharedPointer<SftpChannel> sftp = client->createSftpChannel();
|
||||
sftp->initialize();
|
||||
|
||||
QObject::connect(sftp.data(), &SftpChannel::initialized, &wait, [&](){
|
||||
timer.stop();
|
||||
wait.quit();
|
||||
});
|
||||
QObject::connect(&timer, &QTimer::timeout, &wait, [&](){
|
||||
err= true;
|
||||
wait.quit();
|
||||
});
|
||||
|
||||
wait.exec();
|
||||
|
||||
if (!sftp) {
|
||||
qCritical() << "Failed to create SftpChannel, breaking.";
|
||||
return ErrorCode::SshRemoteProcessCreationError;
|
||||
}
|
||||
|
||||
QTemporaryFile localFile;
|
||||
localFile.open();
|
||||
localFile.write(data);
|
||||
localFile.close();
|
||||
|
||||
auto job = sftp->uploadFile(localFile.fileName(), remotePath, QSsh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
QObject::connect(sftp.data(), &SftpChannel::finished, &wait, [&](QSsh::SftpJobId j, const QString &error){
|
||||
if (job == j) {
|
||||
qDebug() << "Sftp finished with status" << error;
|
||||
wait.quit();
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(sftp.data(), &SftpChannel::channelError, &wait, [&](const QString &reason){
|
||||
qDebug() << "Sftp finished with error" << reason;
|
||||
err= true;
|
||||
wait.quit();
|
||||
});
|
||||
|
||||
wait.exec();
|
||||
|
||||
if (err) {
|
||||
return ErrorCode::SshSftpError;
|
||||
}
|
||||
else {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
|
||||
{
|
||||
switch (error) {
|
||||
@@ -261,140 +309,357 @@ ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
|
||||
SshConnectionParameters ServerController::sshParams(const ServerCredentials &credentials)
|
||||
{
|
||||
QSsh::SshConnectionParameters sshParams;
|
||||
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
|
||||
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
|
||||
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
|
||||
sshParams.privateKeyFile = credentials.password;
|
||||
}
|
||||
else {
|
||||
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
|
||||
sshParams.password = credentials.password;
|
||||
}
|
||||
sshParams.host = credentials.hostName;
|
||||
sshParams.userName = credentials.userName;
|
||||
sshParams.password = credentials.password;
|
||||
sshParams.timeout = 10;
|
||||
sshParams.port = credentials.port;
|
||||
sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone;
|
||||
sshParams.options = SshIgnoreDefaultProxy;
|
||||
|
||||
return sshParams;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto)
|
||||
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
|
||||
{
|
||||
QString scriptFileName;
|
||||
DockerContainer container;
|
||||
|
||||
if (proto == Protocol::Any) {
|
||||
ErrorCode e = removeServer(credentials, Protocol::OpenVpn);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
return removeServer(credentials, Protocol::ShadowSocks);
|
||||
}
|
||||
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;
|
||||
|
||||
QFile file(scriptFileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||
|
||||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
|
||||
return runScript(container, sshParams(credentials), scriptData);
|
||||
return runScript(sshParams(credentials),
|
||||
amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
|
||||
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
if (proto == Protocol::OpenVpn) {
|
||||
return setupOpenVpnServer(credentials);
|
||||
}
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
return setupShadowSocksServer(credentials);
|
||||
}
|
||||
else if (proto == Protocol::Any) {
|
||||
//return ErrorCode::NotImplementedError;
|
||||
|
||||
// TODO: run concurently
|
||||
setupOpenVpnServer(credentials);
|
||||
setupShadowSocksServer(credentials);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
genVarsForScript(credentials, container)));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials)
|
||||
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
QString scriptData;
|
||||
QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh";
|
||||
QFile file(scriptFileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||
qDebug().noquote() << "ServerController::setupContainer" << containerToString(container);
|
||||
qDebug().noquote() << QJsonDocument(config).toJson();
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
// e = runScript(sshParams(credentials),
|
||||
// replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
|
||||
// genVarsForScript(credentials)));
|
||||
|
||||
// if (e) return e;
|
||||
|
||||
|
||||
e = prepareHostWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
|
||||
removeContainer(credentials, container);
|
||||
|
||||
e = buildContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
|
||||
e = runContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
|
||||
e = configureContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
|
||||
return startupContainerWorker(credentials, container, config);
|
||||
|
||||
|
||||
|
||||
|
||||
// if (container == DockerContainer::OpenVpn) {
|
||||
// return setupOpenVpnServer(credentials, config);
|
||||
// }
|
||||
// else if (container == DockerContainer::OpenVpnOverShadowSocks) {
|
||||
// return setupShadowSocksServer(credentials, config);
|
||||
// }
|
||||
// else if (container == DockerContainer::OpenVpnOverCloak) {
|
||||
// return setupOpenVpnOverCloakServer(credentials, config);
|
||||
// }
|
||||
|
||||
// return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||
{
|
||||
bool reinstallRequred = isReinstallContainerRequred(container, oldConfig, newConfig);
|
||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred;
|
||||
|
||||
if (reinstallRequred) {
|
||||
return setupContainer(credentials, container, newConfig);
|
||||
}
|
||||
else {
|
||||
ErrorCode e = configureContainerWorker(credentials, container, newConfig);
|
||||
if (e) return e;
|
||||
|
||||
return startupContainerWorker(credentials, container, newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||
{
|
||||
if (container == DockerContainer::OpenVpn) {
|
||||
const QJsonObject &oldProtoConfig = oldConfig[config_key::openvpn].toObject();
|
||||
const QJsonObject &newProtoConfig = newConfig[config_key::openvpn].toObject();
|
||||
|
||||
if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) !=
|
||||
newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto))
|
||||
return true;
|
||||
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::OpenVpnOverCloak) {
|
||||
const QJsonObject &oldProtoConfig = oldConfig[config_key::cloak].toObject();
|
||||
const QJsonObject &newProtoConfig = newConfig[config_key::cloak].toObject();
|
||||
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::OpenVpnOverShadowSocks) {
|
||||
const QJsonObject &oldProtoConfig = oldConfig[config_key::shadowsocks].toObject();
|
||||
const QJsonObject &newProtoConfig = newConfig[config_key::shadowsocks].toObject();
|
||||
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config)
|
||||
//{
|
||||
// return ErrorCode::NotImplementedError;
|
||||
//}
|
||||
|
||||
//ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config)
|
||||
//{
|
||||
// ErrorCode e = ErrorCode::NoError;
|
||||
// DockerContainer container = DockerContainer::OpenVpnOverCloak;
|
||||
|
||||
// e = prepareHostWorker(credentials, container, config);
|
||||
// if (e) return e;
|
||||
|
||||
// removeContainer(credentials, container);
|
||||
|
||||
// e = buildContainerWorker(credentials, container, config);
|
||||
// if (e) return e;
|
||||
|
||||
// e = runContainerWorker(credentials, container, config);
|
||||
// if (e) return e;
|
||||
|
||||
// e = configureContainerWorker(credentials, container, config);
|
||||
// if (e) return e;
|
||||
|
||||
// return startupContainerWorker(credentials, container, config);
|
||||
//}
|
||||
|
||||
ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data) {
|
||||
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
stdOut += data + "\n";
|
||||
|
||||
if (data.contains("Automatically restart Docker daemon?")) {
|
||||
proc->write("yes\n");
|
||||
}
|
||||
};
|
||||
auto cbReadStdErr = [&](const QString &data) {
|
||||
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
|
||||
stdOut += data + "\n";
|
||||
};
|
||||
|
||||
ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
||||
if (e) return e;
|
||||
QApplication::processEvents();
|
||||
|
||||
if (stdOut.contains("port is already allocated")) return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("Error response from daemon")) return ErrorCode::ServerCheckFailed;
|
||||
|
||||
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
|
||||
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
// Setup openvpn part
|
||||
QString scriptData;
|
||||
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
|
||||
QFile file(scriptFileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||
// create folder on host
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
|
||||
genVarsForScript(credentials, container)));
|
||||
}
|
||||
|
||||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
||||
|
||||
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";
|
||||
// QString stdOut;
|
||||
// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
|
||||
qDebug().noquote() << configData;
|
||||
configData.replace("\"", "\\\"");
|
||||
qDebug().noquote() << configData;
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
||||
genVarsForScript(credentials, container, config)));
|
||||
}
|
||||
|
||||
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
|
||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||
genVarsForScript(credentials, container, config)));
|
||||
}
|
||||
|
||||
// Start ss
|
||||
QString script = QString("docker exec -i %1 sh -c \"ss-server -c %2 &\"").
|
||||
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
|
||||
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
|
||||
genVarsForScript(credentials, container, config)));
|
||||
}
|
||||
|
||||
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
|
||||
return e;
|
||||
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials,
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::container_startup, container),
|
||||
genVarsForScript(credentials, container, config)),
|
||||
"/opt/amnezia/start.sh");
|
||||
if (e) return e;
|
||||
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"",
|
||||
genVarsForScript(credentials, container, config)));
|
||||
}
|
||||
|
||||
//ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config)
|
||||
//{
|
||||
// return ErrorCode::NotImplementedError;
|
||||
//// // Setup openvpn part
|
||||
//// QString scriptData;
|
||||
//// QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
|
||||
//// QFile file(scriptFileName);
|
||||
//// if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||
|
||||
//// scriptData = file.readAll();
|
||||
//// if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
|
||||
//// QString stdOut;
|
||||
//// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
//// stdOut += data + "\n";
|
||||
|
||||
//// if (data.contains("Automatically restart Docker daemon?")) {
|
||||
//// proc->write("yes\n");
|
||||
//// }
|
||||
//// };
|
||||
//// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
//// stdOut += data + "\n";
|
||||
//// };
|
||||
|
||||
//// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
|
||||
//// if (e) return e;
|
||||
|
||||
//// // Create ss config
|
||||
//// QJsonObject ssConfig;
|
||||
//// ssConfig.insert("server", "0.0.0.0");
|
||||
//// ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort());
|
||||
//// ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort());
|
||||
//// ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
|
||||
//// ssConfig.insert("timeout", 60);
|
||||
//// ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption());
|
||||
//// QString configData = QJsonDocument(ssConfig).toJson();
|
||||
//// QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
|
||||
|
||||
//// configData.replace("\"", "\\\"");
|
||||
//// //qDebug().noquote() << configData;
|
||||
|
||||
//// uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
|
||||
|
||||
//// // Start ss
|
||||
//// QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\"").
|
||||
//// arg(amnezia::containerToString(DockerContainer::ShadowSocks)).arg(sSConfigPath);
|
||||
|
||||
//// e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script);
|
||||
// // return e;
|
||||
//}
|
||||
|
||||
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
const QJsonObject &openvpnConfig = config.value(config_key::openvpn).toObject();
|
||||
const QJsonObject &cloakConfig = config.value(config_key::cloak).toObject();
|
||||
const QJsonObject &ssConfig = config.value(config_key::shadowsocks).toObject();
|
||||
//
|
||||
|
||||
Vars vars;
|
||||
|
||||
vars.append({{"$REMOTE_HOST", credentials.hostName}});
|
||||
|
||||
// OpenVPN vars
|
||||
vars.append({{"$VPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(amnezia::protocols::vpnDefaultSubnetAddress) }});
|
||||
vars.append({{"$VPN_SUBNET_MASK_VAL", openvpnConfig.value(config_key::subnet_mask_val).toString(amnezia::protocols::vpnDefaultSubnetMaskVal) }});
|
||||
vars.append({{"$VPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(amnezia::protocols::vpnDefaultSubnetMask) }});
|
||||
|
||||
vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(amnezia::protocols::openvpn::defaultPort) }});
|
||||
vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(amnezia::protocols::openvpn::defaultTransportProto) }});
|
||||
|
||||
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(amnezia::protocols::openvpn::defaultNcpDisable);
|
||||
vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }});
|
||||
vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(amnezia::protocols::openvpn::defaultCipher) }});
|
||||
vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(amnezia::protocols::openvpn::defaultHash) }});
|
||||
|
||||
// ShadowSocks vars
|
||||
vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(amnezia::protocols::shadowsocks::defaultPort) }});
|
||||
vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(amnezia::protocols::shadowsocks::defaultLocalProxyPort) }});
|
||||
vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(amnezia::protocols::shadowsocks::defaultCipher) }});
|
||||
|
||||
vars.append({{"$CONTAINER_NAME", amnezia::containerToString(container)}});
|
||||
vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::containerToString(container)}});
|
||||
|
||||
// Cloak vars
|
||||
vars.append({{"$CLOAK_SERVER_PORT", config.value(config_key::port).toString(protocols::cloak::defaultPort) }});
|
||||
vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }});
|
||||
|
||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
||||
if (!serverIp.isEmpty()) {
|
||||
vars.append({{"$SERVER_IP_ADDRESS", serverIp}});
|
||||
}
|
||||
else {
|
||||
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
stdOut += data + "\n";
|
||||
};
|
||||
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
|
||||
stdOut += data + "\n";
|
||||
};
|
||||
|
||||
ErrorCode e = runScript(sshParams(credentials),
|
||||
amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (errorCode) *errorCode = e;
|
||||
|
||||
return stdOut;
|
||||
}
|
||||
|
||||
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
SshConnection *client = acquireConnection(sshParams);
|
||||
if (!client) return nullptr;
|
||||
|
||||
QEventLoop waitssh;
|
||||
QObject::connect(client, &SshConnection::connected, &waitssh, [&]() {
|
||||
@@ -445,11 +710,25 @@ SshConnection *ServerController::connectToHost(const SshConnectionParameters &ss
|
||||
return client;
|
||||
}
|
||||
|
||||
void ServerController::disconnectFromHost(const ServerCredentials &credentials)
|
||||
{
|
||||
SshConnection *client = acquireConnection(sshParams(credentials));
|
||||
if (client) client->disconnectFromHost();
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
||||
{
|
||||
QFile file(":/server_scripts/setup_firewall.sh");
|
||||
file.open(QIODevice::ReadOnly);
|
||||
|
||||
QString script = file.readAll();
|
||||
return runScript(DockerContainer::OpenVpn, sshParams(credentials), script);
|
||||
return runScript(sshParams(credentials),
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
|
||||
genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)));
|
||||
}
|
||||
|
||||
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||
{
|
||||
QString s = script;
|
||||
for (const QPair<QString, QString> &var : vars) {
|
||||
//qDebug() << "Replacing" << var.first << var.second;
|
||||
s.replace(var.first, var.second);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#ifndef SERVERCONTROLLER_H
|
||||
#define SERVERCONTROLLER_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include "sshconnection.h"
|
||||
#include "sshremoteprocess.h"
|
||||
#include "defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
@@ -12,48 +15,59 @@ class ServerController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef QList<QPair<QString, QString>> Vars;
|
||||
|
||||
static ErrorCode fromSshConnectionErrorCode(QSsh::SshError error);
|
||||
|
||||
// QSsh exitCode and exitStatus are different things
|
||||
static ErrorCode fromSshProcessExitStatus(int exitStatus);
|
||||
|
||||
static QString caCertPath() { return "/opt/amneziavpn_data/pki/ca.crt"; }
|
||||
static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; }
|
||||
static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; }
|
||||
|
||||
static QString getContainerName(amnezia::DockerContainer container);
|
||||
|
||||
static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
|
||||
static void disconnectFromHost(const ServerCredentials &credentials);
|
||||
|
||||
static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto);
|
||||
static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto);
|
||||
static ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
||||
static ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
||||
static ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
static ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &oldConfig, const QJsonObject &newConfig = QJsonObject());
|
||||
|
||||
static bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||
|
||||
static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
|
||||
|
||||
static ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath);
|
||||
|
||||
static ErrorCode uploadTextFileToContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString &file, const QString &path);
|
||||
const ServerCredentials &credentials, const QString &file, const QString &path);
|
||||
|
||||
static QString getTextFileFromContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
|
||||
|
||||
static ErrorCode signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId);
|
||||
|
||||
static int ssRemotePort() { return 6789; } // TODO move to ShadowSocksDefs.h
|
||||
static int ssContainerPort() { return 8585; } // TODO move to ShadowSocksDefs.h
|
||||
static QString ssEncryption() { return "chacha20-ietf-poly1305"; } // TODO move to ShadowSocksDefs.h
|
||||
|
||||
static ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
||||
|
||||
static QString replaceVars(const QString &script, const Vars &vars);
|
||||
|
||||
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script,
|
||||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
|
||||
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
|
||||
|
||||
static Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject());
|
||||
|
||||
static QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
||||
private:
|
||||
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
||||
static ErrorCode runScript(DockerContainer container,
|
||||
const QSsh::SshConnectionParameters &sshParams, QString script,
|
||||
const std::function<void(const QString &)> &cbReadStdOut = nullptr,
|
||||
const std::function<void(const QString &)> &cbReadStdErr = nullptr);
|
||||
|
||||
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
|
||||
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);
|
||||
static ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||
|
||||
//static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
|
||||
//static ErrorCode setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
|
||||
// static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
|
||||
|
||||
static ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
static ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
static ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
static ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
static ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
#define APPLICATION_NAME "AmneziaVPN"
|
||||
#define SERVICE_NAME "AmneziaVPN-service"
|
||||
#define ORGANIZATION_NAME "AmneziaVPN.ORG"
|
||||
#define APP_VERSION "1.0.0.0"
|
||||
#define APP_VERSION "1.6.0.0"
|
||||
|
||||
#endif // DEFINES_H
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 846 B |
|
After Width: | Height: | Size: 871 B |
|
After Width: | Height: | Size: 181 B |
@@ -3,13 +3,19 @@
|
||||
#include <QCommandLineParser>
|
||||
#include <QMessageBox>
|
||||
#include <QTranslator>
|
||||
#include <QTimer>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include "debug.h"
|
||||
#include "defines.h"
|
||||
#include "runguard.h"
|
||||
#include "singleapplication.h"
|
||||
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
static void loadTranslator()
|
||||
{
|
||||
QTranslator* translator = new QTranslator;
|
||||
@@ -20,17 +26,27 @@ static void loadTranslator()
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
RunGuard::instance(APPLICATION_NAME).activate();
|
||||
|
||||
QApplication app(argc, argv);
|
||||
loadTranslator();
|
||||
#ifdef Q_OS_WIN
|
||||
AllowSetForegroundWindow(ASFW_ANY);
|
||||
#endif
|
||||
|
||||
if (!RunGuard::instance().tryToRun()) {
|
||||
qDebug() << "Tried to run second instance. Exiting...";
|
||||
QMessageBox::information(NULL, QObject::tr("Notification"), QObject::tr("AmneziaVPN is already running."));
|
||||
return 0;
|
||||
SingleApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification);
|
||||
|
||||
if (!app.isPrimary()) {
|
||||
QTimer::singleShot(1000, &app, [&](){
|
||||
app.quit();
|
||||
});
|
||||
return app.exec();
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
AllowSetForegroundWindow(0);
|
||||
#endif
|
||||
|
||||
loadTranslator();
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf");
|
||||
@@ -65,5 +81,14 @@ int main(int argc, char *argv[])
|
||||
MainWindow mainWindow;
|
||||
mainWindow.show();
|
||||
|
||||
if (app.isPrimary()) {
|
||||
QObject::connect(&app, &SingleApplication::instanceStarted, &mainWindow, [&](){
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
mainWindow.show();
|
||||
mainWindow.showNormal();
|
||||
mainWindow.raise();
|
||||
});
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ bool ManagementServer::isOpen() const
|
||||
|
||||
void ManagementServer::stop()
|
||||
{
|
||||
m_tcpServer->close();
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
}
|
||||
|
||||
void ManagementServer::onAcceptError(QAbstractSocket::SocketError socketError)
|
||||
@@ -37,7 +37,9 @@ qint64 ManagementServer::writeCommand(const QString& message)
|
||||
}
|
||||
|
||||
const QString command = message + "\n";
|
||||
return m_socket->write(command.toStdString().c_str());
|
||||
qint64 bytesWritten = m_socket->write(command.toStdString().c_str());
|
||||
m_socket->flush();
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
void ManagementServer::onNewConnection()
|
||||
@@ -45,7 +47,7 @@ void ManagementServer::onNewConnection()
|
||||
qDebug() << "New incoming connection";
|
||||
|
||||
m_socket = QPointer<QTcpSocket>(m_tcpServer->nextPendingConnection());
|
||||
m_tcpServer->close();
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
|
||||
QObject::connect(m_socket.data(), SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
||||
QObject::connect(m_socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
|
||||
@@ -78,9 +80,7 @@ void ManagementServer::onReadyRead()
|
||||
|
||||
bool ManagementServer::start(const QString& host, unsigned int port)
|
||||
{
|
||||
if (m_tcpServer) {
|
||||
m_tcpServer->deleteLater();
|
||||
}
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
|
||||
m_tcpServer = QSharedPointer<QTcpServer>(new QTcpServer(this), [](QTcpServer *s){
|
||||
if (s) s->deleteLater();
|
||||
|
||||
@@ -3,11 +3,11 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
IDI_ICON1 ICON "../images/app.ico"
|
||||
|
||||
#define VER_FILEVERSION 1,1,1,1
|
||||
#define VER_FILEVERSION_STR "1.1.1.1\0"
|
||||
#define VER_FILEVERSION 1,6,0,0
|
||||
#define VER_FILEVERSION_STR "1.6.0.0\0"
|
||||
|
||||
#define VER_PRODUCTVERSION 1,1,1,1
|
||||
#define VER_PRODUCTVERSION_STR "1.1.1.1\0"
|
||||
#define VER_PRODUCTVERSION 1,6,0,0
|
||||
#define VER_PRODUCTVERSION_STR "1.6.0.0\0"
|
||||
|
||||
#define VER_COMPANYNAME_STR "AmneziaVPN"
|
||||
#define VER_FILEDESCRIPTION_STR "AmneziaVPN"
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "openvpnovercloakprotocol.h"
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
OpenVpnOverCloakProtocol::OpenVpnOverCloakProtocol(const QJsonObject &configuration, QObject *parent):
|
||||
OpenVpnProtocol(configuration, parent)
|
||||
{
|
||||
readCloakConfiguration(configuration);
|
||||
}
|
||||
|
||||
OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol()
|
||||
{
|
||||
qDebug() << "OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol";
|
||||
OpenVpnOverCloakProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
m_ckProcess.close();
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnOverCloakProtocol::start()
|
||||
{
|
||||
if (Utils::processIsRunning(Utils::executable("ck-client", false))) {
|
||||
Utils::killProcessByName(Utils::executable("ck-client", false));
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
m_cloakCfgFile.setAutoRemove(false);
|
||||
#endif
|
||||
m_cloakCfgFile.open();
|
||||
m_cloakCfgFile.write(QJsonDocument(m_cloakConfig).toJson());
|
||||
m_cloakCfgFile.close();
|
||||
|
||||
QStringList args = QStringList() << "-c" << m_cloakCfgFile.fileName()
|
||||
<< "-s" << m_cloakConfig.value(config_key::remote).toString()
|
||||
<< "-p" << amnezia::protocols::cloak::defaultPort
|
||||
<< "-l" << amnezia::protocols::openvpn::defaultPort;
|
||||
|
||||
if (m_cloakConfig.value(config_key::transport_proto).toString() == protocols::UDP) {
|
||||
args << "-u";
|
||||
}
|
||||
|
||||
qDebug().noquote() << "OpenVpnOverCloakProtocol::start()"
|
||||
<< cloakExecPath() << args.join(" ");
|
||||
|
||||
m_ckProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
m_ckProcess.setProgram(cloakExecPath());
|
||||
m_ckProcess.setArguments(args);
|
||||
|
||||
connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this](){
|
||||
qDebug().noquote() << "ck-client:" << m_ckProcess.readAllStandardOutput();
|
||||
});
|
||||
|
||||
connect(&m_ckProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
|
||||
qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
|
||||
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
|
||||
if (exitStatus != QProcess::NormalExit){
|
||||
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
|
||||
stop();
|
||||
}
|
||||
if (exitCode !=0 ){
|
||||
emit protocolError(amnezia::ErrorCode::InternalError);
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
m_ckProcess.start();
|
||||
m_ckProcess.waitForStarted();
|
||||
|
||||
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
|
||||
setConnectionState(ConnectionState::Connecting);
|
||||
|
||||
return OpenVpnProtocol::start();
|
||||
}
|
||||
else return ErrorCode::CloakExecutableMissing;
|
||||
}
|
||||
|
||||
void OpenVpnOverCloakProtocol::stop()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
qDebug() << "OpenVpnOverCloakProtocol::stop()";
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Utils::signalCtrl(m_ckProcess.processId(), CTRL_C_EVENT);
|
||||
#endif
|
||||
|
||||
m_ckProcess.terminate();
|
||||
}
|
||||
|
||||
QString OpenVpnOverCloakProtocol::cloakExecPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable(QString("cloak/ck-client"), true);
|
||||
#else
|
||||
return Utils::executable(QString("/ck-client"), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
m_cloakConfig = configuration.value(config::key_cloak_config_data).toObject();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef OPENVPNOVERCLOAKPROTOCOL_H
|
||||
#define OPENVPNOVERCLOAKPROTOCOL_H
|
||||
|
||||
#include "openvpnprotocol.h"
|
||||
#include "QProcess"
|
||||
|
||||
class OpenVpnOverCloakProtocol : public OpenVpnProtocol
|
||||
{
|
||||
public:
|
||||
OpenVpnOverCloakProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~OpenVpnOverCloakProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
protected:
|
||||
void readCloakConfiguration(const QJsonObject &configuration);
|
||||
|
||||
protected:
|
||||
QJsonObject m_cloakConfig;
|
||||
|
||||
private:
|
||||
static QString cloakExecPath();
|
||||
|
||||
private:
|
||||
QProcess m_ckProcess;
|
||||
QTemporaryFile m_cloakCfgFile;
|
||||
};
|
||||
|
||||
#endif // OPENVPNOVERCLOAKPROTOCOL_H
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QTcpSocket>
|
||||
|
||||
//#include "communicator.h"
|
||||
#include "debug.h"
|
||||
#include "openvpnprotocol.h"
|
||||
#include "utils.h"
|
||||
@@ -18,12 +17,15 @@ OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* pare
|
||||
|
||||
OpenVpnProtocol::~OpenVpnProtocol()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
qDebug() << "OpenVpnProtocol::~OpenVpnProtocol()";
|
||||
OpenVpnProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == VpnProtocol::ConnectionState::Preparing) ||
|
||||
@@ -33,6 +35,8 @@ void OpenVpnProtocol::stop()
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
qApp->processEvents();
|
||||
setConnectionState(VpnProtocol::ConnectionState::Disconnecting);
|
||||
}
|
||||
}
|
||||
@@ -64,16 +68,16 @@ void OpenVpnProtocol::killOpenVpnProcess()
|
||||
|
||||
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
if (configuration.contains(config::key_openvpn_config_data())) {
|
||||
if (configuration.contains(config::key_openvpn_config_data)) {
|
||||
m_configFile.open();
|
||||
m_configFile.write(configuration.value(config::key_openvpn_config_data()).toString().toUtf8());
|
||||
m_configFile.write(configuration.value(config::key_openvpn_config_data).toString().toUtf8());
|
||||
m_configFile.close();
|
||||
m_configFileName = m_configFile.fileName();
|
||||
|
||||
qDebug().noquote() << QString("Set config data") << m_configFileName;
|
||||
}
|
||||
else if (configuration.contains(config::key_openvpn_config_path())) {
|
||||
m_configFileName = configuration.value(config::key_openvpn_config_path()).toString();
|
||||
else if (configuration.contains(config::key_openvpn_config_path)) {
|
||||
m_configFileName = configuration.value(config::key_openvpn_config_path).toString();
|
||||
QFileInfo file(m_configFileName);
|
||||
|
||||
if (file.fileName().isEmpty()) {
|
||||
@@ -104,12 +108,13 @@ void OpenVpnProtocol::sendManagementCommand(const QString& command)
|
||||
QIODevice *device = dynamic_cast<QIODevice*>(m_managementServer.socket().data());
|
||||
if (device) {
|
||||
QTextStream stream(device);
|
||||
stream << command << endl;
|
||||
stream << command << Qt::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
{
|
||||
// TODO: fix for macos
|
||||
line = line.split("ROUTE_GATEWAY", QString::SkipEmptyParts).at(1);
|
||||
if (!line.contains("/")) return;
|
||||
m_routeGateway = line.split("/", QString::SkipEmptyParts).first();
|
||||
@@ -188,7 +193,6 @@ ErrorCode OpenVpnProtocol::start()
|
||||
|
||||
m_openVpnProcess->start();
|
||||
|
||||
//m_communicator->sendMessage(Message(Message::State::StartRequest, args));
|
||||
//startTimeoutTimer();
|
||||
|
||||
return ErrorCode::NoError;
|
||||
@@ -229,7 +233,6 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
|
||||
if (line.contains("CONNECTED,SUCCESS")) {
|
||||
sendByteCount();
|
||||
stopTimeoutTimer();
|
||||
updateVpnGateway();
|
||||
setConnectionState(VpnProtocol::ConnectionState::Connected);
|
||||
continue;
|
||||
} else if (line.contains("EXITING,SIGTER")) {
|
||||
@@ -246,6 +249,10 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
|
||||
updateRouteGateway(line);
|
||||
}
|
||||
|
||||
if (line.contains("PUSH: Received control message")) {
|
||||
updateVpnGateway(line);
|
||||
}
|
||||
|
||||
if (line.contains("FATAL")) {
|
||||
if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) {
|
||||
emit protocolError(ErrorCode::OpenVpnAdaptersInUseError);
|
||||
@@ -272,46 +279,61 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::updateVpnGateway()
|
||||
void OpenVpnProtocol::updateVpnGateway(const QString &line)
|
||||
{
|
||||
QProcess ipconfig;
|
||||
ipconfig.start("ipconfig", QStringList() << "/all");
|
||||
ipconfig.waitForStarted();
|
||||
ipconfig.waitForFinished();
|
||||
// line looks like
|
||||
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
|
||||
|
||||
QString d = ipconfig.readAll();
|
||||
d.replace("\r", "");
|
||||
//qDebug().noquote() << d;
|
||||
QStringList params = line.split(",");
|
||||
for (const QString &l : params) {
|
||||
if (l.contains("ifconfig")) {
|
||||
if (l.split(" ").size() == 3) {
|
||||
m_vpnAddress = l.split(" ").at(1);
|
||||
m_vpnGateway = l.split(" ").at(2);
|
||||
|
||||
QStringList adapters = d.split(":\n");
|
||||
|
||||
bool isTapV9Present = false;
|
||||
QString tapV9;
|
||||
for (int i = 0; i < adapters.size(); ++i) {
|
||||
if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
|
||||
isTapV9Present = true;
|
||||
tapV9 = adapters.at(i);
|
||||
break;
|
||||
qDebug() << QString("Set vpn address %1, gw %2").arg(m_vpnAddress).arg(vpnGateway());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isTapV9Present) {
|
||||
m_vpnGateway = "";
|
||||
}
|
||||
|
||||
QStringList lines = tapV9.split("\n");
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
if (!lines.at(i).contains("DHCP")) continue;
|
||||
// QProcess ipconfig;
|
||||
// ipconfig.start("ipconfig", QStringList() << "/all");
|
||||
// ipconfig.waitForStarted();
|
||||
// ipconfig.waitForFinished();
|
||||
|
||||
QRegularExpression re("(: )([\\d\\.]+)($)");
|
||||
QRegularExpressionMatch match = re.match(lines.at(i));
|
||||
// QString d = ipconfig.readAll();
|
||||
// d.replace("\r", "");
|
||||
// //qDebug().noquote() << d;
|
||||
|
||||
if (match.hasMatch()) {
|
||||
qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
|
||||
m_vpnGateway = match.captured(2);
|
||||
return;
|
||||
}
|
||||
else continue;
|
||||
}
|
||||
// QStringList adapters = d.split(":\n");
|
||||
|
||||
m_vpnGateway = "";
|
||||
// bool isTapV9Present = false;
|
||||
// QString tapV9;
|
||||
// for (int i = 0; i < adapters.size(); ++i) {
|
||||
// if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
|
||||
// isTapV9Present = true;
|
||||
// tapV9 = adapters.at(i);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!isTapV9Present) {
|
||||
// m_vpnGateway = "";
|
||||
// }
|
||||
|
||||
// QStringList lines = tapV9.split("\n");
|
||||
// for (int i = 0; i < lines.size(); ++i) {
|
||||
// if (!lines.at(i).contains("DHCP")) continue;
|
||||
|
||||
// QRegularExpression re("(: )([\\d\\.]+)($)");
|
||||
// QRegularExpressionMatch match = re.match(lines.at(i));
|
||||
|
||||
// if (match.hasMatch()) {
|
||||
// qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
|
||||
// m_vpnGateway = match.captured(2);
|
||||
// return;
|
||||
// }
|
||||
// else continue;
|
||||
// }
|
||||
|
||||
// m_vpnGateway = "";
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ private:
|
||||
|
||||
private:
|
||||
void updateRouteGateway(QString line);
|
||||
void updateVpnGateway();
|
||||
void updateVpnGateway(const QString &line);
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "protocols_defs.h"
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::Protocol &p)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << protoToString(p);
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << containerToString(c);
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
amnezia::Protocol amnezia::protoFromString(QString proto){
|
||||
if (proto == config_key::openvpn) return Protocol::OpenVpn;
|
||||
if (proto == config_key::cloak) return Protocol::Cloak;
|
||||
if (proto == config_key::shadowsocks) return Protocol::ShadowSocks;
|
||||
if (proto == config_key::wireguard) return Protocol::WireGuard;
|
||||
return Protocol::Any;
|
||||
}
|
||||
|
||||
QString amnezia::protoToString(amnezia::Protocol proto){
|
||||
switch (proto) {
|
||||
case(Protocol::OpenVpn): return config_key::openvpn;
|
||||
case(Protocol::Cloak): return config_key::cloak;
|
||||
case(Protocol::ShadowSocks): return config_key::shadowsocks;
|
||||
case(Protocol::WireGuard): return config_key::wireguard;
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
amnezia::DockerContainer amnezia::containerFromString(const QString &container){
|
||||
if (container == config_key::amnezia_openvpn) return DockerContainer::OpenVpn;
|
||||
if (container == config_key::amnezia_openvpn_cloak) return DockerContainer::OpenVpnOverCloak;
|
||||
if (container == config_key::amnezia_shadowsocks) return DockerContainer::OpenVpnOverShadowSocks;
|
||||
if (container == config_key::amnezia_wireguard) return DockerContainer::WireGuard;
|
||||
return DockerContainer::None;
|
||||
}
|
||||
|
||||
QString amnezia::containerToString(amnezia::DockerContainer container){
|
||||
switch (container) {
|
||||
case(DockerContainer::OpenVpn): return config_key::amnezia_openvpn;
|
||||
case(DockerContainer::OpenVpnOverCloak): return config_key::amnezia_openvpn_cloak;
|
||||
case(DockerContainer::OpenVpnOverShadowSocks): return config_key::amnezia_shadowsocks;
|
||||
case(DockerContainer::WireGuard): return config_key::amnezia_wireguard;
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
QVector<amnezia::Protocol> amnezia::allProtocols()
|
||||
{
|
||||
return QVector<amnezia::Protocol> {
|
||||
Protocol::OpenVpn,
|
||||
Protocol::ShadowSocks,
|
||||
Protocol::Cloak,
|
||||
Protocol::WireGuard
|
||||
};
|
||||
}
|
||||
|
||||
QVector<amnezia::Protocol> amnezia::protocolsForContainer(amnezia::DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::OpenVpn:
|
||||
return { Protocol::OpenVpn };
|
||||
|
||||
case DockerContainer::OpenVpnOverShadowSocks:
|
||||
return { Protocol::OpenVpn, Protocol::ShadowSocks };
|
||||
|
||||
case DockerContainer::OpenVpnOverCloak:
|
||||
return { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak };
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef PROTOCOLS_DEFS_H
|
||||
#define PROTOCOLS_DEFS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
namespace amnezia {
|
||||
namespace config_key {
|
||||
|
||||
// Json config strings
|
||||
constexpr char hostName[] = "hostName";
|
||||
constexpr char userName[] = "userName";
|
||||
constexpr char password[] = "password";
|
||||
constexpr char port[] = "port";
|
||||
constexpr char local_port[] = "local_port";
|
||||
|
||||
constexpr char description[] = "description";
|
||||
|
||||
|
||||
constexpr char containers[] = "containers";
|
||||
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";
|
||||
constexpr char cipher[] = "cipher";
|
||||
constexpr char hash[] = "hash";
|
||||
constexpr char ncp_disable[] = "ncp_disable";
|
||||
|
||||
constexpr char site[] = "site";
|
||||
constexpr char block_outside_dns[] = "block_outside_dns";
|
||||
|
||||
constexpr char subnet_address[] = "subnet_address";
|
||||
constexpr char subnet_mask[] = "subnet_mask";
|
||||
constexpr char subnet_mask_val[] = "subnet_mask_val";
|
||||
|
||||
// proto config keys
|
||||
constexpr char last_config[] = "last_config";
|
||||
|
||||
constexpr char openvpn[] = "openvpn";
|
||||
constexpr char shadowsocks[] = "shadowsocks";
|
||||
constexpr char cloak[] = "cloak";
|
||||
constexpr char wireguard[] = "wireguard";
|
||||
|
||||
// containers config keys
|
||||
constexpr char amnezia_openvpn[] = "amnezia-openvpn";
|
||||
constexpr char amnezia_shadowsocks[] = "amnezia-shadowsocks";
|
||||
constexpr char amnezia_openvpn_cloak[] = "amnezia-openvpn-cloak";
|
||||
constexpr char amnezia_wireguard[] = "amnezia-wireguard";
|
||||
}
|
||||
|
||||
namespace protocols {
|
||||
|
||||
constexpr char vpnDefaultSubnetAddress[] = "10.8.0.0";
|
||||
constexpr char vpnDefaultSubnetMask[] = "255.255.255.0";
|
||||
constexpr char vpnDefaultSubnetMaskVal[] = "24";
|
||||
|
||||
constexpr char UDP[] = "udp"; // case sens
|
||||
constexpr char TCP[] = "tcp";
|
||||
|
||||
namespace openvpn {
|
||||
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
|
||||
constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued";
|
||||
constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key";
|
||||
constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients";
|
||||
constexpr char defaultPort[] = "1194";
|
||||
constexpr char defaultTransportProto[] = "udp";
|
||||
constexpr char defaultCipher[] = "AES-256-GCM";
|
||||
constexpr char defaultHash[] = "SHA512";
|
||||
constexpr bool defaultBlockOutsideDns = true;
|
||||
constexpr bool defaultNcpDisable = false;
|
||||
constexpr char ncpDisableString[] = "ncp-disable";
|
||||
|
||||
}
|
||||
|
||||
namespace shadowsocks {
|
||||
constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key";
|
||||
constexpr char defaultPort[] = "6789";
|
||||
constexpr char defaultLocalProxyPort[] = "8585";
|
||||
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
|
||||
}
|
||||
|
||||
namespace cloak {
|
||||
constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key";
|
||||
constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key";
|
||||
constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key";
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultRedirSite[] = "mail.ru";
|
||||
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace protocols
|
||||
|
||||
enum class Protocol {
|
||||
Any,
|
||||
OpenVpn,
|
||||
ShadowSocks,
|
||||
Cloak,
|
||||
WireGuard
|
||||
};
|
||||
QVector<Protocol> allProtocols();
|
||||
|
||||
Protocol protoFromString(QString proto);
|
||||
QString protoToString(Protocol proto);
|
||||
|
||||
|
||||
enum class DockerContainer {
|
||||
None,
|
||||
OpenVpn,
|
||||
OpenVpnOverShadowSocks,
|
||||
OpenVpnOverCloak,
|
||||
WireGuard
|
||||
};
|
||||
|
||||
DockerContainer containerFromString(const QString &container);
|
||||
QString containerToString(DockerContainer container);
|
||||
|
||||
QVector<Protocol> protocolsForContainer(DockerContainer container);
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::Protocol &p);
|
||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
|
||||
|
||||
#endif // PROTOCOLS_DEFS_H
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "shadowsocksvpnprotocol.h"
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
//#include "communicator.h"
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -14,22 +15,53 @@ ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QJsonObject &configuration,
|
||||
readShadowSocksConfiguration(configuration);
|
||||
}
|
||||
|
||||
ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol()
|
||||
{
|
||||
qDebug() << "ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol";
|
||||
ShadowSocksVpnProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
m_ssProcess.close();
|
||||
}
|
||||
|
||||
ErrorCode ShadowSocksVpnProtocol::start()
|
||||
{
|
||||
qDebug() << "ShadowSocksVpnProtocol::start()";
|
||||
if (Utils::processIsRunning(Utils::executable("ss-local", false))) {
|
||||
Utils::killProcessByName(Utils::executable("ss-local", false));
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
m_shadowSocksCfgFile.setAutoRemove(false);
|
||||
#endif
|
||||
m_shadowSocksCfgFile.open();
|
||||
m_shadowSocksCfgFile.write(QJsonDocument(m_shadowSocksConfig).toJson());
|
||||
m_shadowSocksCfgFile.close();
|
||||
|
||||
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName()
|
||||
<< "--no-delay";
|
||||
|
||||
qDebug().noquote() << "ShadowSocksVpnProtocol::start()"
|
||||
<< shadowSocksExecPath() << args.join(" ");
|
||||
|
||||
m_ssProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
m_ssProcess.setProgram(shadowSocksExecPath());
|
||||
m_ssProcess.setArguments(QStringList() << "-s" << m_shadowSocksConfig.value("server").toString()
|
||||
<< "-p" << QString::number(m_shadowSocksConfig.value("server_port").toInt())
|
||||
<< "-l" << QString::number(m_shadowSocksConfig.value("local_port").toInt())
|
||||
<< "-m" << m_shadowSocksConfig.value("method").toString()
|
||||
<< "-k" << m_shadowSocksConfig.value("password").toString()
|
||||
);
|
||||
m_ssProcess.setArguments(args);
|
||||
|
||||
connect(&m_ssProcess, &QProcess::readyRead, this, [this](){
|
||||
qDebug().noquote() << m_ssProcess.readAll();
|
||||
connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this](){
|
||||
qDebug().noquote() << "ss-local:" << m_ssProcess.readAllStandardOutput();
|
||||
});
|
||||
|
||||
connect(&m_ssProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
|
||||
qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
|
||||
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
|
||||
if (exitStatus != QProcess::NormalExit){
|
||||
emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed);
|
||||
stop();
|
||||
}
|
||||
if (exitCode !=0 ){
|
||||
emit protocolError(amnezia::ErrorCode::InternalError);
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
m_ssProcess.start();
|
||||
@@ -40,7 +72,7 @@ ErrorCode ShadowSocksVpnProtocol::start()
|
||||
|
||||
return OpenVpnProtocol::start();
|
||||
}
|
||||
else return ErrorCode::FailedToStartRemoteProcessError;
|
||||
else return ErrorCode::ShadowSocksExecutableMissing;
|
||||
}
|
||||
|
||||
void ShadowSocksVpnProtocol::stop()
|
||||
@@ -48,10 +80,14 @@ void ShadowSocksVpnProtocol::stop()
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
qDebug() << "ShadowSocksVpnProtocol::stop()";
|
||||
m_ssProcess.close();
|
||||
m_ssProcess.terminate();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Utils::signalCtrl(m_ssProcess.processId(), CTRL_C_EVENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
|
||||
QString ShadowSocksVpnProtocol::shadowSocksExecPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable(QString("ss/ss-local"), true);
|
||||
@@ -60,19 +96,7 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
|
||||
#endif
|
||||
}
|
||||
|
||||
QJsonObject 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 ssConfig;
|
||||
}
|
||||
|
||||
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data()).toObject();
|
||||
m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data).toObject();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,29 @@
|
||||
|
||||
#include "openvpnprotocol.h"
|
||||
#include "QProcess"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
class ShadowSocksVpnProtocol : public OpenVpnProtocol
|
||||
{
|
||||
public:
|
||||
ShadowSocksVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~ShadowSocksVpnProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
static QJsonObject genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocks);
|
||||
|
||||
protected:
|
||||
void readShadowSocksConfiguration(const QJsonObject &configuration);
|
||||
QString shadowSocksExecPath() const;
|
||||
|
||||
protected:
|
||||
QJsonObject m_shadowSocksConfig;
|
||||
|
||||
private:
|
||||
static QString shadowSocksExecPath();
|
||||
|
||||
private:
|
||||
QProcess m_ssProcess;
|
||||
QTemporaryFile m_shadowSocksCfgFile;
|
||||
};
|
||||
|
||||
#endif // SHADOWSOCKSVPNPROTOCOL_H
|
||||
|
||||
@@ -55,6 +55,7 @@ protected:
|
||||
|
||||
ConnectionState m_connectionState;
|
||||
QString m_routeGateway;
|
||||
QString m_vpnAddress;
|
||||
QString m_vpnGateway;
|
||||
|
||||
QJsonObject m_rawConfig;
|
||||
|
||||
@@ -34,11 +34,32 @@
|
||||
<file>images/server_settings.png</file>
|
||||
<file>images/share.png</file>
|
||||
<file>server_scripts/remove_container.sh</file>
|
||||
<file>server_scripts/setup_openvpn_server.sh</file>
|
||||
<file>server_scripts/template_openvpn.ovpn</file>
|
||||
<file>images/background_connected.png</file>
|
||||
<file>server_scripts/setup_shadowsocks_server.sh</file>
|
||||
<file>server_scripts/template_shadowsocks.ovpn</file>
|
||||
<file>server_scripts/setup_firewall.sh</file>
|
||||
<file>server_scripts/setup_host_firewall.sh</file>
|
||||
<file>images/reload.png</file>
|
||||
<file>server_scripts/openvpn_cloak/Dockerfile</file>
|
||||
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/start.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/template.ovpn</file>
|
||||
<file>server_scripts/install_docker.sh</file>
|
||||
<file>server_scripts/build_container.sh</file>
|
||||
<file>server_scripts/prepare_host.sh</file>
|
||||
<file>images/check.png</file>
|
||||
<file>images/uncheck.png</file>
|
||||
<file>images/settings_grey.png</file>
|
||||
<file>images/plus.png</file>
|
||||
<file>server_scripts/check_connection.sh</file>
|
||||
<file>server_scripts/remove_all_containers.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/run_container.sh</file>
|
||||
<file>server_scripts/openvpn/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn/run_container.sh</file>
|
||||
<file>server_scripts/openvpn/template.ovpn</file>
|
||||
<file>server_scripts/openvpn/Dockerfile</file>
|
||||
<file>server_scripts/openvpn/start.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/start.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/template.ovpn</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "runguard.h"
|
||||
#include <QCryptographicHash>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString generateKeyHash( const QString& key, const QString& salt )
|
||||
{
|
||||
QByteArray data;
|
||||
|
||||
data.append( key.toUtf8() );
|
||||
data.append( salt.toUtf8() );
|
||||
data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RunGuard::RunGuard(const QString& key)
|
||||
: key( key )
|
||||
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
|
||||
, sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
|
||||
, sharedMem( sharedmemKey )
|
||||
, memLock( memLockKey, 1 )
|
||||
{
|
||||
qDebug() << "RunGuard::RunGuard key" << key;
|
||||
}
|
||||
|
||||
RunGuard &RunGuard::instance(const QString& key)
|
||||
{
|
||||
static RunGuard s(key);
|
||||
return s;
|
||||
}
|
||||
|
||||
void RunGuard::activate()
|
||||
{
|
||||
memLock.acquire();
|
||||
{
|
||||
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
|
||||
fix.attach();
|
||||
}
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
RunGuard::~RunGuard()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
bool RunGuard::isAnotherRunning() const
|
||||
{
|
||||
if ( sharedMem.isAttached() )
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool isRunning = sharedMem.attach();
|
||||
if ( isRunning )
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
bool RunGuard::tryToRun()
|
||||
{
|
||||
if ( isAnotherRunning() ) // Extra check
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool result = sharedMem.create( sizeof( quint64 ) );
|
||||
memLock.release();
|
||||
if ( !result )
|
||||
{
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RunGuard::release()
|
||||
{
|
||||
memLock.acquire();
|
||||
if ( sharedMem.isAttached() )
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef RUNGUARD_H
|
||||
#define RUNGUARD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* @brief The RunGuard class - The application single instance (via shared memory)
|
||||
*/
|
||||
class RunGuard
|
||||
{
|
||||
|
||||
public:
|
||||
static RunGuard &instance(const QString& key = QString());
|
||||
|
||||
~RunGuard();
|
||||
|
||||
void activate();
|
||||
bool isAnotherRunning() const;
|
||||
bool tryToRun();
|
||||
void release();
|
||||
|
||||
private:
|
||||
RunGuard(const QString& key);
|
||||
Q_DISABLE_COPY( RunGuard )
|
||||
|
||||
const QString key;
|
||||
const QString memLockKey;
|
||||
const QString sharedmemKey;
|
||||
|
||||
mutable QSharedMemory sharedMem;
|
||||
mutable QSystemSemaphore memLock;
|
||||
|
||||
};
|
||||
#endif // RUNGUARD_H
|
||||
@@ -0,0 +1 @@
|
||||
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER
|
||||
@@ -0,0 +1 @@
|
||||
uname -a
|
||||
@@ -0,0 +1,6 @@
|
||||
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi
|
||||
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm update -y -q
|
||||
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm install -y -q curl
|
||||
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then sudo export DEBIAN_FRONTEND=noninteractive; sudo $pm_apt install -y -q docker.io; else sudo $pm_yum install -y -q docker; fi
|
||||
sudo systemctl start docker
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
||||
RUN apk --update upgrade --no-cache
|
||||
|
||||
ENV EASYRSA_BATCH 1
|
||||
ENV PATH="/usr/share/easy-rsa:${PATH}"
|
||||
|
||||
RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
# Tune network
|
||||
RUN echo -e " \n\
|
||||
fs.file-max = 51200 \n\
|
||||
\n\
|
||||
net.core.rmem_max = 67108864 \n\
|
||||
net.core.wmem_max = 67108864 \n\
|
||||
net.core.netdev_max_backlog = 250000 \n\
|
||||
net.core.somaxconn = 4096 \n\
|
||||
\n\
|
||||
net.ipv4.tcp_syncookies = 1 \n\
|
||||
net.ipv4.tcp_tw_reuse = 1 \n\
|
||||
net.ipv4.tcp_tw_recycle = 0 \n\
|
||||
net.ipv4.tcp_fin_timeout = 30 \n\
|
||||
net.ipv4.tcp_keepalive_time = 1200 \n\
|
||||
net.ipv4.ip_local_port_range = 10000 65000 \n\
|
||||
net.ipv4.tcp_max_syn_backlog = 8192 \n\
|
||||
net.ipv4.tcp_max_tw_buckets = 5000 \n\
|
||||
net.ipv4.tcp_fastopen = 3 \n\
|
||||
net.ipv4.tcp_mem = 25600 51200 102400 \n\
|
||||
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
|
||||
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
|
||||
net.ipv4.tcp_mtu_probing = 1 \n\
|
||||
net.ipv4.tcp_congestion_control = hybla \n\
|
||||
# for low-latency network, use cubic instead \n\
|
||||
# net.ipv4.tcp_congestion_control = cubic \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
|
||||
mkdir -p /etc/security && \
|
||||
echo -e " \n\
|
||||
* soft nofile 51200 \n\
|
||||
* hard nofile 51200 \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
|
||||
CMD [ "" ]
|
||||
@@ -0,0 +1,27 @@
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c '\
|
||||
echo -e "\
|
||||
port $OPENVPN_PORT \\n\
|
||||
proto $OPENVPN_TRANSPORT_PROTO \\n\
|
||||
dev tun \\n\
|
||||
ca /opt/amnezia/openvpn/ca.crt \\n\
|
||||
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
|
||||
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
|
||||
dh /opt/amnezia/openvpn/dh.pem \\n\
|
||||
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
|
||||
ifconfig-pool-persist ipp.txt \\n\
|
||||
duplicate-cn \\n\
|
||||
keepalive 10 120 \\n\
|
||||
$OPENVPN_NCP_DISABLE \\n\
|
||||
cipher $OPENVPN_CIPHER \\n\
|
||||
data-ciphers $OPENVPN_CIPHER \\n\
|
||||
auth $OPENVPN_HASH \\n\
|
||||
user nobody \\n\
|
||||
group nobody \\n\
|
||||
persist-key \\n\
|
||||
persist-tun \\n\
|
||||
status openvpn-status.log \\n\
|
||||
verb 1 \\n\
|
||||
tls-server \\n\
|
||||
tls-version-min 1.2 \\n\
|
||||
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf'
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
# Prevent to route packets outside of the container in case if server behind of the NAT
|
||||
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
|
||||
|
||||
# OpenVPN config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
|
||||
cd /opt/amnezia/openvpn && easyrsa init-pki; \
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||
|
||||
echo "Container startup"
|
||||
|
||||
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
|
||||
|
||||
# Allow traffic on the TUN interface.
|
||||
iptables -A INPUT -i tun0 -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -j ACCEPT
|
||||
iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
|
||||
# start daemons if configured
|
||||
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
|
||||
|
||||
tail -f /dev/null
|
||||
@@ -1,12 +1,13 @@
|
||||
client
|
||||
dev tun
|
||||
proto $PROTO
|
||||
proto $OPENVPN_TRANSPORT_PROTO
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
cipher AES-256-GCM
|
||||
auth SHA512
|
||||
$OPENVPN_NCP_DISABLE
|
||||
cipher $OPENVPN_CIPHER
|
||||
auth $OPENVPN_HASH
|
||||
verb 3
|
||||
tls-client
|
||||
tls-version-min 1.2
|
||||
@@ -18,17 +19,17 @@ dhcp-option DNS $PRIMARY_DNS
|
||||
dhcp-option DNS $SECONDARY_DNS
|
||||
block-outside-dns
|
||||
|
||||
remote $REMOTE_HOST $REMOTE_PORT
|
||||
remote $REMOTE_HOST $OPENVPN_PORT
|
||||
|
||||
<ca>
|
||||
$CA_CERT
|
||||
$OPENVPN_CA_CERT
|
||||
</ca>
|
||||
<cert>
|
||||
$CLIENT_CERT
|
||||
$OPENVPN_CLIENT_CERT
|
||||
</cert>
|
||||
<key>
|
||||
$PRIV_KEY
|
||||
$OPENVPN_PRIV_KEY
|
||||
</key>
|
||||
<tls-auth>
|
||||
$TA_KEY
|
||||
$OPENVPN_TA_KEY
|
||||
</tls-auth>
|
||||
@@ -0,0 +1,52 @@
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
||||
RUN apk --update upgrade --no-cache
|
||||
|
||||
ENV EASYRSA_BATCH 1
|
||||
ENV PATH="/usr/share/easy-rsa:${PATH}"
|
||||
|
||||
RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN curl -L https://github.com/cbeuw/Cloak/releases/download/v2.5.3/ck-server-linux-amd64-v2.5.3 > /usr/bin/ck-server
|
||||
RUN chmod a+x /usr/bin/ck-server
|
||||
|
||||
# Tune network
|
||||
RUN echo -e " \n\
|
||||
fs.file-max = 51200 \n\
|
||||
\n\
|
||||
net.core.rmem_max = 67108864 \n\
|
||||
net.core.wmem_max = 67108864 \n\
|
||||
net.core.netdev_max_backlog = 250000 \n\
|
||||
net.core.somaxconn = 4096 \n\
|
||||
\n\
|
||||
net.ipv4.tcp_syncookies = 1 \n\
|
||||
net.ipv4.tcp_tw_reuse = 1 \n\
|
||||
net.ipv4.tcp_tw_recycle = 0 \n\
|
||||
net.ipv4.tcp_fin_timeout = 30 \n\
|
||||
net.ipv4.tcp_keepalive_time = 1200 \n\
|
||||
net.ipv4.ip_local_port_range = 10000 65000 \n\
|
||||
net.ipv4.tcp_max_syn_backlog = 8192 \n\
|
||||
net.ipv4.tcp_max_tw_buckets = 5000 \n\
|
||||
net.ipv4.tcp_fastopen = 3 \n\
|
||||
net.ipv4.tcp_mem = 25600 51200 102400 \n\
|
||||
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
|
||||
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
|
||||
net.ipv4.tcp_mtu_probing = 1 \n\
|
||||
net.ipv4.tcp_congestion_control = hybla \n\
|
||||
# for low-latency network, use cubic instead \n\
|
||||
# net.ipv4.tcp_congestion_control = cubic \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
|
||||
mkdir -p /etc/security && \
|
||||
echo -e " \n\
|
||||
* soft nofile 51200 \n\
|
||||
* hard nofile 51200 \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
|
||||
CMD [ "" ]
|
||||
@@ -0,0 +1,53 @@
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c '\
|
||||
echo -e "\
|
||||
port $OPENVPN_PORT \\n\
|
||||
proto $OPENVPN_TRANSPORT_PROTO \\n\
|
||||
dev tun \\n\
|
||||
ca /opt/amnezia/openvpn/ca.crt \\n\
|
||||
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
|
||||
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
|
||||
dh /opt/amnezia/openvpn/dh.pem \\n\
|
||||
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
|
||||
ifconfig-pool-persist ipp.txt \\n\
|
||||
duplicate-cn \\n\
|
||||
keepalive 10 120 \\n\
|
||||
$OPENVPN_NCP_DISABLE \\n\
|
||||
cipher $OPENVPN_CIPHER \\n\
|
||||
data-ciphers $OPENVPN_CIPHER \\n\
|
||||
auth $OPENVPN_HASH \\n\
|
||||
user nobody \\n\
|
||||
group nobody \\n\
|
||||
persist-key \\n\
|
||||
persist-tun \\n\
|
||||
status openvpn-status.log \\n\
|
||||
verb 1 \\n\
|
||||
tls-server \\n\
|
||||
tls-version-min 1.2 \\n\
|
||||
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf'
|
||||
|
||||
# Cloak config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c '\
|
||||
mkdir -p /opt/amnezia/cloak; \
|
||||
cd /opt/amnezia/cloak || exit 1; \
|
||||
CLOAK_ADMIN_UID=$(ck-server -u) && echo $CLOAK_ADMIN_UID > /opt/amnezia/cloak/cloak_admin_uid.key; \
|
||||
CLOAK_BYPASS_UID=$(ck-server -u) && echo $CLOAK_BYPASS_UID > /opt/amnezia/cloak/cloak_bypass_uid.key; \
|
||||
IFS=, read CLOAK_PUBLIC_KEY CLOAK_PRIVATE_KEY <<<$(ck-server -k); \
|
||||
echo $CLOAK_PUBLIC_KEY > /opt/amnezia/cloak/cloak_public.key; \
|
||||
echo $CLOAK_PRIVATE_KEY > /opt/amnezia/cloak/cloak_private.key; \
|
||||
echo -e "{\\n\
|
||||
\"ProxyBook\": {\\n\
|
||||
\"openvpn\": [\\n\
|
||||
\"$OPENVPN_TRANSPORT_PROTO\",\\n\
|
||||
\"localhost:$OPENVPN_PORT\"\\n\
|
||||
]\\n\
|
||||
},\\n\
|
||||
\"BypassUID\": [\\n\
|
||||
\"$CLOAK_BYPASS_UID\"\\n\
|
||||
],\\n\
|
||||
\"BindAddr\":[\":443\"],\\n\
|
||||
\"RedirAddr\": \"$FAKE_WEB_SITE_ADDRESS\",\\n\
|
||||
\"PrivateKey\": \"$CLOAK_PRIVATE_KEY\",\\n\
|
||||
\"AdminUID\": \"$CLOAK_ADMIN_UID\",\\n\
|
||||
\"DatabasePath\": \"userinfo.db\",\\n\
|
||||
\"StreamTimeout\": 300\\n\
|
||||
}" >/opt/amnezia/cloak/ck-config.json'
|
||||
@@ -0,0 +1,17 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $CLOAK_SERVER_PORT:443/tcp --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
# Prevent to route packets outside of the container in case if server behind of the NAT
|
||||
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
|
||||
|
||||
# OpenVPN config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
|
||||
cd /opt/amnezia/openvpn && easyrsa init-pki; \
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||
|
||||
echo "Container startup"
|
||||
|
||||
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
|
||||
|
||||
# Allow traffic on the TUN interface.
|
||||
iptables -A INPUT -i tun0 -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -j ACCEPT
|
||||
iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
killall -KILL ck-server
|
||||
|
||||
# start daemons if configured
|
||||
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
|
||||
if [ -f /opt/amnezia/cloak/ck-config.json ]; then (ck-server -c /opt/amnezia/cloak/ck-config.json &); fi
|
||||
|
||||
tail -f /dev/null
|
||||
@@ -1,12 +1,13 @@
|
||||
client
|
||||
dev tun
|
||||
proto $PROTO
|
||||
proto $OPENVPN_TRANSPORT_PROTO
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
cipher AES-256-GCM
|
||||
auth SHA512
|
||||
$OPENVPN_NCP_DISABLE
|
||||
cipher $OPENVPN_CIPHER
|
||||
auth $OPENVPN_HASH
|
||||
verb 3
|
||||
tls-client
|
||||
tls-version-min 1.2
|
||||
@@ -18,19 +19,18 @@ dhcp-option DNS $PRIMARY_DNS
|
||||
dhcp-option DNS $SECONDARY_DNS
|
||||
block-outside-dns
|
||||
|
||||
socks-proxy 127.0.0.1 $LOCAL_PROXY_PORT
|
||||
route $REMOTE_HOST 255.255.255.255 net_gateway
|
||||
remote $REMOTE_HOST $REMOTE_PORT
|
||||
remote 127.0.0.1 1194
|
||||
|
||||
<ca>
|
||||
$CA_CERT
|
||||
$OPENVPN_CA_CERT
|
||||
</ca>
|
||||
<cert>
|
||||
$CLIENT_CERT
|
||||
$OPENVPN_CLIENT_CERT
|
||||
</cert>
|
||||
<key>
|
||||
$PRIV_KEY
|
||||
$OPENVPN_PRIV_KEY
|
||||
</key>
|
||||
<tls-auth>
|
||||
$TA_KEY
|
||||
$OPENVPN_TA_KEY
|
||||
</tls-auth>
|
||||
@@ -0,0 +1,53 @@
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
|
||||
RUN apk --update upgrade --no-cache
|
||||
|
||||
ENV EASYRSA_BATCH 1
|
||||
ENV PATH="/usr/share/easy-rsa:${PATH}"
|
||||
|
||||
RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.10.9/shadowsocks-v1.10.9.x86_64-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
|
||||
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
|
||||
RUN chmod a+x /usr/bin/ssserver
|
||||
|
||||
# Tune network
|
||||
RUN echo -e " \n\
|
||||
fs.file-max = 51200 \n\
|
||||
\n\
|
||||
net.core.rmem_max = 67108864 \n\
|
||||
net.core.wmem_max = 67108864 \n\
|
||||
net.core.netdev_max_backlog = 250000 \n\
|
||||
net.core.somaxconn = 4096 \n\
|
||||
\n\
|
||||
net.ipv4.tcp_syncookies = 1 \n\
|
||||
net.ipv4.tcp_tw_reuse = 1 \n\
|
||||
net.ipv4.tcp_tw_recycle = 0 \n\
|
||||
net.ipv4.tcp_fin_timeout = 30 \n\
|
||||
net.ipv4.tcp_keepalive_time = 1200 \n\
|
||||
net.ipv4.ip_local_port_range = 10000 65000 \n\
|
||||
net.ipv4.tcp_max_syn_backlog = 8192 \n\
|
||||
net.ipv4.tcp_max_tw_buckets = 5000 \n\
|
||||
net.ipv4.tcp_fastopen = 3 \n\
|
||||
net.ipv4.tcp_mem = 25600 51200 102400 \n\
|
||||
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
|
||||
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
|
||||
net.ipv4.tcp_mtu_probing = 1 \n\
|
||||
net.ipv4.tcp_congestion_control = hybla \n\
|
||||
# for low-latency network, use cubic instead \n\
|
||||
# net.ipv4.tcp_congestion_control = cubic \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
|
||||
mkdir -p /etc/security && \
|
||||
echo -e " \n\
|
||||
* soft nofile 51200 \n\
|
||||
* hard nofile 51200 \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
|
||||
CMD [ "" ]
|
||||
@@ -0,0 +1,40 @@
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c '\
|
||||
echo -e "\
|
||||
port $OPENVPN_PORT \\n\
|
||||
proto tcp \\n\
|
||||
dev tun \\n\
|
||||
ca /opt/amnezia/openvpn/ca.crt \\n\
|
||||
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
|
||||
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
|
||||
dh /opt/amnezia/openvpn/dh.pem \\n\
|
||||
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
|
||||
ifconfig-pool-persist ipp.txt \\n\
|
||||
duplicate-cn \\n\
|
||||
keepalive 10 120 \\n\
|
||||
$OPENVPN_NCP_DISABLE \\n\
|
||||
cipher $OPENVPN_CIPHER \\n\
|
||||
data-ciphers $OPENVPN_CIPHER \\n\
|
||||
auth $OPENVPN_HASH \\n\
|
||||
user nobody \\n\
|
||||
group nobody \\n\
|
||||
persist-key \\n\
|
||||
persist-tun \\n\
|
||||
status openvpn-status.log \\n\
|
||||
verb 1 \\n\
|
||||
tls-server \\n\
|
||||
tls-version-min 1.2 \\n\
|
||||
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf'
|
||||
|
||||
# Cloak config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c '\
|
||||
mkdir -p /opt/amnezia/shadowsocks; \
|
||||
cd /opt/amnezia/shadowsocks || exit 1; \
|
||||
SHADOWSOCKS_PASSWORD=$(openssl rand -base64 32 | tr "=" "A" | tr "+" "A" | tr "/" "A") && echo $SHADOWSOCKS_PASSWORD > /opt/amnezia/shadowsocks/shadowsocks.key; \
|
||||
echo -e "{\\n\
|
||||
\"local_port\": 8585,\\n\
|
||||
\"method\": \"$SHADOWSOCKS_CIPHER\",\\n\
|
||||
\"password\": \"$SHADOWSOCKS_PASSWORD\",\\n\
|
||||
\"server\": \"0.0.0.0\",\\n\
|
||||
\"server_port\": $SHADOWSOCKS_SERVER_PORT,\\n\
|
||||
\"timeout\": 60\\n\
|
||||
}" >/opt/amnezia/shadowsocks/ss-config.json'
|
||||
@@ -0,0 +1,17 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
# Prevent to route packets outside of the container in case if server behind of the NAT
|
||||
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
|
||||
|
||||
# OpenVPN config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
|
||||
cd /opt/amnezia/openvpn && easyrsa init-pki; \
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||
|
||||
echo "Container startup"
|
||||
|
||||
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
|
||||
|
||||
# Allow traffic on the TUN interface.
|
||||
iptables -A INPUT -i tun0 -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -j ACCEPT
|
||||
iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
killall -KILL ssserver
|
||||
|
||||
# start daemons if configured
|
||||
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
|
||||
if [ -f /opt/amnezia/shadowsocks/ss-config.json ]; then (ssserver -c /opt/amnezia/shadowsocks/ss-config.json &); fi
|
||||
tail -f /dev/null
|
||||
@@ -0,0 +1,37 @@
|
||||
client
|
||||
dev tun
|
||||
proto tcp
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
$OPENVPN_NCP_DISABLE
|
||||
cipher $OPENVPN_CIPHER
|
||||
auth $OPENVPN_HASH
|
||||
verb 3
|
||||
tls-client
|
||||
tls-version-min 1.2
|
||||
key-direction 1
|
||||
remote-cert-tls server
|
||||
redirect-gateway def1 bypass-dhcp
|
||||
|
||||
dhcp-option DNS $PRIMARY_DNS
|
||||
dhcp-option DNS $SECONDARY_DNS
|
||||
block-outside-dns
|
||||
|
||||
socks-proxy 127.0.0.1 $SHADOWSOCKS_LOCAL_PORT
|
||||
route $REMOTE_HOST 255.255.255.255 net_gateway
|
||||
remote $REMOTE_HOST $OPENVPN_PORT
|
||||
|
||||
<ca>
|
||||
$OPENVPN_CA_CERT
|
||||
</ca>
|
||||
<cert>
|
||||
$OPENVPN_CLIENT_CERT
|
||||
</cert>
|
||||
<key>
|
||||
$OPENVPN_PRIV_KEY
|
||||
</key>
|
||||
<tls-auth>
|
||||
$OPENVPN_TA_KEY
|
||||
</tls-auth>
|
||||
@@ -0,0 +1,3 @@
|
||||
CUR_USER=$(whoami);\
|
||||
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
||||
sudo chown $CUR_USER $DOCKERFILE_FOLDER
|
||||
@@ -0,0 +1,3 @@
|
||||
sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker stop
|
||||
sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker rm
|
||||
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi
|
||||
@@ -1,2 +1,2 @@
|
||||
docker stop $CONTAINER_NAME
|
||||
docker rm -f $CONTAINER_NAME
|
||||
sudo docker stop $CONTAINER_NAME
|
||||
sudo docker rm -f $CONTAINER_NAME
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -P FORWARD ACCEPT
|
||||
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
|
||||
@@ -0,0 +1,31 @@
|
||||
sudo sysctl -w net.ipv4.ip_forward=1
|
||||
sudo iptables -C INPUT -p icmp --icmp-type echo-request -j DROP || sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
|
||||
|
||||
#sudo iptables -P FORWARD ACCEPT
|
||||
sudo iptables -C FORWARD -j DOCKER-USER || sudo iptables -A FORWARD -j DOCKER-USER
|
||||
sudo iptables -C FORWARD -j DOCKER-ISOLATION-STAGE-1 || sudo iptables -A FORWARD -j DOCKER-ISOLATION-STAGE-1; \
|
||||
sudo iptables -C FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT || sudo iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; \
|
||||
sudo iptables -C FORWARD -o docker0 -j DOCKER || sudo iptables -A FORWARD -o docker0 -j DOCKER; \
|
||||
sudo iptables -C FORWARD -i docker0 ! -o docker0 -j ACCEPT || sudo iptables -A FORWARD -i docker0 ! -o docker0 -j ACCEPT; \
|
||||
sudo iptables -C FORWARD -i docker0 -o docker0 -j ACCEPT || sudo iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT
|
||||
|
||||
# Tuning network
|
||||
sudo sysctl fs.file-max=51200; \
|
||||
sudo sysctl net.core.rmem_max=67108864; \
|
||||
sudo sysctl net.core.wmem_max=67108864; \
|
||||
sudo sysctl net.core.netdev_max_backlog=250000; \
|
||||
sudo sysctl net.core.somaxconn=4096; \
|
||||
sudo sysctl net.ipv4.tcp_syncookies=1; \
|
||||
sudo sysctl net.ipv4.tcp_tw_reuse=1; \
|
||||
sudo sysctl net.ipv4.tcp_tw_recycle=0; \
|
||||
sudo sysctl net.ipv4.tcp_fin_timeout=30; \
|
||||
sudo sysctl net.ipv4.tcp_keepalive_time=1200; \
|
||||
sudo sysctl net.ipv4.ip_local_port_range="10000 65000"; \
|
||||
sudo sysctl net.ipv4.tcp_max_syn_backlog=8192; \
|
||||
sudo sysctl net.ipv4.tcp_max_tw_buckets=5000; \
|
||||
sudo sysctl net.ipv4.tcp_fastopen=3; \
|
||||
sudo sysctl net.ipv4.tcp_mem="25600 51200 102400"; \
|
||||
sudo sysctl net.ipv4.tcp_rmem="4096 87380 67108864"; \
|
||||
sudo sysctl net.ipv4.tcp_wmem="4096 65536 67108864"; \
|
||||
sudo sysctl net.ipv4.tcp_mtu_probing=1; \
|
||||
sudo sysctl net.ipv4.tcp_congestion_control=hybla
|
||||
@@ -1,24 +0,0 @@
|
||||
#CONTAINER_NAME=... this var will be set in ServerController
|
||||
|
||||
apt-get update
|
||||
|
||||
iptables -P FORWARD ACCEPT
|
||||
|
||||
apt install -y docker.io curl
|
||||
systemctl start docker
|
||||
|
||||
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 $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 -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf"
|
||||
@@ -1,24 +0,0 @@
|
||||
#CONTAINER_NAME=... this var will be set in ServerController
|
||||
|
||||
apt-get update
|
||||
|
||||
iptables -P FORWARD ACCEPT
|
||||
|
||||
apt install -y docker.io curl
|
||||
systemctl start docker
|
||||
|
||||
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 -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf"
|
||||
@@ -1,33 +1,236 @@
|
||||
|
||||
#include "defines.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
const char Settings::cloudFlareNs1[] = "1.1.1.1";
|
||||
const char Settings::cloudFlareNs2[] = "1.0.0.1";
|
||||
|
||||
Settings::Settings(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_settings (ORGANIZATION_NAME, APPLICATION_NAME, this)
|
||||
{
|
||||
// Import old settings
|
||||
if (serversCount() == 0) {
|
||||
QString user = m_settings.value("Server/userName").toString();
|
||||
QString password = m_settings.value("Server/password").toString();
|
||||
QString serverName = m_settings.value("Server/serverName").toString();
|
||||
int port = m_settings.value("Server/serverPort").toInt();
|
||||
|
||||
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){
|
||||
QJsonObject server;
|
||||
server.insert(config_key::userName, user);
|
||||
server.insert(config_key::password, password);
|
||||
server.insert(config_key::hostName, serverName);
|
||||
server.insert(config_key::port, port);
|
||||
server.insert(config_key::description, tr("Server #1"));
|
||||
|
||||
addServer(server);
|
||||
|
||||
m_settings.remove("Server/userName");
|
||||
m_settings.remove("Server/password");
|
||||
m_settings.remove("Server/serverName");
|
||||
m_settings.remove("Server/serverPort");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::haveAuthData() const
|
||||
int Settings::serversCount() const
|
||||
{
|
||||
return (!serverName().isEmpty() && !userName().isEmpty() && !password().isEmpty());
|
||||
return serversArray().size();
|
||||
}
|
||||
|
||||
void Settings::setServerCredentials(const ServerCredentials &credentials)
|
||||
QJsonObject Settings::server(int index) const
|
||||
{
|
||||
setServerName(credentials.hostName);
|
||||
setServerPort(credentials.port);
|
||||
setUserName(credentials.userName);
|
||||
setPassword(credentials.password);
|
||||
const QJsonArray &servers = serversArray();
|
||||
if (index >= servers.size()) return QJsonObject();
|
||||
|
||||
return servers.at(index).toObject();
|
||||
}
|
||||
|
||||
ServerCredentials Settings::serverCredentials()
|
||||
void Settings::addServer(const QJsonObject &server)
|
||||
{
|
||||
QJsonArray servers = serversArray();
|
||||
servers.append(server);
|
||||
setServersArray(servers);
|
||||
}
|
||||
|
||||
void Settings::removeServer(int index)
|
||||
{
|
||||
QJsonArray servers = serversArray();
|
||||
if (index >= servers.size()) return;
|
||||
|
||||
servers.removeAt(index);
|
||||
setServersArray(servers);
|
||||
}
|
||||
|
||||
bool Settings::editServer(int index, const QJsonObject &server)
|
||||
{
|
||||
QJsonArray servers = serversArray();
|
||||
if (index >= servers.size()) return false;
|
||||
|
||||
servers.replace(index, server);
|
||||
setServersArray(servers);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Settings::setDefaultContainer(int serverIndex, DockerContainer container)
|
||||
{
|
||||
QJsonObject s = server(serverIndex);
|
||||
s.insert(config_key::defaultContainer, containerToString(container));
|
||||
editServer(serverIndex, s);
|
||||
}
|
||||
|
||||
DockerContainer Settings::defaultContainer(int serverIndex) const
|
||||
{
|
||||
return containerFromString(defaultContainerName(serverIndex));
|
||||
}
|
||||
|
||||
QString Settings::defaultContainerName(int serverIndex) const
|
||||
{
|
||||
QString name = server(serverIndex).value(config_key::defaultContainer).toString();
|
||||
if (name.isEmpty()) {
|
||||
return containerToString(DockerContainer::None);
|
||||
}
|
||||
else return name;
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QJsonObject> Settings::containers(int serverIndex) const
|
||||
{
|
||||
const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray();
|
||||
|
||||
QMap<DockerContainer, QJsonObject> containersMap;
|
||||
for (const QJsonValue &val : containers) {
|
||||
containersMap.insert(containerFromString(val.toObject().value(config_key::container).toString()), val.toObject());
|
||||
}
|
||||
|
||||
return containersMap;
|
||||
}
|
||||
|
||||
void Settings::setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers)
|
||||
{
|
||||
QJsonObject s = server(serverIndex);
|
||||
QJsonArray c;
|
||||
for (const QJsonObject &o: containers) {
|
||||
c.append(o);
|
||||
}
|
||||
s.insert(config_key::containers, c);
|
||||
editServer(serverIndex, s);
|
||||
}
|
||||
|
||||
|
||||
QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container)
|
||||
{
|
||||
if (container == DockerContainer::None) return QJsonObject();
|
||||
return containers(serverIndex).value(container);
|
||||
}
|
||||
|
||||
void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
if (container == DockerContainer::None) {
|
||||
qCritical() << "Settings::setContainerConfig trying to set config for container == DockerContainer::None";
|
||||
return;
|
||||
}
|
||||
auto c = containers(serverIndex);
|
||||
c[container] = config;
|
||||
c[container][config_key::container] = containerToString(container);
|
||||
setContainers(serverIndex, c);
|
||||
}
|
||||
|
||||
void Settings::removeContainerConfig(int serverIndex, DockerContainer container)
|
||||
{
|
||||
if (container == DockerContainer::None) {
|
||||
qCritical() << "Settings::removeContainerConfig trying to remove config for container == DockerContainer::None";
|
||||
return;
|
||||
}
|
||||
|
||||
auto c = containers(serverIndex);
|
||||
c.remove(container);
|
||||
setContainers(serverIndex, c);
|
||||
}
|
||||
|
||||
QJsonObject Settings::protocolConfig(int serverIndex, DockerContainer container, Protocol proto)
|
||||
{
|
||||
const QJsonObject &c = containerConfig(serverIndex, container);
|
||||
return c.value(protoToString(proto)).toObject();
|
||||
}
|
||||
|
||||
void Settings::setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config)
|
||||
{
|
||||
QJsonObject c = containerConfig(serverIndex, container);
|
||||
c.insert(protoToString(proto), config);
|
||||
|
||||
setContainerConfig(serverIndex, container, c);
|
||||
}
|
||||
|
||||
void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto)
|
||||
{
|
||||
if (proto == Protocol::Any) {
|
||||
for (Protocol p: { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak, Protocol::WireGuard}) {
|
||||
clearLastConnectionConfig(serverIndex, container, p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject c = protocolConfig(serverIndex, container, proto);
|
||||
c.remove(config_key::last_config);
|
||||
setProtocolConfig(serverIndex, container, proto, c);
|
||||
qDebug() << "Settings::clearLastConnectionConfig for" << serverIndex << container << proto;
|
||||
}
|
||||
|
||||
bool Settings::haveAuthData(int serverIndex) const
|
||||
{
|
||||
if (serverIndex < 0) return false;
|
||||
ServerCredentials cred = serverCredentials(serverIndex);
|
||||
return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty());
|
||||
}
|
||||
|
||||
QString Settings::nextAvailableServerName() const
|
||||
{
|
||||
int i = 0;
|
||||
bool nameExist = false;
|
||||
|
||||
do {
|
||||
i++;
|
||||
nameExist = false;
|
||||
for (const QJsonValue &server: serversArray()) {
|
||||
if (server.toObject().value(config_key::description).toString() == tr("Server") + " " + QString::number(i)) {
|
||||
nameExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (nameExist);
|
||||
|
||||
return tr("Server") + " " + QString::number(i);
|
||||
}
|
||||
|
||||
QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); }
|
||||
|
||||
QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); }
|
||||
|
||||
//void Settings::setServerCredentials(const ServerCredentials &credentials)
|
||||
//{
|
||||
// setServerName(credentials.hostName);
|
||||
// setServerPort(credentials.port);
|
||||
// setUserName(credentials.userName);
|
||||
// setPassword(credentials.password);
|
||||
//}
|
||||
|
||||
ServerCredentials Settings::defaultServerCredentials() const
|
||||
{
|
||||
return serverCredentials(defaultServerIndex());
|
||||
}
|
||||
|
||||
ServerCredentials Settings::serverCredentials(int index) const
|
||||
{
|
||||
const QJsonObject &s = server(index);
|
||||
|
||||
ServerCredentials credentials;
|
||||
credentials.hostName = serverName();
|
||||
credentials.userName = userName();
|
||||
credentials.password = password();
|
||||
credentials.port = serverPort();
|
||||
credentials.hostName = s.value(config_key::hostName).toString();
|
||||
credentials.userName = s.value(config_key::userName).toString();
|
||||
credentials.password = s.value(config_key::password).toString();
|
||||
credentials.port = s.value(config_key::port).toInt();
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
@@ -18,21 +23,59 @@ class Settings : public QObject
|
||||
public:
|
||||
explicit Settings(QObject* parent = nullptr);
|
||||
|
||||
QString userName() const { return m_settings.value("Server/userName", QString()).toString(); }
|
||||
void setUserName(const QString& login) { m_settings.setValue("Server/userName", login); }
|
||||
|
||||
QString password() const { return m_settings.value("Server/password", QString()).toString(); }
|
||||
void setPassword(const QString& password) { m_settings.setValue("Server/password", password); }
|
||||
// QString userName() const { return m_settings.value("Server/userName", QString()).toString(); }
|
||||
// void setUserName(const QString& login) { m_settings.setValue("Server/userName", login); }
|
||||
|
||||
QString serverName() const { return m_settings.value("Server/serverName", QString()).toString(); }
|
||||
void setServerName(const QString& serverName) { m_settings.setValue("Server/serverName", serverName); }
|
||||
// QString password() const { return m_settings.value("Server/password", QString()).toString(); }
|
||||
// void setPassword(const QString& password) { m_settings.setValue("Server/password", password); }
|
||||
|
||||
int serverPort() const { return m_settings.value("Server/serverPort", 22).toInt(); }
|
||||
void setServerPort(int serverPort = 22) { m_settings.setValue("Server/serverPort", serverPort); }
|
||||
// QString serverName() const { return m_settings.value("Server/serverName", QString()).toString(); }
|
||||
// void setServerName(const QString& serverName) { m_settings.setValue("Server/serverName", serverName); }
|
||||
|
||||
ServerCredentials serverCredentials();
|
||||
void setServerCredentials(const ServerCredentials &credentials);
|
||||
bool haveAuthData() const;
|
||||
// int serverPort() const { return m_settings.value("Server/serverPort", 22).toInt(); }
|
||||
// void setServerPort(int serverPort = 22) { m_settings.setValue("Server/serverPort", serverPort); }
|
||||
|
||||
ServerCredentials defaultServerCredentials() const;
|
||||
ServerCredentials serverCredentials(int index) const;
|
||||
//void setServerCredentials(const ServerCredentials &credentials);
|
||||
|
||||
QJsonArray serversArray() const { return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); }
|
||||
void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); }
|
||||
|
||||
// Servers section
|
||||
int serversCount() const;
|
||||
QJsonObject server(int index) const;
|
||||
void addServer(const QJsonObject &server);
|
||||
void removeServer(int index);
|
||||
bool editServer(int index, const QJsonObject &server);
|
||||
|
||||
int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); }
|
||||
void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); }
|
||||
QJsonObject defaultServer() const { return server(defaultServerIndex()); }
|
||||
|
||||
void setDefaultContainer(int serverIndex, DockerContainer container);
|
||||
DockerContainer defaultContainer(int serverIndex) const;
|
||||
QString defaultContainerName(int serverIndex) const;
|
||||
|
||||
QMap<DockerContainer, QJsonObject> containers(int serverIndex) const;
|
||||
void setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers);
|
||||
|
||||
QJsonObject containerConfig(int serverIndex, DockerContainer container);
|
||||
void setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config);
|
||||
void removeContainerConfig(int serverIndex, DockerContainer container);
|
||||
|
||||
QJsonObject protocolConfig(int serverIndex, DockerContainer container, Protocol proto);
|
||||
void setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config);
|
||||
|
||||
void clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto = Protocol::Any);
|
||||
|
||||
bool haveAuthData(int serverIndex) const;
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
// App settings section
|
||||
bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", QString()).toBool(); }
|
||||
void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); }
|
||||
|
||||
bool customRouting() const { return m_settings.value("Conf/customRouting", false).toBool(); }
|
||||
void setCustomRouting(bool customRouting) { m_settings.setValue("Conf/customRouting", customRouting); }
|
||||
@@ -45,8 +88,8 @@ public:
|
||||
QStringList customIps() { return m_settings.value("Conf/customIps").toStringList(); }
|
||||
void setCustomIps(const QStringList &customIps) { m_settings.setValue("Conf/customIps", customIps); }
|
||||
|
||||
QString primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1()).toString(); }
|
||||
QString secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2()).toString(); }
|
||||
QString primaryDns() const;
|
||||
QString secondaryDns() const;
|
||||
|
||||
//QString primaryDns() const { return m_primaryDns; }
|
||||
void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); }
|
||||
@@ -54,14 +97,20 @@ public:
|
||||
//QString secondaryDns() const { return m_secondaryDns; }
|
||||
void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); }
|
||||
|
||||
QString cloudFlareNs1() const { return "1.1.1.1"; }
|
||||
QString cloudFlareNs2() const { return "1.0.0.1"; }
|
||||
static const char cloudFlareNs1[];
|
||||
static const char cloudFlareNs2[];
|
||||
|
||||
static constexpr char openNicNs5[] = "94.103.153.176";
|
||||
static constexpr char openNicNs13[] = "144.76.103.143";
|
||||
|
||||
|
||||
public:
|
||||
|
||||
|
||||
QString openNicNs5() const { return "94.103.153.176"; }
|
||||
QString openNicNs13() const { return "144.76.103.143"; }
|
||||
|
||||
private:
|
||||
QSettings m_settings;
|
||||
|
||||
};
|
||||
|
||||
#endif // SETTINGS_H
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "SlidingStackedWidget.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
|
||||
SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
|
||||
: QStackedWidget(parent)
|
||||
{
|
||||
@@ -28,6 +30,14 @@ SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
|
||||
m_wrap = false;
|
||||
m_pnow = QPoint(0,0);
|
||||
m_active = false;
|
||||
|
||||
animnow = new QPropertyAnimation();
|
||||
animnext = new QPropertyAnimation();
|
||||
|
||||
animgroup = new QParallelAnimationGroup;
|
||||
|
||||
animgroup->addAnimation(animnow);
|
||||
animgroup->addAnimation(animnext);
|
||||
}
|
||||
|
||||
SlidingStackedWidget::~SlidingStackedWidget() {
|
||||
@@ -90,6 +100,21 @@ void SlidingStackedWidget::slideInWidget(QWidget *widget, SlidingStackedWidget::
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SlidingStackedWidget::isAnimationRunning()
|
||||
{
|
||||
return animgroup->state() == QAnimationGroup::Running;
|
||||
}
|
||||
|
||||
void SlidingStackedWidget::waitForAnimation()
|
||||
{
|
||||
if (!isAnimationRunning()) return;
|
||||
|
||||
qDebug() << "Wait for stacked widget animation";
|
||||
QEventLoop l;
|
||||
connect(animgroup, &QParallelAnimationGroup::finished, &l, &QEventLoop::quit);
|
||||
l.exec();
|
||||
}
|
||||
|
||||
void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction direction) {
|
||||
if (m_active) {
|
||||
return;
|
||||
@@ -149,22 +174,28 @@ void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction
|
||||
widget(next)->raise();
|
||||
|
||||
// animate both, the now and next widget to the side, using animation framework
|
||||
QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos");
|
||||
//QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos");
|
||||
animnow->setTargetObject(widget(now));
|
||||
animnow->setPropertyName("pos");
|
||||
|
||||
|
||||
animnow->setDuration(m_speed);
|
||||
animnow->setEasingCurve(m_animationtype);
|
||||
animnow->setStartValue(QPoint(pnow.x(), pnow.y()));
|
||||
animnow->setEndValue(QPoint(offsetx + pnow.x(), offsety + pnow.y()));
|
||||
QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos");
|
||||
//QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos");
|
||||
animnext->setTargetObject(widget(next));
|
||||
animnext->setPropertyName("pos");
|
||||
|
||||
animnext->setDuration(m_speed);
|
||||
animnext->setEasingCurve(m_animationtype);
|
||||
animnext->setStartValue(QPoint(-offsetx + pnext.x(), offsety + pnext.y()));
|
||||
animnext->setEndValue(QPoint(pnext.x(), pnext.y()));
|
||||
|
||||
QParallelAnimationGroup *animgroup = new QParallelAnimationGroup;
|
||||
// QParallelAnimationGroup *animgroup = new QParallelAnimationGroup;
|
||||
|
||||
animgroup->addAnimation(animnow);
|
||||
animgroup->addAnimation(animnext);
|
||||
// animgroup->addAnimation(animnow);
|
||||
// animgroup->addAnimation(animnext);
|
||||
|
||||
QObject::connect(animgroup, SIGNAL(finished()),this,SLOT(animationDoneSlot()));
|
||||
m_next = next;
|
||||
|
||||
@@ -47,6 +47,8 @@ public slots:
|
||||
void slideInIdx(int idx, enum t_direction direction = AUTOMATIC);
|
||||
void slideInWidget(QWidget *widget, enum t_direction direction = AUTOMATIC);
|
||||
|
||||
bool isAnimationRunning();
|
||||
void waitForAnimation();
|
||||
signals:
|
||||
// this is used for internal purposes in the class engine
|
||||
void animationFinished(void);
|
||||
@@ -71,6 +73,10 @@ protected:
|
||||
bool m_active;
|
||||
|
||||
QList<QWidget*> blockedPageList;
|
||||
|
||||
QPropertyAnimation *animnow;
|
||||
QPropertyAnimation *animnext;
|
||||
QParallelAnimationGroup *animgroup;
|
||||
};
|
||||
|
||||
#endif // SLIDINGSTACKEDWIDGET_H
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <QApplication>
|
||||
#include <QPoint>
|
||||
#include <QSize>
|
||||
#include <QSysInfo>
|
||||
#include <QOperatingSystemVersion>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
#include <windows.h>
|
||||
@@ -31,9 +34,10 @@ CFramelessWindow::CFramelessWindow(QWidget *parent)
|
||||
// setWindowFlag(Qt::WindowSystemMenuHint, true);
|
||||
// setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead
|
||||
|
||||
setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
|
||||
|
||||
setResizeable(m_bResizeable);
|
||||
if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) {
|
||||
setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
|
||||
setResizeable(m_bResizeable);
|
||||
}
|
||||
}
|
||||
|
||||
void CFramelessWindow::setResizeable(bool resizeable)
|
||||
@@ -99,6 +103,10 @@ void CFramelessWindow::addIgnoreWidget(QWidget* widget)
|
||||
|
||||
bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
|
||||
{
|
||||
if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) {
|
||||
return QMainWindow::nativeEvent(eventType, message, result);
|
||||
}
|
||||
|
||||
//Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
|
||||
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
|
||||
MSG* msg = *reinterpret_cast<MSG**>(message);
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMainWindow>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegExpValidator>
|
||||
#include <QStack>
|
||||
#include <QStringListModel>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include "3rd/QRCodeGenerator/QRCodeGenerator.h"
|
||||
|
||||
#include "framelesswindow.h"
|
||||
#include "protocols/vpnprotocol.h"
|
||||
|
||||
@@ -35,7 +40,9 @@ public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
enum Page {Start, NewServer, Vpn, GeneralSettings, ServerSettings, ShareConnection, Sites};
|
||||
enum Page {Start, NewServer, NewServer_2, Vpn, GeneralSettings, AppSettings, NetworkSettings,
|
||||
ServerSettings, ServerVpnProtocols, ServersList, ShareConnection, Sites,
|
||||
OpenVpnSettings, ShadowSocksSettings, CloakSettings};
|
||||
Q_ENUM(Page)
|
||||
|
||||
private slots:
|
||||
@@ -44,15 +51,15 @@ private slots:
|
||||
void onVpnProtocolError(amnezia::ErrorCode errorCode);
|
||||
|
||||
void onPushButtonConnectClicked(bool checked);
|
||||
void onPushButtonNewServerConnectWithNewData(bool);
|
||||
void onPushButtonNewServerConnectWithExistingCode(bool);
|
||||
void onPushButtonNewServerConnect(bool);
|
||||
void onPushButtonNewServerConnectConfigure(bool);
|
||||
void onPushButtonNewServerImport(bool);
|
||||
|
||||
void onPushButtonReinstallServer(bool);
|
||||
void onPushButtonClearServer(bool);
|
||||
void onPushButtonForgetServer(bool);
|
||||
|
||||
void onPushButtonAddCustomSitesClicked();
|
||||
void onPushButtonDeleteCustomSiteClicked();
|
||||
void onPushButtonDeleteCustomSiteClicked(const QString &siteToDelete);
|
||||
|
||||
void onTrayActionConnect(); // connect from context menu
|
||||
void setTrayState(VpnProtocol::ConnectionState state);
|
||||
@@ -60,25 +67,57 @@ private slots:
|
||||
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
|
||||
void onConnect();
|
||||
void onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig);
|
||||
void onDisconnect();
|
||||
|
||||
|
||||
private:
|
||||
void goToPage(Page page, bool reset = true, bool slide = true);
|
||||
void setStartPage(Page page, bool slide = true);
|
||||
void closePage();
|
||||
|
||||
QWidget *getPageWidget(Page page);
|
||||
Page currentPage();
|
||||
|
||||
bool installServer(ServerCredentials credentials, QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info);
|
||||
bool installServer(ServerCredentials credentials, QList<DockerContainer> containers, QJsonArray configs,
|
||||
QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info);
|
||||
|
||||
ErrorCode doInstallAction(const std::function<ErrorCode()> &action, QWidget *page, QProgressBar *progress, QPushButton *button, QLabel *info);
|
||||
|
||||
void setupTray();
|
||||
void setTrayIcon(const QString &iconPath);
|
||||
|
||||
void setupUiConnections();
|
||||
void updateSettings();
|
||||
void setupProtocolsPageConnections();
|
||||
void setupNewServerPageConnections();
|
||||
void setupServerSettingsPageConnections();
|
||||
void setupSharePageConnections();
|
||||
|
||||
void updateShareCode();
|
||||
void updateStartPage();
|
||||
void updateSitesPage();
|
||||
void updateVpnPage();
|
||||
void updateAppSettingsPage();
|
||||
void updateServerPage();
|
||||
void updateServersListPage();
|
||||
void updateProtocolsPage();
|
||||
void updateShareCodePage();
|
||||
void updateOpenVpnPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData);
|
||||
void updateShadowSocksPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData);
|
||||
void updateCloakPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData);
|
||||
|
||||
void updateSharingPage(int serverIndex, const ServerCredentials &credentials,
|
||||
DockerContainer container);
|
||||
|
||||
void makeSitesListItem(QListWidget* listWidget, const QString &address);
|
||||
void makeServersListItem(QListWidget* listWidget, const QJsonObject &server, bool isDefault, int index);
|
||||
|
||||
void updateQRCodeImage(const QString &text, QLabel *label);
|
||||
|
||||
QJsonObject getOpenVpnConfigFromPage(QJsonObject oldConfig);
|
||||
QJsonObject getShadowSocksConfigFromPage(QJsonObject oldConfig);
|
||||
QJsonObject getCloakConfigFromPage(QJsonObject oldConfig);
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
VpnConnection* m_vpnConnection;
|
||||
Settings m_settings;
|
||||
@@ -89,7 +128,12 @@ private:
|
||||
QSystemTrayIcon m_tray;
|
||||
QMenu* m_menu;
|
||||
|
||||
QStringListModel *customSitesModel = nullptr;
|
||||
QRegExpValidator m_ipAddressValidator;
|
||||
QRegExpValidator m_ipAddressPortValidator;
|
||||
QRegExpValidator m_ipNetwok24Validator;
|
||||
QRegExpValidator m_ipPortValidator;
|
||||
|
||||
CQR_Encode m_qrEncode;
|
||||
|
||||
bool canMove = false;
|
||||
QPoint offset;
|
||||
@@ -102,6 +146,12 @@ private:
|
||||
const QString ConnectedTrayIconName = "active.png";
|
||||
const QString DisconnectedTrayIconName = "default.png";
|
||||
const QString ErrorTrayIconName = "error.png";
|
||||
|
||||
|
||||
QStack<Page> pagesStack;
|
||||
int selectedServerIndex = -1; // server index to use when proto settings page opened
|
||||
DockerContainer selectedDockerContainer; // same
|
||||
ServerCredentials installCredentials; // used to save cred between pages new_server and new_server_2
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2016 Mostafa Sedaghat Joo (mostafa.sedaghat@gmail.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#include "qautostart.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QTextStream>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
#if defined (Q_OS_WIN)
|
||||
#define REG_KEY "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
||||
|
||||
bool Autostart::isAutostart() {
|
||||
QSettings settings(REG_KEY, QSettings::NativeFormat);
|
||||
|
||||
if (settings.value(appName()).isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Autostart::setAutostart(bool autostart) {
|
||||
QSettings settings(REG_KEY, QSettings::NativeFormat);
|
||||
|
||||
if (autostart) {
|
||||
settings.setValue(appName() , appPath().replace('/','\\'));
|
||||
} else {
|
||||
settings.remove(appName());
|
||||
}
|
||||
}
|
||||
|
||||
QString Autostart::appPath() {
|
||||
return QCoreApplication::applicationFilePath() + " --autostart";
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_MAC)
|
||||
|
||||
bool Autostart::isAutostart() {
|
||||
QProcess process;
|
||||
process.start("osascript", {
|
||||
"-e tell application \"System Events\" to get the path of every login item"
|
||||
});
|
||||
process.waitForFinished(3000);
|
||||
const auto output = QString::fromLocal8Bit(process.readAllStandardOutput());
|
||||
return output.contains(appPath());
|
||||
}
|
||||
|
||||
void Autostart::setAutostart(bool autostart) {
|
||||
// Remove any existing login entry for this app first, in case there was one
|
||||
// from a previous installation, that may be under a different launch path.
|
||||
{
|
||||
QProcess::execute("osascript", {
|
||||
"-e tell application \"System Events\" to delete every login item whose name is \"" + appName() + "\""
|
||||
});
|
||||
}
|
||||
|
||||
// Now install the login item, if needed.
|
||||
if ( autostart )
|
||||
{
|
||||
QProcess::execute("osascript", {
|
||||
"-e tell application \"System Events\" to make login item at end with properties {path:\"" + appPath() + "\", hidden:true, name: \"" + appName() + "\"}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString Autostart::appPath() {
|
||||
QDir appDir = QDir(QCoreApplication::applicationDirPath());
|
||||
appDir.cdUp();
|
||||
appDir.cdUp();
|
||||
QString absolutePath = appDir.absolutePath();
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_LINUX)
|
||||
bool Autostart::isAutostart() {
|
||||
QFileInfo check_file(QDir::homePath() + "/.config/autostart/" + appName() +".desktop");
|
||||
|
||||
if (check_file.exists() && check_file.isFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Autostart::setAutostart(bool autostart) {
|
||||
QString path = QDir::homePath() + "/.config/autostart/";
|
||||
QString name = appName() +".desktop";
|
||||
QFile file(path+name);
|
||||
|
||||
file.remove();
|
||||
|
||||
if(autostart) {
|
||||
QDir dir(path);
|
||||
if(!dir.exists()) {
|
||||
dir.mkpath(path);
|
||||
}
|
||||
|
||||
if (file.open(QIODevice::ReadWrite)) {
|
||||
QTextStream stream(&file);
|
||||
stream << "[Desktop Entry]" << endl;
|
||||
stream << "Exec=" << appPath() << endl;
|
||||
stream << "Type=Application" << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Autostart::appPath() {
|
||||
return QCoreApplication::applicationFilePath() + " --autostart";
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool Autostart::isAutostart() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Autostart::setAutostart(bool autostart) {
|
||||
Q_UNUSED(autostart);
|
||||
}
|
||||
|
||||
QString Autostart::appPath() {
|
||||
return QString();
|
||||
}
|
||||
#endif
|
||||
|
||||
QString Autostart::appName() {
|
||||
return QCoreApplication::applicationName();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2016 Mostafa Sedaghat Joo (mostafa.sedaghat@gmail.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#ifndef AUTOSTART_H
|
||||
#define AUTOSTART_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Autostart
|
||||
{
|
||||
public:
|
||||
static bool isAutostart();
|
||||
static void setAutostart(bool autostart);
|
||||
|
||||
protected:
|
||||
static QString appPath();
|
||||
static QString appName();
|
||||
};
|
||||
|
||||
#endif // AUTOSTART_H
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "server_widget.h"
|
||||
#include "ui_server_widget.h"
|
||||
|
||||
#include "settings.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
ServerWidget::ServerWidget(const QJsonObject &server, bool isDefault, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::ServerWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->pushButton_connect->hide();
|
||||
ui->pushButton_share->hide();
|
||||
|
||||
//ui->pushButton_settings->hide();\\
|
||||
|
||||
ui->pushButton_settings->setGraphicsEffect(&eff);
|
||||
animation.setTargetObject(&eff);
|
||||
animation.setPropertyName("opacity");
|
||||
animation.setDuration(150);
|
||||
animation.setEasingCurve(QEasingCurve::InOutQuad);
|
||||
|
||||
eff.setOpacity(0);
|
||||
|
||||
QString desc = server.value(config_key::description).toString();
|
||||
QString address = server.value(config_key::hostName).toString();
|
||||
|
||||
ui->label_address->setText(address);
|
||||
|
||||
if (desc.isEmpty()) {
|
||||
ui->label_description->setText(address);
|
||||
}
|
||||
else {
|
||||
ui->label_description->setText(desc);
|
||||
}
|
||||
|
||||
ui->pushButton_default->setChecked(isDefault);
|
||||
ui->pushButton_default->setDisabled(isDefault);
|
||||
}
|
||||
|
||||
ServerWidget::~ServerWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ServerWidget::enterEvent(QEvent *e)
|
||||
{
|
||||
animation.setStartValue(0);
|
||||
animation.setEndValue(1);
|
||||
|
||||
animation.start();
|
||||
}
|
||||
|
||||
void ServerWidget::leaveEvent(QEvent *e)
|
||||
{
|
||||
animation.setStartValue(1);
|
||||
animation.setEndValue(0);
|
||||
|
||||
animation.start();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef SERVER_WIDGET_H
|
||||
#define SERVER_WIDGET_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QWidget>
|
||||
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
namespace Ui {
|
||||
class ServerWidget;
|
||||
}
|
||||
|
||||
class ServerWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ServerWidget(const QJsonObject &server, bool isDefault, QWidget *parent = nullptr);
|
||||
~ServerWidget();
|
||||
Ui::ServerWidget *ui;
|
||||
private:
|
||||
void enterEvent(QEvent *e) override;
|
||||
void leaveEvent(QEvent *e) override;
|
||||
|
||||
QPropertyAnimation animation;
|
||||
QGraphicsOpacityEffect eff;
|
||||
};
|
||||
|
||||
#endif // SERVER_WIDGET_H
|
||||
@@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ServerWidget</class>
|
||||
<widget class="QWidget" name="ServerWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>325</width>
|
||||
<height>70</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<widget class="QLabel" name="label_description">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>181</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QLabel {
|
||||
font-size: 16px;
|
||||
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
color: #181922;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_address">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>141</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_default">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>300</x>
|
||||
<y>25</y>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Set as default</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton:checked {
|
||||
image: url(:/images/check.png);
|
||||
}
|
||||
QPushButton:!checked {
|
||||
image: url(:/images/uncheck.png);
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_share">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>170</x>
|
||||
<y>25</y>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Share connection</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">image: url(:/images/share.png);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_connect">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>212</x>
|
||||
<y>25</y>
|
||||
<width>32</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Connection</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton:checked {
|
||||
image: url(:/images/connect_button_connected.png);
|
||||
}
|
||||
QPushButton:!checked {
|
||||
image: url(:/images/connect_button_disconnected.png);
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_settings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>25</y>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Server settings</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">image: url(:/images/settings.png);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,24 +1,26 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
#include <QProcess>
|
||||
#include <QRandomGenerator>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "defines.h"
|
||||
#include "utils.h"
|
||||
|
||||
QString Utils::toString(bool value)
|
||||
QString Utils::getRandomString(int len)
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
|
||||
QString Utils::serverName()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return SERVICE_NAME;
|
||||
#else
|
||||
return QString("/tmp/%1").arg(SERVICE_NAME);
|
||||
#endif
|
||||
QString randomString;
|
||||
for(int i=0; i<len; ++i) {
|
||||
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
||||
QChar nextChar = possibleCharacters.at(index);
|
||||
randomString.append(nextChar);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
QString Utils::defaultVpnConfigFileName()
|
||||
@@ -113,45 +115,17 @@ bool Utils::processIsRunning(const QString& fileName)
|
||||
|
||||
QString Utils::getIPAddress(const QString& host)
|
||||
{
|
||||
//TODO rewrite to api calls
|
||||
qDebug().noquote() << "GetIPAddress: checking " + host;
|
||||
if(host.isEmpty()) {
|
||||
qDebug().noquote() << "GetIPAddress: host is empty.";
|
||||
return QString();
|
||||
if (ipAddressRegExp().exactMatch(host)) {
|
||||
return host;
|
||||
}
|
||||
|
||||
if(checkIPFormat(host)) {
|
||||
qDebug().noquote() << "GetIPAddress host is ip:" << host << host;
|
||||
return host; // it is a ip address.
|
||||
QList<QHostAddress> adresses = QHostInfo::fromName(host).addresses();
|
||||
if (!adresses.isEmpty()) {
|
||||
qDebug() << "Resolved address for" << host << adresses.first().toString();
|
||||
return adresses.first().toString();
|
||||
}
|
||||
QProcess ping;
|
||||
|
||||
#ifdef Q_OS_MACX
|
||||
ping.start("ping", QStringList() << "-c1" << host);
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
ping.start("ping", QStringList() << QString("/n") << "1" << QString("/w") << "1" << host);
|
||||
#endif
|
||||
ping.waitForStarted();
|
||||
|
||||
QEventLoop loop;
|
||||
loop.connect(&ping, SIGNAL(finished(int)), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
|
||||
QString d = ping.readAll();
|
||||
if(d.size() == 0)
|
||||
return QString();
|
||||
qDebug().noquote() << d;
|
||||
|
||||
QString ip;
|
||||
#ifdef Q_OS_MACX
|
||||
ip = getStringBetween(d, "(", ")");
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
ip = getStringBetween(d, "[", "]");
|
||||
#endif
|
||||
qDebug().noquote() << "GetIPAddress:" << host << ip;
|
||||
return ip;
|
||||
qDebug() << "Unable to resolve address for " << host;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString Utils::getStringBetween(const QString& s, const QString& a, const QString& b)
|
||||
@@ -179,3 +153,49 @@ bool Utils::checkIPFormat(const QString& ip)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Utils::killProcessByName(const QString &name)
|
||||
{
|
||||
qDebug().noquote() << "Kill process" << name;
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess::execute(QString("taskkill /im %1 /f").arg(name));
|
||||
#else
|
||||
QProcess::execute(QString("pkill %1").arg(name));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Inspired from http://stackoverflow.com/a/15281070/1529139
|
||||
// and http://stackoverflow.com/q/40059902/1529139
|
||||
bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
|
||||
{
|
||||
bool success = false;
|
||||
DWORD thisConsoleId = GetCurrentProcessId();
|
||||
// Leave current console if it exists
|
||||
// (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
|
||||
bool consoleDetached = (FreeConsole() != FALSE);
|
||||
|
||||
if (AttachConsole(dwProcessId) != FALSE)
|
||||
{
|
||||
// Add a fake Ctrl-C handler for avoid instant kill is this console
|
||||
// WARNING: do not revert it or current program will be also killed
|
||||
SetConsoleCtrlHandler(nullptr, true);
|
||||
success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
|
||||
FreeConsole();
|
||||
}
|
||||
|
||||
if (consoleDetached)
|
||||
{
|
||||
// Create a new console if previous was deleted by OS
|
||||
if (AttachConsole(thisConsoleId) == FALSE)
|
||||
{
|
||||
int errorCode = GetLastError();
|
||||
if (errorCode == 31) // 31=ERROR_GEN_FAILURE
|
||||
{
|
||||
AllocConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
class Utils {
|
||||
|
||||
public:
|
||||
static QString getRandomString(int len);
|
||||
|
||||
static QString configPath();
|
||||
static QString defaultVpnConfigFileName();
|
||||
static QString executable(const QString& baseName, bool absPath);
|
||||
static QString serverName();
|
||||
static QString systemLogPath();
|
||||
static QString toString(bool value);
|
||||
static bool createEmptyFile(const QString& path);
|
||||
static bool initializePath(const QString& path);
|
||||
static bool processIsRunning(const QString& fileName);
|
||||
|
||||
static QString getIPAddress(const QString& host);
|
||||
static QString getStringBetween(const QString& s, const QString& a, const QString& b);
|
||||
static bool checkIPFormat(const QString& ip);
|
||||
static QRegExp ipAddressRegExp() { return QRegExp("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); }
|
||||
static QRegExp ipAddressPortRegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); }
|
||||
|
||||
static QRegExp ipNetwork24RegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||
"0$"); }
|
||||
|
||||
static QRegExp ipPortRegExp() { return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); }
|
||||
|
||||
static bool processIsRunning(const QString& fileName);
|
||||
static void killProcessByName(const QString &name);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <core/openvpnconfigurator.h>
|
||||
#include <configurators/openvpn_configurator.h>
|
||||
#include <configurators/cloak_configurator.h>
|
||||
#include <configurators/shadowsocks_configurator.h>
|
||||
#include <core/servercontroller.h>
|
||||
|
||||
#include "ipc.h"
|
||||
#include "core/ipcclient.h"
|
||||
#include "protocols/openvpnprotocol.h"
|
||||
#include "protocols/openvpnovercloakprotocol.h"
|
||||
#include "protocols/shadowsocksvpnprotocol.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "vpnconnection.h"
|
||||
|
||||
@@ -40,16 +44,18 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::ConnectionState state)
|
||||
IpcClient::Interface()->flushDns();
|
||||
|
||||
if (m_settings.customRouting()) {
|
||||
IpcClient::Interface()->routeDelete("0.0.0.0", m_vpnProtocol->vpnGateway());
|
||||
|
||||
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(),
|
||||
QStringList() << m_settings.primaryDns() << m_settings.secondaryDns());
|
||||
|
||||
const QStringList &black_custom = m_settings.customIps();
|
||||
qDebug() << "onConnect :: adding custom black routes, count:" << black_custom.size();
|
||||
qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << black_custom.size();
|
||||
|
||||
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), black_custom);
|
||||
}
|
||||
}
|
||||
else if (state == VpnProtocol::ConnectionState::Error || state == VpnProtocol::ConnectionState::Disconnected) {
|
||||
else if (state == VpnProtocol::ConnectionState::Error) {
|
||||
IpcClient::Interface()->flushDns();
|
||||
|
||||
if (m_settings.customRouting()) {
|
||||
@@ -75,12 +81,81 @@ ErrorCode VpnConnection::lastError() const
|
||||
return m_vpnProtocol.data()->lastError();
|
||||
}
|
||||
|
||||
ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credentials, Protocol protocol)
|
||||
QMap<Protocol, QString> VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig)
|
||||
{
|
||||
QMap<Protocol, QString> configs;
|
||||
for (Protocol proto: { Protocol::OpenVpn,
|
||||
Protocol::ShadowSocks,
|
||||
Protocol::Cloak,
|
||||
Protocol::WireGuard}) {
|
||||
|
||||
QString cfg = containerConfig.value(protoToString(proto)).toObject().value(config_key::last_config).toString();
|
||||
|
||||
if (!cfg.isEmpty()) configs.insert(proto, cfg);
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
QString VpnConnection::createVpnConfigurationForProto(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Protocol proto,
|
||||
ErrorCode *errorCode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
auto lastVpnConfig = getLastVpnConfig(containerConfig);
|
||||
|
||||
QString configData;
|
||||
if (lastVpnConfig.contains(proto)) {
|
||||
configData = OpenVpnConfigurator::processConfigWithLocalSettings(lastVpnConfig.value(proto));
|
||||
qDebug() << "VpnConnection::createVpnConfiguration: using saved config for" << protoToString(proto);
|
||||
}
|
||||
else {
|
||||
qDebug() << "VpnConnection::createVpnConfiguration: gen new config for" << protoToString(proto);
|
||||
if (proto == Protocol::OpenVpn) {
|
||||
configData = OpenVpnConfigurator::genOpenVpnConfig(credentials,
|
||||
container, containerConfig, &e);
|
||||
}
|
||||
else if (proto == Protocol::Cloak) {
|
||||
configData = CloakConfigurator::genCloakConfig(credentials,
|
||||
container, containerConfig, &e);
|
||||
}
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
configData = ShadowSocksConfigurator::genShadowSocksConfig(credentials,
|
||||
container, containerConfig, &e);
|
||||
}
|
||||
|
||||
if (errorCode && e) {
|
||||
*errorCode = e;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
if (serverIndex >= 0) {
|
||||
qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container << proto;
|
||||
QJsonObject protoObject = m_settings.protocolConfig(serverIndex, container, proto);
|
||||
protoObject.insert(config_key::last_config, configData);
|
||||
m_settings.setProtocolConfig(serverIndex, container, proto, protoObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode) *errorCode = e;
|
||||
return configData;
|
||||
}
|
||||
|
||||
ErrorCode VpnConnection::createVpnConfiguration(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks) {
|
||||
QString openVpnConfigData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode);
|
||||
m_vpnConfiguration.insert(config::key_openvpn_config_data(), openVpnConfigData);
|
||||
|
||||
if (container == DockerContainer::OpenVpn ||
|
||||
container == DockerContainer::OpenVpnOverShadowSocks ||
|
||||
container == DockerContainer::OpenVpnOverCloak) {
|
||||
|
||||
QString openVpnConfigData =
|
||||
createVpnConfigurationForProto(
|
||||
serverIndex, credentials, container, containerConfig, Protocol::OpenVpn, &errorCode);
|
||||
|
||||
|
||||
m_vpnConfiguration.insert(config::key_openvpn_config_data, openVpnConfigData);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -89,40 +164,51 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)){
|
||||
QTextStream stream(&file);
|
||||
stream << openVpnConfigData << endl;
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
return ErrorCode::FailedToSaveConfigData;
|
||||
}
|
||||
}
|
||||
|
||||
if (protocol == Protocol::ShadowSocks) {
|
||||
QJsonObject ssConfigData = ShadowSocksVpnProtocol::genShadowSocksConfig(credentials);
|
||||
m_vpnConfiguration.insert(config::key_shadowsocks_config_data(), ssConfigData);
|
||||
if (container == DockerContainer::OpenVpnOverShadowSocks) {
|
||||
QJsonObject ssConfigData = QJsonDocument::fromJson(
|
||||
createVpnConfigurationForProto(
|
||||
serverIndex, credentials, container, containerConfig, Protocol::ShadowSocks, &errorCode).toUtf8()).
|
||||
object();
|
||||
|
||||
m_vpnConfiguration.insert(config::key_shadowsocks_config_data, ssConfigData);
|
||||
}
|
||||
|
||||
if (container == DockerContainer::OpenVpnOverCloak) {
|
||||
QJsonObject cloakConfigData = QJsonDocument::fromJson(
|
||||
createVpnConfigurationForProto(
|
||||
serverIndex, credentials, container, containerConfig, Protocol::Cloak, &errorCode).toUtf8()).
|
||||
object();
|
||||
|
||||
m_vpnConfiguration.insert(config::key_cloak_config_data, cloakConfigData);
|
||||
}
|
||||
|
||||
//qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Protocol protocol)
|
||||
ErrorCode VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
|
||||
{
|
||||
qDebug() << "connectToVpn, CustomRouting is" << m_settings.customRouting();
|
||||
//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";
|
||||
qDebug() << "СonnectToVpn, CustomRouting is" << m_settings.customRouting();
|
||||
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Connecting);
|
||||
|
||||
ServerController::setupServerFirewall(credentials);
|
||||
|
||||
if (m_vpnProtocol) {
|
||||
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
m_vpnProtocol->stop();
|
||||
m_vpnProtocol->deleteLater();
|
||||
m_vpnProtocol.reset();
|
||||
}
|
||||
|
||||
qApp->processEvents();
|
||||
|
||||
if (protocol == Protocol::Any || protocol == Protocol::OpenVpn) {
|
||||
ErrorCode e = createVpnConfiguration(credentials, Protocol::OpenVpn);
|
||||
if (container == DockerContainer::None || container == DockerContainer::OpenVpn) {
|
||||
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig);
|
||||
if (e) {
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||
return e;
|
||||
@@ -135,20 +221,41 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot
|
||||
return e;
|
||||
}
|
||||
}
|
||||
else if (protocol == Protocol::ShadowSocks) {
|
||||
ErrorCode e = createVpnConfiguration(credentials, Protocol::ShadowSocks);
|
||||
else if (container == DockerContainer::OpenVpnOverShadowSocks) {
|
||||
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpnOverShadowSocks, containerConfig);
|
||||
if (e) {
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||
return e;
|
||||
}
|
||||
|
||||
m_vpnProtocol.reset(new ShadowSocksVpnProtocol(m_vpnConfiguration));
|
||||
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
|
||||
if (e) {
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
else if (container == DockerContainer::OpenVpnOverCloak) {
|
||||
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpnOverCloak, containerConfig);
|
||||
if (e) {
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||
return e;
|
||||
}
|
||||
|
||||
m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration));
|
||||
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
|
||||
if (e) {
|
||||
emit connectionStateChanged(VpnProtocol::ConnectionState::Error);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
|
||||
ServerController::disconnectFromHost(credentials);
|
||||
|
||||
return m_vpnProtocol.data()->start();
|
||||
}
|
||||
|
||||
@@ -162,8 +269,11 @@ void VpnConnection::disconnectFromVpn()
|
||||
{
|
||||
qDebug() << "Disconnect from VPN";
|
||||
|
||||
// m_vpnProtocol->communicator()->sendMessage(Message(Message::State::ClearSavedRoutesRequest, QStringList()));
|
||||
// m_vpnProtocol->communicator()->sendMessage(Message(Message::State::FlushDnsRequest, QStringList()));
|
||||
IpcClient::Interface()->flushDns();
|
||||
|
||||
if (m_settings.customRouting()) {
|
||||
IpcClient::Interface()->clearSavedRoutes();
|
||||
}
|
||||
|
||||
if (!m_vpnProtocol.data()) {
|
||||
return;
|
||||
|
||||
@@ -24,9 +24,16 @@ public:
|
||||
static QString bytesPerSecToText(quint64 bytes);
|
||||
|
||||
ErrorCode lastError() const;
|
||||
ErrorCode createVpnConfiguration(const ServerCredentials &credentials, Protocol protocol);
|
||||
|
||||
ErrorCode connectToVpn(const ServerCredentials &credentials, Protocol protocol = Protocol::Any);
|
||||
static QMap<Protocol, QString> getLastVpnConfig(const QJsonObject &containerConfig);
|
||||
QString createVpnConfigurationForProto(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Protocol proto,
|
||||
ErrorCode *errorCode = nullptr);
|
||||
|
||||
ErrorCode createVpnConfiguration(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig);
|
||||
|
||||
ErrorCode connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig);
|
||||
void disconnectFromVpn();
|
||||
|
||||
bool isConnected() const;
|
||||
|
||||
@@ -65,6 +65,8 @@ cd %PROJECT_DIR%
|
||||
cd %WORK_DIR%
|
||||
set CL=/MP
|
||||
nmake /A /NOLOGO
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
nmake clean
|
||||
rem if not exist "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%" break
|
||||
|
||||
|
||||