From b78bf39767a975f897729cdacdca30076864c4a2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 13 Oct 2023 15:45:06 +0500 Subject: [PATCH 01/24] added split tunneling to the config --- client/protocols/protocols_defs.h | 4 ++++ client/translations/amneziavpn_ru.ts | 20 +++++++++++++++++--- client/translations/amneziavpn_zh_CN.ts | 20 +++++++++++++++++--- client/ui/controllers/importController.cpp | 7 +++++++ client/vpnconnection.cpp | 16 ++++++++++++++++ client/vpnconnection.h | 2 ++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index d6af132b..a6840e8c 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -43,6 +43,7 @@ namespace amnezia constexpr char server_priv_key[] = "server_priv_key"; constexpr char server_pub_key[] = "server_pub_key"; constexpr char psk_key[] = "psk_key"; + constexpr char allowed_ips[] = "allowed_ips"; constexpr char client_ip[] = "client_ip"; // internal ip address @@ -78,6 +79,9 @@ namespace amnezia constexpr char sftp[] = "sftp"; constexpr char awg[] = "awg"; + constexpr char splitTunnelSites[] = "splitTunnelSites"; + constexpr char splitTunnelType[] = "splitTunnelType"; + } namespace protocols diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index ac099552..d3c2657e 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -2413,10 +2413,14 @@ It's okay as long as it's from someone you trust. - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. @@ -2537,6 +2541,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2704,7 +2718,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 7a08682c..88e13f04 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -2520,10 +2520,14 @@ It's okay as long as it's from someone you trust. - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. @@ -2644,6 +2648,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2815,7 +2829,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 08b662ec..044ddb37 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -263,6 +263,13 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) // return QJsonObject(); // } + auto allowedIps = configMap.value("AllowedIPs").split(","); + QJsonArray allowedIpsJsonArray; + for (const auto &allowedIp : allowedIps) { + allowedIpsJsonArray.append(allowedIp); + } + lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; + QString protocolName = "wireguard"; if (!configMap.value(config_key::junkPacketCount).isEmpty() && !configMap.value(config_key::junkPacketMinSize).isEmpty() diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 46e8be60..c73df444 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -329,6 +329,8 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede return; } + appendSplitTunnelingConfig(); + #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { @@ -363,6 +365,20 @@ void VpnConnection::createProtocolConnections() connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } +void VpnConnection::appendSplitTunnelingConfig() +{ + auto routeMode = m_settings->routeMode(); + auto sites = m_settings->getVpnIps(routeMode); + + QJsonArray sitesJsonArray; + for (const auto &site : sites) { + sitesJsonArray.append(site); + } + + m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); + m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); +} + #ifdef Q_OS_ANDROID void VpnConnection::restoreConnection() { diff --git a/client/vpnconnection.h b/client/vpnconnection.h index f6b2343c..45582de5 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -112,6 +112,8 @@ private: #endif void createProtocolConnections(); + + void appendSplitTunnelingConfig(); }; #endif // VPNCONNECTION_H From 2df612ec1f760066e61c2f00a1a8d7989fe6659b Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 16 Oct 2023 12:05:35 -0400 Subject: [PATCH 02/24] Android SplitTunnel --- client/amnezia_application.cpp | 2 +- .../src/org/amnezia/vpn/OpenVPNThreadv3.kt | 51 ++++++++++++++++++- .../android/src/org/amnezia/vpn/VPNService.kt | 42 ++++++++++++--- client/configurators/openvpn_configurator.cpp | 10 +++- .../ui/qml/Pages2/PageSettingsConnection.qml | 2 +- 5 files changed, 94 insertions(+), 13 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 904ffaa6..05ced2f0 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -297,7 +297,7 @@ void AmneziaApplication::initModels() connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard && m_sitesModel->isSplitTunnelingEnabled()) { - m_sitesModel->toggleSplitTunneling(false); + m_sitesModel->toggleSplitTunneling(true); emit m_pageController->showNotificationMessage( tr("Split tunneling for WireGuard is not implemented, the option was disabled")); } diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt index 2a6e6bf5..20869e48 100644 --- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt +++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt @@ -72,6 +72,13 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna val jsonVpnConfig = mService.getVpnConfig() val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config") + Log.e(tag, "jsonVpnConfig $jsonVpnConfig") + val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType") + val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites") + + Log.e(tag, "splitTunnelType $splitTunnelType") + Log.e(tag, "splitTunnelSites $splitTunnelSites") + val resultingConfig = StringBuilder() resultingConfig.append(ovpnConfig) @@ -115,6 +122,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna eval_config(config) val status = connect() + if (status.getError()) { Log.i(tag, "connect() error: " + status.getError() + ": " + status.getMessage()) } @@ -139,7 +147,46 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna override fun tun_builder_establish(): Int { Log.v(tag, "tun_builder_establish") - return mService.establish()!!.detachFd() + val Fd = mService.establish()!!.detachFd() + + val jsonVpnConfig = mService.getVpnConfig() + + val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType") + val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites") + + Log.e(tag, "splitTunnelSites $splitTunnelSites") + for (i in 0 until splitTunnelSites.length()) { + val site = splitTunnelSites.getString(i) + if (site.contains("\\/")) { + Log.e(tag, "site $site rawMask 32") + mService.addRoute(site, 32) + } else { + var slash = site.lastIndexOf('/'); + var maskString: String = "" + var rawMask = 32 + var rawAddress: String = "" + if (slash >= 0) { + maskString = site.substring(slash + 1) + try { + rawMask = Integer.parseInt(maskString, 10) + } catch (e: Exception) { + + } + rawAddress = site.substring(0, slash) + } else { + maskString = "" + rawMask = 32 + rawAddress = site + } + Log.e(tag, "rawAddress $rawAddress rawMask $rawMask") + mService.addRoute(rawAddress, rawMask) + //val internet = InetNetwork.parse(site) + //peerBuilder.addAllowedIp(internet) + } + Log.e(tag, "splitTunnelSites $site") + } + + return Fd } override fun tun_builder_add_address(address: String , prefix_length: Int , gateway: String , ipv6:Boolean , net30: Boolean ): Boolean { @@ -159,7 +206,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean , flags: Long): Boolean { Log.v(tag, "tun_builder_reroute_gw") - mService.addRoute("0.0.0.0", 0) + // mService.addRoute("0.0.0.0", 0) return true } diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 06f58980..047192a6 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -571,6 +571,9 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { private fun buildWireguardConfig(obj: JSONObject, type: String): Config { val confBuilder = Config.Builder() val wireguardConfigData = obj.getJSONObject(type) + val splitTunnelType = obj.getInt("splitTunnelType") + val splitTunnelSites = obj.getJSONArray("splitTunnelSites") + val config = parseConfigData(wireguardConfigData.getString("config")) val peerBuilder = Peer.Builder() val peerConfig = config["Peer"]!! @@ -579,15 +582,37 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { peerBuilder.setPreSharedKey(Key.fromBase64(it)) } val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList() - if (allowedIPList.isEmpty()) { - val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. - peerBuilder.addAllowedIp(internet) - } else { - allowedIPList.forEach { - val network = InetNetwork.parse(it.trim()) - peerBuilder.addAllowedIp(network) + + Log.e(tag, "splitTunnelSites $splitTunnelSites") + for (i in 0 until splitTunnelSites.length()) { + val site = splitTunnelSites.getString(i) + if (site.contains("\\/")) { + val internet = InetNetwork.parse(site + "\\32") + peerBuilder.addAllowedIp(internet) + } else { + val internet = InetNetwork.parse(site) + peerBuilder.addAllowedIp(internet) } + Log.e(tag, "splitTunnelSites $site") } + + // if (allowedIPList.isEmpty() /*&& splitTunnelType.equals("0", true) */) { + // Log.e(tag, "splitTunnelSites $splitTunnelSites") + // for (i in 0 until splitTunnelSites.length()) { + // val site = splitTunnelSites.getString(i) + // Log.e(tag, "splitTunnelSites $site") + // } + + // val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. + // peerBuilder.addAllowedIp(internet) + // } else { + + + // allowedIPList.forEach { + // val network = InetNetwork.parse(it.trim()) + // peerBuilder.addAllowedIp(network) + // } + // } val endpointConfig = peerConfig["Endpoint"] val endpoint = InetEndpoint.parse(endpointConfig) peerBuilder.setEndpoint(endpoint) @@ -753,6 +778,9 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { GoBackend.wgTurnOff(currentTunnelHandle) } val wgConfig: String = wireguard_conf.toWgUserspaceString() + + Log.e(tag, "wgConfig : $wgConfig") + val builder = Builder() setupBuilder(wireguard_conf, builder) builder.setSession("Amnezia") diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index a62bdd9c..5c4004ad 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -125,16 +125,22 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.replace(regex, ""); if (m_settings->routeMode() == Settings::VpnAllSites) { - config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); + qDebug() << "Settings::VpnAllSites"; + + //config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); } if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { + qDebug() << "Settings::VpnOnlyForwardSites"; + // no redirect-gateway } if (m_settings->routeMode() == Settings::VpnAllExceptSites) { - config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); + qDebug() << "Settings::VpnAllExceptSites"; + + //config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index b5b1bd97..51096057 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -94,7 +94,7 @@ PageType { DividerType {} LabelWithButtonType { - visible: !GC.isMobile() + visible: GC.isDesktop() || Qt.platform.os === "android" Layout.fillWidth: true From 546d4c1d3dad14f33128f4ec5ce0a33424401ce0 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 17 Oct 2023 14:35:13 -0400 Subject: [PATCH 03/24] WG/AWG Android splitTunnel --- .../src/com/wireguard/config/IPRange.java | 509 ++++++++++++++++++ .../src/com/wireguard/config/IPRangeSet.java | 223 ++++++++ .../src/com/wireguard/config/Utils.java | 77 +++ .../android/src/org/amnezia/vpn/VPNService.kt | 107 ++-- client/configurators/openvpn_configurator.cpp | 4 +- client/settings.cpp | 2 +- 6 files changed, 874 insertions(+), 48 deletions(-) create mode 100644 client/android/src/com/wireguard/config/IPRange.java create mode 100644 client/android/src/com/wireguard/config/IPRangeSet.java create mode 100644 client/android/src/com/wireguard/config/Utils.java diff --git a/client/android/src/com/wireguard/config/IPRange.java b/client/android/src/com/wireguard/config/IPRange.java new file mode 100644 index 00000000..11dd66e1 --- /dev/null +++ b/client/android/src/com/wireguard/config/IPRange.java @@ -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 . + * + * 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 +{ + 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 remove(IPRange range) + { + ArrayList 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 toSubnets() + { + ArrayList 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; + } +} diff --git a/client/android/src/com/wireguard/config/IPRangeSet.java b/client/android/src/com/wireguard/config/IPRangeSet.java new file mode 100644 index 00000000..b77d4f73 --- /dev/null +++ b/client/android/src/com/wireguard/config/IPRangeSet.java @@ -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 . + * + * 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 +{ + private TreeSet 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 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 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 additions = new ArrayList<>(); + Iterator iterator = mRanges.iterator(); + while (iterator.hasNext()) + { + IPRange existing = iterator.next(); + List 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 subnets() + { + return new Iterable() + { + @Override + public Iterator iterator() + { + return new Iterator() + { + private Iterator mIterator = mRanges.iterator(); + private List 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 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(); + } +} diff --git a/client/android/src/com/wireguard/config/Utils.java b/client/android/src/com/wireguard/config/Utils.java new file mode 100644 index 00000000..58b2eb5f --- /dev/null +++ b/client/android/src/com/wireguard/config/Utils.java @@ -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 . + * + * 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); + } +} diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 047192a6..da7391b2 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -564,6 +564,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { return parseData } + /** * Create a Wireguard [Config] from a [json] string - * The [json] will be created in AndroidVpnProtocol.cpp @@ -578,47 +579,67 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { val peerBuilder = Peer.Builder() val peerConfig = config["Peer"]!! peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"])) - peerConfig["PresharedKey"]?.let { - peerBuilder.setPreSharedKey(Key.fromBase64(it)) - } - val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList() - - Log.e(tag, "splitTunnelSites $splitTunnelSites") - for (i in 0 until splitTunnelSites.length()) { - val site = splitTunnelSites.getString(i) - if (site.contains("\\/")) { - val internet = InetNetwork.parse(site + "\\32") - peerBuilder.addAllowedIp(internet) - } else { - val internet = InetNetwork.parse(site) - peerBuilder.addAllowedIp(internet) - } - Log.e(tag, "splitTunnelSites $site") - } - - // if (allowedIPList.isEmpty() /*&& splitTunnelType.equals("0", true) */) { - // Log.e(tag, "splitTunnelSites $splitTunnelSites") - // for (i in 0 until splitTunnelSites.length()) { - // val site = splitTunnelSites.getString(i) - // Log.e(tag, "splitTunnelSites $site") - // } - - // val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. - // peerBuilder.addAllowedIp(internet) - // } else { + peerConfig["PresharedKey"]?.let { peerBuilder.setPreSharedKey(Key.fromBase64(it)) } - - // allowedIPList.forEach { - // val network = InetNetwork.parse(it.trim()) - // peerBuilder.addAllowedIp(network) - // } - // } + val allIpString = peerConfig["AllowedIPs"] + + var allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList() + + /* default value in template */ + if (allIpString == "0.0.0.0/0, ::/0") { + allowedIPList = emptyList() + } + + if (allowedIPList.isEmpty() && (splitTunnelType == 0)) { + /* AllowedIP is empty and splitTunnel is turnoff */ + /* use VPN for whole Internet */ + val internetV4 = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. + peerBuilder.addAllowedIp(internetV4) + val internetV6 = InetNetwork.parse("::/0") // aka The whole internet. + peerBuilder.addAllowedIp(internetV6) + } else { + if (!allowedIPList.isEmpty()) { + /* We have predefined AllowedIP in WG config */ + /* It's have higher priority than system SplitTunnel */ + allowedIPList.forEach { + val network = InetNetwork.parse(it.trim()) + peerBuilder.addAllowedIp(network) + } + } else { + if (splitTunnelType == 1) { + /* Use system SplitTunnel */ + /* VPN connection used only for defined IPs */ + for (i in 0 until splitTunnelSites.length()) { + val site = splitTunnelSites.getString(i) + Log.e(tag, "splitTunnelSites $site") + if (site.contains("\\/")) { + val internet = InetNetwork.parse(site + "\\32") + peerBuilder.addAllowedIp(internet) + } else { + 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" + Log.e(tag, "allowedIps $allowedIps") + peerBuilder.parseAllowedIPs(allowedIps) + } + } + } val endpointConfig = peerConfig["Endpoint"] val endpoint = InetEndpoint.parse(endpointConfig) peerBuilder.setEndpoint(endpoint) - peerConfig["PersistentKeepalive"]?.let { - peerBuilder.setPersistentKeepalive(it.toInt()) - } + peerConfig["PersistentKeepalive"]?.let { peerBuilder.setPersistentKeepalive(it.toInt()) } confBuilder.addPeer(peerBuilder.build()) val ifaceBuilder = Interface.Builder() @@ -628,7 +649,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ifaceConfig["DNS"]!!.split(",").forEach { ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address) } - + ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"]) if (type == "awg_config_data") { ifaceBuilder.parseJc(ifaceConfig["Jc"]) @@ -649,14 +670,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ifaceBuilder.parseH1("0") ifaceBuilder.parseH2("0") ifaceBuilder.parseH3("0") - ifaceBuilder.parseH4("0") - + ifaceBuilder.parseH4("0") } /*val jExcludedApplication = obj.getJSONArray("excludedApps") - (0 until jExcludedApplication.length()).toList().forEach { + (0 until jExcludedApplication.length()).toList().forEach { val appName = jExcludedApplication.get(it).toString() ifaceBuilder.excludeApplication(appName) - }*/ + }*/ confBuilder.setInterface(ifaceBuilder.build()) return confBuilder.build() @@ -771,7 +791,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { private fun startWireGuard(type: String) { val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data") - Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf") if (currentTunnelHandle != -1) { Log.e(tag, "Tunnel already up") // Turn the tunnel down because this might be a switch @@ -779,8 +798,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { } val wgConfig: String = wireguard_conf.toWgUserspaceString() - Log.e(tag, "wgConfig : $wgConfig") - val builder = Builder() setupBuilder(wireguard_conf, builder) builder.setSession("Amnezia") diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 5c4004ad..8b2792cb 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -127,7 +127,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) if (m_settings->routeMode() == Settings::VpnAllSites) { qDebug() << "Settings::VpnAllSites"; - //config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); + config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); @@ -140,7 +140,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) if (m_settings->routeMode() == Settings::VpnAllExceptSites) { qDebug() << "Settings::VpnAllExceptSites"; - //config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); + config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); diff --git a/client/settings.cpp b/client/settings.cpp index 93871834..c6db0b63 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -234,7 +234,7 @@ QString Settings::routeModeString(RouteMode mode) const Settings::RouteMode Settings::routeMode() const { // TODO implement for mobiles -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_IOS) return RouteMode::VpnAllSites; #endif return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); From c14f1b5000476f05e031e6da93f46f27286b2721 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 17 Oct 2023 16:39:56 -0400 Subject: [PATCH 04/24] Android OpenVPN/Cloak Split tunnel --- .../src/org/amnezia/vpn/OpenVPNThreadv3.kt | 59 ++++++++----------- .../android/src/org/amnezia/vpn/VPNService.kt | 9 +-- client/configurators/openvpn_configurator.cpp | 6 +- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt index 20869e48..a59dff6a 100644 --- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt +++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt @@ -16,6 +16,8 @@ import com.wireguard.crypto.Key import org.json.JSONObject import java.util.Base64 +import com.wireguard.config.* + import net.openvpn.ovpn3.ClientAPI_Config import net.openvpn.ovpn3.ClientAPI_EvalConfig import net.openvpn.ovpn3.ClientAPI_Event @@ -147,45 +149,34 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna override fun tun_builder_establish(): Int { Log.v(tag, "tun_builder_establish") - val Fd = mService.establish()!!.detachFd() - val jsonVpnConfig = mService.getVpnConfig() - + val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType") val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites") - - Log.e(tag, "splitTunnelSites $splitTunnelSites") - for (i in 0 until splitTunnelSites.length()) { - val site = splitTunnelSites.getString(i) - if (site.contains("\\/")) { - Log.e(tag, "site $site rawMask 32") - mService.addRoute(site, 32) - } else { - var slash = site.lastIndexOf('/'); - var maskString: String = "" - var rawMask = 32 - var rawAddress: String = "" - if (slash >= 0) { - maskString = site.substring(slash + 1) - try { - rawMask = Integer.parseInt(maskString, 10) - } catch (e: Exception) { - - } - rawAddress = site.substring(0, slash) - } else { - maskString = "" - rawMask = 32 - rawAddress = site - } - Log.e(tag, "rawAddress $rawAddress rawMask $rawMask") - mService.addRoute(rawAddress, rawMask) - //val internet = InetNetwork.parse(site) - //peerBuilder.addAllowedIp(internet) + 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()) + Log.e(tag, "splitTunnelSites $ipRange") } - Log.e(tag, "splitTunnelSites $site") } - + 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(100) + Log.e(tag, "splitTunnelSites $it") + } + mService.addRoute("2000::", 3) + } + val Fd = mService.establish()!!.detachFd() + return Fd } diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index da7391b2..ca468108 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -612,13 +612,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { for (i in 0 until splitTunnelSites.length()) { val site = splitTunnelSites.getString(i) Log.e(tag, "splitTunnelSites $site") - if (site.contains("\\/")) { - val internet = InetNetwork.parse(site + "\\32") - peerBuilder.addAllowedIp(internet) - } else { - val internet = InetNetwork.parse(site) - peerBuilder.addAllowedIp(internet) - } + val internet = InetNetwork.parse(site) + peerBuilder.addAllowedIp(internet) } } if (splitTunnelType == 2) { diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 8b2792cb..cc5c30e5 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -125,8 +125,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.replace(regex, ""); if (m_settings->routeMode() == Settings::VpnAllSites) { - qDebug() << "Settings::VpnAllSites"; - config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); @@ -138,9 +136,9 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) // no redirect-gateway } if (m_settings->routeMode() == Settings::VpnAllExceptSites) { - qDebug() << "Settings::VpnAllExceptSites"; - +#ifndef Q_OS_ANDROID config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); +#endif // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); From 4ea1a195723ce9657adff7e7828e1702c642c9f3 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 18 Oct 2023 13:41:58 -0400 Subject: [PATCH 05/24] Cleanup WG implementation --- .../src/org/amnezia/vpn/OpenVPNThreadv3.kt | 3 +- client/protocols/wireguardprotocol.cpp | 193 +----------------- client/protocols/wireguardprotocol.h | 13 -- 3 files changed, 4 insertions(+), 205 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt index a59dff6a..d96131af 100644 --- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt +++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt @@ -175,9 +175,8 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna } mService.addRoute("2000::", 3) } - val Fd = mService.establish()!!.detachFd() - return Fd + return mService.establish()!!.detachFd() } override fun tun_builder_add_address(address: String , prefix_length: Int , gateway: String , ipv6:Boolean , net30: Boolean ): Boolean { diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index d675cd02..3b95a41a 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -16,8 +16,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); - // MZ -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) m_impl.reset(new LocalSocketController()); connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString &pubkey, const QDateTime &connectionTimestamp) { @@ -26,7 +24,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * connect(m_impl.get(), &ControllerImpl::disconnected, this, [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); m_impl->initialize(nullptr, nullptr); -#endif } WireguardProtocol::~WireguardProtocol() @@ -37,68 +34,10 @@ WireguardProtocol::~WireguardProtocol() void WireguardProtocol::stop() { -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) stopMzImpl(); return; -#endif - - if (!QFileInfo::exists(Utils::wireguardExecPath())) { - qCritical() << "Wireguard executable missing!"; - setLastError(ErrorCode::ExecutableMissing); - return; - } - - m_wireguardStopProcess = IpcClient::CreatePrivilegedProcess(); - - if (!m_wireguardStopProcess) { - qCritical() << "IpcProcess replica is not created!"; - setLastError(ErrorCode::AmneziaServiceConnectionFailed); - return; - } - - m_wireguardStopProcess->waitForSource(1000); - if (!m_wireguardStopProcess->isInitialized()) { - qWarning() << "IpcProcess replica is not connected!"; - setLastError(ErrorCode::AmneziaServiceConnectionFailed); - return; - } - - m_wireguardStopProcess->setProgram(PermittedProcess::Wireguard); - - m_wireguardStopProcess->setArguments(stopArgs()); - qDebug() << stopArgs().join(" "); - - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; - setConnectionState(Vpn::ConnectionState::Disconnected); - }); - - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, - [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; - }); - -#ifdef Q_OS_LINUX - if (IpcClient::Interface()) { - QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); - if (result.returnValue()) { - setConnectionState(Vpn::ConnectionState::Disconnected); - return; - } - } else { - qCritical() << "IPC client not initialized"; - setConnectionState(Vpn::ConnectionState::Disconnected); - return; - } -#endif - - m_wireguardStopProcess->start(); - m_wireguardStopProcess->waitForFinished(10000); - - setConnectionState(Vpn::ConnectionState::Disconnected); } -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) ErrorCode WireguardProtocol::startMzImpl() { m_impl->activate(m_rawConfig); @@ -110,7 +49,6 @@ ErrorCode WireguardProtocol::stopMzImpl() m_impl->deactivate(); return ErrorCode::NoError; } -#endif void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration) { @@ -124,21 +62,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.close(); -#if 0 - if (IpcClient::Interface()) { - QRemoteObjectPendingReply result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName()); - if (result.returnValue()) { - qCritical() << "Failed to copy wireguard config"; - return; - } - } else { - qCritical() << "IPC client not initialized"; - return; - } - m_configFileName = "/etc/wireguard/wg99.conf"; -#else + m_configFileName = m_configFile.fileName(); -#endif m_isConfigLoaded = true; @@ -152,15 +77,9 @@ QString WireguardProtocol::configPath() const return m_configFileName; } -void WireguardProtocol::updateRouteGateway(QString line) +QString WireguardProtocol::serviceName() const { - // TODO: fix for macos - line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) - return; - m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); - m_routeGateway.replace(" ", ""); - qDebug() << "Set VPN route gateway" << m_routeGateway; + return "AmneziaVPN.WireGuard0"; } ErrorCode WireguardProtocol::start() @@ -170,112 +89,6 @@ ErrorCode WireguardProtocol::start() return lastError(); } -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) return startMzImpl(); -#endif - - if (!QFileInfo::exists(Utils::wireguardExecPath())) { - setLastError(ErrorCode::ExecutableMissing); - return lastError(); - } - - if (IpcClient::Interface()) { - QRemoteObjectPendingReply 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 reply = m_wireguardStartProcess->readAll(); - reply.waitForFinished(1000); - qDebug() << "WireguardProtocol::WireguardProtocol readyRead" << reply.returnValue(); - }); - - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardOutput, this, [this]() { - QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAllStandardOutput(); - reply.waitForFinished(1000); - qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardOutput" << reply.returnValue(); - }); - - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardError, this, [this]() { - QRemoteObjectPendingReply 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 -} diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index dea8d6d9..4a6ae1e6 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -8,7 +8,6 @@ #include #include "vpnprotocol.h" -#include "core/ipcclient.h" #include "mozilla/controllerimpl.h" @@ -23,33 +22,21 @@ public: ErrorCode start() override; void stop() override; -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) ErrorCode startMzImpl(); ErrorCode stopMzImpl(); -#endif private: QString configPath() const; void writeWireguardConfiguration(const QJsonObject &configuration); - - void updateRouteGateway(QString line); - void updateVpnGateway(const QString &line); QString serviceName() const; - QStringList stopArgs(); - QStringList startArgs(); private: QString m_configFileName; QFile m_configFile; - QSharedPointer m_wireguardStartProcess; - QSharedPointer m_wireguardStopProcess; - bool m_isConfigLoaded = false; -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) QScopedPointer m_impl; -#endif }; #endif // WIREGUARDPROTOCOL_H From 32c304dc1b16bc8aebf95e2cfa35a07da0f8fa59 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 18 Oct 2023 17:44:28 -0400 Subject: [PATCH 06/24] WG/AWG SplitTunnel for desktop --- client/mozilla/localsocketcontroller.cpp | 53 +++++++++++++++---- .../linux/daemon/linuxroutemonitor.cpp | 14 ++--- client/vpnconnection.cpp | 2 +- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 2f6fe371..b7012dd8 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -115,8 +115,12 @@ void LocalSocketController::daemonConnected() { } void LocalSocketController::activate(const QJsonObject &rawConfig) { + QString protocolName = rawConfig.value("protocol").toString(); + int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray(); + QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject json; @@ -137,23 +141,52 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { QJsonArray jsAllowedIPAddesses; - QJsonObject range_ipv4; - range_ipv4.insert("address", "0.0.0.0"); - range_ipv4.insert("range", 0); - range_ipv4.insert("isIpv6", false); - jsAllowedIPAddesses.append(range_ipv4); + 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); + QJsonObject range_ipv6; + range_ipv6.insert("address", "::"); + range_ipv6.insert("range", 0); + range_ipv6.insert("isIpv6", true); + jsAllowedIPAddesses.append(range_ipv6); + } + + if (splitTunnelType == 1) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + qDebug() << "ipRange " << ipRange; + if (ipRange.split('/').size() > 1){ + QJsonObject range; + range.insert("address", ipRange.split('/')[0]); + range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit())); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } else { + QJsonObject range; + range.insert("address",ipRange); + range.insert("range", 32); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } + } + } json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); QJsonArray jsExcludedAddresses; jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName)); + if (splitTunnelType == 2) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + jsExcludedAddresses.append(ipRange); + } + } + json.insert("excludedAddresses", jsExcludedAddresses); diff --git a/client/platforms/linux/daemon/linuxroutemonitor.cpp b/client/platforms/linux/daemon/linuxroutemonitor.cpp index f0c49eb6..38f2c56c 100644 --- a/client/platforms/linux/daemon/linuxroutemonitor.cpp +++ b/client/platforms/linux/daemon/linuxroutemonitor.cpp @@ -158,15 +158,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type, return false; } nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index); + nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 1); } if (rtm->rtm_type == RTN_THROW) { - int index = if_nametoindex(getgatewayandiface().toUtf8()); - if (index <= 0) { - logger.error() << "if_nametoindex() failed:" << strerror(errno); - return false; - } - nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index); + struct in_addr ip4; + inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4); + nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); + nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); + rtm->rtm_type = RTN_UNICAST; } struct sockaddr_nl nladdr; @@ -334,7 +334,7 @@ QString LinuxRouteMonitor::getgatewayandiface() } } close(sock); - return interface; + return gateway_address; } static bool buildAllowedIp(wg_allowedip* ip, diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index c73df444..878a1cde 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -68,7 +68,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); } QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); - QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString(); + QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); From 414a47e2f2ec852ebae91e1520a511ea6ad63fd0 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 19 Oct 2023 14:50:51 -0400 Subject: [PATCH 07/24] WG/AWG Desktop AllowedIP from plain WG config --- client/mozilla/localsocketcontroller.cpp | 83 ++++++++++++++-------- client/ui/controllers/importController.cpp | 7 +- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index b7012dd8..cc1d6688 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -141,36 +141,63 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { QJsonArray jsAllowedIPAddesses; - 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); + QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray(); + QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(",")); - QJsonObject range_ipv6; - range_ipv6.insert("address", "::"); - range_ipv6.insert("range", 0); - range_ipv6.insert("isIpv6", true); - jsAllowedIPAddesses.append(range_ipv6); - } + if (plainAllowedIP != defaultAllowedIP) { + // Use AllowedIP list from WG config bacouse of higer priority - 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); + for (auto v : plainAllowedIP) { + QString ipRange = v.toString(); + qDebug() << "ipRange " << ipRange; + if (ipRange.split('/').size() > 1){ + QJsonObject range; + range.insert("address", ipRange.split('/')[0]); + range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit())); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } else { + QJsonObject range; + range.insert("address",ipRange); + range.insert("range", 32); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } + } + } else { + + // Use APP split tunnel + if (splitTunnelType == 0 || splitTunnelType == 2) { + QJsonObject range_ipv4; + range_ipv4.insert("address", "0.0.0.0"); + range_ipv4.insert("range", 0); + range_ipv4.insert("isIpv6", false); + jsAllowedIPAddesses.append(range_ipv4); + + QJsonObject range_ipv6; + range_ipv6.insert("address", "::"); + range_ipv6.insert("range", 0); + range_ipv6.insert("isIpv6", true); + jsAllowedIPAddesses.append(range_ipv6); + } + + if (splitTunnelType == 1) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + qDebug() << "ipRange " << ipRange; + if (ipRange.split('/').size() > 1){ + QJsonObject range; + range.insert("address", ipRange.split('/')[0]); + range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit())); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } else { + QJsonObject range; + range.insert("address",ipRange); + range.insert("range", 32); + range.insert("isIpv6", false); + jsAllowedIPAddesses.append(range); + } } } } diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 044ddb37..de8c003e 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -263,11 +263,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) // return QJsonObject(); // } - auto allowedIps = configMap.value("AllowedIPs").split(","); - QJsonArray allowedIpsJsonArray; - for (const auto &allowedIp : allowedIps) { - allowedIpsJsonArray.append(allowedIp); - } + QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(",")); + lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; QString protocolName = "wireguard"; From 78c83b2e216d1db2465f661771cc11bdba74d9ce Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 19 Oct 2023 17:03:24 -0400 Subject: [PATCH 08/24] Some logic fix --- client/mozilla/localsocketcontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index cc1d6688..56178eeb 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -144,7 +144,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray(); QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(",")); - if (plainAllowedIP != defaultAllowedIP) { + if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) { // Use AllowedIP list from WG config bacouse of higer priority for (auto v : plainAllowedIP) { From 0c33432436485c06a9ad98a71f77f0e933a2a1d6 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 21 Oct 2023 14:55:15 -0400 Subject: [PATCH 09/24] Fix pulling exiting AWG config from server --- client/configurators/awg_configurator.cpp | 4 +-- client/core/servercontroller.cpp | 30 ++++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 8962067a..2bf03359 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -16,8 +16,6 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, { QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); - QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); - ServerController serverController(m_settings); QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode); @@ -45,6 +43,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader)); config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader)); + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 443cd5a3..082e1338 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -591,20 +591,22 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); - vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); - vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); - vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); - vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); - vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", - amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); - vars.append({ { "$INIT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); - vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); - vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); - vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); + if (!amneziaWireguarConfig.value(config_key::junkPacketCount).toString().isEmpty()){ + vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); + vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", + amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); + vars.append({ { "$INIT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); + vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); + vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); + vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); + } QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { From 6a12cad1c9741c5ea00614f0aac16264ed78b540 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 23 Oct 2023 21:30:30 +0500 Subject: [PATCH 10/24] fixed page return after installation --- client/ui/qml/Pages2/PageHome.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardInstalling.qml | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index cc49e4f0..222f7764 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -32,7 +32,7 @@ PageType { function onRestorePageHomeState(isContainerInstalled) { buttonContent.state = "expanded" if (isContainerInstalled) { - containersDropDown.menuVisible = true + containersDropDown.rootButtonClickedFunction() } } function onForceCloseDrawer() { diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index a223f646..c82ce855 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -28,17 +28,11 @@ PageType { ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex()) } - PageController.goToStartPage() + PageController.closePage() // close installing page + PageController.closePage() // close protocol settings page + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) - } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - PageController.goToPage(PageEnum.PageSettingsServersList, false) - PageController.goToPage(PageEnum.PageSettingsServerInfo, false) - if (isServiceInstall) { - PageController.goToPageSettingsServerServices() - } - } else { - PageController.goToPage(PageEnum.PageHome) } PageController.showNotificationMessage(finishedMessage) From e749cc7578d0df83a8bc91f97b7143c36b6b40ea Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 23 Oct 2023 20:32:28 +0100 Subject: [PATCH 11/24] Update amneziavpn_ru.ts Typo fix --- client/translations/amneziavpn_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f94e0514..cfa33040 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1332,7 +1332,7 @@ Already installed containers were found on the server. All installed containers Clear Amnezia cache - Очистить кэш Amnezia на сервере + Очистить кэш Amnezia From 22b14dff5f51584ac471a5af239b9693cbba9014 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 22 Oct 2023 16:21:59 +0300 Subject: [PATCH 12/24] iOS AWG/WG split tunnel --- client/platforms/ios/ios_controller.h | 2 + client/platforms/ios/ios_controller.mm | 22 +++++- client/platforms/ios/iostunnel.swift | 71 ++++++++++++++++++- client/settings.cpp | 4 -- .../ui/qml/Pages2/PageSettingsConnection.qml | 8 +-- 5 files changed, 97 insertions(+), 10 deletions(-) diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 68f30ce8..2597e15d 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -28,6 +28,8 @@ struct MessageKey static const char *host; static const char *port; static const char *isOnDemand; + static const char *SplitTunnelType; + static const char *SplitTunnelSites; }; class IosController : public QObject diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 5665ff1d..841ff1e0 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -29,6 +29,9 @@ const char* MessageKey::errorCode = "errorCode"; const char* MessageKey::host = "host"; const char* MessageKey::port = "port"; const char* MessageKey::isOnDemand = "is-on-demand"; +const char* MessageKey::SplitTunnelType = "SplitTunnelType"; +const char* MessageKey::SplitTunnelSites = "SplitTunnelSites"; + Vpn::ConnectionState iosStatusToState(NEVPNStatus status) { switch (status) { @@ -351,6 +354,15 @@ void IosController::startTunnel() { m_rxBytes = 0; m_txBytes = 0; + + qDebug() << "m_rawConfig " << m_rawConfig; + + int STT = m_rawConfig["splitTunnelType"].toInt(); + QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray(); + QJsonDocument doc; + doc.setArray(splitTunnelSites); + QString STS(doc.toJson()); + [m_currentTunnel setEnabled:YES]; [m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) { @@ -376,8 +388,16 @@ void IosController::startTunnel() NSString *actionValue = [NSString stringWithUTF8String:Action::start]; NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId]; NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @""; + NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType]; + NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT]; + NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites]; + NSString *SplitTunnelSitesValue = STS.toNSString(); + - NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue}; + NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue, + SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue}; + + qDebug() << "sendVpnExtensionMessage " << message; sendVpnExtensionMessage(message); diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 41d835ae..45eee6a3 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -29,6 +29,8 @@ struct Constants { static let kMessageKeyHost = "host" static let kMessageKeyPort = "port" static let kMessageKeyOnDemand = "is-on-demand" + static let kMessageKeySplitTunnelType = "SplitTunnelType" + static let kMessageKeySplitTunnelSites = "SplitTunnelSites" } class PacketTunnelProvider: NEPacketTunnelProvider { @@ -49,6 +51,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility) private var openVPNConfig: Data? = nil + private var SplitTunnelType: String? = nil + private var SplitTunnelSites: String? = nil let vpnReachability = OpenVPNReachability() @@ -63,6 +67,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + + let tmpStr = String(data: messageData, encoding: .utf8)! + wg_log(.error, message: tmpStr) guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else { Logger.global?.log(message: "Failed to serialize message from app") return @@ -83,6 +90,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { handleStatusAppMessage(messageData, completionHandler: completionHandler) } + if action == Constants.kActionStart { + SplitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String + SplitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String + } + let callbackWrapper: (NSNumber?) -> Void = { errorCode in //let tunnelId = self.tunnelConfig?.id ?? "" let response: [String: Any] = [ @@ -175,6 +187,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } + let wgConfigStr = String(data: wgConfig, encoding: .utf8)! guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr) else { @@ -182,7 +195,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + + wg_log(.error, message: tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")) + + if (tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") == "0.0.0.0/0, ::/0"){ + if (SplitTunnelType == "1") { + wg_log(.error, message: SplitTunnelSites!) + for index in tunnelConfiguration.peers.indices { + tunnelConfiguration.peers[index].allowedIPs.removeAll() + var allowedIPs = [IPAddressRange]() + + let data = Data(SplitTunnelSites!.utf8) + do { + let array = try JSONSerialization.jsonObject(with: data) as! [String] + for allowedIPString in array { + wg_log(.error,message: allowedIPString) + guard let allowedIP = IPAddressRange(from: allowedIPString) else { + wg_log(.error,message: "Parse SplitTunnelSites Error") + return + } + allowedIPs.append(allowedIP) + } + + } catch { + wg_log(.error,message: "Parse JSONSerialization Error") + } + tunnelConfiguration.peers[index].allowedIPs = allowedIPs + } + } else { + if (SplitTunnelType == "2") + { + wg_log(.error, message: SplitTunnelSites!) + for index in tunnelConfiguration.peers.indices { + var excludeIPs = [IPAddressRange]() + + let data = Data(SplitTunnelSites!.utf8) + do { + let array = try JSONSerialization.jsonObject(with: data) as! [String] + for excludeIPString in array { + wg_log(.error,message: excludeIPString) + guard let excludeIP = IPAddressRange(from: excludeIPString) else { + wg_log(.error,message: "Parse SplitTunnelSites Error") + return + } + excludeIPs.append(excludeIP) + } + + } catch { + wg_log(.error,message: "Parse JSONSerialization Error") + } + tunnelConfiguration.peers[index].excludeIPs = excludeIPs + } + } + } + } + + wg_log(.error, message: tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")) + wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app")) // Start the tunnel diff --git a/client/settings.cpp b/client/settings.cpp index c6db0b63..4757dba6 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -233,10 +233,6 @@ QString Settings::routeModeString(RouteMode mode) const Settings::RouteMode Settings::routeMode() const { -// TODO implement for mobiles -#if defined(Q_OS_IOS) - return RouteMode::VpnAllSites; -#endif return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index c4f3b64a..c12c335d 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -94,7 +94,7 @@ PageType { DividerType {} LabelWithButtonType { - visible: GC.isDesktop() || Qt.platform.os === "android" + visible: true Layout.fillWidth: true @@ -108,11 +108,11 @@ PageType { } DividerType { - visible: !GC.isMobile() + visible: GC.isDesktop() } LabelWithButtonType { - visible: false//!GC.isMobile() + visible: false Layout.fillWidth: true @@ -125,7 +125,7 @@ PageType { } DividerType { - visible: false//!GC.isMobile() + visible: false } } } From a386d3949555ada04374047800c078da5cbd028d Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 00:28:41 +0300 Subject: [PATCH 13/24] iOS Cloak/OVPN SplitTunnel --- client/platforms/ios/iostunnel.swift | 234 +++++++++++++++------------ 1 file changed, 132 insertions(+), 102 deletions(-) diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 45eee6a3..49e01767 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -15,7 +15,7 @@ struct Constants { static let ovpnConfigKey = "ovpn" static let wireGuardConfigKey = "wireguard" static let loggerTag = "NET" - + static let kActionStart = "start" static let kActionRestart = "restart" static let kActionStop = "stop" @@ -40,7 +40,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wg_log(logLevel.osLogLevel, message: message) } }() - + private lazy var ovpnAdapter: OpenVPNAdapter = { let adapter = OpenVPNAdapter() adapter.delegate = self @@ -53,9 +53,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private var openVPNConfig: Data? = nil private var SplitTunnelType: String? = nil private var SplitTunnelSites: String? = nil - + let vpnReachability = OpenVPNReachability() - + var startHandler: ((Error?) -> Void)? var stopHandler: (() -> Void)? var protoType: TunnelProtoType = .none @@ -74,18 +74,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider { Logger.global?.log(message: "Failed to serialize message from app") return } - + guard let completionHandler = completionHandler else { Logger.global?.log(message: "Missing message completion handler") return } - + guard let action = message[Constants.kMessageKeyAction] as? String else { Logger.global?.log(message: "Missing action key in app message") completionHandler(nil) return } - + if action == Constants.kActionStatus { handleStatusAppMessage(messageData, completionHandler: completionHandler) } @@ -102,11 +102,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { Constants.kMessageKeyErrorCode: errorCode ?? NSNull(), Constants.kMessageKeyTunnelId: 0 ] - + completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) } } - + override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { dispatchQueue.async { let activationAttemptId = options?[Constants.kActivationAttemptId] as? String @@ -130,8 +130,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { switch self.protoType { case .wireguard: self.startWireguard(activationAttemptId: activationAttemptId, - errorNotifier: errorNotifier, - completionHandler: completionHandler) + errorNotifier: errorNotifier, + completionHandler: completionHandler) case .openvpn: self.startOpenVPN(completionHandler: completionHandler) case .shadowsocks: @@ -168,7 +168,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler) case .shadowsocks: break -// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler) + // handleShadowSocksAppMessage(messageData, completionHandler: completionHandler) case .none: break @@ -180,12 +180,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { errorNotifier: ErrorNotifier, completionHandler: @escaping (Error?) -> Void) { guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, - let providerConfiguration = protocolConfiguration.providerConfiguration, - let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else { - wg_log(.error, message: "Can't start WireGuard config missing") - completionHandler(nil) - return - } + let providerConfiguration = protocolConfiguration.providerConfiguration, + let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else { + wg_log(.error, message: "Can't start WireGuard config missing") + completionHandler(nil) + return + } let wgConfigStr = String(data: wgConfig, encoding: .utf8)! @@ -196,26 +196,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } - wg_log(.error, message: tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")) - - if (tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") == "0.0.0.0/0, ::/0"){ + + if (tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") == "0.0.0.0/0, ::/0") { if (SplitTunnelType == "1") { - wg_log(.error, message: SplitTunnelSites!) for index in tunnelConfiguration.peers.indices { tunnelConfiguration.peers[index].allowedIPs.removeAll() var allowedIPs = [IPAddressRange]() - - let data = Data(SplitTunnelSites!.utf8) + let STSdata = Data(SplitTunnelSites!.utf8) do { - let array = try JSONSerialization.jsonObject(with: data) as! [String] - for allowedIPString in array { - wg_log(.error,message: allowedIPString) - guard let allowedIP = IPAddressRange(from: allowedIPString) else { - wg_log(.error,message: "Parse SplitTunnelSites Error") - return - } - allowedIPs.append(allowedIP) + 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") @@ -225,22 +219,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } else { if (SplitTunnelType == "2") { - wg_log(.error, message: SplitTunnelSites!) for index in tunnelConfiguration.peers.indices { var excludeIPs = [IPAddressRange]() - - let data = Data(SplitTunnelSites!.utf8) + let STSdata = Data(SplitTunnelSites!.utf8) do { - let array = try JSONSerialization.jsonObject(with: data) as! [String] - for excludeIPString in array { - wg_log(.error,message: excludeIPString) - guard let excludeIP = IPAddressRange(from: excludeIPString) else { - wg_log(.error,message: "Parse SplitTunnelSites Error") - return + let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String] + for excludeIPString in STSarray { + if let excludeIP = IPAddressRange(from: excludeIPString) { + excludeIPs.append(excludeIP) } - excludeIPs.append(excludeIP) - } - + } } catch { wg_log(.error,message: "Parse JSONSerialization Error") } @@ -250,8 +238,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } - wg_log(.error, message: tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")) - wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app")) // Start the tunnel @@ -262,30 +248,30 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + switch adapterError { case .cannotLocateTunnelFileDescriptor: wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor") errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor) completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor) - + case .dnsResolution(let dnsErrors): let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address } .joined(separator: ", ") wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)") errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure) completionHandler(PacketTunnelProviderError.dnsResolutionFailure) - + case .setNetworkSettings(let error): wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)") errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings) completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings) - + case .startWireGuardBackend(let errorCode): wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)") errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend) completionHandler(PacketTunnelProviderError.couldNotStartBackend) - + case .invalidState: // Must never happen fatalError() @@ -295,27 +281,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) { guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, - let providerConfiguration = protocolConfiguration.providerConfiguration, + let providerConfiguration = protocolConfiguration.providerConfiguration, let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else { // TODO: handle errors properly - wg_log(.error, message: "Can't start startOpenVPN()") + wg_log(.error, message: "Can't start startOpenVPN()") return } setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler) } - + private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { wg_log(.info, staticMessage: "Stopping tunnel") wgAdapter.stop { error in ErrorNotifier.removeLastErrorFile() - + if let error = error { wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)") } completionHandler() - + #if os(macOS) // HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107). // Remove it when they finally fix this upstream and the fix has been rolled out to @@ -332,7 +318,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } ovpnAdapter.disconnect() } - + func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } wgAdapter.getRuntimeConfiguration { settings in @@ -347,8 +333,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { for component in components{ let pair = component.components(separatedBy: "=") if pair.count == 2 { - settingsDictionary[pair[0]] = pair[1] - } + settingsDictionary[pair[0]] = pair[1] + } } let response: [String: Any] = [ @@ -378,7 +364,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + do { let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString) wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in @@ -387,7 +373,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + self.wgAdapter.getRuntimeConfiguration { settings in var data: Data? if let settings = settings { @@ -403,76 +389,76 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) } } - + private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } - let bytesin = ovpnAdapter.transportStatistics.bytesIn - let bytesout = ovpnAdapter.transportStatistics.bytesOut - - let response: [String: Any] = [ - "rx_bytes" : bytesin, - "tx_bytes" : bytesout - ] - - completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) + let bytesin = ovpnAdapter.transportStatistics.bytesIn + let bytesout = ovpnAdapter.transportStatistics.bytesOut + + let response: [String: Any] = [ + "rx_bytes" : bytesin, + "tx_bytes" : bytesout + ] + + completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) } - - + + // TODO review private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) { wg_log(.info, message: "setupAndlaunchOpenVPN") - + let str = String(decoding: ovpnConfiguration, as: UTF8.self) - + let configuration = OpenVPNConfiguration() configuration.fileContent = ovpnConfiguration if(str.contains("cloak")){ configuration.setPTCloak(); } - + let evaluation: OpenVPNConfigurationEvaluation do { evaluation = try ovpnAdapter.apply(configuration: configuration) - + } catch { completionHandler(error) return } - + if !evaluation.autologin { wg_log(.info, message: "Implement login with user credentials") } - + vpnReachability.startTracking { [weak self] status in guard status == .reachableViaWiFi else { return } self?.ovpnAdapter.reconnect(afterTimeInterval: 5) } - + startHandler = completionHandler ovpnAdapter.connect(using: packetFlow) - -// let ifaces = Interface.allInterfaces() -// .filter { $0.family == .ipv4 } -// .map { iface in iface.name } - -// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") + + // let ifaces = Interface.allInterfaces() + // .filter { $0.family == .ipv4 } + // .map { iface in iface.name } + + // wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") } - + // MARK: -- Network observing methods - + private func startListeningForNetworkChanges() { stopListeningForNetworkChanges() addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil) } - + private func stopListeningForNetworkChanges() { removeObserver(self, forKeyPath: Constants.kDefaultPathKey) } - + override func observeValue(forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer?) { + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { guard Constants.kDefaultPathKey != keyPath else { return } // Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi, // even though the underlying network has not changed (i.e. `isEqualToPath` returns false), @@ -481,28 +467,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider { guard let lastPath: NWPath = change?[.oldKey] as? NWPath, let defPath = defaultPath, lastPath != defPath || lastPath.description != defPath.description else { - return - } + return + } DispatchQueue.main.async { [weak self] in guard let `self` = self, self.defaultPath != nil else { return } self.handle(networkChange: self.defaultPath!) { _ in } } } - + private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) { wg_log(.info, message: "Tunnel restarted.") startTunnel(options: nil, completionHandler: completion) } - + private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) { dispatchPrecondition(condition: .onQueue(dispatchQueue)) - + let emptyTunnelConfiguration = TunnelConfiguration( name: nil, interface: InterfaceConfiguration(privateKey: PrivateKey()), peers: [] ) - + wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in self.dispatchQueue.async { if let error { @@ -514,9 +500,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } } - + let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") - + self.setTunnelNetworkSettings(settings) { error in completionHandler(error) } @@ -547,6 +533,50 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate { // send empty string to NEDNSSettings.matchDomains networkSettings?.dnsSettings?.matchDomains = [""] + if (SplitTunnelType == "1") { + var ipv4IncludedRoutes = [NEIPv4Route]() + let STSdata = Data(SplitTunnelSites!.utf8) + do { + let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String] + for allowedIPString in STSarray { + if let allowedIP = IPAddressRange(from: allowedIPString){ + ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allowedIP.address)", subnetMask: "\(allowedIP.subnetMask())")) + } + } + } catch { + wg_log(.error,message: "Parse JSONSerialization Error") + } + networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes + } else { + if (SplitTunnelType == "2") + { + var ipv4ExcludedRoutes = [NEIPv4Route]() + var ipv4IncludedRoutes = [NEIPv4Route]() + var ipv6IncludedRoutes = [NEIPv6Route]() + let STSdata = Data(SplitTunnelSites!.utf8) + do { + let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String] + for excludeIPString in STSarray { + if let excludeIP = IPAddressRange(from: excludeIPString) { + ipv4ExcludedRoutes.append(NEIPv4Route(destinationAddress: "\(excludeIP.address)", subnetMask: "\(excludeIP.subnetMask())")) + } + } + } catch { + wg_log(.error,message: "Parse JSONSerialization Error") + } + if let allIPv4 = IPAddressRange(from: "0.0.0.0/0"){ + ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allIPv4.address)", subnetMask: "\(allIPv4.subnetMask())")) + } + if let allIPv6 = IPAddressRange(from: "::/0") { + ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(allIPv6.address)", networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength))) + } + networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes + networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes + networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes + } + + } + // Set the network settings for the current tunneling session. setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler) } @@ -607,7 +637,7 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate { wg_log(.info, message: logMessage) } } - + extension WireGuardLogLevel { var osLogLevel: OSLogType { switch self { From 306d4f70a89ff81e3b33ff5fa627fff6eec65a17 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 00:33:35 +0300 Subject: [PATCH 14/24] Update NE Sources --- client/3rd/awg-apple | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple index fab07138..233eda67 160000 --- a/client/3rd/awg-apple +++ b/client/3rd/awg-apple @@ -1 +1 @@ -Subproject commit fab07138dbab06ac0de256021e47e273f4df8e88 +Subproject commit 233eda6760962efddc860f177a0ce2bcdf533d85 From c772f56da720af1275435c071d2fe2b6cc2f8d30 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 11:00:40 +0300 Subject: [PATCH 15/24] Fixes after merge --- client/core/servercontroller.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index d96c03f4..da76e1ff 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -591,22 +591,20 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); - if (!amneziaWireguarConfig.value(config_key::junkPacketCount).toString().isEmpty()){ - vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); - vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); - vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); - vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); - vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", - amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); - vars.append({ { "$INIT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); - vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); - vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); - vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); - } + vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); + vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", + amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); + vars.append({ { "$INIT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); + vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); + vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); + vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { From 1b37ca805f4737f748dcdae0d24303ebfba4bd82 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 11:10:16 +0300 Subject: [PATCH 16/24] Cleanup debug stuff --- client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt | 11 ++--------- client/android/src/org/amnezia/vpn/VPNService.kt | 2 -- client/configurators/openvpn_configurator.cpp | 1 - client/platforms/ios/ios_controller.mm | 3 --- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt index d96131af..b35face4 100644 --- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt +++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt @@ -74,14 +74,9 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna val jsonVpnConfig = mService.getVpnConfig() val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config") - Log.e(tag, "jsonVpnConfig $jsonVpnConfig") val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType") val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites") - Log.e(tag, "splitTunnelType $splitTunnelType") - Log.e(tag, "splitTunnelSites $splitTunnelSites") - - val resultingConfig = StringBuilder() resultingConfig.append(ovpnConfig) @@ -158,7 +153,6 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna val site = splitTunnelSites.getString(i) val ipRange = IPRange(site) mService.addRoute(ipRange.getFrom().getHostAddress(), ipRange.getPrefix()) - Log.e(tag, "splitTunnelSites $ipRange") } } if (splitTunnelType == 2) { @@ -170,8 +164,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna } ipRangeSet.subnets().forEach { mService.addRoute(it.getFrom().getHostAddress(), it.getPrefix()) - Thread.sleep(100) - Log.e(tag, "splitTunnelSites $it") + Thread.sleep(10) } mService.addRoute("2000::", 3) } @@ -196,7 +189,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean , flags: Long): Boolean { Log.v(tag, "tun_builder_reroute_gw") - // mService.addRoute("0.0.0.0", 0) + mService.addRoute("0.0.0.0", 0) return true } diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index ca468108..929545d6 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -611,7 +611,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { /* VPN connection used only for defined IPs */ for (i in 0 until splitTunnelSites.length()) { val site = splitTunnelSites.getString(i) - Log.e(tag, "splitTunnelSites $site") val internet = InetNetwork.parse(site) peerBuilder.addAllowedIp(internet) } @@ -626,7 +625,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ipRangeSet.remove(IPRange(site)) } val allowedIps = ipRangeSet.subnets().joinToString(", ") + ", 2000::/3" - Log.e(tag, "allowedIps $allowedIps") peerBuilder.parseAllowedIPs(allowedIps) } } diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index cc5c30e5..8c58a376 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -131,7 +131,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.append("block-ipv6\n"); } if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - qDebug() << "Settings::VpnOnlyForwardSites"; // no redirect-gateway } diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 841ff1e0..79eddefd 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -355,8 +355,6 @@ void IosController::startTunnel() m_rxBytes = 0; m_txBytes = 0; - qDebug() << "m_rawConfig " << m_rawConfig; - int STT = m_rawConfig["splitTunnelType"].toInt(); QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray(); QJsonDocument doc; @@ -397,7 +395,6 @@ void IosController::startTunnel() NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue, SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue}; - qDebug() << "sendVpnExtensionMessage " << message; sendVpnExtensionMessage(message); From 3e9dea6f07f983af091ef7fd6b6ee05192ffe291 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 13:37:40 +0300 Subject: [PATCH 17/24] Remove some not implemented notification --- client/amnezia_application.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index e50c67d6..b1ed39ee 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -296,17 +296,7 @@ void AmneziaApplication::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); - connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { - if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard - || m_containersModel->getDefaultContainer() == DockerContainer::Awg) - && m_sitesModel->isSplitTunnelingEnabled()) { - m_sitesModel->toggleSplitTunneling(true); - emit m_pageController->showNotificationMessage( - tr("Split tunneling for %1 is not implemented, the option was disabled") - .arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer()))); - } - }); - + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); From a6b6e7850d82600ab944baa93c393348c2a02e1a Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 24 Oct 2023 22:21:10 +0300 Subject: [PATCH 18/24] Allow traffic for excluded route on Windows kill switch --- .../windows/daemon/windowsfirewall.cpp | 66 ++++++++++++++++++- .../windows/daemon/windowsfirewall.h | 3 + 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 2cf5e205..3d45f228 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -236,6 +236,17 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { } } + if (!config.m_excludedAddresses.empty()) { + for (const QString& i : config.m_excludedAddresses) { + logger.debug() << "range: " << i; + + if (!allowTrafficToRange(i, HIGH_WEIGHT, + "Allow Ecxlude route", config.m_serverPublicKey)) { + return false; + } + } + } + result = FwpmTransactionCommit0(m_sessionHandle); if (result != ERROR_SUCCESS) { logger.error() << "FwpmTransactionCommit0 failed with error:" << result; @@ -411,8 +422,8 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight, } bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port, - int weight, const QString& title, - const QString& peer) { + int weight, const QString& title, + const QString& peer) { bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol; GUID layerOut = isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6; @@ -473,6 +484,57 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port, return true; } +bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight, + const QString& title, + const QString& peer) { + QString description("Allow traffic %1 %2 "); + + auto lower = addr.address(); + auto upper = addr.broadcastAddress(); + + const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol; + const GUID layerKeyOut = + isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6; + const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 + : FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; + + // Assemble the Filter base + FWPM_FILTER0 filter; + memset(&filter, 0, sizeof(filter)); + filter.action.type = FWP_ACTION_PERMIT; + filter.weight.type = FWP_UINT8; + filter.weight.uint8 = weight; + filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; + + FWPM_FILTER_CONDITION0 cond[1] = {0}; + FWP_RANGE0 ipRange; + QByteArray lowIpV6Buffer; + QByteArray highIpV6Buffer; + + importAddress(lower, ipRange.valueLow, &lowIpV6Buffer); + importAddress(upper, ipRange.valueHigh, &highIpV6Buffer); + + cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS; + cond[0].matchType = FWP_MATCH_RANGE; + cond[0].conditionValue.type = FWP_RANGE_TYPE; + cond[0].conditionValue.rangeValue = &ipRange; + + filter.numFilterConditions = 1; + filter.filterCondition = cond; + + filter.layerKey = layerKeyOut; + if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()), + peer)) { + return false; + } + filter.layerKey = layerKeyIn; + if (!enableFilter(&filter, title, + description.arg("from").arg(addr.toString()), peer)) { + return false; + } + return true; +} + bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) { // Allow outbound DHCPv4 { diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index e1891322..e0d5ebe8 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -52,6 +52,9 @@ class WindowsFirewall final : public QObject { bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title); bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight, const QString& title, const QString& peer = QString()); + bool allowTrafficToRange(const IPAddress& addr, uint8_t weight, + const QString& title, + const QString& peer); bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight, const QString& title); bool allowDHCPTraffic(uint8_t weight, const QString& title); From 1739d4861e6b4b39d159b0c9c74dada7413f08cc Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 25 Oct 2023 21:48:44 +0300 Subject: [PATCH 19/24] Allow acces to Amnezia DNS when used only for selected sites --- client/mozilla/localsocketcontroller.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 56178eeb..db9f9663 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -199,6 +199,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { jsAllowedIPAddesses.append(range); } } + + // Allow access to Amnezia DNS + QJsonObject range_ipv4; + range_ipv4.insert("address", amnezia::protocols::dns::amneziaDnsIp); + range_ipv4.insert("range", 32); + range_ipv4.insert("isIpv6", false); + jsAllowedIPAddesses.append(range_ipv4); + } } From 0e23b3a1ac077aaf0913cbf7a45f8c76fc0dafd3 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 25 Oct 2023 22:19:07 +0300 Subject: [PATCH 20/24] Allow traffic to Amezia DNS for all OS --- client/mozilla/localsocketcontroller.cpp | 8 -------- client/vpnconnection.cpp | 5 +++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index db9f9663..56178eeb 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -199,14 +199,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { jsAllowedIPAddesses.append(range); } } - - // Allow access to Amnezia DNS - QJsonObject range_ipv4; - range_ipv4.insert("address", amnezia::protocols::dns::amneziaDnsIp); - range_ipv4.insert("range", 32); - range_ipv4.insert("isIpv6", false); - jsAllowedIPAddesses.append(range_ipv4); - } } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 878a1cde..dea40f24 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -375,6 +375,11 @@ void VpnConnection::appendSplitTunnelingConfig() 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); } From 8164026891c8c916c5a542d5c4d58b0f38fa3079 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 26 Oct 2023 23:34:39 +0500 Subject: [PATCH 21/24] fixed server config update, after container config change --- client/amnezia_application.cpp | 2 ++ client/ui/models/containers_model.cpp | 12 ++---------- client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 6 ++++++ client/ui/models/servers_model.h | 2 ++ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 3e227863..25a131f8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -288,6 +288,8 @@ void AmneziaApplication::initModels() &ContainersModel::setCurrentlyProcessedServerIndex); connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(), &ContainersModel::setCurrentlyProcessedServerIndex); + connect(m_containersModel.get(), &ContainersModel::containersModelUpdated, m_serversModel.get(), + &ServersModel::updateContainersConfig); m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 6a4c0e63..3fff22d4 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -22,10 +22,6 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - // return ContainerProps::containerHumanNames().value(container); - case DescriptionRole: - // return ContainerProps::containerDescriptions().value(container); case ConfigRole: { m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); m_containers = m_settings->containers(m_currentlyProcessedServerIndex); @@ -35,19 +31,15 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i break; } } - case ServiceTypeRole: - // return ContainerProps::containerService(container); - case DockerContainerRole: - // return container; - case IsInstalledRole: - // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); case IsDefaultRole: { //todo remove m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); m_defaultContainerIndex = container; emit defaultContainerChanged(); } + default: break; } + emit containersModelUpdated(); emit dataChanged(index, index); return true; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 997b21e3..8f087d87 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -73,6 +73,7 @@ protected: signals: void defaultContainerChanged(); + void containersModelUpdated(); private: QMap m_containers; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index a2a28630..f3b2337f 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -193,6 +193,12 @@ bool ServersModel::isDefaultServerConfigContainsAmneziaDns() return primaryDns == protocols::dns::amneziaDnsIp; } +void ServersModel::updateContainersConfig() +{ + auto server = m_settings->server(m_currentlyProcessedServerIndex); + m_servers.replace(m_currentlyProcessedServerIndex, server); +} + QHash ServersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index ad1d5a83..97d8015f 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -59,6 +59,8 @@ public slots: bool isDefaultServerConfigContainsAmneziaDns(); + void updateContainersConfig(); + protected: QHash roleNames() const override; From 2fc33875bb20c09d1f20dbca6b631ca5fd043906 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 27 Oct 2023 15:38:24 -0400 Subject: [PATCH 22/24] Bump version --- CMakeLists.txt | 2 +- client/android/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e7be435..abb382cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.6 +project(${PROJECT} VERSION 4.1.0.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/android/build.gradle b/client/android/build.gradle index a6b3f651..3819a4fe 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -138,8 +138,8 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 37 // Change to a higher number - versionName "4.0.8" // Change to a higher number + versionCode 39 // Change to a higher number + versionName "4.1.0" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ "room.schemaLocation": "${qtAndroidDir}/schemas".toString() From 4ef8c77a2d474b85c9a75db02efaf01acd73ccfa Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:56:10 +0400 Subject: [PATCH 23/24] Update DNS description --- client/translations/amneziavpn_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index cfa33040..861beec2 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -686,7 +686,7 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - На вашем сервере устанавливается DNS-сервис, доступ к нему возможен только через VPN. + На вашем сервере установлен DNS-сервис, доступ к нему возможен только через VPN. From 4848091203cc865203d7cb7831264a8dc70a279d Mon Sep 17 00:00:00 2001 From: useribs Date: Mon, 30 Oct 2023 20:09:13 +0300 Subject: [PATCH 24/24] Update servercontroller.cpp, replace 2 calls (shred ; rm) with one (shred -u) --- client/core/servercontroller.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index da76e1ff..398b46b3 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -167,11 +167,8 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, return ErrorCode::ServerContainerMissingError; } - runScript(credentials, - replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container))); - - runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container))); - + runScript(credentials, + replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container))); return e; }