Merge branch 'dev' into feature/import-config-from-cloud
This commit is contained in:
commit
147726ecb0
33 changed files with 1299 additions and 365 deletions
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.0.8.6
|
project(${PROJECT} VERSION 4.1.0.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
|
2
client/3rd/awg-apple
vendored
2
client/3rd/awg-apple
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit fab07138dbab06ac0de256021e47e273f4df8e88
|
Subproject commit 233eda6760962efddc860f177a0ce2bcdf533d85
|
|
@ -288,6 +288,8 @@ void AmneziaApplication::initModels()
|
||||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(),
|
||||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||||
|
connect(m_containersModel.get(), &ContainersModel::containersModelUpdated, m_serversModel.get(),
|
||||||
|
&ServersModel::updateContainersConfig);
|
||||||
|
|
||||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||||
|
@ -296,17 +298,7 @@ void AmneziaApplication::initModels()
|
||||||
|
|
||||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() {
|
|
||||||
if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard
|
|
||||||
|| m_containersModel->getDefaultContainer() == DockerContainer::Awg)
|
|
||||||
&& m_sitesModel->isSplitTunnelingEnabled()) {
|
|
||||||
m_sitesModel->toggleSplitTunneling(false);
|
|
||||||
emit m_pageController->showNotificationMessage(
|
|
||||||
tr("Split tunneling for %1 is not implemented, the option was disabled")
|
|
||||||
.arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer())));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,8 @@ android {
|
||||||
resConfig "en"
|
resConfig "en"
|
||||||
minSdkVersion = 24
|
minSdkVersion = 24
|
||||||
targetSdkVersion = 34
|
targetSdkVersion = 34
|
||||||
versionCode 37 // Change to a higher number
|
versionCode 39 // Change to a higher number
|
||||||
versionName "4.0.8" // Change to a higher number
|
versionName "4.1.0" // Change to a higher number
|
||||||
|
|
||||||
javaCompileOptions.annotationProcessorOptions.arguments = [
|
javaCompileOptions.annotationProcessorOptions.arguments = [
|
||||||
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
|
"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 org.json.JSONObject
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
|
|
||||||
|
import com.wireguard.config.*
|
||||||
|
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
||||||
import net.openvpn.ovpn3.ClientAPI_Event
|
import net.openvpn.ovpn3.ClientAPI_Event
|
||||||
|
@ -72,6 +74,8 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||||
|
|
||||||
val jsonVpnConfig = mService.getVpnConfig()
|
val jsonVpnConfig = mService.getVpnConfig()
|
||||||
val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config")
|
val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config")
|
||||||
|
val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType")
|
||||||
|
val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites")
|
||||||
|
|
||||||
val resultingConfig = StringBuilder()
|
val resultingConfig = StringBuilder()
|
||||||
resultingConfig.append(ovpnConfig)
|
resultingConfig.append(ovpnConfig)
|
||||||
|
@ -115,6 +119,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||||
eval_config(config)
|
eval_config(config)
|
||||||
|
|
||||||
val status = connect()
|
val status = connect()
|
||||||
|
|
||||||
if (status.getError()) {
|
if (status.getError()) {
|
||||||
Log.i(tag, "connect() error: " + status.getError() + ": " + status.getMessage())
|
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 {
|
override fun tun_builder_establish(): Int {
|
||||||
Log.v(tag, "tun_builder_establish")
|
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()
|
return mService.establish()!!.detachFd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -564,6 +564,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||||
return parseData
|
return parseData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Wireguard [Config] from a [json] string -
|
* Create a Wireguard [Config] from a [json] string -
|
||||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
* 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 {
|
private fun buildWireguardConfig(obj: JSONObject, type: String): Config {
|
||||||
val confBuilder = Config.Builder()
|
val confBuilder = Config.Builder()
|
||||||
val wireguardConfigData = obj.getJSONObject(type)
|
val wireguardConfigData = obj.getJSONObject(type)
|
||||||
|
val splitTunnelType = obj.getInt("splitTunnelType")
|
||||||
|
val splitTunnelSites = obj.getJSONArray("splitTunnelSites")
|
||||||
|
|
||||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||||
val peerBuilder = Peer.Builder()
|
val peerBuilder = Peer.Builder()
|
||||||
val peerConfig = config["Peer"]!!
|
val peerConfig = config["Peer"]!!
|
||||||
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
||||||
peerConfig["PresharedKey"]?.let {
|
peerConfig["PresharedKey"]?.let { peerBuilder.setPreSharedKey(Key.fromBase64(it)) }
|
||||||
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()) {
|
if (allowedIPList.isEmpty() && (splitTunnelType == 0)) {
|
||||||
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
/* AllowedIP is empty and splitTunnel is turnoff */
|
||||||
peerBuilder.addAllowedIp(internet)
|
/* 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 {
|
} else {
|
||||||
allowedIPList.forEach {
|
if (!allowedIPList.isEmpty()) {
|
||||||
val network = InetNetwork.parse(it.trim())
|
/* We have predefined AllowedIP in WG config */
|
||||||
peerBuilder.addAllowedIp(network)
|
/* 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 endpointConfig = peerConfig["Endpoint"]
|
||||||
val endpoint = InetEndpoint.parse(endpointConfig)
|
val endpoint = InetEndpoint.parse(endpointConfig)
|
||||||
peerBuilder.setEndpoint(endpoint)
|
peerBuilder.setEndpoint(endpoint)
|
||||||
peerConfig["PersistentKeepalive"]?.let {
|
peerConfig["PersistentKeepalive"]?.let { peerBuilder.setPersistentKeepalive(it.toInt()) }
|
||||||
peerBuilder.setPersistentKeepalive(it.toInt())
|
|
||||||
}
|
|
||||||
confBuilder.addPeer(peerBuilder.build())
|
confBuilder.addPeer(peerBuilder.build())
|
||||||
|
|
||||||
val ifaceBuilder = Interface.Builder()
|
val ifaceBuilder = Interface.Builder()
|
||||||
|
@ -603,7 +642,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||||
ifaceConfig["DNS"]!!.split(",").forEach {
|
ifaceConfig["DNS"]!!.split(",").forEach {
|
||||||
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
||||||
if (type == "awg_config_data") {
|
if (type == "awg_config_data") {
|
||||||
ifaceBuilder.parseJc(ifaceConfig["Jc"])
|
ifaceBuilder.parseJc(ifaceConfig["Jc"])
|
||||||
|
@ -624,14 +663,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||||
ifaceBuilder.parseH1("0")
|
ifaceBuilder.parseH1("0")
|
||||||
ifaceBuilder.parseH2("0")
|
ifaceBuilder.parseH2("0")
|
||||||
ifaceBuilder.parseH3("0")
|
ifaceBuilder.parseH3("0")
|
||||||
ifaceBuilder.parseH4("0")
|
ifaceBuilder.parseH4("0")
|
||||||
|
|
||||||
}
|
}
|
||||||
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
||||||
(0 until jExcludedApplication.length()).toList().forEach {
|
(0 until jExcludedApplication.length()).toList().forEach {
|
||||||
val appName = jExcludedApplication.get(it).toString()
|
val appName = jExcludedApplication.get(it).toString()
|
||||||
ifaceBuilder.excludeApplication(appName)
|
ifaceBuilder.excludeApplication(appName)
|
||||||
}*/
|
}*/
|
||||||
confBuilder.setInterface(ifaceBuilder.build())
|
confBuilder.setInterface(ifaceBuilder.build())
|
||||||
|
|
||||||
return confBuilder.build()
|
return confBuilder.build()
|
||||||
|
@ -746,13 +784,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||||
|
|
||||||
private fun startWireGuard(type: String) {
|
private fun startWireGuard(type: String) {
|
||||||
val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data")
|
val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data")
|
||||||
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
|
|
||||||
if (currentTunnelHandle != -1) {
|
if (currentTunnelHandle != -1) {
|
||||||
Log.e(tag, "Tunnel already up")
|
Log.e(tag, "Tunnel already up")
|
||||||
// Turn the tunnel down because this might be a switch
|
// Turn the tunnel down because this might be a switch
|
||||||
GoBackend.wgTurnOff(currentTunnelHandle)
|
GoBackend.wgTurnOff(currentTunnelHandle)
|
||||||
}
|
}
|
||||||
val wgConfig: String = wireguard_conf.toWgUserspaceString()
|
val wgConfig: String = wireguard_conf.toWgUserspaceString()
|
||||||
|
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
setupBuilder(wireguard_conf, builder)
|
setupBuilder(wireguard_conf, builder)
|
||||||
builder.setSession("Amnezia")
|
builder.setSession("Amnezia")
|
||||||
|
|
|
@ -131,10 +131,13 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
}
|
}
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
}
|
}
|
||||||
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||||
|
#endif
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
|
|
|
@ -167,11 +167,8 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||||
return ErrorCode::ServerContainerMissingError;
|
return ErrorCode::ServerContainerMissingError;
|
||||||
}
|
}
|
||||||
|
|
||||||
runScript(credentials,
|
runScript(credentials,
|
||||||
replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,12 @@ void LocalSocketController::daemonConnected() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
|
|
||||||
QString protocolName = rawConfig.value("protocol").toString();
|
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 wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
|
@ -137,23 +141,79 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
|
|
||||||
QJsonArray jsAllowedIPAddesses;
|
QJsonArray jsAllowedIPAddesses;
|
||||||
|
|
||||||
QJsonObject range_ipv4;
|
QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray();
|
||||||
range_ipv4.insert("address", "0.0.0.0");
|
QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(","));
|
||||||
range_ipv4.insert("range", 0);
|
|
||||||
range_ipv4.insert("isIpv6", false);
|
|
||||||
jsAllowedIPAddesses.append(range_ipv4);
|
|
||||||
|
|
||||||
QJsonObject range_ipv6;
|
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||||
range_ipv6.insert("address", "::");
|
// Use AllowedIP list from WG config bacouse of higer priority
|
||||||
range_ipv6.insert("range", 0);
|
|
||||||
range_ipv6.insert("isIpv6", true);
|
for (auto v : plainAllowedIP) {
|
||||||
jsAllowedIPAddesses.append(range_ipv6);
|
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);
|
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||||
|
|
||||||
|
|
||||||
QJsonArray jsExcludedAddresses;
|
QJsonArray jsExcludedAddresses;
|
||||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
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);
|
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ struct MessageKey
|
||||||
static const char *host;
|
static const char *host;
|
||||||
static const char *port;
|
static const char *port;
|
||||||
static const char *isOnDemand;
|
static const char *isOnDemand;
|
||||||
|
static const char *SplitTunnelType;
|
||||||
|
static const char *SplitTunnelSites;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IosController : public QObject
|
class IosController : public QObject
|
||||||
|
|
|
@ -29,6 +29,9 @@ const char* MessageKey::errorCode = "errorCode";
|
||||||
const char* MessageKey::host = "host";
|
const char* MessageKey::host = "host";
|
||||||
const char* MessageKey::port = "port";
|
const char* MessageKey::port = "port";
|
||||||
const char* MessageKey::isOnDemand = "is-on-demand";
|
const char* MessageKey::isOnDemand = "is-on-demand";
|
||||||
|
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||||
|
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||||
|
|
||||||
|
|
||||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
@ -351,6 +354,13 @@ void IosController::startTunnel()
|
||||||
{
|
{
|
||||||
m_rxBytes = 0;
|
m_rxBytes = 0;
|
||||||
m_txBytes = 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 setEnabled:YES];
|
||||||
|
|
||||||
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
||||||
|
@ -376,8 +386,15 @@ void IosController::startTunnel()
|
||||||
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
|
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
|
||||||
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
||||||
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
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);
|
sendVpnExtensionMessage(message);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ struct Constants {
|
||||||
static let ovpnConfigKey = "ovpn"
|
static let ovpnConfigKey = "ovpn"
|
||||||
static let wireGuardConfigKey = "wireguard"
|
static let wireGuardConfigKey = "wireguard"
|
||||||
static let loggerTag = "NET"
|
static let loggerTag = "NET"
|
||||||
|
|
||||||
static let kActionStart = "start"
|
static let kActionStart = "start"
|
||||||
static let kActionRestart = "restart"
|
static let kActionRestart = "restart"
|
||||||
static let kActionStop = "stop"
|
static let kActionStop = "stop"
|
||||||
|
@ -29,6 +29,8 @@ struct Constants {
|
||||||
static let kMessageKeyHost = "host"
|
static let kMessageKeyHost = "host"
|
||||||
static let kMessageKeyPort = "port"
|
static let kMessageKeyPort = "port"
|
||||||
static let kMessageKeyOnDemand = "is-on-demand"
|
static let kMessageKeyOnDemand = "is-on-demand"
|
||||||
|
static let kMessageKeySplitTunnelType = "SplitTunnelType"
|
||||||
|
static let kMessageKeySplitTunnelSites = "SplitTunnelSites"
|
||||||
}
|
}
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
@ -38,7 +40,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
wg_log(logLevel.osLogLevel, message: message)
|
wg_log(logLevel.osLogLevel, message: message)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||||
let adapter = OpenVPNAdapter()
|
let adapter = OpenVPNAdapter()
|
||||||
adapter.delegate = self
|
adapter.delegate = self
|
||||||
|
@ -49,9 +51,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||||
|
|
||||||
private var openVPNConfig: Data? = nil
|
private var openVPNConfig: Data? = nil
|
||||||
|
private var SplitTunnelType: String? = nil
|
||||||
|
private var SplitTunnelSites: String? = nil
|
||||||
|
|
||||||
let vpnReachability = OpenVPNReachability()
|
let vpnReachability = OpenVPNReachability()
|
||||||
|
|
||||||
var startHandler: ((Error?) -> Void)?
|
var startHandler: ((Error?) -> Void)?
|
||||||
var stopHandler: (() -> Void)?
|
var stopHandler: (() -> Void)?
|
||||||
var protoType: TunnelProtoType = .none
|
var protoType: TunnelProtoType = .none
|
||||||
|
@ -63,26 +67,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
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 {
|
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||||
Logger.global?.log(message: "Failed to serialize message from app")
|
Logger.global?.log(message: "Failed to serialize message from app")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let completionHandler = completionHandler else {
|
guard let completionHandler = completionHandler else {
|
||||||
Logger.global?.log(message: "Missing message completion handler")
|
Logger.global?.log(message: "Missing message completion handler")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||||
Logger.global?.log(message: "Missing action key in app message")
|
Logger.global?.log(message: "Missing action key in app message")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStatus {
|
if action == Constants.kActionStatus {
|
||||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
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 callbackWrapper: (NSNumber?) -> Void = { errorCode in
|
||||||
//let tunnelId = self.tunnelConfig?.id ?? ""
|
//let tunnelId = self.tunnelConfig?.id ?? ""
|
||||||
let response: [String: Any] = [
|
let response: [String: Any] = [
|
||||||
|
@ -90,11 +102,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
||||||
Constants.kMessageKeyTunnelId: 0
|
Constants.kMessageKeyTunnelId: 0
|
||||||
]
|
]
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||||
dispatchQueue.async {
|
dispatchQueue.async {
|
||||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||||
|
@ -118,8 +130,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
switch self.protoType {
|
switch self.protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
self.startWireguard(activationAttemptId: activationAttemptId,
|
||||||
errorNotifier: errorNotifier,
|
errorNotifier: errorNotifier,
|
||||||
completionHandler: completionHandler)
|
completionHandler: completionHandler)
|
||||||
case .openvpn:
|
case .openvpn:
|
||||||
self.startOpenVPN(completionHandler: completionHandler)
|
self.startOpenVPN(completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
case .shadowsocks:
|
||||||
|
@ -156,7 +168,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
case .shadowsocks:
|
||||||
break
|
break
|
||||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -168,12 +180,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
errorNotifier: ErrorNotifier,
|
errorNotifier: ErrorNotifier,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let wgConfigStr = String(data: wgConfig, encoding: .utf8)!
|
let wgConfigStr = String(data: wgConfig, encoding: .utf8)!
|
||||||
|
|
||||||
|
@ -182,7 +195,49 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
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"))
|
wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
|
@ -193,30 +248,30 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch adapterError {
|
switch adapterError {
|
||||||
case .cannotLocateTunnelFileDescriptor:
|
case .cannotLocateTunnelFileDescriptor:
|
||||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
|
|
||||||
case .dnsResolution(let dnsErrors):
|
case .dnsResolution(let dnsErrors):
|
||||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||||
.joined(separator: ", ")
|
.joined(separator: ", ")
|
||||||
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
|
|
||||||
case .setNetworkSettings(let error):
|
case .setNetworkSettings(let error):
|
||||||
wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
|
|
||||||
case .startWireGuardBackend(let errorCode):
|
case .startWireGuardBackend(let errorCode):
|
||||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
|
|
||||||
case .invalidState:
|
case .invalidState:
|
||||||
// Must never happen
|
// Must never happen
|
||||||
fatalError()
|
fatalError()
|
||||||
|
@ -226,27 +281,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||||
// TODO: handle errors properly
|
// TODO: handle errors properly
|
||||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||||
|
|
||||||
wgAdapter.stop { error in
|
wgAdapter.stop { error in
|
||||||
ErrorNotifier.removeLastErrorFile()
|
ErrorNotifier.removeLastErrorFile()
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
// 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
|
// 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()
|
ovpnAdapter.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
wgAdapter.getRuntimeConfiguration { settings in
|
||||||
|
@ -278,8 +333,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
for component in components{
|
for component in components{
|
||||||
let pair = component.components(separatedBy: "=")
|
let pair = component.components(separatedBy: "=")
|
||||||
if pair.count == 2 {
|
if pair.count == 2 {
|
||||||
settingsDictionary[pair[0]] = pair[1]
|
settingsDictionary[pair[0]] = pair[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: [String: Any] = [
|
let response: [String: Any] = [
|
||||||
|
@ -309,7 +364,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||||
|
@ -318,7 +373,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||||
var data: Data?
|
var data: Data?
|
||||||
if let settings = settings {
|
if let settings = settings {
|
||||||
|
@ -334,76 +389,76 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||||
|
|
||||||
let response: [String: Any] = [
|
let response: [String: Any] = [
|
||||||
"rx_bytes" : bytesin,
|
"rx_bytes" : bytesin,
|
||||||
"tx_bytes" : bytesout
|
"tx_bytes" : bytesout
|
||||||
]
|
]
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO review
|
// TODO review
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
|
||||||
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
||||||
|
|
||||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
let configuration = OpenVPNConfiguration()
|
||||||
configuration.fileContent = ovpnConfiguration
|
configuration.fileContent = ovpnConfiguration
|
||||||
if(str.contains("cloak")){
|
if(str.contains("cloak")){
|
||||||
configuration.setPTCloak();
|
configuration.setPTCloak();
|
||||||
}
|
}
|
||||||
|
|
||||||
let evaluation: OpenVPNConfigurationEvaluation
|
let evaluation: OpenVPNConfigurationEvaluation
|
||||||
do {
|
do {
|
||||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !evaluation.autologin {
|
if !evaluation.autologin {
|
||||||
wg_log(.info, message: "Implement login with user credentials")
|
wg_log(.info, message: "Implement login with user credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
guard status == .reachableViaWiFi else { return }
|
guard status == .reachableViaWiFi else { return }
|
||||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
ovpnAdapter.connect(using: packetFlow)
|
ovpnAdapter.connect(using: packetFlow)
|
||||||
|
|
||||||
// let ifaces = Interface.allInterfaces()
|
// let ifaces = Interface.allInterfaces()
|
||||||
// .filter { $0.family == .ipv4 }
|
// .filter { $0.family == .ipv4 }
|
||||||
// .map { iface in iface.name }
|
// .map { iface in iface.name }
|
||||||
|
|
||||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- Network observing methods
|
// MARK: -- Network observing methods
|
||||||
|
|
||||||
private func startListeningForNetworkChanges() {
|
private func startListeningForNetworkChanges() {
|
||||||
stopListeningForNetworkChanges()
|
stopListeningForNetworkChanges()
|
||||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopListeningForNetworkChanges() {
|
private func stopListeningForNetworkChanges() {
|
||||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func observeValue(forKeyPath keyPath: String?,
|
override func observeValue(forKeyPath keyPath: String?,
|
||||||
of object: Any?,
|
of object: Any?,
|
||||||
change: [NSKeyValueChangeKey : Any]?,
|
change: [NSKeyValueChangeKey : Any]?,
|
||||||
context: UnsafeMutableRawPointer?) {
|
context: UnsafeMutableRawPointer?) {
|
||||||
guard Constants.kDefaultPathKey != keyPath else { return }
|
guard Constants.kDefaultPathKey != keyPath else { return }
|
||||||
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
|
// 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),
|
// 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,
|
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
|
||||||
let defPath = defaultPath,
|
let defPath = defaultPath,
|
||||||
lastPath != defPath || lastPath.description != defPath.description else {
|
lastPath != defPath || lastPath.description != defPath.description else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let `self` = self, self.defaultPath != nil else { return }
|
guard let `self` = self, self.defaultPath != nil else { return }
|
||||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||||
|
|
||||||
let emptyTunnelConfiguration = TunnelConfiguration(
|
let emptyTunnelConfiguration = TunnelConfiguration(
|
||||||
name: nil,
|
name: nil,
|
||||||
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||||
peers: []
|
peers: []
|
||||||
)
|
)
|
||||||
|
|
||||||
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||||
self.dispatchQueue.async {
|
self.dispatchQueue.async {
|
||||||
if let error {
|
if let error {
|
||||||
|
@ -445,9 +500,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||||
|
|
||||||
self.setTunnelNetworkSettings(settings) { error in
|
self.setTunnelNetworkSettings(settings) { error in
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
}
|
}
|
||||||
|
@ -478,6 +533,50 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
// send empty string to NEDNSSettings.matchDomains
|
// send empty string to NEDNSSettings.matchDomains
|
||||||
networkSettings?.dnsSettings?.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.
|
// Set the network settings for the current tunneling session.
|
||||||
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
|
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
@ -538,7 +637,7 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
wg_log(.info, message: logMessage)
|
wg_log(.info, message: logMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WireGuardLogLevel {
|
extension WireGuardLogLevel {
|
||||||
var osLogLevel: OSLogType {
|
var osLogLevel: OSLogType {
|
||||||
switch self {
|
switch self {
|
||||||
|
|
|
@ -158,15 +158,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
||||||
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rtm->rtm_type == RTN_THROW) {
|
if (rtm->rtm_type == RTN_THROW) {
|
||||||
int index = if_nametoindex(getgatewayandiface().toUtf8());
|
struct in_addr ip4;
|
||||||
if (index <= 0) {
|
inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4);
|
||||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||||
return false;
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||||
}
|
rtm->rtm_type = RTN_UNICAST;
|
||||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_nl nladdr;
|
struct sockaddr_nl nladdr;
|
||||||
|
@ -334,7 +334,7 @@ QString LinuxRouteMonitor::getgatewayandiface()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(sock);
|
close(sock);
|
||||||
return interface;
|
return gateway_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool buildAllowedIp(wg_allowedip* ip,
|
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);
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
if (result != ERROR_SUCCESS) {
|
if (result != ERROR_SUCCESS) {
|
||||||
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
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,
|
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||||
int weight, const QString& title,
|
int weight, const QString& title,
|
||||||
const QString& peer) {
|
const QString& peer) {
|
||||||
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
GUID layerOut =
|
GUID layerOut =
|
||||||
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
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;
|
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) {
|
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||||
// Allow outbound DHCPv4
|
// Allow outbound DHCPv4
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,6 +52,9 @@ class WindowsFirewall final : public QObject {
|
||||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||||
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||||
const QString& title, const QString& peer = QString());
|
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,
|
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
const QString& title);
|
const QString& title);
|
||||||
bool allowDHCPTraffic(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_priv_key[] = "server_priv_key";
|
||||||
constexpr char server_pub_key[] = "server_pub_key";
|
constexpr char server_pub_key[] = "server_pub_key";
|
||||||
constexpr char psk_key[] = "psk_key";
|
constexpr char psk_key[] = "psk_key";
|
||||||
|
constexpr char allowed_ips[] = "allowed_ips";
|
||||||
|
|
||||||
constexpr char client_ip[] = "client_ip"; // internal ip address
|
constexpr char client_ip[] = "client_ip"; // internal ip address
|
||||||
|
|
||||||
|
@ -84,7 +85,11 @@ namespace amnezia
|
||||||
constexpr char accessToken[] = "access_token";
|
constexpr char accessToken[] = "access_token";
|
||||||
constexpr char certificate[] = "certificate";
|
constexpr char certificate[] = "certificate";
|
||||||
constexpr char publicKey[] = "public_key";
|
constexpr char publicKey[] = "public_key";
|
||||||
}
|
|
||||||
|
constexpr char splitTunnelSites[] = "splitTunnelSites";
|
||||||
|
constexpr char splitTunnelType[] = "splitTunnelType";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace protocols
|
namespace protocols
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,8 +16,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||||
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
|
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
|
||||||
writeWireguardConfiguration(configuration);
|
writeWireguardConfiguration(configuration);
|
||||||
|
|
||||||
// MZ
|
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
||||||
m_impl.reset(new LocalSocketController());
|
m_impl.reset(new LocalSocketController());
|
||||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||||
|
@ -26,7 +24,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||||
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
||||||
m_impl->initialize(nullptr, nullptr);
|
m_impl->initialize(nullptr, nullptr);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WireguardProtocol::~WireguardProtocol()
|
WireguardProtocol::~WireguardProtocol()
|
||||||
|
@ -37,68 +34,10 @@ WireguardProtocol::~WireguardProtocol()
|
||||||
|
|
||||||
void WireguardProtocol::stop()
|
void WireguardProtocol::stop()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
||||||
stopMzImpl();
|
stopMzImpl();
|
||||||
return;
|
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()
|
ErrorCode WireguardProtocol::startMzImpl()
|
||||||
{
|
{
|
||||||
m_impl->activate(m_rawConfig);
|
m_impl->activate(m_rawConfig);
|
||||||
|
@ -110,7 +49,6 @@ ErrorCode WireguardProtocol::stopMzImpl()
|
||||||
m_impl->deactivate();
|
m_impl->deactivate();
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
|
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.write(jConfig.value(config_key::config).toString().toUtf8());
|
||||||
m_configFile.close();
|
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();
|
m_configFileName = m_configFile.fileName();
|
||||||
#endif
|
|
||||||
|
|
||||||
m_isConfigLoaded = true;
|
m_isConfigLoaded = true;
|
||||||
|
|
||||||
|
@ -152,15 +77,9 @@ QString WireguardProtocol::configPath() const
|
||||||
return m_configFileName;
|
return m_configFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WireguardProtocol::updateRouteGateway(QString line)
|
QString WireguardProtocol::serviceName() const
|
||||||
{
|
{
|
||||||
// TODO: fix for macos
|
return "AmneziaVPN.WireGuard0";
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode WireguardProtocol::start()
|
ErrorCode WireguardProtocol::start()
|
||||||
|
@ -170,112 +89,6 @@ ErrorCode WireguardProtocol::start()
|
||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
||||||
return startMzImpl();
|
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 <QTimer>
|
||||||
|
|
||||||
#include "vpnprotocol.h"
|
#include "vpnprotocol.h"
|
||||||
#include "core/ipcclient.h"
|
|
||||||
|
|
||||||
#include "mozilla/controllerimpl.h"
|
#include "mozilla/controllerimpl.h"
|
||||||
|
|
||||||
|
@ -23,33 +22,21 @@ public:
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
||||||
ErrorCode startMzImpl();
|
ErrorCode startMzImpl();
|
||||||
ErrorCode stopMzImpl();
|
ErrorCode stopMzImpl();
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString configPath() const;
|
QString configPath() const;
|
||||||
void writeWireguardConfiguration(const QJsonObject &configuration);
|
void writeWireguardConfiguration(const QJsonObject &configuration);
|
||||||
|
|
||||||
void updateRouteGateway(QString line);
|
|
||||||
void updateVpnGateway(const QString &line);
|
|
||||||
QString serviceName() const;
|
QString serviceName() const;
|
||||||
QStringList stopArgs();
|
|
||||||
QStringList startArgs();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_configFileName;
|
QString m_configFileName;
|
||||||
QFile m_configFile;
|
QFile m_configFile;
|
||||||
|
|
||||||
QSharedPointer<PrivilegedProcess> m_wireguardStartProcess;
|
|
||||||
QSharedPointer<PrivilegedProcess> m_wireguardStopProcess;
|
|
||||||
|
|
||||||
bool m_isConfigLoaded = false;
|
bool m_isConfigLoaded = false;
|
||||||
|
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
||||||
QScopedPointer<ControllerImpl> m_impl;
|
QScopedPointer<ControllerImpl> m_impl;
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIREGUARDPROTOCOL_H
|
#endif // WIREGUARDPROTOCOL_H
|
||||||
|
|
|
@ -233,10 +233,6 @@ QString Settings::routeModeString(RouteMode mode) const
|
||||||
|
|
||||||
Settings::RouteMode Settings::routeMode() 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());
|
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -697,7 +697,7 @@ Already installed containers were found on the server. All installed containers
|
||||||
<location filename="../ui/qml/Pages2/PageServiceDnsSettings.qml" line="52"/>
|
<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>A DNS service is installed on your server, and it is only accessible via VPN.
|
||||||
</source>
|
</source>
|
||||||
<translation>На вашем сервере устанавливается DNS-сервис, доступ к нему возможен только через VPN.
|
<translation>На вашем сервере установлен DNS-сервис, доступ к нему возможен только через VPN.
|
||||||
</translation>
|
</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
@ -1343,7 +1343,7 @@ Already installed containers were found on the server. All installed containers
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
|
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
|
||||||
<source>Clear Amnezia cache</source>
|
<source>Clear Amnezia cache</source>
|
||||||
<translation>Очистить кэш Amnezia на сервере</translation>
|
<translation>Очистить кэш Amnezia</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
|
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
|
||||||
|
|
|
@ -267,6 +267,10 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||||
// return QJsonObject();
|
// return QJsonObject();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));
|
||||||
|
|
||||||
|
lastConfig[config_key::allowed_ips] = allowedIpsJsonArray;
|
||||||
|
|
||||||
QString protocolName = "wireguard";
|
QString protocolName = "wireguard";
|
||||||
if (!configMap.value(config_key::junkPacketCount).isEmpty()
|
if (!configMap.value(config_key::junkPacketCount).isEmpty()
|
||||||
&& !configMap.value(config_key::junkPacketMinSize).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());
|
DockerContainer container = ContainerProps::allContainers().at(index.row());
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case NameRole:
|
|
||||||
// return ContainerProps::containerHumanNames().value(container);
|
|
||||||
case DescriptionRole:
|
|
||||||
// return ContainerProps::containerDescriptions().value(container);
|
|
||||||
case ConfigRole: {
|
case ConfigRole: {
|
||||||
m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject());
|
m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject());
|
||||||
m_containers = m_settings->containers(m_currentlyProcessedServerIndex);
|
m_containers = m_settings->containers(m_currentlyProcessedServerIndex);
|
||||||
|
@ -35,19 +31,15 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
|
||||||
break;
|
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
|
case IsDefaultRole: { //todo remove
|
||||||
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
|
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
|
||||||
m_defaultContainerIndex = container;
|
m_defaultContainerIndex = container;
|
||||||
emit defaultContainerChanged();
|
emit defaultContainerChanged();
|
||||||
}
|
}
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit containersModelUpdated();
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ protected:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void defaultContainerChanged();
|
void defaultContainerChanged();
|
||||||
|
void containersModelUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<DockerContainer, QJsonObject> m_containers;
|
QMap<DockerContainer, QJsonObject> m_containers;
|
||||||
|
|
|
@ -201,6 +201,12 @@ bool ServersModel::isDefaultServerConfigContainsAmneziaDns()
|
||||||
return primaryDns == protocols::dns::amneziaDnsIp;
|
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> ServersModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
|
@ -61,6 +61,7 @@ public slots:
|
||||||
bool isDefaultServerConfigContainsAmneziaDns();
|
bool isDefaultServerConfigContainsAmneziaDns();
|
||||||
|
|
||||||
QJsonObject getDefaultServerConfig();
|
QJsonObject getDefaultServerConfig();
|
||||||
|
void updateContainersConfig();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
|
@ -32,7 +32,7 @@ PageType {
|
||||||
function onRestorePageHomeState(isContainerInstalled) {
|
function onRestorePageHomeState(isContainerInstalled) {
|
||||||
buttonContent.state = "expanded"
|
buttonContent.state = "expanded"
|
||||||
if (isContainerInstalled) {
|
if (isContainerInstalled) {
|
||||||
containersDropDown.menuVisible = true
|
containersDropDown.rootButtonClickedFunction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onForceCloseDrawer() {
|
function onForceCloseDrawer() {
|
||||||
|
|
|
@ -94,7 +94,7 @@ PageType {
|
||||||
DividerType {}
|
DividerType {}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
visible: GC.isDesktop()
|
visible: true
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
|
|
@ -28,17 +28,11 @@ PageType {
|
||||||
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)) {
|
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) {
|
||||||
PageController.restorePageHomeState(true)
|
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)
|
PageController.showNotificationMessage(finishedMessage)
|
||||||
|
|
|
@ -68,7 +68,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||||
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
|
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
|
||||||
}
|
}
|
||||||
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
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);
|
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||||
|
|
||||||
|
@ -327,6 +327,8 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendSplitTunnelingConfig();
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
||||||
if (!m_vpnProtocol) {
|
if (!m_vpnProtocol) {
|
||||||
|
@ -361,6 +363,25 @@ void VpnConnection::createProtocolConnections()
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
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(amnezia::protocols::dns::amneziaDnsIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode);
|
||||||
|
m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
void VpnConnection::restoreConnection()
|
void VpnConnection::restoreConnection()
|
||||||
{
|
{
|
||||||
|
|
|
@ -112,6 +112,8 @@ private:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void createProtocolConnections();
|
void createProtocolConnections();
|
||||||
|
|
||||||
|
void appendSplitTunnelingConfig();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VPNCONNECTION_H
|
#endif // VPNCONNECTION_H
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue