mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-24 20:06:50 +03:00
Compare commits
86 Commits
bugfix/upd
...
sudo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c6daf1e83 | ||
|
|
f4c06f952e | ||
|
|
ab13e98ad2 | ||
|
|
4e29ce7139 | ||
|
|
60d58915c0 | ||
|
|
c0302b7a01 | ||
|
|
fecdd60787 | ||
|
|
75987b8b45 | ||
|
|
4109e9db58 | ||
|
|
8e10144717 | ||
|
|
2410bf6838 | ||
|
|
bd595f2c94 | ||
|
|
7d7a4c154d | ||
|
|
d66304f814 | ||
|
|
e7a1a93d84 | ||
|
|
9f29bc9afe | ||
|
|
a6ffd2a675 | ||
|
|
795737bbc3 | ||
|
|
61f55af76b | ||
|
|
6c5a12e626 | ||
|
|
ef0530ec6b | ||
|
|
e8a2e54d05 | ||
|
|
b4694313a0 | ||
|
|
abb2cae1f8 | ||
|
|
b0004fd9dc | ||
|
|
362a82f944 | ||
|
|
19fe61ed29 | ||
|
|
72de38b4fb | ||
|
|
02c0f96e5e | ||
|
|
5e9f688000 | ||
|
|
6a7e346695 | ||
|
|
071738116e | ||
|
|
ae4ee6431d | ||
|
|
d1ccde2a4b | ||
|
|
4848091203 | ||
|
|
282f159311 | ||
|
|
4ef8c77a2d | ||
|
|
08c1cf2439 | ||
|
|
2fc33875bb | ||
|
|
9e92e4b5ff | ||
|
|
7f2cf70bf5 | ||
|
|
8164026891 | ||
|
|
0e23b3a1ac | ||
|
|
1739d4861e | ||
|
|
a6b6e7850d | ||
|
|
3e9dea6f07 | ||
|
|
1b37ca805f | ||
|
|
c772f56da7 | ||
|
|
bc183e39bb | ||
|
|
306d4f70a8 | ||
|
|
a386d39495 | ||
|
|
22b14dff5f | ||
|
|
e749cc7578 | ||
|
|
6a12cad1c9 | ||
|
|
c15665803d | ||
|
|
97090888d5 | ||
|
|
4642308fbb | ||
|
|
59bccb1188 | ||
|
|
cd8fc007ac | ||
|
|
7cfb38307e | ||
|
|
994aa32745 | ||
|
|
f0b872e86b | ||
|
|
0c33432436 | ||
|
|
0bb4dd9442 | ||
|
|
7a54dc15da | ||
|
|
e16a1100d8 | ||
|
|
99214e22e3 | ||
|
|
c77d35a2ed | ||
|
|
d98fdbdc5c | ||
|
|
78c83b2e21 | ||
|
|
414a47e2f2 | ||
|
|
32c304dc1b | ||
|
|
4ea1a19572 | ||
|
|
fcf6bb43b7 | ||
|
|
f5f72f87a6 | ||
|
|
3340451245 | ||
|
|
c14f1b5000 | ||
|
|
546d4c1d3d | ||
|
|
2a0166bb26 | ||
|
|
2df612ec1f | ||
|
|
9cf5590371 | ||
|
|
2a4a01a4be | ||
|
|
501670bdd2 | ||
|
|
24637a1693 | ||
|
|
7bd1340190 | ||
|
|
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
@@ -139,7 +139,8 @@ void AmneziaApplication::init()
|
||||
&ConnectionController::openConnection);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
|
||||
&NotificationHandler::onTranslationsUpdated);
|
||||
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
@@ -226,14 +227,13 @@ void AmneziaApplication::loadTranslator()
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
|
||||
void AmneziaApplication::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm";
|
||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
@@ -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,15 +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_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) {
|
||||
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
|
||||
emit m_pageController->showNotificationMessage(
|
||||
tr("Split tunneling for WireGuard is not implemented, the option was disabled"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
@@ -337,7 +331,8 @@ void AmneziaApplication::initControllers()
|
||||
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -17,43 +17,31 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials,
|
||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
for (auto &line : serverConfigLines) {
|
||||
QMap<QString, QString> configMap;
|
||||
auto configLines = awgConfig.split("\n");
|
||||
for (auto &line : configLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.replace("$JUNK_PACKET_COUNT", serverConfigMap.value(config_key::junkPacketCount));
|
||||
config.replace("$JUNK_PACKET_MIN_SIZE", serverConfigMap.value(config_key::junkPacketMinSize));
|
||||
config.replace("$JUNK_PACKET_MAX_SIZE", serverConfigMap.value(config_key::junkPacketMaxSize));
|
||||
config.replace("$INIT_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::initPacketJunkSize));
|
||||
config.replace("$RESPONSE_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::responsePacketJunkSize));
|
||||
config.replace("$INIT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::initPacketMagicHeader));
|
||||
config.replace("$RESPONSE_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::responsePacketMagicHeader));
|
||||
config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader));
|
||||
config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader));
|
||||
|
||||
jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
||||
jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
||||
jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
||||
jsonConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
|
||||
jsonConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
|
||||
jsonConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
|
||||
jsonConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
|
||||
jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount);
|
||||
jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize);
|
||||
jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize);
|
||||
jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize);
|
||||
jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize);
|
||||
jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader);
|
||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
return QJsonDocument(jsonConfig).toJson();
|
||||
}
|
||||
|
||||
@@ -131,10 +131,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");
|
||||
|
||||
@@ -36,7 +36,7 @@ enum ErrorCode
|
||||
ServerPacketManagerError,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
|
||||
SshRequestDeniedError, SshInterruptedError, SshInternalError,
|
||||
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
|
||||
|
||||
// Ssh sftp errors
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -834,6 +831,34 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
|
||||
containerConfig.insert(config_key::port, port);
|
||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
||||
|
||||
if (protocol == Proto::Awg) {
|
||||
QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
for (auto &line : serverConfigLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
||||
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
|
||||
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
|
||||
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
|
||||
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
|
||||
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
|
||||
}
|
||||
|
||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
}
|
||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -216,7 +216,6 @@
|
||||
<file>ui/qml/Pages2/PageServiceDnsSettings.qml</file>
|
||||
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
|
||||
<file>images/controls/x-circle.svg</file>
|
||||
<file>ui/qml/Controls2/Drawer2Type.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolAwgSettings.qml</file>
|
||||
<file>server_scripts/awg/template.conf</file>
|
||||
<file>server_scripts/awg/start.sh</file>
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
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";\
|
||||
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 [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\
|
||||
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;\
|
||||
sleep 5 && sudo systemctl enable --now docker && sleep 5;\
|
||||
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;\
|
||||
sleep 3 && sudo systemctl start docker && sleep 3;\
|
||||
if [ "$(systemctl is-active docker)" != "active" ]; then sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||
sleep 5 && sudo systemctl start docker && sleep 5;\
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
<context>
|
||||
<name>AmneziaApplication</name>
|
||||
<message>
|
||||
<location filename="../amnezia_application.cpp" line="304"/>
|
||||
<source>Split tunneling for WireGuard is not implemented, the option was disabled</source>
|
||||
<translation>Раздельное туннелирование для "Wireguard" не реализовано,опция отключена</translation>
|
||||
<translation type="vanished">Раздельное туннелирование для "Wireguard" не реализовано,опция отключена</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../amnezia_application.cpp" line="305"/>
|
||||
<source>Split tunneling for %1 is not implemented, the option was disabled</source>
|
||||
<translation>Раздельное туннелирование для %1 не реализовано, опция отключена</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -120,7 +124,7 @@
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
|
||||
<source>Unable change protocol while there is an active connection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Невозможно изменить протокол при активном соединении</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="69"/>
|
||||
@@ -274,7 +278,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="490"/>
|
||||
<source>Unable change server while there is an active connection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Невозможно изменить сервер при активном соединении</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -346,9 +350,13 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation type="vanished">Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="280"/>
|
||||
<source>Continue</source>
|
||||
@@ -404,7 +412,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/>
|
||||
<source>VPN Addresses Subnet</source>
|
||||
<translation>VPN Адреса Подсеть</translation>
|
||||
<translation>Подсеть для VPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="98"/>
|
||||
@@ -571,9 +579,13 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="368"/>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation type="vanished">Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="369"/>
|
||||
<source>Continue</source>
|
||||
@@ -619,8 +631,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolRaw.qml" line="179"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation type="unfinished">Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
<translation type="obsolete">Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolRaw.qml" line="180"/>
|
||||
@@ -670,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>
|
||||
@@ -1132,7 +1148,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="41"/>
|
||||
<source>Connection</source>
|
||||
<translation>Подключение</translation>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="50"/>
|
||||
@@ -1278,7 +1294,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/>
|
||||
<source>Save logs to file</source>
|
||||
<translation>Сохранять логи в файл</translation>
|
||||
<translation>Сохранить логи в файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/>
|
||||
@@ -1316,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"/>
|
||||
@@ -1440,8 +1456,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="117"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation>Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться.</translation>
|
||||
<translation type="vanished">Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="118"/>
|
||||
@@ -1480,75 +1500,75 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation>Раздельное VPN-туннелирование</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="128"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
|
||||
<source>Mode</source>
|
||||
<translation>Режим</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
|
||||
<source>Remove </source>
|
||||
<translation>Удалить </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
|
||||
<source>Continue</source>
|
||||
<translation>Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="208"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Отменить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="255"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
|
||||
<source>Site or IP</source>
|
||||
<translation>Сайт или IP</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="299"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
|
||||
<source>Import/Export Sites</source>
|
||||
<translation>Импорт/экспорт Сайтов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="305"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
|
||||
<source>Import</source>
|
||||
<translation>Импорт</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
|
||||
<source>Save site list</source>
|
||||
<translation>Сохранить список сайтов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
|
||||
<source>Save sites</source>
|
||||
<translation>Сохранить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="325"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="392"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="407"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
|
||||
<source>Sites files (*.json)</source>
|
||||
<translation>Sites files (*.json)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="382"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
|
||||
<source>Import a list of sites</source>
|
||||
<translation>Импортировать список с сайтами</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="388"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
|
||||
<source>Replace site list</source>
|
||||
<translation>Заменить список сайтов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
|
||||
<source>Open sites file</source>
|
||||
<translation>Открыть список с сайтами</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="403"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
|
||||
<source>Add imported sites to existing ones</source>
|
||||
<translation>Добавить импортированные сайты к существующим</translation>
|
||||
</message>
|
||||
@@ -1884,9 +1904,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>WireGuard нативный формат</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="121"/>
|
||||
<source>VPN Access</source>
|
||||
<translation>VPN-Доступ</translation>
|
||||
<translation type="vanished">VPN-Доступ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="146"/>
|
||||
@@ -1894,14 +1913,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="174"/>
|
||||
<source>VPN access without the ability to manage the server</source>
|
||||
<translation>Доступ к VPN, без возможности управления сервером</translation>
|
||||
<translation type="vanished">Доступ к VPN, без возможности управления сервером</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="175"/>
|
||||
<source>Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings.</source>
|
||||
<translation>Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки.</translation>
|
||||
<translation type="vanished">Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="190"/>
|
||||
@@ -1944,11 +1961,26 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<source>For the AmneziaVPN app</source>
|
||||
<translation>Для AmneziaVPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="121"/>
|
||||
<source>Share VPN Access</source>
|
||||
<translation>Поделиться VPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="158"/>
|
||||
<source>Full access</source>
|
||||
<translation>Полный доступ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="174"/>
|
||||
<source>Share VPN access without the ability to manage the server</source>
|
||||
<translation>Поделиться доступом к VPN, без возможности управления сервером</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="175"/>
|
||||
<source>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.</source>
|
||||
<translation>Поделиться доступом к управлению сервером. Пользователь, с которым вы делитесь полным доступом к серверу, сможет добавлять и удалять любые протоколы и службы на сервере, а также изменять настройки.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="251"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="252"/>
|
||||
@@ -2571,9 +2603,9 @@ OpenVPN обеспечивает безопасное VPN-соединение
|
||||
|
||||
Cloak защищает OpenVPN от обнаружения и блокировок.
|
||||
|
||||
Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает ее очень устойчивой к обнаружению
|
||||
Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению
|
||||
|
||||
Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваша VPN становится невидимой для аналитических систем.
|
||||
Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем.
|
||||
|
||||
Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
<context>
|
||||
<name>AmneziaApplication</name>
|
||||
<message>
|
||||
<location filename="../amnezia_application.cpp" line="304"/>
|
||||
<source>Split tunneling for WireGuard is not implemented, the option was disabled</source>
|
||||
<translation>未启用选项,还未实现基于WireGuard协议的VPN分离</translation>
|
||||
<translation type="vanished">未启用选项,还未实现基于WireGuard协议的VPN分离</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../amnezia_application.cpp" line="305"/>
|
||||
<source>Split tunneling for %1 is not implemented, the option was disabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -131,7 +135,7 @@
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
|
||||
<source>Unable change protocol while there is an active connection</source>
|
||||
<translation type="unfinished">已建立连接时无法更改服务器配置</translation>
|
||||
<translation>已建立连接时无法更改服务器配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="69"/>
|
||||
@@ -380,8 +384,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>与您共享连接的所有用户将无法再连接到该连接。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation>使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
<translation type="vanished">使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="280"/>
|
||||
@@ -605,8 +613,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="368"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>与您共享连接的所有用户将无法再连接到该连接。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation>使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
<translation type="vanished">使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it</source>
|
||||
@@ -661,8 +673,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageProtocolRaw.qml" line="179"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>与您共享连接的所有用户将无法再连接到该连接。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation>使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
<translation type="vanished">使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> from server?</source>
|
||||
@@ -1521,8 +1537,12 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="117"/>
|
||||
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
|
||||
<translation>与您共享连接的所有用户将无法再连接到该连接。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All users who you shared a connection with will no longer be able to connect to it.</source>
|
||||
<translation>使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
<translation type="vanished">使用此共享连接的所有用户,将无法再连接它。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> from server?</source>
|
||||
@@ -1586,75 +1606,75 @@ And if you don't like the app, all the more support it - the donation will
|
||||
<translation>隧道分离</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="128"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
|
||||
<source>Mode</source>
|
||||
<translation>规则</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
|
||||
<source>Remove </source>
|
||||
<translation>移除 </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
|
||||
<source>Continue</source>
|
||||
<translation>继续</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="208"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
|
||||
<source>Cancel</source>
|
||||
<translation>取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="255"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
|
||||
<source>Site or IP</source>
|
||||
<translation>网站或IP地址</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="299"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
|
||||
<source>Import/Export Sites</source>
|
||||
<translation>导入/导出网站</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="305"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
|
||||
<source>Import</source>
|
||||
<translation>导入</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
|
||||
<source>Save site list</source>
|
||||
<translation>保存网址</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
|
||||
<source>Save sites</source>
|
||||
<translation>保存网址</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="325"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="392"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="407"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
|
||||
<source>Sites files (*.json)</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="382"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
|
||||
<source>Import a list of sites</source>
|
||||
<translation>导入网址列表</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="388"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
|
||||
<source>Replace site list</source>
|
||||
<translation>替换网址列表</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
|
||||
<source>Open sites file</source>
|
||||
<translation>打开网址文件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="403"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
|
||||
<source>Add imported sites to existing ones</source>
|
||||
<translation>将导入的网址添加到现有网址中</translation>
|
||||
</message>
|
||||
@@ -2006,8 +2026,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="121"/>
|
||||
<source>Share VPN Access</source>
|
||||
<translation>共享 VPN 访问</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="174"/>
|
||||
<source>Share VPN access without the ability to manage the server</source>
|
||||
<translation>共享 VPN 访问,无需管理服务器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="175"/>
|
||||
<source>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.</source>
|
||||
<translation>共享服务器管理访问权限。与您共享服务器全部访问权限的用户将可以添加和删除服务器上的任何协议和服务,以及更改设置。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VPN Access</source>
|
||||
<translation>访问VPN</translation>
|
||||
<translation type="vanished">访问VPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="146"/>
|
||||
@@ -2020,14 +2054,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>完全访问</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="174"/>
|
||||
<source>VPN access without the ability to manage the server</source>
|
||||
<translation>访问VPN,但没有权限管理服务。</translation>
|
||||
<translation type="vanished">访问VPN,但没有权限管理服务。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="175"/>
|
||||
<source>Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings.</source>
|
||||
<translation>除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息</translation>
|
||||
<translation type="vanished">除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings.</source>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -117,8 +109,9 @@ QString ContainersModel::getDefaultContainerName()
|
||||
return ContainerProps::containerHumanNames().value(m_defaultContainerIndex);
|
||||
}
|
||||
|
||||
void ContainersModel::setDefaultContainer(DockerContainer container)
|
||||
void ContainersModel::setDefaultContainer(int index)
|
||||
{
|
||||
auto container = static_cast<DockerContainer>(index);
|
||||
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
|
||||
m_defaultContainerIndex = container;
|
||||
emit defaultContainerChanged();
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
public slots:
|
||||
DockerContainer getDefaultContainer();
|
||||
QString getDefaultContainerName();
|
||||
void setDefaultContainer(DockerContainer container);
|
||||
void setDefaultContainer(int index);
|
||||
|
||||
void setCurrentlyProcessedServerIndex(const int index);
|
||||
|
||||
@@ -73,6 +73,7 @@ protected:
|
||||
|
||||
signals:
|
||||
void defaultContainerChanged();
|
||||
void containersModelUpdated();
|
||||
|
||||
private:
|
||||
QMap<DockerContainer, QJsonObject> m_containers;
|
||||
|
||||
@@ -193,6 +193,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;
|
||||
|
||||
@@ -59,6 +59,8 @@ public slots:
|
||||
|
||||
bool isDefaultServerConfigContainsAmneziaDns();
|
||||
|
||||
void updateContainersConfig();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
|
||||
@@ -3,7 +3,14 @@
|
||||
SitesModel::SitesModel(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: QAbstractListModel(parent), m_settings(settings)
|
||||
{
|
||||
m_currentRouteMode = m_settings->routeMode();
|
||||
auto routeMode = m_settings->routeMode();
|
||||
if (routeMode == Settings::RouteMode::VpnAllSites) {
|
||||
m_isSplitTunnelingEnabled = false;
|
||||
m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites;
|
||||
} else {
|
||||
m_isSplitTunnelingEnabled = true;
|
||||
m_currentRouteMode = routeMode;
|
||||
}
|
||||
fillSites();
|
||||
}
|
||||
|
||||
@@ -93,6 +100,21 @@ void SitesModel::setRouteMode(int routeMode)
|
||||
emit routeModeChanged();
|
||||
}
|
||||
|
||||
bool SitesModel::isSplitTunnelingEnabled()
|
||||
{
|
||||
return m_isSplitTunnelingEnabled;
|
||||
}
|
||||
|
||||
void SitesModel::toggleSplitTunneling(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
setRouteMode(m_currentRouteMode);
|
||||
} else {
|
||||
m_settings->setRouteMode(Settings::RouteMode::VpnAllSites);
|
||||
}
|
||||
m_isSplitTunnelingEnabled = enabled;
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QString> > SitesModel::getCurrentSites()
|
||||
{
|
||||
return m_sites;
|
||||
|
||||
@@ -31,6 +31,9 @@ public slots:
|
||||
int getRouteMode();
|
||||
void setRouteMode(int routeMode);
|
||||
|
||||
bool isSplitTunnelingEnabled();
|
||||
void toggleSplitTunneling(bool enabled);
|
||||
|
||||
QVector<QPair<QString, QString>> getCurrentSites();
|
||||
|
||||
signals:
|
||||
@@ -44,6 +47,7 @@ private:
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
bool m_isSplitTunnelingEnabled;
|
||||
Settings::RouteMode m_currentRouteMode;
|
||||
|
||||
QVector<QPair<QString, QString>> m_sites;
|
||||
|
||||
@@ -8,16 +8,13 @@ import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.4375
|
||||
height: parent.height * 0.4375
|
||||
|
||||
ColumnLayout {
|
||||
parent: root.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -63,7 +63,7 @@ ListView {
|
||||
isDefault = true
|
||||
|
||||
menuContent.currentIndex = index
|
||||
containersDropDown.menu.close()
|
||||
containersDropDown.menuVisible = false
|
||||
} else {
|
||||
if (!isSupported && isInstalled) {
|
||||
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))
|
||||
|
||||
@@ -5,7 +5,7 @@ import QtQuick.Layouts
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: root
|
||||
|
||||
property string headerText
|
||||
@@ -15,14 +15,12 @@ Drawer2Type {
|
||||
|
||||
property var yesButtonFunction
|
||||
property var noButtonFunction
|
||||
property real drawerHeight: 0.5
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * drawerHeight
|
||||
height: content.implicitHeight + 32
|
||||
|
||||
ColumnLayout {
|
||||
parent: root.contentParent
|
||||
id: content
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
@@ -31,8 +29,6 @@ Drawer2Type {
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
// visible: false
|
||||
|
||||
spacing: 8
|
||||
|
||||
Header2TextType {
|
||||
|
||||
@@ -5,18 +5,15 @@ import QtQuick.Layouts
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.9
|
||||
height: parent.height * 0.9
|
||||
|
||||
ColumnLayout {
|
||||
id: backButton
|
||||
|
||||
parent: root.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -31,7 +28,6 @@ Drawer2Type {
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: root.contentParent
|
||||
anchors.top: backButton.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -16,7 +16,7 @@ import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: root
|
||||
|
||||
property alias headerText: header.headerText
|
||||
@@ -28,10 +28,9 @@ Drawer2Type {
|
||||
property string configFileName: "amnezia_config.vpn"
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.9
|
||||
height: parent.height * 0.9
|
||||
|
||||
onDrawerClosed: {
|
||||
onClosed: {
|
||||
configExtension = ".vpn"
|
||||
configCaption = qsTr("Save AmneziaVPN config")
|
||||
configFileName = "amnezia_config"
|
||||
@@ -42,9 +41,6 @@ Drawer2Type {
|
||||
|
||||
Header2Type {
|
||||
id: header
|
||||
|
||||
parent: root.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -54,8 +50,6 @@ Drawer2Type {
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: root.contentParent
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.height + 32
|
||||
@@ -132,37 +126,30 @@ Drawer2Type {
|
||||
text: qsTr("Show connection settings")
|
||||
|
||||
onClicked: {
|
||||
configContentDrawer.open()
|
||||
configContentDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: configContentDrawer
|
||||
|
||||
parent: root
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
contentHeight: parent.height * 0.9
|
||||
height: parent.height * 0.9
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
parent: configContentDrawer.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
|
||||
backButtonFunction: function() {
|
||||
configContentDrawer.close()
|
||||
configContentDrawer.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: configContentDrawer.contentParent
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Shapes
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: PageController
|
||||
|
||||
function onForceCloseDrawer() {
|
||||
if (root.expanded()) {
|
||||
collapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal drawerClosed
|
||||
signal collapsedEntered
|
||||
signal collapsedExited
|
||||
signal collapsedEnter
|
||||
signal collapsedPressChanged
|
||||
|
||||
|
||||
visible: false
|
||||
|
||||
property bool needCloseButton: true
|
||||
|
||||
property string defaultColor: "#1C1D21"
|
||||
property string borderColor: "#2C2D30"
|
||||
property string semitransparentColor: "#90000000"
|
||||
|
||||
property bool needCollapsed: false
|
||||
|
||||
property int contentHeight: 0
|
||||
property Item contentParent: contentArea
|
||||
|
||||
property bool dragActive: dragArea.drag.active
|
||||
|
||||
property int collapsedHeight: 0
|
||||
|
||||
property bool fullMouseAreaVisible: true
|
||||
property MouseArea drawerDragArea: dragArea
|
||||
|
||||
state: "collapsed"
|
||||
|
||||
Rectangle {
|
||||
id: draw2Background
|
||||
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
radius: 16
|
||||
color: "transparent"
|
||||
border.color: "transparent"
|
||||
border.width: 1
|
||||
visible: true
|
||||
|
||||
MouseArea {
|
||||
id: fullMouseArea
|
||||
anchors.fill: parent
|
||||
enabled: root.expanded()
|
||||
hoverEnabled: true
|
||||
visible: fullMouseAreaVisible
|
||||
|
||||
onClicked: {
|
||||
if (root.expanded()) {
|
||||
collapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: placeAreaHolder
|
||||
|
||||
// for apdating home drawer, normal drawer will reset it
|
||||
height: 0
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
visible: true
|
||||
color: "transparent"
|
||||
|
||||
Drag.active: dragArea.drag.active
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: contentArea
|
||||
|
||||
anchors.top: placeAreaHolder.bottom
|
||||
height: contentHeight
|
||||
radius: 16
|
||||
color: root.defaultColor
|
||||
border.width: 1
|
||||
border.color: root.borderColor
|
||||
width: parent.width
|
||||
visible: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.radius
|
||||
height: parent.radius
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
color: parent.color
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: root.collapsed() ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
|
||||
drag.target: placeAreaHolder
|
||||
drag.axis: Drag.YAxis
|
||||
drag.maximumY: root.height - root.collapsedHeight
|
||||
drag.minimumY: root.collapsedHeight > 0 ? root.height - root.height * 0.9 : 0
|
||||
|
||||
/** If drag area is released at any point other than min or max y, transition to the other state */
|
||||
onReleased: {
|
||||
if (root.collapsed() && placeAreaHolder.y < drag.maximumY) {
|
||||
root.state = "expanded"
|
||||
return
|
||||
}
|
||||
if (root.expanded() && placeAreaHolder.y > drag.minimumY) {
|
||||
root.state = "collapsed"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (root.expanded()) {
|
||||
collapse()
|
||||
return
|
||||
}
|
||||
|
||||
if (root.collapsed()) {
|
||||
root.state = "expanded"
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
collapsedExited()
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
collapsedEnter()
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
collapsedPressChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onStateChanged: {
|
||||
if (root.collapsed()) {
|
||||
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
|
||||
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
|
||||
}
|
||||
|
||||
if (needCloseButton) {
|
||||
PageController.drawerClose()
|
||||
}
|
||||
|
||||
drawerClosed()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (root.expanded()) {
|
||||
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(0xFF1C1D21)
|
||||
}
|
||||
|
||||
if (needCloseButton) {
|
||||
PageController.drawerOpen()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** Two states of buttonContent, great place to add any future animations for the drawer */
|
||||
states: [
|
||||
State {
|
||||
name: "collapsed"
|
||||
PropertyChanges {
|
||||
target: placeAreaHolder
|
||||
y: dragArea.drag.maximumY
|
||||
}
|
||||
},
|
||||
|
||||
State {
|
||||
name: "expanded"
|
||||
PropertyChanges {
|
||||
target: placeAreaHolder
|
||||
y: dragArea.drag.minimumY
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "expanded"
|
||||
to: "collapsed"
|
||||
PropertyAnimation {
|
||||
target: placeAreaHolder
|
||||
properties: "y"
|
||||
duration: 200
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
draw2Background.color = "transparent"
|
||||
fullMouseArea.visible = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Transition {
|
||||
from: "collapsed"
|
||||
to: "expanded"
|
||||
PropertyAnimation {
|
||||
target: placeAreaHolder
|
||||
properties: "y"
|
||||
duration: 200
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
draw2Background.color = semitransparentColor
|
||||
fullMouseArea.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
NumberAnimation {
|
||||
id: animationVisible
|
||||
target: placeAreaHolder
|
||||
property: "y"
|
||||
from: parent.height
|
||||
to: 0
|
||||
duration: 200
|
||||
}
|
||||
|
||||
// for normal drawer
|
||||
function open() {
|
||||
if (root.expanded()) {
|
||||
return
|
||||
}
|
||||
|
||||
draw2Background.color = semitransparentColor
|
||||
fullMouseArea.visible = true
|
||||
|
||||
collapsedHeight = 0
|
||||
|
||||
root.y = 0
|
||||
root.state = "expanded"
|
||||
root.visible = true
|
||||
root.height = parent.height
|
||||
|
||||
contentArea.height = contentHeight
|
||||
|
||||
placeAreaHolder.y = 0
|
||||
placeAreaHolder.height = root.height - contentHeight
|
||||
|
||||
animationVisible.running = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
collapse()
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
draw2Background.color = "transparent"
|
||||
root.state = "collapsed"
|
||||
}
|
||||
|
||||
// for page home
|
||||
function expand() {
|
||||
draw2Background.color = semitransparentColor
|
||||
root.state = "expanded"
|
||||
}
|
||||
|
||||
function expanded() {
|
||||
return root.state === "expanded" ? true : false
|
||||
}
|
||||
|
||||
function collapsed() {
|
||||
return root.state === "collapsed" ? true : false
|
||||
}
|
||||
|
||||
|
||||
onVisibleChanged: {
|
||||
// e.g cancel, ......
|
||||
if (!visible) {
|
||||
if (root.expanded()) {
|
||||
if (needCloseButton) {
|
||||
PageController.drawerClose()
|
||||
}
|
||||
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,6 @@ Item {
|
||||
|
||||
property alias menuVisible: menu.visible
|
||||
|
||||
property Item drawerParent: root
|
||||
|
||||
property Drawer2Type menu: menu
|
||||
|
||||
implicitWidth: rootButtonContent.implicitWidth
|
||||
implicitHeight: rootButtonContent.implicitHeight
|
||||
|
||||
@@ -159,26 +155,21 @@ Item {
|
||||
onClicked: {
|
||||
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
|
||||
rootButtonClickedFunction()
|
||||
} else {
|
||||
menu.visible = true
|
||||
}
|
||||
|
||||
menu.open()
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: menu
|
||||
|
||||
parent: drawerParent
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * drawerHeight
|
||||
height: parent.height * drawerHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
|
||||
parent: menu.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -187,14 +178,12 @@ Item {
|
||||
BackButtonType {
|
||||
backButtonImage: root.headerBackButtonImage
|
||||
backButtonFunction: function() {
|
||||
menu.close()
|
||||
root.menuVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: menu.contentParent
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: 16
|
||||
contentHeight: col.implicitHeight
|
||||
|
||||
@@ -30,13 +30,13 @@ PageType {
|
||||
target: PageController
|
||||
|
||||
function onRestorePageHomeState(isContainerInstalled) {
|
||||
buttonContent.collapse()
|
||||
buttonContent.state = "expanded"
|
||||
if (isContainerInstalled) {
|
||||
containersDropDown.menuVisible = true
|
||||
containersDropDown.rootButtonClickedFunction()
|
||||
}
|
||||
}
|
||||
function onForceCloseDrawer() {
|
||||
buttonContent.collapse()
|
||||
buttonContent.state = "collapsed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,14 @@ PageType {
|
||||
expandedServersMenuDescription.text = description + root.defaultServerHostName
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDescriptions()
|
||||
Component.onCompleted: updateDescriptions()
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: buttonContent.state === "expanded"
|
||||
onClicked: {
|
||||
buttonContent.state = "collapsed"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -86,10 +92,56 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
|
||||
anchors.fill: buttonBackground
|
||||
cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
|
||||
drag.target: buttonContent
|
||||
drag.axis: Drag.YAxis
|
||||
drag.maximumY: root.height - buttonContent.collapsedHeight
|
||||
drag.minimumY: root.height - root.height * 0.9
|
||||
|
||||
/** If drag area is released at any point other than min or max y, transition to the other state */
|
||||
onReleased: {
|
||||
if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) {
|
||||
buttonContent.state = "expanded"
|
||||
return
|
||||
}
|
||||
if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) {
|
||||
buttonContent.state = "collapsed"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor
|
||||
collapsedButtonHeader.opacity = 0.8
|
||||
}
|
||||
onExited: {
|
||||
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
|
||||
collapsedButtonHeader.opacity = 1
|
||||
}
|
||||
onPressedChanged: {
|
||||
collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor
|
||||
collapsedButtonHeader.opacity = 0.7
|
||||
}
|
||||
|
||||
|
||||
onClicked: {
|
||||
if (buttonContent.state === "collapsed") {
|
||||
buttonContent.state = "expanded"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: buttonBackground
|
||||
anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top }
|
||||
|
||||
anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top }
|
||||
height: root.height
|
||||
radius: 16
|
||||
color: root.defaultColor
|
||||
border.color: root.borderColor
|
||||
@@ -105,126 +157,161 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
ColumnLayout {
|
||||
id: buttonContent
|
||||
visible: true
|
||||
|
||||
fullMouseAreaVisible: false
|
||||
|
||||
/** Initial height of button content */
|
||||
property int collapsedHeight: 0
|
||||
/** True when expanded objects should be visible */
|
||||
property bool expandedVisibility: buttonContent.expanded() || (buttonContent.collapsed() && buttonContent.dragActive)
|
||||
property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true)
|
||||
/** True when collapsed objects should be visible */
|
||||
property bool collapsedVisibility: buttonContent.collapsed() && !buttonContent.dragActive
|
||||
property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.9
|
||||
Drag.active: dragArea.drag.active
|
||||
anchors.right: root.right
|
||||
anchors.left: root.left
|
||||
y: root.height - buttonContent.height
|
||||
|
||||
Component.onCompleted: {
|
||||
buttonContent.state = "collapsed"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: collapsedButtonContent
|
||||
|
||||
parent: buttonContent.contentParent
|
||||
|
||||
visible: buttonContent.collapsedVisibility
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
onImplicitHeightChanged: {
|
||||
if (buttonContent.collapsed() && buttonContent.collapsedHeight === 0) {
|
||||
buttonContent.collapsedHeight = implicitHeight
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
Layout.topMargin: 10
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 2
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 14
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
Header1TextType {
|
||||
id: collapsedButtonHeader
|
||||
Layout.maximumWidth: root.width - 48 - 18 - 12 // todo
|
||||
|
||||
maximumLineCount: 2
|
||||
elide: Qt.ElideRight
|
||||
|
||||
text: root.defaultServerName
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
|
||||
|
||||
ImageButtonType {
|
||||
id: collapsedButtonChevron
|
||||
|
||||
hoverEnabled: false
|
||||
image: "qrc:/images/controls/chevron-down.svg"
|
||||
imageColor: "#d7d8db"
|
||||
|
||||
horizontalPadding: 0
|
||||
padding: 0
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
id: rightImageBackground
|
||||
anchors.fill: parent
|
||||
radius: 16
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (buttonContent.collapsed()) {
|
||||
buttonContent.expand()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
id: collapsedServerMenuDescription
|
||||
Layout.bottomMargin: 44
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
visible: buttonContent.collapsedVisibility
|
||||
/** Set once based on first implicit height change once all children are layed out */
|
||||
onImplicitHeightChanged: {
|
||||
if (buttonContent.state === "collapsed" && collapsedHeight == 0) {
|
||||
collapsedHeight = implicitHeight
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
buttonContent.collapse()
|
||||
onStateChanged: {
|
||||
if (buttonContent.state === "collapsed") {
|
||||
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
|
||||
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
|
||||
}
|
||||
PageController.drawerClose()
|
||||
return
|
||||
}
|
||||
if (buttonContent.state === "expanded") {
|
||||
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(0xFF1C1D21)
|
||||
}
|
||||
PageController.drawerOpen()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** Two states of buttonContent, great place to add any future animations for the drawer */
|
||||
states: [
|
||||
State {
|
||||
name: "collapsed"
|
||||
PropertyChanges {
|
||||
target: buttonContent
|
||||
y: root.height - collapsedHeight
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
PropertyChanges {
|
||||
target: buttonContent
|
||||
y: dragArea.drag.minimumY
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "collapsed"
|
||||
to: "expanded"
|
||||
PropertyAnimation {
|
||||
target: buttonContent
|
||||
properties: "y"
|
||||
duration: 200
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "expanded"
|
||||
to: "collapsed"
|
||||
PropertyAnimation {
|
||||
target: buttonContent
|
||||
properties: "y"
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
DividerType {
|
||||
Layout.topMargin: 10
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 2
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 14
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
visible: buttonContent.collapsedVisibility
|
||||
|
||||
spacing: 0
|
||||
|
||||
Header1TextType {
|
||||
id: collapsedButtonHeader
|
||||
Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo
|
||||
|
||||
maximumLineCount: 2
|
||||
elide: Qt.ElideRight
|
||||
|
||||
text: root.defaultServerName
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
|
||||
Behavior on opacity {
|
||||
PropertyAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
id: collapsedButtonChevron
|
||||
|
||||
Layout.leftMargin: 8
|
||||
|
||||
hoverEnabled: false
|
||||
image: "qrc:/images/controls/chevron-down.svg"
|
||||
imageColor: "#d7d8db"
|
||||
|
||||
icon.width: 18
|
||||
icon.height: 18
|
||||
backgroundRadius: 16
|
||||
horizontalPadding: 4
|
||||
topPadding: 4
|
||||
bottomPadding: 3
|
||||
|
||||
onClicked: {
|
||||
if (buttonContent.state === "collapsed") {
|
||||
buttonContent.state = "expanded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
id: collapsedServerMenuDescription
|
||||
Layout.bottomMargin: 44
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
visible: buttonContent.collapsedVisibility
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: serversMenuHeader
|
||||
|
||||
parent: buttonContent.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
visible: buttonContent.expandedVisibility
|
||||
|
||||
DividerType {
|
||||
Layout.topMargin: 10
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 2
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Header1TextType {
|
||||
Layout.fillWidth: true
|
||||
@@ -253,8 +340,6 @@ PageType {
|
||||
DropDownType {
|
||||
id: containersDropDown
|
||||
|
||||
drawerParent: root
|
||||
|
||||
rootButtonImageColor: "#0E0E11"
|
||||
rootButtonBackgroundColor: "#D7D8DB"
|
||||
rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8)
|
||||
@@ -316,18 +401,12 @@ PageType {
|
||||
|
||||
Flickable {
|
||||
id: serversContainer
|
||||
|
||||
parent: buttonContent.contentParent
|
||||
|
||||
anchors.top: serversMenuHeader.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 16
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
contentHeight: col.implicitHeight
|
||||
|
||||
implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height
|
||||
visible: buttonContent.expandedVisibility
|
||||
|
||||
clip: true
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
@@ -437,7 +516,7 @@ PageType {
|
||||
onClicked: function() {
|
||||
ServersModel.currentlyProcessedIndex = index
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||
buttonContent.collapse()
|
||||
buttonContent.state = "collapsed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,22 +531,5 @@ PageType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCollapsedEnter: {
|
||||
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor
|
||||
collapsedButtonHeader.opacity = 0.8
|
||||
}
|
||||
|
||||
onCollapsedExited: {
|
||||
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
|
||||
collapsedButtonHeader.opacity = 1
|
||||
}
|
||||
|
||||
onCollapsedPressChanged: {
|
||||
collapsedButtonChevron.backgroundColor = buttonContent.drawerDragArea.pressed ?
|
||||
collapsedButtonChevron.pressedColor : buttonContent.drawerDragArea.entered ?
|
||||
collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor
|
||||
collapsedButtonHeader.opacity = 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ PageType {
|
||||
|
||||
onClicked: {
|
||||
questionDrawer.headerText = qsTr("Remove AmneziaWG from server?")
|
||||
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
|
||||
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -117,8 +117,6 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
drawerParent: root
|
||||
|
||||
descriptionText: qsTr("Cipher")
|
||||
headerText: qsTr("Cipher")
|
||||
|
||||
|
||||
@@ -157,8 +157,6 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
|
||||
drawerParent: root
|
||||
|
||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||
|
||||
descriptionText: qsTr("Hash")
|
||||
@@ -205,8 +203,6 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
drawerParent: root
|
||||
|
||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||
|
||||
descriptionText: qsTr("Cipher")
|
||||
@@ -369,19 +365,19 @@ PageType {
|
||||
|
||||
onClicked: {
|
||||
questionDrawer.headerText = qsTr("Remove OpenVpn from server?")
|
||||
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
|
||||
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +402,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +90,15 @@ PageType {
|
||||
|
||||
DividerType {}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: configContentDrawer
|
||||
parent: root
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.9
|
||||
height: parent.height * 0.9
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
parent: configContentDrawer.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -180,19 +176,19 @@ PageType {
|
||||
|
||||
clickedFunction: function() {
|
||||
questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName())
|
||||
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
|
||||
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -207,7 +203,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
|
||||
drawerParent: root
|
||||
|
||||
descriptionText: qsTr("Cipher")
|
||||
headerText: qsTr("Cipher")
|
||||
|
||||
|
||||
@@ -68,14 +68,14 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -89,7 +89,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,14 +253,14 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,21 +131,20 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,6 @@ PageType {
|
||||
|
||||
SelectLanguageDrawer {
|
||||
id: selectLanguageDrawer
|
||||
parent: root
|
||||
}
|
||||
|
||||
|
||||
@@ -152,14 +151,14 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
SettingsController.clearSettings()
|
||||
PageController.replaceStartPage()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +166,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,19 +139,18 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.restoreAppConfig(filePath)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ PageType {
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
visible: GC.isDesktop()
|
||||
visible: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
SettingsController.primaryDns = "1.1.1.1"
|
||||
primaryDns.textFieldText = SettingsController.primaryDns
|
||||
SettingsController.secondaryDns = "1.0.0.1"
|
||||
@@ -100,9 +100,9 @@ PageType {
|
||||
PageController.showNotificationMessage(qsTr("Settings have been reset"))
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ PageType {
|
||||
}
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,16 +147,16 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.clearLogs()
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property Item questionDrawerParent
|
||||
|
||||
Connections {
|
||||
target: InstallController
|
||||
|
||||
@@ -96,15 +94,15 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.clearCachedProfiles()
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +141,7 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.showBusyIndicator(true)
|
||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
ConnectionController.closeConnection()
|
||||
@@ -152,9 +150,9 @@ PageType {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +172,7 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
ConnectionController.closeConnection()
|
||||
@@ -182,9 +180,9 @@ PageType {
|
||||
InstallController.removeAllContainers()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +192,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
|
||||
drawerHeight: 0.5
|
||||
|
||||
parent: questionDrawerParent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,17 +71,15 @@ PageType {
|
||||
}
|
||||
|
||||
actionButtonFunction: function() {
|
||||
serverNameEditDrawer.open()
|
||||
serverNameEditDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: serverNameEditDrawer
|
||||
|
||||
parent: root
|
||||
width: root.width
|
||||
height: root.height // * 0.35
|
||||
contentHeight: root.height * 0.35
|
||||
height: root.height * 0.35
|
||||
|
||||
onVisibleChanged: {
|
||||
if (serverNameEditDrawer.visible) {
|
||||
@@ -90,8 +88,6 @@ PageType {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
parent: serverNameEditDrawer.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -99,7 +95,6 @@ PageType {
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: serverName
|
||||
|
||||
@@ -169,7 +164,6 @@ PageType {
|
||||
}
|
||||
PageSettingsServerData {
|
||||
stackView: root.stackView
|
||||
questionDrawerParent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,19 +114,19 @@ PageType {
|
||||
|
||||
clickedFunction: function() {
|
||||
questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName())
|
||||
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
|
||||
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -141,7 +141,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,22 +93,15 @@ PageType {
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
property int lastActiveRouteMode: routeMode.onlyForwardSites
|
||||
|
||||
enabled: root.pageEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
|
||||
checked: SitesModel.routeMode !== routeMode.allSites
|
||||
onToggled: {
|
||||
if (checked) {
|
||||
SitesModel.routeMode = lastActiveRouteMode
|
||||
} else {
|
||||
lastActiveRouteMode = SitesModel.routeMode
|
||||
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
|
||||
SitesModel.routeMode = routeMode.allSites
|
||||
}
|
||||
checked: SitesModel.isSplitTunnelingEnabled()
|
||||
onToggled: {
|
||||
SitesModel.toggleSplitTunneling(checked)
|
||||
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,8 +109,6 @@ PageType {
|
||||
DropDownType {
|
||||
id: selector
|
||||
|
||||
drawerParent: root
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
@@ -125,7 +116,7 @@ PageType {
|
||||
|
||||
drawerHeight: 0.4375
|
||||
|
||||
enabled: switcher.checked && root.pageEnabled
|
||||
enabled: root.pageEnabled
|
||||
|
||||
headerText: qsTr("Mode")
|
||||
|
||||
@@ -167,7 +158,7 @@ PageType {
|
||||
anchors.topMargin: 16
|
||||
contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin
|
||||
|
||||
enabled: switcher.checked && root.pageEnabled
|
||||
enabled: root.pageEnabled
|
||||
|
||||
Column {
|
||||
id: col
|
||||
@@ -210,13 +201,13 @@ PageType {
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
SitesController.removeSite(index)
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.close()
|
||||
questionDrawer.visible = false
|
||||
}
|
||||
questionDrawer.open()
|
||||
questionDrawer.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +215,6 @@ PageType {
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,18 +269,13 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: moreActionsDrawer
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.4375
|
||||
|
||||
parent: root
|
||||
height: parent.height * 0.4375
|
||||
|
||||
FlickableType {
|
||||
parent: moreActionsDrawer.contentParent
|
||||
|
||||
anchors.fill: parent
|
||||
contentHeight: moreActionsDrawerContent.height
|
||||
ColumnLayout {
|
||||
@@ -349,20 +334,15 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: importSitesDrawer
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.4375
|
||||
|
||||
parent: root
|
||||
height: parent.height * 0.4375
|
||||
|
||||
BackButtonType {
|
||||
id: importSitesDrawerBackButton
|
||||
|
||||
parent: importSitesDrawer.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -374,8 +354,6 @@ PageType {
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: importSitesDrawer.contentParent
|
||||
|
||||
anchors.top: importSitesDrawerBackButton.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -25,20 +25,14 @@ PageType {
|
||||
|
||||
function onInstallContainerFinished(finishedMessage, isServiceInstall) {
|
||||
if (!ConnectionController.isConnected && !isServiceInstall) {
|
||||
ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex)
|
||||
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)
|
||||
|
||||
@@ -97,20 +97,15 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Drawer2Type {
|
||||
DrawerType {
|
||||
id: showDetailsDrawer
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentHeight: parent.height * 0.9
|
||||
|
||||
parent: root
|
||||
height: parent.height * 0.9
|
||||
|
||||
BackButtonType {
|
||||
id: showDetailsBackButton
|
||||
|
||||
parent: showDetailsDrawer.contentParent
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -122,8 +117,6 @@ PageType {
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
parent: showDetailsDrawer.contentParent
|
||||
|
||||
anchors.top: showDetailsBackButton.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -115,7 +115,7 @@ PageType {
|
||||
text: qsTr("I have the data to connect")
|
||||
|
||||
onClicked: {
|
||||
connectionTypeSelection.open()
|
||||
connectionTypeSelection.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +134,12 @@ PageType {
|
||||
|
||||
text: qsTr("I have nothing")
|
||||
|
||||
onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions")
|
||||
onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide")
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionTypeSelectionDrawer {
|
||||
id: connectionTypeSelection
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
headerText: qsTr("VPN Access")
|
||||
headerText: qsTr("Share VPN Access")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -171,16 +171,14 @@ PageType {
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") :
|
||||
qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings.")
|
||||
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.")
|
||||
color: "#878B91"
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: serverSelector
|
||||
|
||||
drawerParent: root
|
||||
|
||||
signal severSelectorIndexChanged
|
||||
property int currentIndex: 0
|
||||
|
||||
@@ -243,8 +241,6 @@ PageType {
|
||||
DropDownType {
|
||||
id: protocolSelector
|
||||
|
||||
drawerParent: root
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -334,8 +330,6 @@ PageType {
|
||||
DropDownType {
|
||||
id: exportTypeSelector
|
||||
|
||||
drawerParent: root
|
||||
|
||||
property int currentIndex: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -377,7 +371,6 @@ PageType {
|
||||
|
||||
ShareConnectionDrawer {
|
||||
id: shareConnectionDrawer
|
||||
parent: root
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
|
||||
@@ -135,8 +135,6 @@ PageType {
|
||||
var pagePath = PageController.getPagePath(PageEnum.PageHome)
|
||||
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
|
||||
tabBarStackView.push(pagePath, { "objectName" : pagePath })
|
||||
|
||||
connectionTypeSelection.parent = tabBarStackView
|
||||
}
|
||||
|
||||
// onWidthChanged: {
|
||||
@@ -176,12 +174,6 @@ PageType {
|
||||
strokeColor: "#2C2D30"
|
||||
fillColor: "#1C1D21"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: noPropagateMouseEvent
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
TabImageButtonType {
|
||||
@@ -252,9 +244,7 @@ PageType {
|
||||
ConnectionTypeSelectionDrawer {
|
||||
id: connectionTypeSelection
|
||||
|
||||
z: 1
|
||||
|
||||
onDrawerClosed: {
|
||||
onAboutToHide: {
|
||||
tabBar.setCurrentIndex(tabBar.previousIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -329,6 +329,8 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
||||
return;
|
||||
}
|
||||
|
||||
appendSplitTunnelingConfig();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
||||
if (!m_vpnProtocol) {
|
||||
@@ -363,6 +365,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()
|
||||
{
|
||||
|
||||
@@ -112,6 +112,8 @@ private:
|
||||
#endif
|
||||
|
||||
void createProtocolConnections();
|
||||
|
||||
void appendSplitTunnelingConfig();
|
||||
};
|
||||
|
||||
#endif // VPNCONNECTION_H
|
||||
|
||||
Reference in New Issue
Block a user