mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-17 08:16:06 +03:00
Compare commits
75 Commits
bugfix/spl
...
ArchLinux-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5d7a9514a | ||
|
|
1bae323a1a | ||
|
|
a36eafa56c | ||
|
|
4fd3760a35 | ||
|
|
51c46bae12 | ||
|
|
112df79f94 | ||
|
|
2904bb96b9 | ||
|
|
669f5c21dd | ||
|
|
dd233f77fc | ||
|
|
02efd9c217 | ||
|
|
b897e7102e | ||
|
|
1cc5c5384e | ||
|
|
db602ac65b | ||
|
|
2d22b52b5d | ||
|
|
426ac49f6f | ||
|
|
c164814abd | ||
|
|
3084892ed8 | ||
|
|
4fbacab643 | ||
|
|
1bf808c9ee | ||
|
|
5dc3b64e0b | ||
|
|
e8ceeb6e20 | ||
|
|
d38c7ce6a5 | ||
|
|
c6a312845a | ||
|
|
ef0530ec6b | ||
|
|
e8a2e54d05 | ||
|
|
b4694313a0 | ||
|
|
abb2cae1f8 | ||
|
|
b0004fd9dc | ||
|
|
362a82f944 | ||
|
|
19fe61ed29 | ||
|
|
72de38b4fb | ||
|
|
02c0f96e5e | ||
|
|
5e9f688000 | ||
|
|
6a7e346695 | ||
|
|
071738116e | ||
|
|
ae4ee6431d | ||
|
|
9cfcb714ae | ||
|
|
d1ccde2a4b | ||
|
|
4848091203 | ||
|
|
abb4678b0a | ||
|
|
e609b22cf8 | ||
|
|
282f159311 | ||
|
|
4ef8c77a2d | ||
|
|
08c1cf2439 | ||
|
|
2fc33875bb | ||
|
|
9e92e4b5ff | ||
|
|
7f2cf70bf5 | ||
|
|
8164026891 | ||
|
|
0e23b3a1ac | ||
|
|
1739d4861e | ||
|
|
a6b6e7850d | ||
|
|
3e9dea6f07 | ||
|
|
1b37ca805f | ||
|
|
c772f56da7 | ||
|
|
bc183e39bb | ||
|
|
306d4f70a8 | ||
|
|
a386d39495 | ||
|
|
22b14dff5f | ||
|
|
e749cc7578 | ||
|
|
6a12cad1c9 | ||
|
|
c15665803d | ||
|
|
97090888d5 | ||
|
|
4642308fbb | ||
|
|
f0b872e86b | ||
|
|
0c33432436 | ||
|
|
78c83b2e21 | ||
|
|
414a47e2f2 | ||
|
|
32c304dc1b | ||
|
|
4ea1a19572 | ||
|
|
c14f1b5000 | ||
|
|
546d4c1d3d | ||
|
|
2a0166bb26 | ||
|
|
2df612ec1f | ||
|
|
501670bdd2 | ||
|
|
b78bf39767 |
27
.github/workflows/deploy.yml
vendored
27
.github/workflows/deploy.yml
vendored
@@ -1,7 +1,12 @@
|
||||
name: 'Deploy workflow'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
on: [push]
|
||||
env:
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
|
||||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
@@ -25,7 +30,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
@@ -89,7 +94,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
@@ -119,15 +124,14 @@ jobs:
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-IOS:
|
||||
name: 'Build-IOS'
|
||||
Build-iOS:
|
||||
name: 'Build-iOS'
|
||||
runs-on: macos-12
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.5.2
|
||||
|
||||
steps:
|
||||
# Just select XCode
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -143,6 +147,7 @@ jobs:
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install iOS Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -154,7 +159,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v3
|
||||
@@ -174,7 +179,7 @@ jobs:
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: Install dependencies
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
@@ -232,7 +237,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
@@ -296,7 +301,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -309,7 +314,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Grant execute permission for qt-cmake'
|
||||
shell: bash
|
||||
|
||||
64
.github/workflows/tag-upload.yml
vendored
Normal file
64
.github/workflows/tag-upload.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
GIT_TAG=${{ github.ref_name }}
|
||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
else
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.0.8.5
|
||||
project(${PROJECT} VERSION 4.1.0.1
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
Submodule client/3rd-prebuilt updated: ac32d33555...fcf3022a27
2
client/3rd/awg-apple
vendored
2
client/3rd/awg-apple
vendored
Submodule client/3rd/awg-apple updated: fab07138db...233eda6760
@@ -279,7 +279,7 @@ void AmneziaApplication::initModels()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||
connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(),
|
||||
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, m_containersModel.get(),
|
||||
&ContainersModel::updateContainersConfig);
|
||||
|
||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||
@@ -288,6 +288,8 @@ void AmneziaApplication::initModels()
|
||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(),
|
||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||
connect(m_containersModel.get(), &ContainersModel::containersModelUpdated, m_serversModel.get(),
|
||||
&ServersModel::updateContainersConfig);
|
||||
|
||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||
@@ -296,17 +298,7 @@ void AmneziaApplication::initModels()
|
||||
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() {
|
||||
if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard
|
||||
|| m_containersModel->getDefaultContainer() == DockerContainer::Awg)
|
||||
&& m_sitesModel->isSplitTunnelingEnabled()) {
|
||||
m_sitesModel->toggleSplitTunneling(false);
|
||||
emit m_pageController->showNotificationMessage(
|
||||
tr("Split tunneling for %1 is not implemented, the option was disabled")
|
||||
.arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer())));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
@@ -332,6 +324,11 @@ void AmneziaApplication::initModels()
|
||||
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, m_clientManagementModel.get(),
|
||||
&ClientManagementModel::appendClient);
|
||||
}
|
||||
|
||||
void AmneziaApplication::initControllers()
|
||||
@@ -357,12 +354,12 @@ void AmneziaApplication::initControllers()
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings, m_configurator));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
|
||||
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||
if (m_settingsController->isAutoStartEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
@@ -94,6 +95,7 @@ private:
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
|
||||
@@ -138,8 +138,8 @@ android {
|
||||
resConfig "en"
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 34
|
||||
versionCode 37 // Change to a higher number
|
||||
versionName "4.0.8" // Change to a higher number
|
||||
versionCode 39 // Change to a higher number
|
||||
versionName "4.1.0" // Change to a higher number
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments = [
|
||||
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
|
||||
|
||||
509
client/android/src/com/wireguard/config/IPRange.java
Normal file
509
client/android/src/com/wireguard/config/IPRange.java
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Class that represents a range of IP addresses. This range could be a proper subnet, but that's
|
||||
* not necessarily the case (see {@code getPrefix} and {@code toSubnets}).
|
||||
*/
|
||||
public class IPRange implements Comparable<IPRange>
|
||||
{
|
||||
private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||
private byte[] mFrom;
|
||||
private byte[] mTo;
|
||||
private Integer mPrefix;
|
||||
|
||||
/**
|
||||
* Determine if the range is a proper subnet and, if so, what the network prefix is.
|
||||
*/
|
||||
private void determinePrefix()
|
||||
{
|
||||
boolean matching = true;
|
||||
|
||||
mPrefix = mFrom.length * 8;
|
||||
for (int i = 0; i < mFrom.length; i++)
|
||||
{
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (matching)
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit]))
|
||||
{
|
||||
mPrefix = (i * 8) + bit;
|
||||
matching = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0)
|
||||
{
|
||||
mPrefix = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, byte[] to)
|
||||
{
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String from, String to) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(from), Utils.parseInetAddress(to));
|
||||
}
|
||||
|
||||
public IPRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
|
||||
private void initializeFromRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
byte[] fa = from.getAddress(), ta = to.getAddress();
|
||||
if (fa.length != ta.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid range");
|
||||
}
|
||||
if (compareAddr(fa, ta) < 0)
|
||||
{
|
||||
mFrom = fa;
|
||||
mTo = ta;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTo = fa;
|
||||
mFrom = ta;
|
||||
}
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String base, int prefix) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(base), prefix);
|
||||
}
|
||||
|
||||
public IPRange(InetAddress base, int prefix)
|
||||
{
|
||||
this(base.getAddress(), prefix);
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, int prefix)
|
||||
{
|
||||
initializeFromCIDR(from, prefix);
|
||||
}
|
||||
|
||||
private void initializeFromCIDR(byte[] from, int prefix)
|
||||
{
|
||||
if (from.length != 4 && from.length != 16)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
}
|
||||
if (prefix < 0 || prefix > from.length * 8)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid prefix");
|
||||
}
|
||||
byte[] to = from.clone();
|
||||
byte mask = (byte)(0xff << (8 - prefix % 8));
|
||||
int i = prefix / 8;
|
||||
|
||||
if (i < from.length)
|
||||
{
|
||||
from[i] = (byte)(from[i] & mask);
|
||||
to[i] = (byte)(to[i] | ~mask);
|
||||
Arrays.fill(from, i+1, from.length, (byte)0);
|
||||
Arrays.fill(to, i+1, to.length, (byte)0xff);
|
||||
}
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
mPrefix = prefix;
|
||||
}
|
||||
|
||||
public IPRange(String cidr) throws UnknownHostException
|
||||
{
|
||||
/* only verify the basic structure */
|
||||
if (!cidr.matches("(?i)^(([0-9.]+)|([0-9a-f:]+))(-(([0-9.]+)|([0-9a-f:]+))|(/\\d+))?$"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid CIDR or range notation");
|
||||
}
|
||||
if (cidr.contains("-"))
|
||||
{
|
||||
String[] parts = cidr.split("-");
|
||||
InetAddress from = InetAddress.getByName(parts[0]);
|
||||
InetAddress to = InetAddress.getByName(parts[1]);
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] parts = cidr.split("/");
|
||||
InetAddress addr = InetAddress.getByName(parts[0]);
|
||||
byte[] base = addr.getAddress();
|
||||
int prefix = base.length * 8;
|
||||
if (parts.length > 1)
|
||||
{
|
||||
prefix = Integer.parseInt(parts[1]);
|
||||
}
|
||||
initializeFromCIDR(base, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first address of the range. The network ID in case this is a proper subnet.
|
||||
*/
|
||||
public InetAddress getFrom()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last address of the range.
|
||||
*/
|
||||
public InetAddress getTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mTo);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this range is a proper subnet returns its prefix, otherwise returns null.
|
||||
*/
|
||||
public Integer getPrefix()
|
||||
{
|
||||
return mPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull IPRange other)
|
||||
{
|
||||
int cmp = compareAddr(mFrom, other.mFrom);
|
||||
if (cmp == 0)
|
||||
{ /* smaller ranges first */
|
||||
cmp = compareAddr(mTo, other.mTo);
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o == null || !(o instanceof IPRange))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this == o || compareTo((IPRange)o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mPrefix != null)
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix;
|
||||
}
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "-" +
|
||||
InetAddress.getByAddress(mTo).getHostAddress();
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private int compareAddr(byte a[], byte b[])
|
||||
{
|
||||
if (a.length != b.length)
|
||||
{
|
||||
return (a.length < b.length) ? -1 : 1;
|
||||
}
|
||||
for (int i = 0; i < a.length; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
if (((int)a[i] & 0xff) < ((int)b[i] & 0xff))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this range fully contains the given range.
|
||||
*/
|
||||
public boolean contains(IPRange range)
|
||||
{
|
||||
return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this and the given range overlap.
|
||||
*/
|
||||
public boolean overlaps(IPRange range)
|
||||
{
|
||||
return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0);
|
||||
}
|
||||
|
||||
private byte[] dec(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (--addr[i] != (byte)0xff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private byte[] inc(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (++addr[i] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from the current range. Returns a list of resulting ranges (these are
|
||||
* not proper subnets). At most two ranges are returned, in case the given range is contained in
|
||||
* this but does not equal it, which would result in an empty list (which is also the case if
|
||||
* this range is fully contained in the given range).
|
||||
*/
|
||||
public List<IPRange> remove(IPRange range)
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (!overlaps(range))
|
||||
{ /* | this | or | this |
|
||||
* | range | | range | */
|
||||
list.add(this);
|
||||
}
|
||||
else if (!range.contains(this))
|
||||
{ /* we are not completely removed, so none of these cases applies:
|
||||
* | this | or | this | or | this |
|
||||
* | range | | range | | range | */
|
||||
if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0)
|
||||
{ /* the removed range is completely within our boundaries:
|
||||
* | this |
|
||||
* | range | */
|
||||
list.add(new IPRange(mFrom, dec(range.mFrom.clone())));
|
||||
list.add(new IPRange(inc(range.mTo.clone()), mTo));
|
||||
}
|
||||
else
|
||||
{ /* one end is within our boundaries the other at or outside it:
|
||||
* | this | or | this | or | this | or | this |
|
||||
* | range | | range | | range | | range | */
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone());
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone());
|
||||
list.add(new IPRange(from, to));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean adjacent(IPRange range)
|
||||
{
|
||||
if (compareAddr(mTo, range.mFrom) < 0)
|
||||
{
|
||||
byte[] to = inc(mTo.clone());
|
||||
return compareAddr(to, range.mFrom) == 0;
|
||||
}
|
||||
byte[] from = dec(mFrom.clone());
|
||||
return compareAddr(from, range.mTo) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them.
|
||||
*/
|
||||
public IPRange merge(IPRange range)
|
||||
{
|
||||
if (overlaps(range))
|
||||
{
|
||||
if (contains(range))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else if (range.contains(this))
|
||||
{
|
||||
return range;
|
||||
}
|
||||
}
|
||||
else if (!adjacent(range))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom;
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo;
|
||||
return new IPRange(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given range into a sorted list of proper subnets.
|
||||
*/
|
||||
public List<IPRange> toSubnets()
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (mPrefix != null)
|
||||
{
|
||||
list.add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0, bit = 0, prefix, netmask, common_byte, common_bit;
|
||||
int from_cur, from_prev = 0, to_cur, to_prev = 1;
|
||||
boolean from_full = true, to_full = true;
|
||||
|
||||
byte[] from = mFrom.clone();
|
||||
byte[] to = mTo.clone();
|
||||
|
||||
/* find a common prefix */
|
||||
while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit]))
|
||||
{
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
prefix = i * 8 + bit;
|
||||
|
||||
/* at this point we know that the addresses are either equal, or that the
|
||||
* current bits in the 'from' and 'to' addresses are 0 and 1, respectively.
|
||||
* we now look at the rest of the bits as two binary trees (0=left, 1=right)
|
||||
* where 'from' and 'to' are both leaf nodes. all leaf nodes between these
|
||||
* nodes are addresses contained in the range. to collect them as subnets
|
||||
* we follow the trees from both leaf nodes to their root node and record
|
||||
* all complete subtrees (right for from, left for to) we come across as
|
||||
* subnets. in that process host bits are zeroed out. if both addresses
|
||||
* are equal we won't enter the loop below.
|
||||
* 0_____|_____1 for the 'from' address we assume we start on a
|
||||
* 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until
|
||||
* _|_ _|_ _|_ _|_ we reach the root of this subtree, which is
|
||||
* | | | | | | | | either the root of this whole 'from'-subtree
|
||||
* 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node
|
||||
* of the right subtree (1) of another node (which actually could be the
|
||||
* leaf node we start from). that whole subtree gets recorded as subnet.
|
||||
* next we follow the right edges to the root of that subtree which again is
|
||||
* either the 'from'-root or the root node in the left subtree (0) of
|
||||
* another node. the complete right subtree of that node is the next subnet
|
||||
* we record. from there we assume that we are in that right subtree and
|
||||
* recursively follow right edges to its root. for the 'to' address the
|
||||
* procedure is exactly the same but with left and right reversed.
|
||||
*/
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
common_byte = i;
|
||||
common_bit = bit;
|
||||
netmask = from.length * 8;
|
||||
for (i = from.length - 1; i >= common_byte; i--)
|
||||
{
|
||||
int bit_min = (i == common_byte) ? common_bit : 0;
|
||||
for (bit = 7; bit >= bit_min; bit--)
|
||||
{
|
||||
byte mask = mBitmask[bit];
|
||||
|
||||
from_cur = from[i] & mask;
|
||||
if (from_prev == 0 && from_cur != 0)
|
||||
{ /* 0 -> 1: subnet is the whole current (right) subtree */
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_full = false;
|
||||
}
|
||||
else if (from_prev != 0 && from_cur == 0)
|
||||
{ /* 1 -> 0: invert bit to switch to right subtree and add it */
|
||||
from[i] ^= mask;
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_cur = 1;
|
||||
}
|
||||
/* clear the current bit */
|
||||
from[i] &= ~mask;
|
||||
from_prev = from_cur;
|
||||
|
||||
to_cur = to[i] & mask;
|
||||
if (to_prev != 0 && to_cur == 0)
|
||||
{ /* 1 -> 0: subnet is the whole current (left) subtree */
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_full = false;
|
||||
}
|
||||
else if (to_prev == 0 && to_cur != 0)
|
||||
{ /* 0 -> 1: invert bit to switch to left subtree and add it */
|
||||
to[i] ^= mask;
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_cur = 0;
|
||||
}
|
||||
/* clear the current bit */
|
||||
to[i] &= ~mask;
|
||||
to_prev = to_cur;
|
||||
netmask--;
|
||||
}
|
||||
}
|
||||
|
||||
if (from_full && to_full)
|
||||
{ /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable
|
||||
* due to the shortcut at the top */
|
||||
list.add(new IPRange(from.clone(), prefix));
|
||||
}
|
||||
else if (from_full)
|
||||
{ /* full from subnet (from=0.. after prefix) */
|
||||
list.add(new IPRange(from.clone(), prefix + 1));
|
||||
}
|
||||
else if (to_full)
|
||||
{ /* full to subnet (to=1.. after prefix) */
|
||||
list.add(new IPRange(to.clone(), prefix + 1));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
223
client/android/src/com/wireguard/config/IPRangeSet.java
Normal file
223
client/android/src/com/wireguard/config/IPRangeSet.java
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
|
||||
* modifying the set and enumerating the resulting subnets.
|
||||
*/
|
||||
public class IPRangeSet implements Iterable<IPRange>
|
||||
{
|
||||
private TreeSet<IPRange> mRanges = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Parse the given string (space separated ranges in CIDR or range notation) and return the
|
||||
* resulting set or {@code null} if the string was invalid. An empty set is returned if the given string
|
||||
* is {@code null}.
|
||||
*/
|
||||
public static IPRangeSet fromString(String ranges)
|
||||
{
|
||||
IPRangeSet set = new IPRangeSet();
|
||||
if (ranges != null)
|
||||
{
|
||||
for (String range : ranges.split("\\s+"))
|
||||
{
|
||||
try
|
||||
{
|
||||
set.add(new IPRange(range));
|
||||
}
|
||||
catch (Exception unused)
|
||||
{ /* besides due to invalid strings exceptions might get thrown if the string
|
||||
* contains a hostname (NetworkOnMainThreadException) */
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a range to this set. Automatically gets merged with existing ranges.
|
||||
*/
|
||||
public void add(IPRange range)
|
||||
{
|
||||
if (mRanges.contains(range))
|
||||
{
|
||||
return;
|
||||
}
|
||||
reinsert:
|
||||
while (true)
|
||||
{
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
IPRange replacement = existing.merge(range);
|
||||
if (replacement != null)
|
||||
{
|
||||
iterator.remove();
|
||||
range = replacement;
|
||||
continue reinsert;
|
||||
}
|
||||
}
|
||||
mRanges.add(range);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given set.
|
||||
*/
|
||||
public void add(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given collection to this set.
|
||||
*/
|
||||
public void addAll(Collection<? extends IPRange> coll)
|
||||
{
|
||||
for (IPRange range : coll)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from this set. Existing ranges are automatically adjusted.
|
||||
*/
|
||||
public void remove(IPRange range)
|
||||
{
|
||||
ArrayList <IPRange> additions = new ArrayList<>();
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
List<IPRange> result = existing.remove(range);
|
||||
if (result.size() == 0)
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
else if (!result.get(0).equals(existing))
|
||||
{
|
||||
iterator.remove();
|
||||
additions.addAll(result);
|
||||
}
|
||||
}
|
||||
mRanges.addAll(additions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given ranges from ranges in this set.
|
||||
*/
|
||||
public void remove(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
mRanges.clear();
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
remove(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the subnets derived from all the ranges in this set.
|
||||
*/
|
||||
public Iterable<IPRange> subnets()
|
||||
{
|
||||
return new Iterable<IPRange>()
|
||||
{
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return new Iterator<IPRange>()
|
||||
{
|
||||
private Iterator<IPRange> mIterator = mRanges.iterator();
|
||||
private List<IPRange> mSubnets;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPRange next()
|
||||
{
|
||||
if (mSubnets == null || mSubnets.size() == 0)
|
||||
{
|
||||
IPRange range = mIterator.next();
|
||||
mSubnets = range.toSubnets();
|
||||
}
|
||||
return mSubnets.remove(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return mRanges.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ranges, not subnets.
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return mRanges.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{ /* we could use TextUtils, but that causes the unit tests to fail */
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (IPRange range : mRanges)
|
||||
{
|
||||
if (sb.length() > 0)
|
||||
{
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(range.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
77
client/android/src/com/wireguard/config/Utils.java
Normal file
77
client/android/src/com/wireguard/config/Utils.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Converts the given byte array to a hexadecimal string encoding.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return hex string
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
char[] hex = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int value = bytes[i];
|
||||
hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
|
||||
hex[i*2+1] = HEXDIGITS[ value & 0x0f];
|
||||
}
|
||||
return new String(hex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given proposal string
|
||||
*
|
||||
* @param ike true for IKE, false for ESP
|
||||
* @param proposal proposal string
|
||||
* @return true if valid
|
||||
*/
|
||||
public native static boolean isProposalValid(boolean ike, String proposal);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address bytes if valid
|
||||
*/
|
||||
private native static byte[] parseInetAddressBytes(String address);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup (as compared to InetAddress.fromName())
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address if valid
|
||||
* @throws UnknownHostException if address is invalid
|
||||
*/
|
||||
public static InetAddress parseInetAddress(String address) throws UnknownHostException
|
||||
{
|
||||
byte[] bytes = parseInetAddressBytes(address);
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new UnknownHostException();
|
||||
}
|
||||
return InetAddress.getByAddress(bytes);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import com.wireguard.crypto.Key
|
||||
import org.json.JSONObject
|
||||
import java.util.Base64
|
||||
|
||||
import com.wireguard.config.*
|
||||
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
||||
import net.openvpn.ovpn3.ClientAPI_Event
|
||||
@@ -72,6 +74,8 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
|
||||
val jsonVpnConfig = mService.getVpnConfig()
|
||||
val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config")
|
||||
val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType")
|
||||
val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites")
|
||||
|
||||
val resultingConfig = StringBuilder()
|
||||
resultingConfig.append(ovpnConfig)
|
||||
@@ -115,6 +119,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
eval_config(config)
|
||||
|
||||
val status = connect()
|
||||
|
||||
if (status.getError()) {
|
||||
Log.i(tag, "connect() error: " + status.getError() + ": " + status.getMessage())
|
||||
}
|
||||
@@ -139,6 +144,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
|
||||
override fun tun_builder_establish(): Int {
|
||||
Log.v(tag, "tun_builder_establish")
|
||||
val jsonVpnConfig = mService.getVpnConfig()
|
||||
|
||||
val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType")
|
||||
val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites")
|
||||
if (splitTunnelType == 1) {
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
val ipRange = IPRange(site)
|
||||
mService.addRoute(ipRange.getFrom().getHostAddress(), ipRange.getPrefix())
|
||||
}
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
val ipRangeSet = IPRangeSet.fromString("0.0.0.0/0")
|
||||
ipRangeSet.remove(IPRange("127.0.0.0/8"))
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
ipRangeSet.remove(IPRange(site))
|
||||
}
|
||||
ipRangeSet.subnets().forEach {
|
||||
mService.addRoute(it.getFrom().getHostAddress(), it.getPrefix())
|
||||
Thread.sleep(10)
|
||||
}
|
||||
mService.addRoute("2000::", 3)
|
||||
}
|
||||
|
||||
return mService.establish()!!.detachFd()
|
||||
}
|
||||
|
||||
|
||||
@@ -564,6 +564,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
return parseData
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a Wireguard [Config] from a [json] string -
|
||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
||||
@@ -571,29 +572,67 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
private fun buildWireguardConfig(obj: JSONObject, type: String): Config {
|
||||
val confBuilder = Config.Builder()
|
||||
val wireguardConfigData = obj.getJSONObject(type)
|
||||
val splitTunnelType = obj.getInt("splitTunnelType")
|
||||
val splitTunnelSites = obj.getJSONArray("splitTunnelSites")
|
||||
|
||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||
val peerBuilder = Peer.Builder()
|
||||
val peerConfig = config["Peer"]!!
|
||||
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
||||
peerConfig["PresharedKey"]?.let {
|
||||
peerBuilder.setPreSharedKey(Key.fromBase64(it))
|
||||
peerConfig["PresharedKey"]?.let { peerBuilder.setPreSharedKey(Key.fromBase64(it)) }
|
||||
|
||||
val allIpString = peerConfig["AllowedIPs"]
|
||||
|
||||
var allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
|
||||
/* default value in template */
|
||||
if (allIpString == "0.0.0.0/0, ::/0") {
|
||||
allowedIPList = emptyList()
|
||||
}
|
||||
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
if (allowedIPList.isEmpty()) {
|
||||
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
|
||||
if (allowedIPList.isEmpty() && (splitTunnelType == 0)) {
|
||||
/* AllowedIP is empty and splitTunnel is turnoff */
|
||||
/* use VPN for whole Internet */
|
||||
val internetV4 = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internetV4)
|
||||
val internetV6 = InetNetwork.parse("::/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internetV6)
|
||||
} else {
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
if (!allowedIPList.isEmpty()) {
|
||||
/* We have predefined AllowedIP in WG config */
|
||||
/* It's have higher priority than system SplitTunnel */
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
}
|
||||
} else {
|
||||
if (splitTunnelType == 1) {
|
||||
/* Use system SplitTunnel */
|
||||
/* VPN connection used only for defined IPs */
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
val internet = InetNetwork.parse(site)
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
}
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
/* Use system SplitTunnel */
|
||||
/* VPN connection used for all Internet exclude defined IPs */
|
||||
val ipRangeSet = IPRangeSet.fromString("0.0.0.0/0")
|
||||
ipRangeSet.remove(IPRange("127.0.0.0/8"))
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
ipRangeSet.remove(IPRange(site))
|
||||
}
|
||||
val allowedIps = ipRangeSet.subnets().joinToString(", ") + ", 2000::/3"
|
||||
peerBuilder.parseAllowedIPs(allowedIps)
|
||||
}
|
||||
}
|
||||
}
|
||||
val endpointConfig = peerConfig["Endpoint"]
|
||||
val endpoint = InetEndpoint.parse(endpointConfig)
|
||||
peerBuilder.setEndpoint(endpoint)
|
||||
peerConfig["PersistentKeepalive"]?.let {
|
||||
peerBuilder.setPersistentKeepalive(it.toInt())
|
||||
}
|
||||
peerConfig["PersistentKeepalive"]?.let { peerBuilder.setPersistentKeepalive(it.toInt()) }
|
||||
confBuilder.addPeer(peerBuilder.build())
|
||||
|
||||
val ifaceBuilder = Interface.Builder()
|
||||
@@ -603,7 +642,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
ifaceConfig["DNS"]!!.split(",").forEach {
|
||||
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
||||
}
|
||||
|
||||
|
||||
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
||||
if (type == "awg_config_data") {
|
||||
ifaceBuilder.parseJc(ifaceConfig["Jc"])
|
||||
@@ -624,14 +663,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
ifaceBuilder.parseH1("0")
|
||||
ifaceBuilder.parseH2("0")
|
||||
ifaceBuilder.parseH3("0")
|
||||
ifaceBuilder.parseH4("0")
|
||||
|
||||
ifaceBuilder.parseH4("0")
|
||||
}
|
||||
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
val appName = jExcludedApplication.get(it).toString()
|
||||
ifaceBuilder.excludeApplication(appName)
|
||||
}*/
|
||||
}*/
|
||||
confBuilder.setInterface(ifaceBuilder.build())
|
||||
|
||||
return confBuilder.build()
|
||||
@@ -746,13 +784,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
|
||||
private fun startWireGuard(type: String) {
|
||||
val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data")
|
||||
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.e(tag, "Tunnel already up")
|
||||
// Turn the tunnel down because this might be a switch
|
||||
GoBackend.wgTurnOff(currentTunnelHandle)
|
||||
}
|
||||
val wgConfig: String = wireguard_conf.toWgUserspaceString()
|
||||
|
||||
val builder = Builder()
|
||||
setupBuilder(wireguard_conf, builder)
|
||||
builder.setSession("Amnezia")
|
||||
|
||||
@@ -10,11 +10,10 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *pa
|
||||
{
|
||||
}
|
||||
|
||||
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
||||
{
|
||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // AWGCONFIGURATOR_H
|
||||
|
||||
@@ -83,7 +83,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
||||
{
|
||||
ServerController serverController(m_settings);
|
||||
QString config =
|
||||
@@ -113,6 +113,8 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
||||
QJsonObject jConfig;
|
||||
jConfig[config_key::config] = config;
|
||||
|
||||
clientId = connData.clientId;
|
||||
|
||||
return QJsonDocument(jConfig).toJson();
|
||||
}
|
||||
|
||||
@@ -131,10 +133,13 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
// no redirect-gateway
|
||||
}
|
||||
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#ifndef Q_OS_ANDROID
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
#endif
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
config.append("block-ipv6\n");
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
};
|
||||
|
||||
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
||||
|
||||
QString processConfigWithLocalSettings(QString jsonConfig);
|
||||
QString processConfigWithExportSettings(QString jsonConfig);
|
||||
|
||||
@@ -28,11 +28,11 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *pa
|
||||
}
|
||||
|
||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
|
||||
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
|
||||
{
|
||||
switch (proto) {
|
||||
case Proto::OpenVpn:
|
||||
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, errorCode);
|
||||
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
case Proto::ShadowSocks:
|
||||
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
|
||||
@@ -40,10 +40,10 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
|
||||
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Proto::WireGuard:
|
||||
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
case Proto::Awg:
|
||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode);
|
||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
|
||||
class OpenVpnConfigurator;
|
||||
class ShadowSocksConfigurator;
|
||||
class CloakConfigurator;
|
||||
@@ -16,14 +15,15 @@ class SshConfigurator;
|
||||
class AwgConfigurator;
|
||||
|
||||
// Retrieve connection settings from server
|
||||
class VpnConfigurator : ConfiguratorBase
|
||||
class VpnConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
|
||||
const QJsonObject &containerConfig, Proto proto, QString &clientId,
|
||||
ErrorCode *errorCode = nullptr);
|
||||
|
||||
QPair<QString, QString> getDnsForConfig(int serverIndex);
|
||||
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
@@ -32,8 +32,8 @@ public:
|
||||
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
|
||||
// workaround for containers which is not support normal configuration
|
||||
void updateContainerConfigAfterInstallation(DockerContainer container,
|
||||
QJsonObject &containerConfig, const QString &stdOut);
|
||||
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
||||
const QString &stdOut);
|
||||
|
||||
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
|
||||
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
|
||||
@@ -42,6 +42,10 @@ public:
|
||||
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
|
||||
std::shared_ptr<SshConfigurator> sshConfigurator;
|
||||
std::shared_ptr<AwgConfigurator> awgConfigurator;
|
||||
|
||||
signals:
|
||||
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
|
||||
ServerCredentials credentials);
|
||||
};
|
||||
|
||||
#endif // VPN_CONFIGURATOR_H
|
||||
|
||||
@@ -177,7 +177,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
||||
{
|
||||
ServerController serverController(m_settings);
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
@@ -205,6 +205,8 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
|
||||
jConfig[config_key::psk_key] = connData.pskKey;
|
||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||
|
||||
clientId = connData.clientPubKey;
|
||||
|
||||
return QJsonDocument(jConfig).toJson();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
};
|
||||
|
||||
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
||||
|
||||
QString processConfigWithLocalSettings(QString config);
|
||||
QString processConfigWithExportSettings(QString config);
|
||||
|
||||
@@ -36,7 +36,7 @@ enum ErrorCode
|
||||
ServerPacketManagerError,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
|
||||
SshRequestDeniedError, SshInterruptedError, SshInternalError,
|
||||
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
|
||||
|
||||
// Ssh sftp errors
|
||||
@@ -47,7 +47,6 @@ enum ErrorCode
|
||||
SshSftpNoMediaError,
|
||||
|
||||
// Local errors
|
||||
FailedToSaveConfigData,
|
||||
OpenVpnConfigMissing,
|
||||
OpenVpnManagementServerError,
|
||||
ConfigMissing,
|
||||
@@ -67,7 +66,6 @@ enum ErrorCode
|
||||
|
||||
// 3rd party utils errors
|
||||
OpenSslFailed,
|
||||
OpenVpnExecutableCrashed,
|
||||
ShadowSocksExecutableCrashed,
|
||||
CloakExecutableCrashed,
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ QString errorString(ErrorCode code){
|
||||
case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
|
||||
|
||||
// Libssh errors
|
||||
case(SshRequsetDeniedError): return QObject::tr("Ssh request was denied");
|
||||
case(SshRequestDeniedError): return QObject::tr("Ssh request was denied");
|
||||
case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
|
||||
case(SshInternalError): return QObject::tr("Ssh internal error");
|
||||
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
|
||||
@@ -42,7 +42,6 @@ QString errorString(ErrorCode code){
|
||||
case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
|
||||
|
||||
// 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");
|
||||
|
||||
|
||||
@@ -167,11 +167,8 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
return ErrorCode::ServerContainerMissingError;
|
||||
}
|
||||
|
||||
runScript(credentials,
|
||||
replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||
|
||||
runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||
|
||||
runScript(credentials,
|
||||
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ namespace libssh {
|
||||
|
||||
switch (errorCode) {
|
||||
case(SSH_NO_ERROR): return ErrorCode::NoError;
|
||||
case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError;
|
||||
case(SSH_REQUEST_DENIED): return ErrorCode::SshRequestDeniedError;
|
||||
case(SSH_EINTR): return ErrorCode::SshInterruptedError;
|
||||
case(SSH_FATAL): return ErrorCode::SshInternalError;
|
||||
default: return ErrorCode::SshInternalError;
|
||||
|
||||
6
client/images/controls/close.svg
Normal file
6
client/images/controls/close.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="x, ×, close">
|
||||
<path id="Vector" d="M18 6L6 18" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M6 6L18 18" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
6
client/images/controls/search.svg
Normal file
6
client/images/controls/search.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="search">
|
||||
<path id="Vector" d="M11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M21.0004 20.9984L16.6504 16.6484" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
@@ -115,8 +115,12 @@ void LocalSocketController::daemonConnected() {
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QString protocolName = rawConfig.value("protocol").toString();
|
||||
|
||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
|
||||
|
||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||
|
||||
QJsonObject json;
|
||||
@@ -137,23 +141,79 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QJsonArray jsAllowedIPAddesses;
|
||||
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray();
|
||||
QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(","));
|
||||
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||
// Use AllowedIP list from WG config bacouse of higer priority
|
||||
|
||||
for (auto v : plainAllowedIP) {
|
||||
QString ipRange = v.toString();
|
||||
qDebug() << "ipRange " << ipRange;
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Use APP split tunnel
|
||||
if (splitTunnelType == 0 || splitTunnelType == 2) {
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
}
|
||||
|
||||
if (splitTunnelType == 1) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
qDebug() << "ipRange " << ipRange;
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||
if (splitTunnelType == 2) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
jsExcludedAddresses.append(ipRange);
|
||||
}
|
||||
}
|
||||
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ struct MessageKey
|
||||
static const char *host;
|
||||
static const char *port;
|
||||
static const char *isOnDemand;
|
||||
static const char *SplitTunnelType;
|
||||
static const char *SplitTunnelSites;
|
||||
};
|
||||
|
||||
class IosController : public QObject
|
||||
|
||||
@@ -29,6 +29,9 @@ const char* MessageKey::errorCode = "errorCode";
|
||||
const char* MessageKey::host = "host";
|
||||
const char* MessageKey::port = "port";
|
||||
const char* MessageKey::isOnDemand = "is-on-demand";
|
||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
|
||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||
switch (status) {
|
||||
@@ -351,6 +354,13 @@ void IosController::startTunnel()
|
||||
{
|
||||
m_rxBytes = 0;
|
||||
m_txBytes = 0;
|
||||
|
||||
int STT = m_rawConfig["splitTunnelType"].toInt();
|
||||
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
|
||||
QJsonDocument doc;
|
||||
doc.setArray(splitTunnelSites);
|
||||
QString STS(doc.toJson());
|
||||
|
||||
[m_currentTunnel setEnabled:YES];
|
||||
|
||||
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
||||
@@ -376,8 +386,15 @@ void IosController::startTunnel()
|
||||
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
|
||||
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
||||
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
||||
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
|
||||
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
|
||||
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
|
||||
NSString *SplitTunnelSitesValue = STS.toNSString();
|
||||
|
||||
|
||||
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
|
||||
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
|
||||
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
|
||||
|
||||
sendVpnExtensionMessage(message);
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Constants {
|
||||
static let ovpnConfigKey = "ovpn"
|
||||
static let wireGuardConfigKey = "wireguard"
|
||||
static let loggerTag = "NET"
|
||||
|
||||
|
||||
static let kActionStart = "start"
|
||||
static let kActionRestart = "restart"
|
||||
static let kActionStop = "stop"
|
||||
@@ -29,6 +29,8 @@ struct Constants {
|
||||
static let kMessageKeyHost = "host"
|
||||
static let kMessageKeyPort = "port"
|
||||
static let kMessageKeyOnDemand = "is-on-demand"
|
||||
static let kMessageKeySplitTunnelType = "SplitTunnelType"
|
||||
static let kMessageKeySplitTunnelSites = "SplitTunnelSites"
|
||||
}
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
@@ -38,7 +40,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
wg_log(logLevel.osLogLevel, message: message)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||
let adapter = OpenVPNAdapter()
|
||||
adapter.delegate = self
|
||||
@@ -49,9 +51,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||
|
||||
private var openVPNConfig: Data? = nil
|
||||
|
||||
private var SplitTunnelType: String? = nil
|
||||
private var SplitTunnelSites: String? = nil
|
||||
|
||||
let vpnReachability = OpenVPNReachability()
|
||||
|
||||
|
||||
var startHandler: ((Error?) -> Void)?
|
||||
var stopHandler: (() -> Void)?
|
||||
var protoType: TunnelProtoType = .none
|
||||
@@ -63,26 +67,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
|
||||
let tmpStr = String(data: messageData, encoding: .utf8)!
|
||||
wg_log(.error, message: tmpStr)
|
||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||
Logger.global?.log(message: "Failed to serialize message from app")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let completionHandler = completionHandler else {
|
||||
Logger.global?.log(message: "Missing message completion handler")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||
Logger.global?.log(message: "Missing action key in app message")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if action == Constants.kActionStatus {
|
||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
if action == Constants.kActionStart {
|
||||
SplitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
|
||||
SplitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
|
||||
}
|
||||
|
||||
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
|
||||
//let tunnelId = self.tunnelConfig?.id ?? ""
|
||||
let response: [String: Any] = [
|
||||
@@ -90,11 +102,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
||||
Constants.kMessageKeyTunnelId: 0
|
||||
]
|
||||
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchQueue.async {
|
||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||
@@ -118,8 +130,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
switch self.protoType {
|
||||
case .wireguard:
|
||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
||||
errorNotifier: errorNotifier,
|
||||
completionHandler: completionHandler)
|
||||
errorNotifier: errorNotifier,
|
||||
completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
self.startOpenVPN(completionHandler: completionHandler)
|
||||
case .shadowsocks:
|
||||
@@ -156,7 +168,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||
case .shadowsocks:
|
||||
break
|
||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
||||
case .none:
|
||||
break
|
||||
|
||||
@@ -168,12 +180,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
errorNotifier: ErrorNotifier,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let wgConfigStr = String(data: wgConfig, encoding: .utf8)!
|
||||
|
||||
@@ -182,7 +195,49 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") == "0.0.0.0/0, ::/0") {
|
||||
if (SplitTunnelType == "1") {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
let STSdata = Data(SplitTunnelSites!.utf8)
|
||||
do {
|
||||
let STSArray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
|
||||
for allowedIPString in STSArray {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
wg_log(.error,message: "Parse JSONSerialization Error")
|
||||
}
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else {
|
||||
if (SplitTunnelType == "2")
|
||||
{
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
let STSdata = Data(SplitTunnelSites!.utf8)
|
||||
do {
|
||||
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
|
||||
for excludeIPString in STSarray {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error,message: "Parse JSONSerialization Error")
|
||||
}
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
@@ -193,30 +248,30 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
|
||||
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
|
||||
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
|
||||
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
|
||||
|
||||
case .invalidState:
|
||||
// Must never happen
|
||||
fatalError()
|
||||
@@ -226,27 +281,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
|
||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
// TODO: handle errors properly
|
||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||
return
|
||||
}
|
||||
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
|
||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
@@ -263,7 +318,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
|
||||
|
||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
@@ -278,8 +333,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
for component in components{
|
||||
let pair = component.components(separatedBy: "=")
|
||||
if pair.count == 2 {
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
@@ -309,7 +364,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
@@ -318,7 +373,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings = settings {
|
||||
@@ -334,76 +389,76 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes" : bytesin,
|
||||
"tx_bytes" : bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes" : bytesin,
|
||||
"tx_bytes" : bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO review
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
||||
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if(str.contains("cloak")){
|
||||
configuration.setPTCloak();
|
||||
}
|
||||
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
|
||||
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !evaluation.autologin {
|
||||
wg_log(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
}
|
||||
|
||||
|
||||
// MARK: -- Network observing methods
|
||||
|
||||
|
||||
private func startListeningForNetworkChanges() {
|
||||
stopListeningForNetworkChanges()
|
||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||
}
|
||||
|
||||
|
||||
private func stopListeningForNetworkChanges() {
|
||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
||||
}
|
||||
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?) {
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?) {
|
||||
guard Constants.kDefaultPathKey != keyPath else { return }
|
||||
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
|
||||
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
|
||||
@@ -412,28 +467,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
|
||||
let defPath = defaultPath,
|
||||
lastPath != defPath || lastPath.description != defPath.description else {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let `self` = self, self.defaultPath != nil else { return }
|
||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "Tunnel restarted.")
|
||||
startTunnel(options: nil, completionHandler: completion)
|
||||
}
|
||||
|
||||
|
||||
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
|
||||
|
||||
let emptyTunnelConfiguration = TunnelConfiguration(
|
||||
name: nil,
|
||||
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||
peers: []
|
||||
)
|
||||
|
||||
|
||||
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||
self.dispatchQueue.async {
|
||||
if let error {
|
||||
@@ -445,9 +500,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
|
||||
|
||||
self.setTunnelNetworkSettings(settings) { error in
|
||||
completionHandler(error)
|
||||
}
|
||||
@@ -478,6 +533,50 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// send empty string to NEDNSSettings.matchDomains
|
||||
networkSettings?.dnsSettings?.matchDomains = [""]
|
||||
|
||||
if (SplitTunnelType == "1") {
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
let STSdata = Data(SplitTunnelSites!.utf8)
|
||||
do {
|
||||
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
|
||||
for allowedIPString in STSarray {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString){
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allowedIP.address)", subnetMask: "\(allowedIP.subnetMask())"))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error,message: "Parse JSONSerialization Error")
|
||||
}
|
||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||
} else {
|
||||
if (SplitTunnelType == "2")
|
||||
{
|
||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||
let STSdata = Data(SplitTunnelSites!.utf8)
|
||||
do {
|
||||
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
|
||||
for excludeIPString in STSarray {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
ipv4ExcludedRoutes.append(NEIPv4Route(destinationAddress: "\(excludeIP.address)", subnetMask: "\(excludeIP.subnetMask())"))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error,message: "Parse JSONSerialization Error")
|
||||
}
|
||||
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0"){
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allIPv4.address)", subnetMask: "\(allIPv4.subnetMask())"))
|
||||
}
|
||||
if let allIPv6 = IPAddressRange(from: "::/0") {
|
||||
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(allIPv6.address)", networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
|
||||
}
|
||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
|
||||
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set the network settings for the current tunneling session.
|
||||
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
|
||||
}
|
||||
@@ -538,7 +637,7 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
wg_log(.info, message: logMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension WireGuardLogLevel {
|
||||
var osLogLevel: OSLogType {
|
||||
switch self {
|
||||
|
||||
@@ -158,15 +158,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||
return false;
|
||||
}
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 1);
|
||||
}
|
||||
|
||||
if (rtm->rtm_type == RTN_THROW) {
|
||||
int index = if_nametoindex(getgatewayandiface().toUtf8());
|
||||
if (index <= 0) {
|
||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
||||
struct in_addr ip4;
|
||||
inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4);
|
||||
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||
rtm->rtm_type = RTN_UNICAST;
|
||||
}
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
@@ -334,7 +334,7 @@ QString LinuxRouteMonitor::getgatewayandiface()
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return interface;
|
||||
return gateway_address;
|
||||
}
|
||||
|
||||
static bool buildAllowedIp(wg_allowedip* ip,
|
||||
|
||||
@@ -236,6 +236,17 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.m_excludedAddresses.empty()) {
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
logger.debug() << "range: " << i;
|
||||
|
||||
if (!allowTrafficToRange(i, HIGH_WEIGHT,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||
@@ -411,8 +422,8 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||
int weight, const QString& title,
|
||||
const QString& peer) {
|
||||
int weight, const QString& title,
|
||||
const QString& peer) {
|
||||
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||
GUID layerOut =
|
||||
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
@@ -473,6 +484,57 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title,
|
||||
const QString& peer) {
|
||||
QString description("Allow traffic %1 %2 ");
|
||||
|
||||
auto lower = addr.address();
|
||||
auto upper = addr.broadcastAddress();
|
||||
|
||||
const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
|
||||
const GUID layerKeyOut =
|
||||
isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
|
||||
// Assemble the Filter base
|
||||
FWPM_FILTER0 filter;
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
filter.action.type = FWP_ACTION_PERMIT;
|
||||
filter.weight.type = FWP_UINT8;
|
||||
filter.weight.uint8 = weight;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
|
||||
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
||||
FWP_RANGE0 ipRange;
|
||||
QByteArray lowIpV6Buffer;
|
||||
QByteArray highIpV6Buffer;
|
||||
|
||||
importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
|
||||
importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
|
||||
|
||||
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||
cond[0].matchType = FWP_MATCH_RANGE;
|
||||
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
||||
cond[0].conditionValue.rangeValue = &ipRange;
|
||||
|
||||
filter.numFilterConditions = 1;
|
||||
filter.filterCondition = cond;
|
||||
|
||||
filter.layerKey = layerKeyOut;
|
||||
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
||||
peer)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerKeyIn;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("from").arg(addr.toString()), peer)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
// Allow outbound DHCPv4
|
||||
{
|
||||
|
||||
@@ -52,6 +52,9 @@ class WindowsFirewall final : public QObject {
|
||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||
const QString& title, const QString& peer = QString());
|
||||
bool allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title,
|
||||
const QString& peer);
|
||||
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
const QString& title);
|
||||
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace amnezia
|
||||
constexpr char server_priv_key[] = "server_priv_key";
|
||||
constexpr char server_pub_key[] = "server_pub_key";
|
||||
constexpr char psk_key[] = "psk_key";
|
||||
constexpr char allowed_ips[] = "allowed_ips";
|
||||
|
||||
constexpr char client_ip[] = "client_ip"; // internal ip address
|
||||
|
||||
@@ -78,6 +79,9 @@ namespace amnezia
|
||||
constexpr char sftp[] = "sftp";
|
||||
constexpr char awg[] = "awg";
|
||||
|
||||
constexpr char splitTunnelSites[] = "splitTunnelSites";
|
||||
constexpr char splitTunnelType[] = "splitTunnelType";
|
||||
|
||||
}
|
||||
|
||||
namespace protocols
|
||||
|
||||
@@ -16,8 +16,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
|
||||
writeWireguardConfiguration(configuration);
|
||||
|
||||
// MZ
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
m_impl.reset(new LocalSocketController());
|
||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||
@@ -26,7 +24,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
||||
m_impl->initialize(nullptr, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
WireguardProtocol::~WireguardProtocol()
|
||||
@@ -37,68 +34,10 @@ WireguardProtocol::~WireguardProtocol()
|
||||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
stopMzImpl();
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (!QFileInfo::exists(Utils::wireguardExecPath())) {
|
||||
qCritical() << "Wireguard executable missing!";
|
||||
setLastError(ErrorCode::ExecutableMissing);
|
||||
return;
|
||||
}
|
||||
|
||||
m_wireguardStopProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!m_wireguardStopProcess) {
|
||||
qCritical() << "IpcProcess replica is not created!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
m_wireguardStopProcess->waitForSource(1000);
|
||||
if (!m_wireguardStopProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
m_wireguardStopProcess->setProgram(PermittedProcess::Wireguard);
|
||||
|
||||
m_wireguardStopProcess->setArguments(stopArgs());
|
||||
qDebug() << stopArgs().join(" ");
|
||||
|
||||
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this,
|
||||
[this](QProcess::ProcessState newState) {
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState;
|
||||
});
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (IpcClient::Interface()) {
|
||||
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->isWireguardRunning();
|
||||
if (result.returnValue()) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qCritical() << "IPC client not initialized";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_wireguardStopProcess->start();
|
||||
m_wireguardStopProcess->waitForFinished(10000);
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
{
|
||||
m_impl->activate(m_rawConfig);
|
||||
@@ -110,7 +49,6 @@ ErrorCode WireguardProtocol::stopMzImpl()
|
||||
m_impl->deactivate();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
#endif
|
||||
|
||||
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
@@ -124,21 +62,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura
|
||||
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
||||
m_configFile.close();
|
||||
|
||||
#if 0
|
||||
if (IpcClient::Interface()) {
|
||||
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
|
||||
if (result.returnValue()) {
|
||||
qCritical() << "Failed to copy wireguard config";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qCritical() << "IPC client not initialized";
|
||||
return;
|
||||
}
|
||||
m_configFileName = "/etc/wireguard/wg99.conf";
|
||||
#else
|
||||
|
||||
m_configFileName = m_configFile.fileName();
|
||||
#endif
|
||||
|
||||
m_isConfigLoaded = true;
|
||||
|
||||
@@ -152,15 +77,9 @@ QString WireguardProtocol::configPath() const
|
||||
return m_configFileName;
|
||||
}
|
||||
|
||||
void WireguardProtocol::updateRouteGateway(QString line)
|
||||
QString WireguardProtocol::serviceName() const
|
||||
{
|
||||
// TODO: fix for macos
|
||||
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
|
||||
if (!line.contains("/"))
|
||||
return;
|
||||
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
|
||||
m_routeGateway.replace(" ", "");
|
||||
qDebug() << "Set VPN route gateway" << m_routeGateway;
|
||||
return "AmneziaVPN.WireGuard0";
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::start()
|
||||
@@ -170,112 +89,6 @@ ErrorCode WireguardProtocol::start()
|
||||
return lastError();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
return startMzImpl();
|
||||
#endif
|
||||
|
||||
if (!QFileInfo::exists(Utils::wireguardExecPath())) {
|
||||
setLastError(ErrorCode::ExecutableMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
if (IpcClient::Interface()) {
|
||||
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->isWireguardConfigExists(configPath());
|
||||
if (result.returnValue()) {
|
||||
setLastError(ErrorCode::ConfigMissing);
|
||||
return lastError();
|
||||
}
|
||||
} else {
|
||||
qCritical() << "IPC client not initialized";
|
||||
setLastError(ErrorCode::InternalError);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
|
||||
m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!m_wireguardStartProcess) {
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_wireguardStartProcess->waitForSource(1000);
|
||||
if (!m_wireguardStartProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_wireguardStartProcess->setProgram(PermittedProcess::Wireguard);
|
||||
|
||||
m_wireguardStartProcess->setArguments(startArgs());
|
||||
qDebug() << startArgs().join(" ");
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this,
|
||||
[this](QProcess::ProcessState newState) {
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState;
|
||||
});
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this,
|
||||
[this]() { setConnectionState(Vpn::ConnectionState::Connected); });
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() {
|
||||
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAll();
|
||||
reply.waitForFinished(1000);
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol readyRead" << reply.returnValue();
|
||||
});
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardOutput, this, [this]() {
|
||||
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAllStandardOutput();
|
||||
reply.waitForFinished(1000);
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardOutput" << reply.returnValue();
|
||||
});
|
||||
|
||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardError, this, [this]() {
|
||||
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAllStandardError();
|
||||
reply.waitForFinished(10);
|
||||
qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardError" << reply.returnValue();
|
||||
});
|
||||
|
||||
m_wireguardStartProcess->start();
|
||||
m_wireguardStartProcess->waitForFinished(10000);
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void WireguardProtocol::updateVpnGateway(const QString &line)
|
||||
{
|
||||
}
|
||||
|
||||
QString WireguardProtocol::serviceName() const
|
||||
{
|
||||
return "AmneziaVPN.WireGuard0";
|
||||
}
|
||||
|
||||
QStringList WireguardProtocol::stopArgs()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return { "--remove", configPath() };
|
||||
#elif defined Q_OS_LINUX
|
||||
return { "down", "wg99" };
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList WireguardProtocol::startArgs()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return { "--add", configPath() };
|
||||
#elif defined Q_OS_LINUX
|
||||
return { "up", "wg99" };
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "vpnprotocol.h"
|
||||
#include "core/ipcclient.h"
|
||||
|
||||
#include "mozilla/controllerimpl.h"
|
||||
|
||||
@@ -23,33 +22,21 @@ public:
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
ErrorCode startMzImpl();
|
||||
ErrorCode stopMzImpl();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QString configPath() const;
|
||||
void writeWireguardConfiguration(const QJsonObject &configuration);
|
||||
|
||||
void updateRouteGateway(QString line);
|
||||
void updateVpnGateway(const QString &line);
|
||||
QString serviceName() const;
|
||||
QStringList stopArgs();
|
||||
QStringList startArgs();
|
||||
|
||||
private:
|
||||
QString m_configFileName;
|
||||
QFile m_configFile;
|
||||
|
||||
QSharedPointer<PrivilegedProcess> m_wireguardStartProcess;
|
||||
QSharedPointer<PrivilegedProcess> m_wireguardStopProcess;
|
||||
|
||||
bool m_isConfigLoaded = false;
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // WIREGUARDPROTOCOL_H
|
||||
|
||||
@@ -222,5 +222,8 @@
|
||||
<file>server_scripts/awg/configure_container.sh</file>
|
||||
<file>server_scripts/awg/run_container.sh</file>
|
||||
<file>server_scripts/awg/Dockerfile</file>
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>images/controls/close.svg</file>
|
||||
<file>images/controls/search.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\
|
||||
elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
|
||||
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi
|
||||
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); docker_pkg="docker.io"; dist="debian";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); docker_pkg="docker"; dist="fedora";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); docker_pkg="docker"; dist="centos";\
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="--noconfirm -S"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\
|
||||
else echo "Packet manager not found"; exit 1; fi;\
|
||||
echo "Dist: $dist, Packet manager: $pm, Docker pkg: $docker_pkg";\
|
||||
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
|
||||
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo; fi;\
|
||||
if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\
|
||||
if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\
|
||||
if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
|
||||
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
|
||||
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
|
||||
if ! command -v docker > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||
if [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\
|
||||
fi;\
|
||||
if [ "$dist" = "debian" ]; then \
|
||||
docker_service=$(systemctl list-units --full --all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\
|
||||
if [ -z "$docker_service" ]; then sudo $pm update -yq; sudo $pm install -yq curl $docker_pkg; fi;\
|
||||
if [ -z "$docker_service" ]; then sudo $pm $check_pkgs; sudo $pm $silent_inst curl $docker_pkg; fi;\
|
||||
sleep 3 && sudo systemctl start docker && sleep 3;\
|
||||
fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker";exit 1;fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker"; exit 1; fi;\
|
||||
docker --version
|
||||
|
||||
@@ -233,10 +233,6 @@ QString Settings::routeModeString(RouteMode mode) const
|
||||
|
||||
Settings::RouteMode Settings::routeMode() const
|
||||
{
|
||||
// TODO implement for mobiles
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
return RouteMode::VpnAllSites;
|
||||
#endif
|
||||
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
|
||||
}
|
||||
|
||||
|
||||
@@ -686,7 +686,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<location filename="../ui/qml/Pages2/PageServiceDnsSettings.qml" line="52"/>
|
||||
<source>A DNS service is installed on your server, and it is only accessible via VPN.
|
||||
</source>
|
||||
<translation>На вашем сервере устанавливается DNS-сервис, доступ к нему возможен только через VPN.
|
||||
<translation>На вашем сервере установлен DNS-сервис, доступ к нему возможен только через VPN.
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1332,7 +1332,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
|
||||
<source>Clear Amnezia cache</source>
|
||||
<translation>Очистить кэш Amnezia на сервере</translation>
|
||||
<translation>Очистить кэш Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <QImage>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "configurators/cloak_configurator.h"
|
||||
#include "configurators/openvpn_configurator.h"
|
||||
#include "configurators/shadowsocks_configurator.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
#include "core/errorstrings.h"
|
||||
#include "systemController.h"
|
||||
@@ -19,11 +21,13 @@
|
||||
|
||||
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel,
|
||||
const QSharedPointer<ContainersModel> &containersModel,
|
||||
const QSharedPointer<ClientManagementModel> &clientManagementModel,
|
||||
const std::shared_ptr<Settings> &settings,
|
||||
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_serversModel(serversModel),
|
||||
m_containersModel(containersModel),
|
||||
m_clientManagementModel(clientManagementModel),
|
||||
m_settings(settings),
|
||||
m_configurator(configurator)
|
||||
{
|
||||
@@ -75,13 +79,12 @@ void ExportController::generateFullAccessConfigAndroid()
|
||||
}
|
||||
#endif
|
||||
|
||||
void ExportController::generateConnectionConfig()
|
||||
void ExportController::generateConnectionConfig(const QString &clientName)
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
|
||||
ServerCredentials credentials =
|
||||
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
|
||||
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QModelIndex containerModelIndex = m_containersModel->index(container);
|
||||
@@ -93,17 +96,25 @@ void ExportController::generateConnectionConfig()
|
||||
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||
QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol);
|
||||
|
||||
QString vpnConfig =
|
||||
m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode);
|
||||
QString clientId;
|
||||
QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol,
|
||||
clientId, &errorCode);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
protocolConfig.insert(config_key::last_config, vpnConfig);
|
||||
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
|
||||
if (protocol == Proto::OpenVpn || protocol == Proto::Awg || protocol == Proto::WireGuard) {
|
||||
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject config = m_settings->server(serverIndex);
|
||||
QJsonObject config = m_settings->server(serverIndex); // todo change to servers_model
|
||||
if (!errorCode) {
|
||||
config.remove(config_key::userName);
|
||||
config.remove(config_key::password);
|
||||
@@ -126,7 +137,127 @@ void ExportController::generateConnectionConfig()
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateOpenVpnConfig()
|
||||
void ExportController::generateOpenVpnConfig(const QString &clientName)
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
|
||||
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QModelIndex containerModelIndex = m_containersModel->index(container);
|
||||
QJsonObject containerConfig =
|
||||
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
|
||||
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString clientId;
|
||||
QString config = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig,
|
||||
clientId, &errorCode);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config);
|
||||
|
||||
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
|
||||
m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8());
|
||||
|
||||
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateWireGuardConfig(const QString &clientName)
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
|
||||
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QModelIndex containerModelIndex = m_containersModel->index(container);
|
||||
QJsonObject containerConfig =
|
||||
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
|
||||
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
QString clientId;
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig,
|
||||
clientId, &errorCode);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config);
|
||||
|
||||
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
|
||||
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||
|
||||
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateShadowSocksConfig()
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
|
||||
ServerCredentials credentials =
|
||||
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QModelIndex containerModelIndex = m_containersModel->index(container);
|
||||
QJsonObject containerConfig =
|
||||
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
|
||||
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString config = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container,
|
||||
containerConfig, &errorCode);
|
||||
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::ShadowSocks, config);
|
||||
QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
|
||||
QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
|
||||
m_nativeConfigString =
|
||||
QString("%1:%2@%3:%4")
|
||||
.arg(configJson.value("method").toString(), configJson.value("password").toString(),
|
||||
configJson.value("server").toString(), configJson.value("server_port").toString());
|
||||
|
||||
m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64();
|
||||
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
|
||||
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateCloakConfig()
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
@@ -142,47 +273,19 @@ void ExportController::generateOpenVpnConfig()
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString config =
|
||||
m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode);
|
||||
m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &errorCode);
|
||||
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config);
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Cloak, config);
|
||||
QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
|
||||
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
configJson.remove(config_key::transport_proto);
|
||||
configJson.insert("ProxyMethod", "shadowsocks");
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateWireGuardConfig()
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
|
||||
ServerCredentials credentials =
|
||||
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QModelIndex containerModelIndex = m_containersModel->index(container);
|
||||
QJsonObject containerConfig =
|
||||
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
|
||||
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig,
|
||||
&errorCode);
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config);
|
||||
|
||||
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
|
||||
QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
@@ -195,6 +298,11 @@ QString ExportController::getConfig()
|
||||
return m_config;
|
||||
}
|
||||
|
||||
QString ExportController::getNativeConfigString()
|
||||
{
|
||||
return m_nativeConfigString;
|
||||
}
|
||||
|
||||
QList<QString> ExportController::getQrCodes()
|
||||
{
|
||||
return m_qrCodes;
|
||||
@@ -205,6 +313,30 @@ void ExportController::exportConfig(const QString &fileName)
|
||||
SystemController::saveFile(fileName, m_config);
|
||||
}
|
||||
|
||||
void ExportController::updateClientManagementModel(const DockerContainer container, ServerCredentials credentials)
|
||||
{
|
||||
ErrorCode errorCode = m_clientManagementModel->updateModel(container, credentials);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
|
||||
{
|
||||
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
|
||||
{
|
||||
ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
|
||||
{
|
||||
double k = 850;
|
||||
@@ -219,7 +351,7 @@ QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &dat
|
||||
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
|
||||
QString svg = QString::fromStdString(toSvgString(qr, 0));
|
||||
QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||
chunks.append(svgToBase64(svg));
|
||||
}
|
||||
|
||||
@@ -239,5 +371,6 @@ int ExportController::getQrCodesCount()
|
||||
void ExportController::clearPreviousConfig()
|
||||
{
|
||||
m_config.clear();
|
||||
m_nativeConfigString.clear();
|
||||
m_qrCodes.clear();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "configurators/vpn_configurator.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/authResultReceiver.h"
|
||||
#endif
|
||||
@@ -16,27 +17,36 @@ class ExportController : public QObject
|
||||
public:
|
||||
explicit ExportController(const QSharedPointer<ServersModel> &serversModel,
|
||||
const QSharedPointer<ContainersModel> &containersModel,
|
||||
const QSharedPointer<ClientManagementModel> &clientManagementModel,
|
||||
const std::shared_ptr<Settings> &settings,
|
||||
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY exportConfigChanged)
|
||||
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged)
|
||||
Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged)
|
||||
Q_PROPERTY(QString nativeConfigString READ getNativeConfigString NOTIFY exportConfigChanged)
|
||||
|
||||
public slots:
|
||||
void generateFullAccessConfig();
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void generateFullAccessConfigAndroid();
|
||||
#endif
|
||||
void generateConnectionConfig();
|
||||
void generateOpenVpnConfig();
|
||||
void generateWireGuardConfig();
|
||||
void generateConnectionConfig(const QString &clientName);
|
||||
void generateOpenVpnConfig(const QString &clientName);
|
||||
void generateWireGuardConfig(const QString &clientName);
|
||||
void generateShadowSocksConfig();
|
||||
void generateCloakConfig();
|
||||
|
||||
QString getConfig();
|
||||
QString getNativeConfigString();
|
||||
QList<QString> getQrCodes();
|
||||
|
||||
void exportConfig(const QString &fileName);
|
||||
|
||||
void updateClientManagementModel(const DockerContainer container, ServerCredentials credentials);
|
||||
void revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials);
|
||||
void renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials);
|
||||
|
||||
signals:
|
||||
void generateConfig(int type);
|
||||
void exportErrorOccurred(const QString &errorMessage);
|
||||
@@ -55,10 +65,12 @@ private:
|
||||
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
std::shared_ptr<VpnConfigurator> m_configurator;
|
||||
|
||||
QString m_config;
|
||||
QString m_nativeConfigString;
|
||||
QList<QString> m_qrCodes;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
@@ -261,6 +261,10 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
// return QJsonObject();
|
||||
// }
|
||||
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));
|
||||
|
||||
lastConfig[config_key::allowed_ips] = allowedIpsJsonArray;
|
||||
|
||||
QString protocolName = "wireguard";
|
||||
if (!configMap.value(config_key::junkPacketCount).isEmpty()
|
||||
&& !configMap.value(config_key::junkPacketMinSize).isEmpty()
|
||||
|
||||
@@ -51,7 +51,9 @@ namespace PageLoader
|
||||
PageProtocolWireGuardSettings,
|
||||
PageProtocolAwgSettings,
|
||||
PageProtocolIKev2Settings,
|
||||
PageProtocolRaw
|
||||
PageProtocolRaw,
|
||||
|
||||
PageShareFullAccess
|
||||
};
|
||||
Q_ENUM_NS(PageEnum)
|
||||
|
||||
|
||||
@@ -1,104 +1,373 @@
|
||||
#include "clientManagementModel.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
#include "core/servercontroller.h"
|
||||
#include "logger.h"
|
||||
|
||||
}
|
||||
|
||||
void ClientManagementModel::clearData()
|
||||
namespace
|
||||
{
|
||||
beginResetModel();
|
||||
m_content.clear();
|
||||
endResetModel();
|
||||
}
|
||||
Logger logger("ClientManagementModel");
|
||||
|
||||
void ClientManagementModel::setContent(const QVector<QVariant> &data)
|
||||
{
|
||||
beginResetModel();
|
||||
m_content = data;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
|
||||
{
|
||||
QJsonObject clientsTable;
|
||||
for (const auto &item : m_content) {
|
||||
if (protocol == amnezia::Proto::OpenVpn) {
|
||||
clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
|
||||
} else if (protocol == amnezia::Proto::WireGuard) {
|
||||
clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
|
||||
}
|
||||
namespace configKey {
|
||||
constexpr char clientId[] = "clientId";
|
||||
constexpr char clientName[] = "clientName";
|
||||
constexpr char container[] = "container";
|
||||
constexpr char userData[] = "userData";
|
||||
}
|
||||
return clientsTable;
|
||||
}
|
||||
|
||||
ClientManagementModel::ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: m_settings(settings), QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int ClientManagementModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return static_cast<int>(m_content.size());
|
||||
return static_cast<int>(m_clientsTable.size());
|
||||
}
|
||||
|
||||
QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0
|
||||
|| index.row() >= static_cast<int>(m_content.size())) {
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_clientsTable.size())) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
return m_content[index.row()].toJsonObject()["clientName"].toString();
|
||||
} else if (role == OpenVpnCertIdRole) {
|
||||
return m_content[index.row()].toJsonObject()["openvpnCertId"].toString();
|
||||
} else if (role == OpenVpnCertDataRole) {
|
||||
return m_content[index.row()].toJsonObject()["openvpnCertData"].toString();
|
||||
} else if (role == WireGuardPublicKey) {
|
||||
return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString();
|
||||
auto client = m_clientsTable.at(index.row()).toObject();
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
|
||||
ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0
|
||||
|| index.row() >= static_cast<int>(m_content.size())) {
|
||||
return;
|
||||
beginResetModel();
|
||||
m_clientsTable = QJsonArray();
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
const QString clientsTableFile =
|
||||
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|
||||
const QByteArray clientsTableString =
|
||||
serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the clientsTable file from the server";
|
||||
endResetModel();
|
||||
return error;
|
||||
}
|
||||
|
||||
auto client = m_content[index.row()].toJsonObject();
|
||||
if (role == NameRole) {
|
||||
client["clientName"] = data.toString();
|
||||
} else if (role == OpenVpnCertIdRole) {
|
||||
client["openvpnCertId"] = data.toString();
|
||||
} else if (role == OpenVpnCertDataRole) {
|
||||
client["openvpnCertData"] = data.toString();
|
||||
} else if (role == WireGuardPublicKey) {
|
||||
client["wireguardPublicKey"] = data.toString();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (m_content[index.row()] != client) {
|
||||
m_content[index.row()] = client;
|
||||
emit dataChanged(index, index);
|
||||
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
|
||||
|
||||
if (m_clientsTable.isEmpty()) {
|
||||
int count = 0;
|
||||
|
||||
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|
||||
|| container == DockerContainer::Cloak) {
|
||||
error = getOpenVpnClients(serverController, container, credentials, count);
|
||||
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
|
||||
error = getWireGuardClients(serverController, container, credentials, count);
|
||||
}
|
||||
if (error != ErrorCode::NoError) {
|
||||
endResetModel();
|
||||
return error;
|
||||
}
|
||||
|
||||
const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson();
|
||||
if (clientsTableString != newClientsTableString) {
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString,
|
||||
clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
return error;
|
||||
}
|
||||
|
||||
bool ClientManagementModel::removeRows(int row)
|
||||
ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
const QString getOpenVpnClientsList =
|
||||
"sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
|
||||
QString script = serverController.replaceVars(getOpenVpnClientsList,
|
||||
serverController.genVarsForScript(credentials, container));
|
||||
error = serverController.runScript(credentials, script, cbReadStdOut);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to retrieve the list of issued certificates on the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!stdOut.isEmpty()) {
|
||||
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||
certsIds.removeAll("AmneziaReq.crt");
|
||||
|
||||
for (auto &openvpnCertId : certsIds) {
|
||||
openvpnCertId.replace(".crt", "");
|
||||
if (!isClientExists(openvpnCertId)) {
|
||||
QJsonObject client;
|
||||
client[configKey::clientId] = openvpnCertId;
|
||||
|
||||
QJsonObject userData;
|
||||
userData[configKey::clientName] = QString("Client %1").arg(count);
|
||||
client[configKey::userData] = userData;
|
||||
|
||||
m_clientsTable.push_back(client);
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::getWireGuardClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
const QString wireGuardConfigFile =
|
||||
QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
|
||||
const QString wireguardConfigString =
|
||||
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the wg conf file from the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
|
||||
QStringList wireguardKeys;
|
||||
for (const auto &line : configLines) {
|
||||
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
|
||||
if (configPair.front() == "PublicKey") {
|
||||
wireguardKeys.push_back(configPair.back());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &wireguardKey : wireguardKeys) {
|
||||
if (!isClientExists(wireguardKey)) {
|
||||
QJsonObject client;
|
||||
client[configKey::clientId] = wireguardKey;
|
||||
|
||||
QJsonObject userData;
|
||||
userData[configKey::clientName] = QString("Client %1").arg(count);
|
||||
client[configKey::userData] = userData;
|
||||
|
||||
m_clientsTable.push_back(client);
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
bool ClientManagementModel::isClientExists(const QString &clientId)
|
||||
{
|
||||
for (const QJsonValue &value : qAsConst(m_clientsTable)) {
|
||||
if (value.isObject()) {
|
||||
QJsonObject obj = value.toObject();
|
||||
if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName,
|
||||
const DockerContainer container, ServerCredentials credentials)
|
||||
{
|
||||
ErrorCode error;
|
||||
|
||||
error = updateModel(container, credentials);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_clientsTable.size(); i++) {
|
||||
if (m_clientsTable.at(i).toObject().value(configKey::clientId) == clientId) {
|
||||
return renameClient(i, clientName, container, credentials);
|
||||
}
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
QJsonObject client;
|
||||
client[configKey::clientId] = clientId;
|
||||
|
||||
QJsonObject userData;
|
||||
userData[configKey::clientName] = clientName;
|
||||
client[configKey::userData] = userData;
|
||||
m_clientsTable.push_back(client);
|
||||
endResetModel();
|
||||
|
||||
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
const QString clientsTableFile =
|
||||
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|
||||
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
|
||||
ServerCredentials credentials)
|
||||
{
|
||||
auto client = m_clientsTable.at(row).toObject();
|
||||
auto userData = client[configKey::userData].toObject();
|
||||
userData[configKey::clientName] = clientName;
|
||||
client[configKey::userData] = userData;
|
||||
|
||||
m_clientsTable.replace(row, client);
|
||||
emit dataChanged(index(row, 0), index(row, 0));
|
||||
|
||||
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
const QString clientsTableFile =
|
||||
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|
||||
|
||||
ErrorCode error =
|
||||
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
|
||||
ServerCredentials credentials)
|
||||
{
|
||||
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|
||||
|| container == DockerContainer::Cloak) {
|
||||
return revokeOpenVpn(row, container, credentials);
|
||||
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
|
||||
return revokeWireGuard(row, container, credentials);
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
|
||||
ServerCredentials credentials)
|
||||
{
|
||||
auto client = m_clientsTable.at(row).toObject();
|
||||
QString clientId = client.value(configKey::clientId).toString();
|
||||
|
||||
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
|
||||
"cd /opt/amnezia/openvpn ;\\"
|
||||
"easyrsa revoke %1 ;\\"
|
||||
"easyrsa gen-crl ;\\"
|
||||
"cp pki/crl.pem .'")
|
||||
.arg(clientId);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
const QString script =
|
||||
serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
|
||||
ErrorCode error = serverController.runScript(credentials, script);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to revoke the certificate";
|
||||
return error;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_content.removeAt(row);
|
||||
m_clientsTable.removeAt(row);
|
||||
endRemoveRows();
|
||||
return true;
|
||||
|
||||
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
|
||||
|
||||
const QString clientsTableFile =
|
||||
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container,
|
||||
ServerCredentials credentials)
|
||||
{
|
||||
ErrorCode error;
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
const QString wireGuardConfigFile =
|
||||
QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
|
||||
const QString wireguardConfigString =
|
||||
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the wg conf file from the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
auto client = m_clientsTable.at(row).toObject();
|
||||
QString clientId = client.value(configKey::clientId).toString();
|
||||
|
||||
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
|
||||
for (auto §ion : configSections) {
|
||||
if (section.contains(clientId)) {
|
||||
configSections.removeOne(section);
|
||||
break;
|
||||
}
|
||||
}
|
||||
QString newWireGuardConfig = configSections.join("[");
|
||||
newWireGuardConfig.insert(0, "[");
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, wireGuardConfigFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the wg conf file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_clientsTable.removeAt(row);
|
||||
endRemoveRows();
|
||||
|
||||
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
|
||||
|
||||
const QString clientsTableFile =
|
||||
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'";
|
||||
error = serverController.runScript(
|
||||
credentials,
|
||||
serverController.replaceVars(script.arg(wireGuardConfigFile),
|
||||
serverController.genVarsForScript(credentials, container)));
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to execute the command 'wg syncconf' on the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[NameRole] = "clientName";
|
||||
roles[OpenVpnCertIdRole] = "openvpnCertId";
|
||||
roles[OpenVpnCertDataRole] = "openvpnCertData";
|
||||
roles[WireGuardPublicKey] = "wireguardPublicKey";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -2,36 +2,48 @@
|
||||
#define CLIENTMANAGEMENTMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "settings.h"
|
||||
|
||||
class ClientManagementModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ClientRoles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
OpenVpnCertIdRole,
|
||||
OpenVpnCertDataRole,
|
||||
WireGuardPublicKey,
|
||||
enum Roles {
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
};
|
||||
|
||||
ClientManagementModel(QObject *parent = nullptr);
|
||||
ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
void clearData();
|
||||
void setContent(const QVector<QVariant> &data);
|
||||
QJsonObject getContent(amnezia::Proto protocol);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole);
|
||||
bool removeRows(int row);
|
||||
|
||||
public slots:
|
||||
ErrorCode updateModel(DockerContainer container, ServerCredentials credentials);
|
||||
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
|
||||
ServerCredentials credentials);
|
||||
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
|
||||
ServerCredentials credentials);
|
||||
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QVector<QVariant> m_content;
|
||||
bool isClientExists(const QString &clientId);
|
||||
|
||||
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
|
||||
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
|
||||
|
||||
ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
|
||||
ErrorCode getWireGuardClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
|
||||
|
||||
QJsonArray m_clientsTable;
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
};
|
||||
|
||||
#endif // CLIENTMANAGEMENTMODEL_H
|
||||
|
||||
@@ -22,10 +22,6 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
|
||||
DockerContainer container = ContainerProps::allContainers().at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case NameRole:
|
||||
// return ContainerProps::containerHumanNames().value(container);
|
||||
case DescriptionRole:
|
||||
// return ContainerProps::containerDescriptions().value(container);
|
||||
case ConfigRole: {
|
||||
m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject());
|
||||
m_containers = m_settings->containers(m_currentlyProcessedServerIndex);
|
||||
@@ -35,19 +31,15 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
|
||||
break;
|
||||
}
|
||||
}
|
||||
case ServiceTypeRole:
|
||||
// return ContainerProps::containerService(container);
|
||||
case DockerContainerRole:
|
||||
// return container;
|
||||
case IsInstalledRole:
|
||||
// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container);
|
||||
case IsDefaultRole: { //todo remove
|
||||
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
|
||||
m_defaultContainerIndex = container;
|
||||
emit defaultContainerChanged();
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
emit containersModelUpdated();
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ protected:
|
||||
|
||||
signals:
|
||||
void defaultContainerChanged();
|
||||
void containersModelUpdated();
|
||||
|
||||
private:
|
||||
QMap<DockerContainer, QJsonObject> m_containers;
|
||||
|
||||
@@ -145,6 +145,11 @@ QString ServersModel::getCurrentlyProcessedServerHostName()
|
||||
return qvariant_cast<QString>(data(m_currentlyProcessedServerIndex, HostNameRole));
|
||||
}
|
||||
|
||||
const ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials()
|
||||
{
|
||||
return serverCredentials(m_currentlyProcessedServerIndex);
|
||||
}
|
||||
|
||||
bool ServersModel::isDefaultServerCurrentlyProcessed()
|
||||
{
|
||||
return m_defaultServerIndex == m_currentlyProcessedServerIndex;
|
||||
@@ -193,6 +198,12 @@ bool ServersModel::isDefaultServerConfigContainsAmneziaDns()
|
||||
return primaryDns == protocols::dns::amneziaDnsIp;
|
||||
}
|
||||
|
||||
void ServersModel::updateContainersConfig()
|
||||
{
|
||||
auto server = m_settings->server(m_currentlyProcessedServerIndex);
|
||||
m_servers.replace(m_currentlyProcessedServerIndex, server);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ServersModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
@@ -53,12 +53,15 @@ public slots:
|
||||
int getCurrentlyProcessedServerIndex();
|
||||
|
||||
QString getCurrentlyProcessedServerHostName();
|
||||
const ServerCredentials getCurrentlyProcessedServerCredentials();
|
||||
|
||||
void addServer(const QJsonObject &server);
|
||||
void removeServer();
|
||||
|
||||
bool isDefaultServerConfigContainsAmneziaDns();
|
||||
|
||||
void updateContainersConfig();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
|
||||
@@ -112,6 +112,30 @@ DrawerType {
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
visible: nativeConfigString.text !== ""
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#D7D8DB"
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Copy config string")
|
||||
imageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
onClicked: {
|
||||
nativeConfigString.selectAll()
|
||||
nativeConfigString.copy()
|
||||
nativeConfigString.select(0, 0)
|
||||
PageController.showNotificationMessage(qsTr("Copied"))
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
@@ -170,6 +194,12 @@ DrawerType {
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: nativeConfigString
|
||||
visible: false
|
||||
text: ExportController.nativeConfigString
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: configText
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -213,7 +243,6 @@ DrawerType {
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
smooth: false
|
||||
|
||||
source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : ""
|
||||
|
||||
@@ -87,6 +87,7 @@ Switch {
|
||||
id: content
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
|
||||
ListItemTitleType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -32,7 +32,7 @@ PageType {
|
||||
function onRestorePageHomeState(isContainerInstalled) {
|
||||
buttonContent.state = "expanded"
|
||||
if (isContainerInstalled) {
|
||||
containersDropDown.menuVisible = true
|
||||
containersDropDown.rootButtonClickedFunction()
|
||||
}
|
||||
}
|
||||
function onForceCloseDrawer() {
|
||||
|
||||
@@ -94,7 +94,7 @@ PageType {
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
visible: GC.isDesktop()
|
||||
visible: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ PageType {
|
||||
regularExpression: InstallController.ipAddressPortRegExp()
|
||||
}
|
||||
|
||||
onTextFieldTextChanged: {
|
||||
onFocusChanged: {
|
||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,10 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
hidePassword = !hidePassword
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
@@ -90,6 +94,7 @@ PageType {
|
||||
text: qsTr("Continue")
|
||||
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
if (!isCredentialsFilled()) {
|
||||
return
|
||||
}
|
||||
@@ -112,8 +117,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 12
|
||||
|
||||
text: qsTr("All data you enter will remain strictly confidential
|
||||
and will not be shared or disclosed to the Amnezia or any third parties")
|
||||
text: qsTr("All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,17 +28,11 @@ PageType {
|
||||
ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex())
|
||||
}
|
||||
|
||||
PageController.goToStartPage()
|
||||
PageController.closePage() // close installing page
|
||||
PageController.closePage() // close protocol settings page
|
||||
|
||||
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) {
|
||||
PageController.restorePageHomeState(true)
|
||||
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
|
||||
PageController.goToPage(PageEnum.PageSettingsServersList, false)
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo, false)
|
||||
if (isServiceInstall) {
|
||||
PageController.goToPageSettingsServerServices()
|
||||
}
|
||||
} else {
|
||||
PageController.goToPage(PageEnum.PageHome)
|
||||
}
|
||||
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
|
||||
@@ -24,7 +24,7 @@ PageType {
|
||||
}
|
||||
|
||||
function onImportFinished() {
|
||||
if (ConnectionController.isConnected) {
|
||||
if (!ConnectionController.isConnected) {
|
||||
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,28 @@ PageType {
|
||||
|
||||
enum ConfigType {
|
||||
AmneziaConnection,
|
||||
AmneziaFullAccess,
|
||||
OpenVpn,
|
||||
WireGuard
|
||||
WireGuard,
|
||||
ShadowSocks,
|
||||
Cloak
|
||||
}
|
||||
|
||||
signal revokeConfig(int index)
|
||||
onRevokeConfig: function(index) {
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.revokeConfig(index,
|
||||
ContainersModel.getCurrentlyProcessedContainerIndex(),
|
||||
ServersModel.getCurrentlyProcessedServerCredentials())
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ExportController
|
||||
|
||||
function onGenerateConfig(type) {
|
||||
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
|
||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
||||
|
||||
shareConnectionDrawer.needCloseButton = false
|
||||
|
||||
shareConnectionDrawer.open()
|
||||
@@ -34,28 +47,34 @@ PageType {
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
switch (type) {
|
||||
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break;
|
||||
case PageShare.ConfigType.AmneziaFullAccess: {
|
||||
if (Qt.platform.os === "android") {
|
||||
ExportController.generateFullAccessConfigAndroid();
|
||||
} else {
|
||||
ExportController.generateFullAccessConfig();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(clientNameTextField.textFieldText); break;
|
||||
case PageShare.ConfigType.OpenVpn: {
|
||||
ExportController.generateOpenVpnConfig();
|
||||
ExportController.generateOpenVpnConfig(clientNameTextField.textFieldText)
|
||||
shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config")
|
||||
shareConnectionDrawer.configExtension = ".ovpn"
|
||||
shareConnectionDrawer.configFileName = "amnezia_for_openvpn"
|
||||
break;
|
||||
break
|
||||
}
|
||||
case PageShare.ConfigType.WireGuard: {
|
||||
ExportController.generateWireGuardConfig();
|
||||
ExportController.generateWireGuardConfig(clientNameTextField.textFieldText)
|
||||
shareConnectionDrawer.configCaption = qsTr("Save WireGuard config")
|
||||
shareConnectionDrawer.configExtension = ".conf"
|
||||
shareConnectionDrawer.configFileName = "amnezia_for_wireguard"
|
||||
break;
|
||||
break
|
||||
}
|
||||
case PageShare.ConfigType.ShadowSocks: {
|
||||
ExportController.generateShadowSocksConfig()
|
||||
shareConnectionDrawer.configCaption = qsTr("Save ShadowSocks config")
|
||||
shareConnectionDrawer.configExtension = ".json"
|
||||
shareConnectionDrawer.configFileName = "amnezia_for_shadowsocks"
|
||||
break
|
||||
}
|
||||
case PageShare.ConfigType.Cloak: {
|
||||
ExportController.generateCloakConfig()
|
||||
shareConnectionDrawer.configCaption = qsTr("Save Cloak config")
|
||||
shareConnectionDrawer.configExtension = ".json"
|
||||
shareConnectionDrawer.configFileName = "amnezia_for_cloak"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +92,7 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
property string fullConfigServerSelectorText
|
||||
property string connectionServerSelectorText
|
||||
property bool isSearchBarVisible: false
|
||||
property bool showContent: false
|
||||
property bool shareButtonEnabled: true
|
||||
property list<QtObject> connectionTypesModel: [
|
||||
@@ -96,6 +114,16 @@ PageType {
|
||||
property string name: qsTr("WireGuard native format")
|
||||
property var type: PageShare.ConfigType.WireGuard
|
||||
}
|
||||
QtObject {
|
||||
id: shadowSocksConnectionFormat
|
||||
property string name: qsTr("ShadowSocks native format")
|
||||
property var type: PageShare.ConfigType.ShadowSocks
|
||||
}
|
||||
QtObject {
|
||||
id: cloakConnectionFormat
|
||||
property string name: qsTr("Cloak native format")
|
||||
property var type: PageShare.ConfigType.Cloak
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.top: parent.top
|
||||
@@ -119,6 +147,51 @@ PageType {
|
||||
Layout.topMargin: 24
|
||||
|
||||
headerText: qsTr("Share VPN Access")
|
||||
|
||||
actionButtonImage: "qrc:/images/controls/more-vertical.svg"
|
||||
actionButtonFunction: function() {
|
||||
shareFullAccessDrawer.open()
|
||||
}
|
||||
|
||||
DrawerType {
|
||||
id: shareFullAccessDrawer
|
||||
|
||||
width: root.width
|
||||
height: root.height * 0.45
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
|
||||
spacing: 0
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Share full access to the server and VPN")
|
||||
descriptionText: qsTr("Use for your own devices, or share with those you trust to manage the server.")
|
||||
}
|
||||
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Share")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageShareFullAccess)
|
||||
shareFullAccessDrawer.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -147,20 +220,21 @@ PageType {
|
||||
|
||||
onClicked: {
|
||||
accessTypeSelector.currentIndex = 0
|
||||
serverSelector.text = root.connectionServerSelectorText
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalRadioButton {
|
||||
checked: root.currentIndex === 1
|
||||
checked: accessTypeSelector.currentIndex === 1
|
||||
|
||||
implicitWidth: (root.width - 32) / 2
|
||||
text: qsTr("Full access")
|
||||
text: qsTr("Users")
|
||||
|
||||
onClicked: {
|
||||
accessTypeSelector.currentIndex = 1
|
||||
serverSelector.text = root.fullConfigServerSelectorText
|
||||
root.shareButtonEnabled = true
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
|
||||
ServersModel.getCurrentlyProcessedServerCredentials())
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,16 +245,30 @@ PageType {
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
text: accessTypeSelector.currentIndex === 0 ? qsTr("Share VPN access without the ability to manage the server") :
|
||||
qsTr("Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings.")
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
text: qsTr("Share VPN access without the ability to manage the server")
|
||||
color: "#878B91"
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: clientNameTextField
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
headerText: qsTr("User name")
|
||||
textFieldText: "New client"
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: serverSelector
|
||||
|
||||
signal severSelectorIndexChanged
|
||||
property int currentIndex: 0
|
||||
property int currentIndex: -1
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
@@ -207,8 +295,6 @@ PageType {
|
||||
]
|
||||
}
|
||||
|
||||
currentIndex: 0
|
||||
|
||||
clickedFunction: function() {
|
||||
handler()
|
||||
|
||||
@@ -217,22 +303,17 @@ PageType {
|
||||
serverSelector.severSelectorIndexChanged()
|
||||
}
|
||||
|
||||
if (accessTypeSelector.currentIndex !== 0) {
|
||||
shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
|
||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
|
||||
}
|
||||
serverSelector.menuVisible = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
handler()
|
||||
serverSelector.severSelectorIndexChanged()
|
||||
serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ?
|
||||
proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0
|
||||
serverSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
|
||||
function handler() {
|
||||
serverSelector.text = selectedText
|
||||
root.fullConfigServerSelectorText = selectedText
|
||||
root.connectionServerSelectorText = selectedText
|
||||
ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex)
|
||||
}
|
||||
}
|
||||
@@ -241,8 +322,6 @@ PageType {
|
||||
DropDownType {
|
||||
id: protocolSelector
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -275,22 +354,18 @@ PageType {
|
||||
currentIndex: 0
|
||||
|
||||
clickedFunction: function() {
|
||||
protocolSelectorListView.currentItem.y
|
||||
|
||||
handler()
|
||||
|
||||
protocolSelector.menuVisible = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (accessTypeSelector.currentIndex === 0) {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: serverSelector
|
||||
|
||||
function onSeverSelectorIndexChanged() {
|
||||
protocolSelectorListView.currentIndex = 0
|
||||
protocolSelectorListView.currentIndex = proxyContainersModel.mapFromSource(ContainersModel.getDefaultContainer())
|
||||
protocolSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
}
|
||||
@@ -304,13 +379,17 @@ PageType {
|
||||
}
|
||||
|
||||
protocolSelector.text = selectedText
|
||||
root.connectionServerSelectorText = serverSelector.text
|
||||
|
||||
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
|
||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
||||
ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex))
|
||||
|
||||
fillConnectionTypeModel()
|
||||
|
||||
if (accessTypeSelector.currentIndex === 1) {
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
|
||||
ServersModel.getCurrentlyProcessedServerCredentials())
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
function fillConnectionTypeModel() {
|
||||
@@ -322,6 +401,13 @@ PageType {
|
||||
root.connectionTypesModel.push(openVpnConnectionFormat)
|
||||
} else if (index === ContainerProps.containerFromString("amnezia-wireguard")) {
|
||||
root.connectionTypesModel.push(wireGuardConnectionFormat)
|
||||
} else if (index === ContainerProps.containerFromString("amnezia-shadowsocks")) {
|
||||
root.connectionTypesModel.push(openVpnConnectionFormat)
|
||||
root.connectionTypesModel.push(shadowSocksConnectionFormat)
|
||||
} else if (index === ContainerProps.containerFromString("amnezia-openvpn-cloak")) {
|
||||
root.connectionTypesModel.push(openVpnConnectionFormat)
|
||||
root.connectionTypesModel.push(shadowSocksConnectionFormat)
|
||||
root.connectionTypesModel.push(cloakConnectionFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,18 +464,235 @@ PageType {
|
||||
Layout.topMargin: 40
|
||||
|
||||
enabled: shareButtonEnabled
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
text: qsTr("Share")
|
||||
imageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
onClicked: {
|
||||
if (accessTypeSelector.currentIndex === 0) {
|
||||
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
|
||||
} else {
|
||||
ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess)
|
||||
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 1 && !root.isSearchBarVisible
|
||||
|
||||
headerText: qsTr("Users")
|
||||
actionButtonImage: "qrc:/images/controls/search.svg"
|
||||
actionButtonFunction: function() {
|
||||
root.isSearchBarVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 16
|
||||
visible: accessTypeSelector.currentIndex === 1 && root.isSearchBarVisible
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: searchTextField
|
||||
Layout.fillWidth: true
|
||||
|
||||
textFieldPlaceholderText: qsTr("Search")
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
image: "qrc:/images/controls/close.svg"
|
||||
imageColor: "#D7D8DB"
|
||||
|
||||
onClicked: function() {
|
||||
root.isSearchBarVisible = false
|
||||
searchTextField.textFieldText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: clientsListView
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 1
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyClientManagementModel
|
||||
sourceModel: ClientManagementModel
|
||||
filters: RegExpFilter {
|
||||
roleName: "clientName"
|
||||
pattern: ".*" + searchTextField.textFieldText + ".*"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: clientsListView.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.rightMargin: -16
|
||||
anchors.leftMargin: -16
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: clientName
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
clientInfoDrawer.open()
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
DrawerType {
|
||||
id: clientInfoDrawer
|
||||
|
||||
width: root.width
|
||||
height: root.height * 0.5
|
||||
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
spacing: 8
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
headerText: clientName
|
||||
descriptionText: serverSelector.text
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#D7D8DB"
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Rename")
|
||||
|
||||
onClicked: function() {
|
||||
clientNameEditDrawer.open()
|
||||
}
|
||||
|
||||
DrawerType {
|
||||
id: clientNameEditDrawer
|
||||
|
||||
width: root.width
|
||||
height: root.height * 0.35
|
||||
|
||||
onVisibleChanged: {
|
||||
if (clientNameEditDrawer.visible) {
|
||||
clientNameEditor.textField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: clientNameEditor
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Client name")
|
||||
textFieldText: clientName
|
||||
textField.maximumLength: 30
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Save")
|
||||
|
||||
onClicked: {
|
||||
if (clientNameEditor.textFieldText !== clientName) {
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.renameClient(index,
|
||||
clientNameEditor.textFieldText,
|
||||
ContainersModel.getCurrentlyProcessedContainerIndex(),
|
||||
ServersModel.getCurrentlyProcessedServerCredentials())
|
||||
PageController.showBusyIndicator(false)
|
||||
clientNameEditDrawer.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#D7D8DB"
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Revoke")
|
||||
|
||||
onClicked: function() {
|
||||
questionDrawer.headerText = qsTr("Revoke the config for a user - ") + clientName + "?"
|
||||
questionDrawer.descriptionText = qsTr("The user will no longer be able to connect to your server.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
clientInfoDrawer.close()
|
||||
root.revokeConfig(index)
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
}
|
||||
questionDrawer.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: function(mouse) {
|
||||
forceActiveFocus()
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
155
client/ui/qml/Pages2/PageShareFullAccess.qml
Normal file
155
client/ui/qml/Pages2/PageShareFullAccess.qml
Normal file
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.height
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
headerText: qsTr("Full access to the server and VPN")
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") +
|
||||
qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ")
|
||||
color: "#878B91"
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: serverSelector
|
||||
|
||||
signal severSelectorIndexChanged
|
||||
property int currentIndex: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
drawerHeight: 0.4375
|
||||
|
||||
descriptionText: qsTr("Server")
|
||||
headerText: qsTr("Server")
|
||||
|
||||
listView: ListViewWithRadioButtonType {
|
||||
id: serverSelectorListView
|
||||
|
||||
rootWidth: root.width
|
||||
imageSource: "qrc:/images/controls/check.svg"
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyServersModel
|
||||
sourceModel: ServersModel
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "hasWriteAccess"
|
||||
value: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
currentIndex: 0
|
||||
|
||||
clickedFunction: function() {
|
||||
handler()
|
||||
|
||||
if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) {
|
||||
serverSelector.currentIndex = serverSelectorListView.currentIndex
|
||||
}
|
||||
|
||||
shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
|
||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
|
||||
serverSelector.menuVisible = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
handler()
|
||||
}
|
||||
|
||||
function handler() {
|
||||
serverSelector.text = selectedText
|
||||
ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 40
|
||||
|
||||
text: qsTr("Share")
|
||||
imageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
onClicked: function() {
|
||||
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
|
||||
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
|
||||
|
||||
shareConnectionDrawer.needCloseButton = false
|
||||
|
||||
shareConnectionDrawer.open()
|
||||
shareConnectionDrawer.contentVisible = false
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
if (Qt.platform.os === "android") {
|
||||
ExportController.generateFullAccessConfigAndroid();
|
||||
} else {
|
||||
ExportController.generateFullAccessConfig();
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(false)
|
||||
|
||||
shareConnectionDrawer.needCloseButton = true
|
||||
PageController.showTopCloseButton(true)
|
||||
|
||||
shareConnectionDrawer.contentVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
ShareConnectionDrawer {
|
||||
id: shareConnectionDrawer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
|
||||
}
|
||||
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
||||
QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString();
|
||||
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
||||
|
||||
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||
|
||||
@@ -227,12 +227,15 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser
|
||||
configData = lastVpnConfig.value(proto);
|
||||
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
|
||||
} else {
|
||||
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode);
|
||||
QString clientId;
|
||||
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, clientId, errorCode);
|
||||
|
||||
if (errorCode && *errorCode) {
|
||||
return "";
|
||||
}
|
||||
|
||||
emit m_configurator->newVpnConfigCreated(clientId, "unnamed client", container, credentials);
|
||||
|
||||
QString configDataBeforeLocalProcessing = configData;
|
||||
|
||||
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
|
||||
@@ -323,12 +326,14 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e);
|
||||
emit newVpnConfigurationCreated();
|
||||
|
||||
if (e) {
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
appendSplitTunnelingConfig();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
||||
if (!m_vpnProtocol) {
|
||||
@@ -363,6 +368,26 @@ void VpnConnection::createProtocolConnections()
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
}
|
||||
|
||||
void VpnConnection::appendSplitTunnelingConfig()
|
||||
{
|
||||
auto routeMode = m_settings->routeMode();
|
||||
auto sites = m_settings->getVpnIps(routeMode);
|
||||
|
||||
QJsonArray sitesJsonArray;
|
||||
for (const auto &site : sites) {
|
||||
sitesJsonArray.append(site);
|
||||
}
|
||||
|
||||
// Allow traffic to Amezia DNS
|
||||
if (routeMode == Settings::VpnOnlyForwardSites){
|
||||
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
|
||||
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
|
||||
}
|
||||
|
||||
m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode);
|
||||
m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void VpnConnection::restoreConnection()
|
||||
{
|
||||
|
||||
@@ -79,8 +79,6 @@ signals:
|
||||
|
||||
void serviceIsNotReady();
|
||||
|
||||
void newVpnConfigurationCreated();
|
||||
|
||||
protected slots:
|
||||
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
@@ -112,6 +110,8 @@ private:
|
||||
#endif
|
||||
|
||||
void createProtocolConnections();
|
||||
|
||||
void appendSplitTunnelingConfig();
|
||||
};
|
||||
|
||||
#endif // VPNCONNECTION_H
|
||||
|
||||
Reference in New Issue
Block a user