diff --git a/.gitmodules b/.gitmodules index 453a8ee4..c96dd6bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "client/3rd/wireguard-apple"] - path = client/3rd/wireguard-apple - url = https://github.com/WireGuard/wireguard-apple [submodule "client/3rd/OpenVPNAdapter"] path = client/3rd/OpenVPNAdapter url = https://github.com/amnezia-vpn/OpenVPNAdapter.git @@ -25,3 +22,6 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt +[submodule "client/3rd/awg-apple"] + path = client/3rd/awg-apple + url = https://github.com/amnezia-vpn/awg-apple diff --git a/CMakeLists.txt b/CMakeLists.txt index 716d6a7f..2e7be435 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.7.1 +project(${PROJECT} VERSION 4.0.8.6 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index e8795854..ac32d335 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit e8795854a5cf27004fe78caecc90a961688d1d41 +Subproject commit ac32d33555bd62f0b0af314b1e5119d6d78a1a4e diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple new file mode 160000 index 00000000..fab07138 --- /dev/null +++ b/client/3rd/awg-apple @@ -0,0 +1 @@ +Subproject commit fab07138dbab06ac0de256021e47e273f4df8e88 diff --git a/client/3rd/wireguard-apple b/client/3rd/wireguard-apple deleted file mode 160000 index 23618f99..00000000 --- a/client/3rd/wireguard-apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23618f994f17d8ad8f2f65d79b4a1e8a0830b334 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 004b64f9..784408e2 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -74,7 +74,6 @@ qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) # -- i18n end if(IOS) - #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) endif() @@ -282,6 +281,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h ) set(SOURCES ${SOURCES} @@ -292,6 +292,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index cb1512cc..174170d5 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -279,11 +279,15 @@ void AmneziaApplication::initModels() { m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(), + &ContainersModel::updateContainersConfig); m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), &ContainersModel::setCurrentlyProcessedServerIndex); + connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); @@ -293,11 +297,13 @@ void AmneziaApplication::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { - if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard - && m_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) { - m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); + if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + || m_containersModel->getDefaultContainer() == DockerContainer::Awg) + && m_sitesModel->isSplitTunnelingEnabled()) { + m_sitesModel->toggleSplitTunneling(false); emit m_pageController->showNotificationMessage( - tr("Split tunneling for WireGuard is not implemented, the option was disabled")); + tr("Split tunneling for %1 is not implemented, the option was disabled") + .arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer()))); } }); @@ -313,8 +319,11 @@ void AmneziaApplication::initModels() m_cloakConfigModel.reset(new CloakConfigModel(this)); m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); - m_wireguardConfigModel.reset(new WireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireguardConfigModel.get()); + m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + + m_awgConfigModel.reset(new AwgConfigModel(this)); + m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); #ifdef Q_OS_WINDOWS m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 394ff943..b82bb9bb 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -32,6 +32,7 @@ #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif +#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/openvpnConfigModel.h" #include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" @@ -98,7 +99,8 @@ private: QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; QScopedPointer m_cloakConfigModel; - QScopedPointer m_wireguardConfigModel; + QScopedPointer m_wireGuardConfigModel; + QScopedPointer m_awgConfigModel; #ifdef Q_OS_WINDOWS QScopedPointer m_ikev2ConfigModel; #endif diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 4ec807e9..1115b74d 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -45,6 +45,7 @@ android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleInstance" + android:windowSoftInputMode="adjustResize" android:exported="true"> diff --git a/client/android/build.gradle b/client/android/build.gradle index cfc53460..a6b3f651 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -138,8 +138,8 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 32 // Change to a higher number - versionName "3.0.9" // Change to a higher number + versionCode 37 // Change to a higher number + versionName "4.0.8" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ "room.schemaLocation": "${qtAndroidDir}/schemas".toString() diff --git a/client/android/src/com/wireguard/config/BadConfigException.java b/client/android/src/com/wireguard/config/BadConfigException.java index 33910501..af909b0d 100644 --- a/client/android/src/com/wireguard/config/BadConfigException.java +++ b/client/android/src/com/wireguard/config/BadConfigException.java @@ -70,6 +70,15 @@ public class BadConfigException extends Exception { EXCLUDED_APPLICATIONS("ExcludedApplications"), INCLUDED_APPLICATIONS("IncludedApplications"), LISTEN_PORT("ListenPort"), + JC("Jc"), + JMIN("Jmin"), + JMAX("Jmax"), + S1("S1"), + S2("S2"), + H1("H1"), + H2("H2"), + H3("H3"), + H4("H4"), MTU("MTU"), PERSISTENT_KEEPALIVE("PersistentKeepalive"), PRE_SHARED_KEY("PresharedKey"), diff --git a/client/android/src/com/wireguard/config/Interface.java b/client/android/src/com/wireguard/config/Interface.java index 2594d701..4b561680 100644 --- a/client/android/src/com/wireguard/config/Interface.java +++ b/client/android/src/com/wireguard/config/Interface.java @@ -44,6 +44,15 @@ public final class Interface { private final KeyPair keyPair; private final Optional listenPort; private final Optional mtu; + private final Optional jc; + private final Optional jmin; + private final Optional jmax; + private final Optional s1; + private final Optional s2; + private final Optional h1; + private final Optional h2; + private final Optional h3; + private final Optional h4; private Interface(final Builder builder) { // Defensively copy to ensure immutability even if the Builder is reused. @@ -56,6 +65,15 @@ public final class Interface { keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key"); listenPort = builder.listenPort; mtu = builder.mtu; + jc = builder.jc; + jmax = builder.jmax; + jmin = builder.jmin; + s1 = builder.s1; + s2 = builder.s2; + h1 = builder.h1; + h2 = builder.h2; + h3 = builder.h3; + h4 = builder.h4; } /** @@ -95,6 +113,33 @@ public final class Interface { case "privatekey": builder.parsePrivateKey(attribute.getValue()); break; + case "jc": + builder.parseJc(attribute.getValue()); + break; + case "jmin": + builder.parseJmin(attribute.getValue()); + break; + case "jmax": + builder.parseJmax(attribute.getValue()); + break; + case "s1": + builder.parseS1(attribute.getValue()); + break; + case "s2": + builder.parseS2(attribute.getValue()); + break; + case "h1": + builder.parseH1(attribute.getValue()); + break; + case "h2": + builder.parseH2(attribute.getValue()); + break; + case "h3": + builder.parseH3(attribute.getValue()); + break; + case "h4": + builder.parseH4(attribute.getValue()); + break; default: throw new BadConfigException( Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey()); @@ -111,7 +156,9 @@ public final class Interface { return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers) && excludedApplications.equals(other.excludedApplications) && includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair) - && listenPort.equals(other.listenPort) && mtu.equals(other.mtu); + && listenPort.equals(other.listenPort) && mtu.equals(other.mtu) && jc.equals(other.jc) && jmin.equals(other.jmin) + && jmax.equals(other.jmax) && s1.equals(other.s1) && s2.equals(other.s2) && h1.equals(other.h1) && h2.equals(other.h2) + && h3.equals(other.h3) && h4.equals(other.h4); } /** @@ -180,6 +227,42 @@ public final class Interface { public Optional getMtu() { return mtu; } + + public Optional getJc() { + return jc; + } + + public Optional getJmin() { + return jmin; + } + + public Optional getJmax() { + return jmax; + } + + public Optional getS1() { + return s1; + } + + public Optional getS2() { + return s2; + } + + public Optional getH1() { + return h1; + } + + public Optional getH2() { + return h2; + } + + public Optional getH3() { + return h3; + } + + public Optional getH4() { + return h4; + } @Override public int hashCode() { @@ -191,6 +274,15 @@ public final class Interface { hash = 31 * hash + keyPair.hashCode(); hash = 31 * hash + listenPort.hashCode(); hash = 31 * hash + mtu.hashCode(); + hash = 31 * hash + jc.hashCode(); + hash = 31 * hash + jmin.hashCode(); + hash = 31 * hash + jmax.hashCode(); + hash = 31 * hash + s1.hashCode(); + hash = 31 * hash + s2.hashCode(); + hash = 31 * hash + h1.hashCode(); + hash = 31 * hash + h2.hashCode(); + hash = 31 * hash + h3.hashCode(); + hash = 31 * hash + h4.hashCode(); return hash; } @@ -234,6 +326,19 @@ public final class Interface { .append('\n'); listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n')); mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n')); + + jc.ifPresent(t_jc -> sb.append("Jc = ").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("Jmin = ").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("Jmax = ").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("S1 = ").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("S2 = ").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("H1 = ").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("H2 = ").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("H3 = ").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("H4 = ").append(t_h4).append('\n')); + sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n'); return sb.toString(); } @@ -248,6 +353,18 @@ public final class Interface { final StringBuilder sb = new StringBuilder(); sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n'); listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n')); + + jc.ifPresent(t_jc -> sb.append("jc=").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("jmin=").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("jmax=").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("s1=").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("s2=").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("h1=").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("h2=").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("h3=").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("h4=").append(t_h4).append('\n')); return sb.toString(); } @@ -267,6 +384,17 @@ public final class Interface { private Optional listenPort = Optional.empty(); // Defaults to not present. private Optional mtu = Optional.empty(); + private Optional jc = Optional.empty(); + private Optional jmin = Optional.empty(); + private Optional jmax = Optional.empty(); + + private Optional s1 = Optional.empty(); + private Optional s2 = Optional.empty(); + + private Optional h1 = Optional.empty(); + private Optional h2 = Optional.empty(); + private Optional h3 = Optional.empty(); + private Optional h4 = Optional.empty(); public Builder addAddress(final InetNetwork address) { addresses.add(address); @@ -362,6 +490,78 @@ public final class Interface { } } + public Builder parseJc(final String jc) throws BadConfigException { + try { + return setJc(Integer.parseInt(jc)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JC, jc, e); + } + } + + public Builder parseJmax(final String jmax) throws BadConfigException { + try { + return setJmax(Integer.parseInt(jmax)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMAX, jmax, e); + } + } + + public Builder parseJmin(final String jmin) throws BadConfigException { + try { + return setJmin(Integer.parseInt(jmin)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMIN, jmin, e); + } + } + + public Builder parseS1(final String s1) throws BadConfigException { + try { + return setS1(Integer.parseInt(s1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S1, s1, e); + } + } + + public Builder parseS2(final String s2) throws BadConfigException { + try { + return setS2(Integer.parseInt(s2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S2, s2, e); + } + } + + public Builder parseH1(final String h1) throws BadConfigException { + try { + return setH1(Long.parseLong(h1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H1, h1, e); + } + } + + public Builder parseH2(final String h2) throws BadConfigException { + try { + return setH2(Long.parseLong(h2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H2, h2, e); + } + } + + public Builder parseH3(final String h3) throws BadConfigException { + try { + return setH3(Long.parseLong(h3)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H3, h3, e); + } + } + + public Builder parseH4(final String h4) throws BadConfigException { + try { + return setH4(Long.parseLong(h4)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H4, h4, e); + } + } + public Builder parsePrivateKey(final String privateKey) throws BadConfigException { try { return setKeyPair(new KeyPair(Key.fromBase64(privateKey))); @@ -386,9 +586,81 @@ public final class Interface { public Builder setMtu(final int mtu) throws BadConfigException { if (mtu < 0) throw new BadConfigException( - Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE, String.valueOf(mtu)); + Section.INTERFACE, Location.MTU, Reason.INVALID_VALUE, String.valueOf(mtu)); this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu); return this; } + + public Builder setJc(final int jc) throws BadConfigException { + if (jc < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc)); + this.jc = Optional.of(jc); + return this; + } + + public Builder setJmin(final int jmin) throws BadConfigException { + if (jmin < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin)); + this.jmin = Optional.of(jmin); + return this; + } + + public Builder setJmax(final int jmax) throws BadConfigException { + if (jmax < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax)); + this.jmax = Optional.of(jmax); + return this; + } + + public Builder setS1(final int s1) throws BadConfigException { + if (s1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1)); + this.s1 = Optional.of(s1); + return this; + } + + public Builder setS2(final int s2) throws BadConfigException { + if (s2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2)); + this.s2 = Optional.of(s2); + return this; + } + + public Builder setH1(final long h1) throws BadConfigException { + if (h1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1)); + this.h1 = Optional.of(h1); + return this; + } + + public Builder setH2(final long h2) throws BadConfigException { + if (h2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2)); + this.h2 = Optional.of(h2); + return this; + } + + public Builder setH3(final long h3) throws BadConfigException { + if (h3 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3)); + this.h3 = Optional.of(h3); + return this; + } + + public Builder setH4(final long h4) throws BadConfigException { + if (h4 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4)); + this.h4 = Optional.of(h4); + return this; + } } -} +} diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 082fe412..06f58980 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -380,7 +380,10 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { mNetworkState.bindNetworkListener() } "wireguard" -> { - startWireGuard() + startWireGuard("wireguard") + } + "awg" -> { + startWireGuard("awg") } "shadowsocks" -> { startShadowsocks() @@ -457,7 +460,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { fun turnOff() { Log.v(tag, "Aman: turnOff....................") when (mProtocol) { - "wireguard" -> { + "wireguard", + "awg" -> { GoBackend.wgTurnOff(currentTunnelHandle) } "cloak", @@ -559,14 +563,14 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { } return parseData } - + /** * Create a Wireguard [Config] from a [json] string - * The [json] will be created in AndroidVpnProtocol.cpp */ - private fun buildWireguardConfig(obj: JSONObject): Config { + private fun buildWireguardConfig(obj: JSONObject, type: String): Config { val confBuilder = Config.Builder() - val wireguardConfigData = obj.getJSONObject("wireguard_config_data") + val wireguardConfigData = obj.getJSONObject(type) val config = parseConfigData(wireguardConfigData.getString("config")) val peerBuilder = Peer.Builder() val peerConfig = config["Peer"]!! @@ -599,6 +603,30 @@ 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"]) + ifaceBuilder.parseJmin(ifaceConfig["Jmin"]) + ifaceBuilder.parseJmax(ifaceConfig["Jmax"]) + ifaceBuilder.parseS1(ifaceConfig["S1"]) + ifaceBuilder.parseS2(ifaceConfig["S2"]) + ifaceBuilder.parseH1(ifaceConfig["H1"]) + ifaceBuilder.parseH2(ifaceConfig["H2"]) + ifaceBuilder.parseH3(ifaceConfig["H3"]) + ifaceBuilder.parseH4(ifaceConfig["H4"]) + } else { + ifaceBuilder.parseJc("0") + ifaceBuilder.parseJmin("0") + ifaceBuilder.parseJmax("0") + ifaceBuilder.parseS1("0") + ifaceBuilder.parseS2("0") + ifaceBuilder.parseH1("0") + ifaceBuilder.parseH2("0") + ifaceBuilder.parseH3("0") + ifaceBuilder.parseH4("0") + + } /*val jExcludedApplication = obj.getJSONArray("excludedApps") (0 until jExcludedApplication.length()).toList().forEach { val appName = jExcludedApplication.get(it).toString() @@ -716,8 +744,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { }).start() } - private fun startWireGuard() { - val wireguard_conf = buildWireguardConfig(mConfig!!) + 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") @@ -728,9 +756,15 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { val builder = Builder() setupBuilder(wireguard_conf, builder) builder.setSession("Amnezia") + + builder.establish().use { tun -> - if (tun == null) return - currentTunnelHandle = GoBackend.wgTurnOn("Amnezia", tun.detachFd(), wgConfig) + if (tun == null) return + if (type == "awg"){ + currentTunnelHandle = GoBackend.wgTurnOn("awg0", tun.detachFd(), wgConfig) + } else { + currentTunnelHandle = GoBackend.wgTurnOn("amn0", tun.detachFd(), wgConfig) + } } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 5dc1b2e7..7aa9f1a9 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" ) -set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp new file mode 100644 index 00000000..c3e42258 --- /dev/null +++ b/client/configurators/awg_configurator.cpp @@ -0,0 +1,47 @@ +#include "awg_configurator.h" + +#include +#include + +#include "core/servercontroller.h" + +AwgConfigurator::AwgConfigurator(std::shared_ptr settings, QObject *parent) + : WireguardConfigurator(settings, true, parent) +{ +} + +QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, + DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) +{ + QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); + + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + QString awgConfig = jsonConfig.value(config_key::config).toString(); + + QMap configMap; + auto configLines = awgConfig.split("\n"); + for (auto &line : configLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + return QJsonDocument(jsonConfig).toJson(); +} diff --git a/client/configurators/awg_configurator.h b/client/configurators/awg_configurator.h new file mode 100644 index 00000000..cf0f2cae --- /dev/null +++ b/client/configurators/awg_configurator.h @@ -0,0 +1,18 @@ +#ifndef AWGCONFIGURATOR_H +#define AWGCONFIGURATOR_H + +#include + +#include "wireguard_configurator.h" + +class AwgConfigurator : public WireguardConfigurator +{ + Q_OBJECT +public: + AwgConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + + QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); +}; + +#endif // AWGCONFIGURATOR_H diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index ceb6a5a4..6c5286c2 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -1,32 +1,34 @@ #include "vpn_configurator.h" -#include "openvpn_configurator.h" #include "cloak_configurator.h" -#include "shadowsocks_configurator.h" -#include "wireguard_configurator.h" #include "ikev2_configurator.h" +#include "openvpn_configurator.h" +#include "shadowsocks_configurator.h" #include "ssh_configurator.h" +#include "wireguard_configurator.h" +#include "awg_configurator.h" #include -#include #include +#include #include "containers/containers_defs.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" -VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, this)); - wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, this)); + wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, false, this)); ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); + awgConfigurator = std::shared_ptr(new AwgConfigurator(settings, this)); } -QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) +QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) { switch (proto) { case Proto::OpenVpn: @@ -35,17 +37,17 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Proto::ShadowSocks: return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode); - case Proto::Cloak: - return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); + case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); case Proto::WireGuard: return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); - case Proto::Ikev2: - return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + case Proto::Awg: + return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode); - default: - return ""; + case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + + default: return ""; } } @@ -62,8 +64,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) { if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) { dns.first = protocols::dns::amneziaDnsIp; - } - else dns.first = m_settings->primaryDns(); + } else + dns.first = m_settings->primaryDns(); } if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) { dns.second = m_settings->secondaryDns(); @@ -73,8 +75,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) return dns; } -QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { auto dns = getDnsForConfig(serverIndex); @@ -84,8 +86,8 @@ QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerCo return config; } -QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -95,8 +97,8 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker return config; } -QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -107,7 +109,7 @@ QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, Docke } void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut) + const QString &stdOut) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 3b9c761b..ac89b0e4 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -13,13 +13,14 @@ class CloakConfigurator; class WireguardConfigurator; class Ikev2Configurator; class SshConfigurator; +class AwgConfigurator; // Retrieve connection settings from server class VpnConfigurator : ConfiguratorBase { Q_OBJECT public: - VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + explicit VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr); @@ -40,6 +41,7 @@ public: std::shared_ptr wireguardConfigurator; std::shared_ptr ikev2Configurator; std::shared_ptr sshConfigurator; + std::shared_ptr awgConfigurator; }; #endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 7b7d94d2..c11816cc 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -19,9 +19,20 @@ #include "settings.h" #include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent) - : ConfiguratorBase(settings, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent) + : ConfiguratorBase(settings, parent), m_isAwg(isAwg) { + m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath + : amnezia::protocols::wireguard::serverConfigPath; + m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath + : amnezia::protocols::wireguard::serverPublicKeyPath; + m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath + : amnezia::protocols::wireguard::serverPskKeyPath; + m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template + : ProtocolScriptType::wireguard_template; + + m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; + m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -62,7 +73,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); + connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { if (errorCode) @@ -76,7 +87,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Get list of already created clients (only IP addresses) QString nextIpNumber; { - QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath); + QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; @@ -123,8 +134,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer( - container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { if (errorCode) @@ -132,8 +142,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { @@ -147,12 +156,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon "PublicKey = %1\n" "PresharedKey = %2\n" "AllowedIPs = %3/32\n\n") - .arg(connData.clientPubKey) - .arg(connData.pskKey) - .arg(connData.clientIP); + .arg(connData.clientPubKey, connData.pskKey, connData.clientIP); - e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, + e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { @@ -161,11 +167,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } + QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") + .arg(m_serverConfigPath); + e = serverController.runScript( - credentials, - serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick " - "strip /opt/amnezia/wireguard/wg0.conf)'", - serverController.genVarsForScript(credentials, container))); + credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); return connData; } @@ -174,9 +180,9 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = - serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString scriptData = amnezia::scriptData(m_configTemplate, container); + QString config = serverController.replaceVars( + scriptData, serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 7674eb06..7f8e1587 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -6,35 +6,44 @@ #include "configurator_base.h" #include "core/defs.h" +#include "core/scripts_registry.h" -class WireguardConfigurator : ConfiguratorBase +class WireguardConfigurator : public ConfiguratorBase { Q_OBJECT public: - WireguardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent = nullptr); - struct ConnectionData { + struct ConnectionData + { QString clientPrivKey; // client private key - QString clientPubKey; // client public key - QString clientIP; // internal client IP address - QString serverPubKey; // tls-auth key - QString pskKey; // preshared key - QString host; // host ip + QString clientPubKey; // client public key + QString clientIP; // internal client IP address + QString serverPubKey; // tls-auth key + QString pskKey; // preshared key + QString host; // host ip QString port; }; QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); QString processConfigWithLocalSettings(QString config); QString processConfigWithExportSettings(QString config); - private: - ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); ConnectionData genClientKeys(); + + bool m_isAwg; + QString m_serverConfigPath; + QString m_serverPublicKeyPath; + QString m_serverPskKeyPath; + amnezia::ProtocolScriptType m_configTemplate; + QString m_protocolName; + QString m_defaultPort; }; #endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index d2231415..e133e79e 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -84,11 +84,11 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, + { DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; } @@ -107,6 +107,10 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, + { DockerContainer::Awg, + QObject::tr("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.") }, { DockerContainer::Ipsec, QObject::tr("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.") }, @@ -114,42 +118,108 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") }, { DockerContainer::Dns, QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, { DockerContainer::Sftp, QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; } QMap ContainerProps::containerDetailedDescriptions() { - return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, - { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, - { DockerContainer::Cloak, - QObject::tr("Container with OpenVpn and ShadowSocks protocols " - "configured with traffic masking by Cloak plugin") }, - { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, - { DockerContainer::Ipsec, QObject::tr("IPsec container") }, + return { + { DockerContainer::OpenVpn, + QObject::tr( + "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" + "It employs its unique security protocol, " + "leveraging the strength of SSL/TLS for encryption and key exchange. " + "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " + "catering to a wide range of devices and operating systems. " + "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " + "which continually reinforces its security. " + "With a strong balance of performance, security, and compatibility, " + "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Normal power consumption on mobile devices\n" + "* Flexible customisation to suit user needs to work with different operating systems and devices\n" + "* Recognised by DPI analysis systems and therefore susceptible to blocking\n" + "* Can operate over both TCP and UDP network protocols.") }, + { DockerContainer::ShadowSocks, + QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " + "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection." + "However, certain traffic analysis systems might still detect a Shadowsocks connection. " + "Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n" + "* Available in the AmneziaVPN only on desktop platforms\n" + "* Normal power consumption on mobile devices\n\n" + "* Configurable encryption protocol\n" + "* Detectable by some DPI systems\n" + "* Works over TCP network protocol.") }, + { DockerContainer::Cloak, + QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " + "blocking protection.\n\n" + "OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client " + "and the server.\n\n" + "Cloak protects OpenVPN from detection and blocking. \n\n" + "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " + "and also protects the VPN from detection by Active Probing. This makes it very resistant to " + "being detected\n\n" + "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " + "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " + "invisible to analysis systems.\n\n" + "If there is a extreme level of Internet censorship in your region, we advise you to use only " + "OpenVPN over Cloak from the first connection\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* High power consumption on mobile devices\n" + "* Flexible settings\n" + "* Not recognised by DPI analysis systems\n" + "* Works over TCP network protocol, 443 port.\n") }, + { DockerContainer::WireGuard, + QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" + "Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption " + "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" + "WireGuard is very susceptible to blocking due to its distinct packet signatures. " + "Unlike some other VPN protocols that employ obfuscation techniques, " + "the consistent signature patterns of WireGuard packets can be more easily identified and " + "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Easily recognised by DPI analysis systems, susceptible to blocking\n" + "* Works over UDP network protocol.") }, + { DockerContainer::Awg, + QObject::tr("A modern iteration of the popular VPN protocol, " + "AmneziaWG builds upon the foundation set by WireGuard, " + "retaining its simplified architecture and high-performance capabilities across devices.\n" + "While WireGuard is known for its efficiency, " + "it had issues with being easily detected due to its distinct packet signatures. " + "AmneziaWG solves this problem by using better obfuscation methods, " + "making its traffic blend in with regular internet traffic.\n" + "This means that AmneziaWG keeps the fast performance of the original " + "while adding an extra layer of stealth, " + "making it a great choice for those wanting a fast and discreet VPN connection.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Not recognised by DPI analysis systems, resistant to blocking\n" + "* Works over UDP network protocol.") }, + { DockerContainer::Ipsec, + QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" + "One of its distinguishing features is its ability to swiftly switch between networks and devices, " + "making it particularly adaptive in dynamic network environments. \n" + "While it offers a blend of security, stability, and speed, " + "it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n" + "* Available in the AmneziaVPN only on Windows\n" + "* Low power consumption, on mobile devices\n" + "* Minimal configuration\n" + "* Recognised by DPI analysis systems\n" + "* Works over UDP network protocol, ports 500 and 4500.") }, - { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, - { DockerContainer::Dns, QObject::tr("DNS Service") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, - { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, + { DockerContainer::Dns, QObject::tr("DNS Service") }, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } + }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) { - switch (c) { - case DockerContainer::None: return ServiceType::None; - case DockerContainer::OpenVpn: return ServiceType::Vpn; - case DockerContainer::Cloak: return ServiceType::Vpn; - case DockerContainer::ShadowSocks: return ServiceType::Vpn; - case DockerContainer::WireGuard: return ServiceType::Vpn; - case DockerContainer::Ipsec: return ServiceType::Vpn; - case DockerContainer::TorWebSite: return ServiceType::Other; - case DockerContainer::Dns: return ServiceType::Other; - // case DockerContainer::FileShare : return ServiceType::Other; - case DockerContainer::Sftp: return ServiceType::Other; - default: return ServiceType::Other; - } + return ProtocolProps::protocolService(defaultProtocol(c)); } Proto ContainerProps::defaultProtocol(DockerContainer c) @@ -160,11 +230,11 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::Cloak: return Proto::Cloak; case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::WireGuard: return Proto::WireGuard; + case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::TorWebSite: return Proto::TorWebSite; case DockerContainer::Dns: return Proto::Dns; - // case DockerContainer::FileShare : return Protocol::FileShare; case DockerContainer::Sftp: return Proto::Sftp; default: return Proto::Any; } @@ -179,6 +249,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; // case DockerContainer::ShadowSocks: return true; @@ -196,6 +267,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; case DockerContainer::ShadowSocks: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; default: return false; } @@ -224,8 +296,8 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { case DockerContainer::WireGuard: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; - case DockerContainer::OpenVpn: return true; default: return false; } } @@ -234,8 +306,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { case DockerContainer::WireGuard: return tr("Low"); - case DockerContainer::Cloak: return tr("High"); - case DockerContainer::OpenVpn: return tr("Medium"); + case DockerContainer::Awg: return tr("Medium or High"); + case DockerContainer::Cloak: return tr("Extreme"); default: return ""; } } @@ -243,9 +315,10 @@ QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy"); - case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); + case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); + case DockerContainer::Cloak: + return tr("Most VPN protocols are blocked. Recommended if other options are not working."); default: return ""; } } @@ -253,9 +326,9 @@ QString ContainerProps::easySetupDescription(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return 1; - case DockerContainer::Cloak: return 3; - case DockerContainer::OpenVpn: return 2; + case DockerContainer::WireGuard: return 3; + case DockerContainer::Awg: return 2; + case DockerContainer::Cloak: return 1; default: return 0; } } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 9ca51a96..92ca4f18 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -16,16 +16,16 @@ namespace amnezia Q_NAMESPACE enum DockerContainer { None = 0, - OpenVpn, - ShadowSocks, - Cloak, + Awg, WireGuard, + OpenVpn, + Cloak, + ShadowSocks, Ipsec, // non-vpn TorWebSite, Dns, - // FileShare, Sftp }; Q_ENUM_NS(DockerContainer) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 96306a58..cc81ed94 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -338,6 +338,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c return true; } + if (container == DockerContainer::Awg) { + return true; + } + return false; } @@ -486,6 +490,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); + const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; @@ -582,6 +587,25 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); + // Amnezia wireguard vars + 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() } }); + QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); @@ -810,6 +834,34 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential containerConfig.insert(config_key::port, port); containerConfig.insert(config_key::transport_proto, transportProto); + if (protocol == Proto::Awg) { + QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode); + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + } + config.insert(config_key::container, ContainerProps::containerToString(container)); } config.insert(ProtocolProps::protoToString(protocol), containerConfig); diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 1b379ea1..61ae8962 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -1,8 +1,8 @@ #include "scripts_registry.h" -#include #include #include +#include QString amnezia::scriptFolder(amnezia::DockerContainer container) { @@ -11,11 +11,11 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); + case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); - //case DockerContainer::FileShare: return QLatin1String("file_share"); case DockerContainer::Sftp: return QLatin1String("sftp"); default: return ""; } @@ -45,6 +45,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); + case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); } } @@ -52,7 +53,7 @@ QString amnezia::scriptData(amnezia::SharedScriptType type) { QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } @@ -67,7 +68,7 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co { QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index b30be2ff..02fc94fd 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -26,7 +26,8 @@ enum ProtocolScriptType { configure_container, container_startup, openvpn_template, - wireguard_template + wireguard_template, + awg_template }; diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3a0dc4d9..b85b2c33 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -359,6 +359,23 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + + if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull() + && !obj.value("Jmax").isNull() && !obj.value("S1").isNull() + && !obj.value("S2").isNull() && !obj.value("H1").isNull() + && !obj.value("H2").isNull() && !obj.value("H3").isNull() + && !obj.value("H4").isNull()) { + config.m_junkPacketCount = obj.value("Jc").toString(); + config.m_junkPacketMinSize = obj.value("Jmin").toString(); + config.m_junkPacketMaxSize = obj.value("Jmax").toString(); + config.m_initPacketJunkSize = obj.value("S1").toString(); + config.m_responsePacketJunkSize = obj.value("S2").toString(); + config.m_initPacketMagicHeader = obj.value("H1").toString(); + config.m_responsePacketMagicHeader = obj.value("H2").toString(); + config.m_underloadPacketMagicHeader = obj.value("H3").toString(); + config.m_transportPacketMagicHeader = obj.value("H4").toString(); + } + return true; } diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index 68bebca0..8aa06b9b 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -97,6 +97,34 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "DNS = " << dnsServers.join(", ") << "\n"; } + if (!m_junkPacketCount.isNull()) { + out << "Jc = " << m_junkPacketCount << "\n"; + } + if (!m_junkPacketMinSize.isNull()) { + out << "JMin = " << m_junkPacketMinSize << "\n"; + } + if (!m_junkPacketMaxSize.isNull()) { + out << "JMax = " << m_junkPacketMaxSize << "\n"; + } + if (!m_initPacketJunkSize.isNull()) { + out << "S1 = " << m_initPacketJunkSize << "\n"; + } + if (!m_responsePacketJunkSize.isNull()) { + out << "S2 = " << m_responsePacketJunkSize << "\n"; + } + if (!m_initPacketMagicHeader.isNull()) { + out << "H1 = " << m_initPacketMagicHeader << "\n"; + } + if (!m_responsePacketMagicHeader.isNull()) { + out << "H2 = " << m_responsePacketMagicHeader << "\n"; + } + if (!m_underloadPacketMagicHeader.isNull()) { + out << "H3 = " << m_underloadPacketMagicHeader << "\n"; + } + if (!m_transportPacketMagicHeader.isNull()) { + out << "H4 = " << m_transportPacketMagicHeader << "\n"; + } + // If any extra config was provided, append it now. for (const QString& key : extra.keys()) { out << key << " = " << extra[key] << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 61ffdd83..29aef085 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -40,6 +40,16 @@ class InterfaceConfig { QString m_installationId; #endif + QString m_junkPacketCount; + QString m_junkPacketMinSize; + QString m_junkPacketMaxSize; + QString m_initPacketJunkSize; + QString m_responsePacketJunkSize; + QString m_initPacketMagicHeader; + QString m_responsePacketMagicHeader; + QString m_underloadPacketMagicHeader; + QString m_transportPacketMagicHeader; + QJsonObject toJson() const; QString toWgConf( const QMap& extra = QMap()) const; diff --git a/client/images/controls/x-circle.svg b/client/images/controls/x-circle.svg new file mode 100644 index 00000000..2d3f5b26 --- /dev/null +++ b/client/images/controls/x-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 29dc0bbe..16769ea3 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) -set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources) target_sources(networkextension PRIVATE ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift diff --git a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 03a987ad..44d0b6b0 100644 --- a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -1,6 +1,6 @@ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitGo/wireguard.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/app/WireGuard-Bridging-Header.h b/client/macos/app/WireGuard-Bridging-Header.h index 40b6c89d..da71002d 100644 --- a/client/macos/app/WireGuard-Bridging-Header.h +++ b/client/macos/app/WireGuard-Bridging-Header.h @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 8a437ce0..ea5c8e38 100644 --- a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -4,7 +4,7 @@ #include "macos/gobridge/wireguard.h" #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/ShadowSocks/ShadowSocks/ShadowSocks.h" #include "platforms/ios/ssconnectivity.h" #include "platforms/ios/iosopenvpn2ssadapter.h" diff --git a/client/main.cpp b/client/main.cpp index 396b7625..bf861dc2 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -26,6 +26,11 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #endif +// QTBUG-95974 QTBUG-95764 QTBUG-102168 +#ifdef Q_OS_ANDROID + qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1"); +#endif + #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); #else diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 00811500..2f6fe371 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -115,7 +115,9 @@ void LocalSocketController::daemonConnected() { } void LocalSocketController::activate(const QJsonObject &rawConfig) { - QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject(); + QString protocolName = rawConfig.value("protocol").toString(); + + QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject json; json.insert("type", "activate"); @@ -160,6 +162,19 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // splitTunnelApps.append(QJsonValue(uri)); // } // json.insert("vpnDisabledApps", splitTunnelApps); + + if (protocolName == amnezia::config_key::awg) { + json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); + json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); + json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); + json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); + json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); + json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); + json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); + json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + } + write(json); } diff --git a/client/platforms/ios/WireGuard-Bridging-Header.h b/client/platforms/ios/WireGuard-Bridging-Header.h index e5dfa39f..fbccb2d4 100644 --- a/client/platforms/ios/WireGuard-Bridging-Header.h +++ b/client/platforms/ios/WireGuard-Bridging-Header.h @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index ea8adbc0..68f30ce8 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -62,6 +62,7 @@ private: bool setupOpenVPN(); bool setupCloak(); bool setupWireGuard(); + bool setupAwg(); bool startOpenVPN(const QString &config); bool startWireGuard(const QString &jsonConfig); diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 57394383..5665ff1d 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -204,6 +204,9 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur if (proto == amnezia::Proto::WireGuard) { return setupWireGuard(); } + if (proto == amnezia::Proto::Awg) { + return setupAwg(); + } return false; } @@ -307,6 +310,15 @@ bool IosController::setupWireGuard() return startWireGuard(wgConfig); } +bool IosController::setupAwg() +{ + QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); + + QString wgConfig = config[config_key::config].toString(); + + return startWireGuard(wgConfig); +} + bool IosController::startOpenVPN(const QString &config) { qDebug() << "IosController::startOpenVPN"; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index a8b7b04a..792120a7 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 1f422462..ef13f4c7 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/protocols/awgprotocol.cpp b/client/protocols/awgprotocol.cpp new file mode 100644 index 00000000..64415dbe --- /dev/null +++ b/client/protocols/awgprotocol.cpp @@ -0,0 +1,10 @@ +#include "awgprotocol.h" + +Awg::Awg(const QJsonObject &configuration, QObject *parent) + : WireguardProtocol(configuration, parent) +{ +} + +Awg::~Awg() +{ +} diff --git a/client/protocols/awgprotocol.h b/client/protocols/awgprotocol.h new file mode 100644 index 00000000..d7fc9c92 --- /dev/null +++ b/client/protocols/awgprotocol.h @@ -0,0 +1,17 @@ +#ifndef AWGPROTOCOL_H +#define AWGPROTOCOL_H + +#include + +#include "wireguardprotocol.h" + +class Awg : public WireguardProtocol +{ + Q_OBJECT + +public: + explicit Awg(const QJsonObject &configuration, QObject *parent = nullptr); + virtual ~Awg() override; +}; + +#endif // AWGPROTOCOL_H diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 5f8600db..a451014c 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -1,5 +1,7 @@ #include "protocols_defs.h" +#include + using namespace amnezia; QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) @@ -66,12 +68,12 @@ QMap ProtocolProps::protocolHumanNames() { Proto::ShadowSocks, "ShadowSocks" }, { Proto::Cloak, "Cloak" }, { Proto::WireGuard, "WireGuard" }, + { Proto::Awg, "AmneziaWG" }, { Proto::Ikev2, "IKEv2" }, { Proto::L2tp, "L2TP" }, { Proto::TorWebSite, "Website in Tor network" }, { Proto::Dns, "DNS Service" }, - { Proto::FileShare, "File Sharing Service" }, { Proto::Sftp, QObject::tr("Sftp service") } }; } @@ -88,27 +90,43 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::Cloak: return ServiceType::Vpn; case Proto::ShadowSocks: return ServiceType::Vpn; case Proto::WireGuard: return ServiceType::Vpn; + case Proto::Awg: return ServiceType::Vpn; + case Proto::Ikev2: return ServiceType::Vpn; + case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; - case Proto::FileShare: return ServiceType::Other; + case Proto::Sftp: return ServiceType::Other; default: return ServiceType::Other; } } +int ProtocolProps::getPortForInstall(Proto p) +{ + switch (p) { + case Awg: + case WireGuard: + case ShadowSocks: + case OpenVpn: + return QRandomGenerator::global()->bounded(30000, 50000); + default: + return defaultPort(p); + } +} + int ProtocolProps::defaultPort(Proto p) { switch (p) { case Proto::Any: return -1; - case Proto::OpenVpn: return 1194; - case Proto::Cloak: return 443; - case Proto::ShadowSocks: return 6789; - case Proto::WireGuard: return 51820; + case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt(); + case Proto::Cloak: return QString(protocols::cloak::defaultPort).toInt(); + case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt(); + case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt(); + case Proto::Awg: return QString(protocols::awg::defaultPort).toInt(); case Proto::Ikev2: return -1; case Proto::L2tp: return -1; case Proto::TorWebSite: return -1; case Proto::Dns: return 53; - case Proto::FileShare: return 139; case Proto::Sftp: return 222; default: return -1; } @@ -122,13 +140,14 @@ bool ProtocolProps::defaultPortChangeable(Proto p) case Proto::Cloak: return true; case Proto::ShadowSocks: return true; case Proto::WireGuard: return true; + case Proto::Awg: return true; case Proto::Ikev2: return false; case Proto::L2tp: return false; - case Proto::TorWebSite: return true; + case Proto::TorWebSite: return false; case Proto::Dns: return false; - case Proto::FileShare: return false; - default: return -1; + case Proto::Sftp: return true; + default: return false; } } @@ -140,12 +159,12 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p) case Proto::Cloak: return TransportProto::Tcp; case Proto::ShadowSocks: return TransportProto::Tcp; case Proto::WireGuard: return TransportProto::Udp; + case Proto::Awg: return TransportProto::Udp; case Proto::Ikev2: return TransportProto::Udp; case Proto::L2tp: return TransportProto::Udp; // non-vpn case Proto::TorWebSite: return TransportProto::Tcp; case Proto::Dns: return TransportProto::Udp; - case Proto::FileShare: return TransportProto::Udp; case Proto::Sftp: return TransportProto::Tcp; } } @@ -158,12 +177,12 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) case Proto::Cloak: return false; case Proto::ShadowSocks: return false; case Proto::WireGuard: return false; + case Proto::Awg: return false; case Proto::Ikev2: return false; case Proto::L2tp: return false; // non-vpn case Proto::TorWebSite: return false; case Proto::Dns: return false; - case Proto::FileShare: return false; case Proto::Sftp: return false; default: return false; } diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index fa326b2a..ce33137c 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -2,8 +2,8 @@ #define PROTOCOLS_DEFS_H #include -#include #include +#include namespace amnezia { @@ -61,11 +61,22 @@ namespace amnezia constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + constexpr char junkPacketCount[] = "Jc"; + constexpr char junkPacketMinSize[] = "Jmin"; + constexpr char junkPacketMaxSize[] = "Jmax"; + constexpr char initPacketJunkSize[] = "S1"; + constexpr char responsePacketJunkSize[] = "S2"; + constexpr char initPacketMagicHeader[] = "H1"; + constexpr char responsePacketMagicHeader[] = "H2"; + constexpr char underloadPacketMagicHeader[] = "H3"; + constexpr char transportPacketMagicHeader[] = "H4"; + constexpr char openvpn[] = "openvpn"; constexpr char wireguard[] = "wireguard"; constexpr char shadowsocks[] = "shadowsocks"; constexpr char cloak[] = "cloak"; constexpr char sftp[] = "sftp"; + constexpr char awg[] = "awg"; constexpr char configVersion[] = "config_version"; constexpr char apiEdnpoint[] = "api_endpoint"; @@ -146,6 +157,25 @@ namespace amnezia } // namespace sftp + namespace awg + { + constexpr char defaultPort[] = "55424"; + + constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; + + constexpr char defaultJunkPacketCount[] = "3"; + constexpr char defaultJunkPacketMinSize[] = "10"; + constexpr char defaultJunkPacketMaxSize[] = "30"; + constexpr char defaultInitPacketJunkSize[] = "15"; + constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; + constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; + constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; + constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + } + } // namespace protocols namespace ProtocolEnumNS @@ -164,13 +194,13 @@ namespace amnezia ShadowSocks, Cloak, WireGuard, + Awg, Ikev2, L2tp, // non-vpn TorWebSite, Dns, - FileShare, Sftp }; Q_ENUM_NS(Proto) @@ -204,6 +234,8 @@ namespace amnezia Q_INVOKABLE static ServiceType protocolService(Proto p); + Q_INVOKABLE static int getPortForInstall(Proto p); + Q_INVOKABLE static int defaultPort(Proto p); Q_INVOKABLE static bool defaultPortChangeable(Proto p); diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 841d307c..2ddc0684 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -1,22 +1,21 @@ #include #include -#include "vpnprotocol.h" #include "core/errorstrings.h" +#include "vpnprotocol.h" #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) -#include "openvpnprotocol.h" -#include "shadowsocksvpnprotocol.h" -#include "openvpnovercloakprotocol.h" -#include "wireguardprotocol.h" + #include "openvpnovercloakprotocol.h" + #include "openvpnprotocol.h" + #include "shadowsocksvpnprotocol.h" + #include "wireguardprotocol.h" #endif #ifdef Q_OS_WINDOWS -#include "ikev2_vpn_protocol_windows.h" + #include "ikev2_vpn_protocol_windows.h" #endif - -VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) +VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) : QObject(parent), m_connectionState(Vpn::ConnectionState::Unknown), m_rawConfig(configuration), @@ -31,7 +30,7 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; - if (lastError){ + if (lastError) { setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); @@ -103,7 +102,7 @@ QString VpnProtocol::vpnGateway() const return m_vpnGateway; } -VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration) +VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { #if defined(Q_OS_WINDOWS) @@ -114,6 +113,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); + case DockerContainer::Awg: return new WireguardProtocol(configuration); #endif default: return nullptr; } @@ -135,8 +135,7 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState) case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); case Vpn::ConnectionState::Error: return tr("Error"); - default: - ; + default:; } return QString(); diff --git a/client/resources.qrc b/client/resources.qrc index 5b4d6ae7..4c63383c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,5 +215,12 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml + images/controls/x-circle.svg + ui/qml/Pages2/PageProtocolAwgSettings.qml + server_scripts/awg/template.conf + server_scripts/awg/start.sh + server_scripts/awg/configure_container.sh + server_scripts/awg/run_container.sh + server_scripts/awg/Dockerfile diff --git a/client/server_scripts/awg/Dockerfile b/client/server_scripts/awg/Dockerfile new file mode 100644 index 00000000..8c536fc7 --- /dev/null +++ b/client/server_scripts/awg/Dockerfile @@ -0,0 +1,46 @@ +FROM amneziavpn/amnezia-wg:latest + +LABEL maintainer="AmneziaVPN" + +#Install required packages +RUN apk add --no-cache bash curl dumb-init +RUN apk --update upgrade --no-cache + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = hybla \n\ + # for low-latency network, use cubic instead \n\ + # net.ipv4.tcp_congestion_control = cubic \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] +CMD [ "" ] diff --git a/client/server_scripts/awg/configure_container.sh b/client/server_scripts/awg/configure_container.sh new file mode 100644 index 00000000..322cc38f --- /dev/null +++ b/client/server_scripts/awg/configure_container.sh @@ -0,0 +1,26 @@ +mkdir -p /opt/amnezia/awg +cd /opt/amnezia/awg +WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey) +echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key + +WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey) +echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key + +WIREGUARD_PSK=$(wg genpsk) +echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key + +cat > /opt/amnezia/awg/wg0.conf < /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo; fi;\ -if ! command -v fuser > /dev/null 2>&1; then $pm install -yq psmisc; fi;\ -if ! command -v lsof > /dev/null 2>&1; then $pm install -yq lsof; fi;\ -if ! command -v docker > /dev/null 2>&1; then $pm update -yq; $pm install -yq $docker_pkg;\ - if [ "$dist" = "fedora" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ +if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\ +if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\ +if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\ + if [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ fi;\ if [ "$dist" = "debian" ]; then \ docker_service=$(systemctl list-units --full --all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\ @@ -17,4 +17,3 @@ if [ "$dist" = "debian" ]; then \ fi;\ if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker";exit 1;fi;\ docker --version - diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 20a7f022..0d3422f7 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,9 +4,13 @@ AmneziaApplication - Split tunneling for WireGuard is not implemented, the option was disabled - + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена + + + + Split tunneling for %1 is not implemented, the option was disabled + Раздельное туннелирование для %1 не реализовано, опция отключена @@ -14,12 +18,20 @@ AmneziaVPN - + AmneziaVPN VPN Connected Refers to the app - which is currently running the background and waiting + VPN Подключен + + + + CloudController + + + Error when retrieving configuration from cloud server @@ -34,61 +46,63 @@ ConnectionController + VPN Protocols is not installed. Please install VPN container at first - + VPN протоколы не установлены. + Пожалуйста, установите протокол Connection... - + Подключение... - + Connected - + Подключено - + Settings updated successfully, Reconnnection... - + Настройки успешно обновлены. Подключение... - + Reconnection... - + Переподключение... - - - + + + Connect - + Подключиться - + Disconnection... - + Отключение... ConnectionTypeSelectionDrawer - - Connection data - + + Add new connection + Добавить новое соединение - - Server IP, login and password - + + Configure your server + Настроить ваш сервер - - QR code, key or configuration file - + + Open config file, key or QR code + Открыть файл конфига, ключ или QR код @@ -96,22 +110,22 @@ C&ut - + &Вырезать &Copy - + &Копировать &Paste - + &Вставить &SelectAll - + &ВыбратьВсе @@ -119,85 +133,85 @@ Access error! - + Ошибка доступа! HomeContainersListView - The selected protocol is not supported on the current platform - + Unable change protocol while there is an active connection + Невозможно изменить протокол при активном соединении + + + + The selected protocol is not supported on the current platform + Выбранный протокол не поддерживается на данном устройстве - Reconnect via VPN Procotol: - + Переподключение через VPN протокол: ImportController - + Scanned %1 of %2. - + Отсканировано %1 из%2. InstallController - + %1 installed successfully. - + %1 успешно установлен. - + %1 is already installed on the server. - + %1 уже установлен на сервер. - -Added containers that were already installed on the server - - - - + Already installed containers were found on the server. All installed containers have been added to the application - + +На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение - + Settings updated successfully - + Настройки успешно обновлены - + Server '%1' was removed - + Сервер '%1' был удален - + All containers from server '%1' have been removed - + Все протоклы и сервисы были удалены с сервера '%1' - + %1 has been removed from the server '%2' - + %1 был удален с сервера '%2' - + Please login as the user - + Пожалуйста, войдите в систему от имени пользователя - + Server added successfully - + Сервер успешно добавлен @@ -205,17 +219,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + Не удалось считать ключ: %1 Write key failed: %1 - + Не удалось записать ключ: %1 Delete key failed: %1 - + Не удалось удалить ключ: %1 @@ -224,27 +238,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + AmneziaVPN VPN Connected - + VPN Подключен VPN Disconnected - + VPN Выключен AmneziaVPN notification - + Уведомление AmneziaVPN Unsecured network detected: - + Обнаружена незащищенная сеть: @@ -252,25 +266,122 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - + Удаление сервисов c %1 Usually it takes no more than 5 minutes - + Обычно это занимает не более 5 минут PageHome - + VPN protocol - + VPN протокол - + Servers - + Серверы + + + + Unable change server while there is an active connection + Невозможно изменить сервер при активном соединении + + + + PageProtocolAwgSettings + + + AmneziaWG settings + AmneziaWG настройки + + + + Port + Порт + + + + Junk packet count + Junk packet count + + + + Junk packet minimum size + Junk packet minimum size + + + + Junk packet maximum size + Junk packet maximum size + + + + Init packet junk size + Init packet junk size + + + + Response packet junk size + Response packet junk size + + + + Init packet magic header + Init packet magic header + + + + Response packet magic header + Response packet magic header + + + + Transport packet magic header + Transport packet magic header + + + + Underload packet magic header + Underload packet magic header + + + + Remove AmneziaWG + Удалить AmneziaWG + + + + Remove AmneziaWG from server? + Удалить AmneziaWG с сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + Save and Restart Amnezia + Сохранить и пререзагрузить Amnezia @@ -278,224 +389,228 @@ Already installed containers were found on the server. All installed containers Cloak settings - + Настройки Cloak Disguised as traffic from - + Замаскировать трафик под Port - + Порт Cipher - + Шифрование Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia PageProtocolOpenVpnSettings - + OpenVPN settings - + Настройки OpenVPN - + VPN Addresses Subnet - + Подсеть для VPN - + Network protocol - + Сетевой протокол - + Port - + Порт - + Auto-negotiate encryption - + Шифрование с автоматическим согласованием - + Hash - - - - - SHA512 - + Хэш - SHA384 - + SHA512 + SHA512 - SHA256 - + SHA384 + SHA384 - SHA3-512 - + SHA256 + SHA256 - SHA3-384 - + SHA3-512 + SHA3-512 - SHA3-256 - + SHA3-384 + SHA3-384 - whirlpool - + SHA3-256 + SHA3-256 - BLAKE2b512 - + whirlpool + whirlpool - BLAKE2s256 - + BLAKE2b512 + BLAKE2b512 + BLAKE2s256 + BLAKE2s256 + + + SHA1 - + SHA1 - + Cipher - - - - - AES-256-GCM - + Шифрование - AES-192-GCM - + AES-256-GCM + AES-256-GCM - AES-128-GCM - + AES-192-GCM + AES-192-GCM - AES-256-CBC - + AES-128-GCM + AES-128-GCM - AES-192-CBC - + AES-256-CBC + AES-256-CBC - AES-128-CBC - + AES-192-CBC + AES-192-CBC - ChaCha20-Poly1305 - + AES-128-CBC + AES-128-CBC - ARIA-256-CBC - + ChaCha20-Poly1305 + ChaCha20-Poly1305 - CAMELLIA-256-CBC - + ARIA-256-CBC + ARIA-256-CBC + CAMELLIA-256-CBC + CAMELLIA-256-CBC + + + none - + none - + TLS auth - + TLS авторизация - + Block DNS requests outside of VPN - + Блокировать DNS запросы за пределами VPN - + Additional client configuration commands - + Дополнительные команды конфигурации клиента - - + + Commands: - + Commands: - + Additional server configuration commands - - - - - Remove OpenVPN - - - - - Remove OpenVpn from server? - - - - - All users with whom you shared a connection will no longer be able to connect to it - + Дополнительные команды конфигурации сервера + Remove OpenVPN + Удалить OpenVPN + + + + Remove OpenVpn from server? + Удалить OpenVpn с сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + Continue - Продолжить + Продолжить - + Cancel - + Отменить - + Save and Restart Amnezia - + Сохранить и перезагрузить @@ -503,42 +618,46 @@ Already installed containers were found on the server. All installed containers settings - + настройки Show connection options - + Показать параметры подключения - Connection options - + Connection options %1 + Параметры подключения %1 - + Remove - - - - - Remove %1 from server? - - - - - All users with whom you shared a connection will no longer be able to connect to it - + Удалить - Continue - Продолжить + Remove %1 from server? + Удалить %1 с сервера? + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + + Continue + Продолжить + + + Cancel - + Отменить @@ -546,23 +665,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + Настройки ShadowSocks Port - + Порт Cipher - + Шифрование Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia @@ -578,32 +697,33 @@ 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. + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно во вкладке "Соединения" настроек приложения Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? Continue - Продолжить + Продолжить Cancel - + Отменить @@ -611,17 +731,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены SFTP settings - + Настройки SFTP Host - + Хост @@ -629,69 +749,69 @@ Already installed containers were found on the server. All installed containers Copied - + Скопировано Port - + Порт Login - + Логин Password - + Пароль - + Mount folder on device - + Смонтировать папку на вашем устройстве - + In order to mount remote SFTP folder as local drive, perform following steps: <br> - + Чтобы смонтировать SFTP-папку как локальный диск на вашем устройстве, выполните следующие действия - - + + <br>1. Install the latest version of - + <br>1. Установите последнюю версию - - + + <br>2. Install the latest version of - + <br>2. Установите последнюю версию - + Detailed instructions - + Подробные инструкции - + Remove SFTP and all data stored there - + Удалить SFTP-хранилище со всеми данными - + Remove SFTP and all data stored there? - + Удалить SFTP-хранилище и все хранящиеся на нем данные? - + Continue - Продолжить + Продолжить - + Cancel - + Отменить @@ -699,57 +819,61 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены Tor website settings - + Настройки сайта в сети Тоr Website address - + Адрес сайта Copied - + Скопировано - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - + Используйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для открытия этой ссылки. - + After installation it takes several minutes while your onion site will become available in the Tor Network. - + Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. - - When configuring WordPress set the domain as this onion address. - + + When configuring WordPress set the this onion address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. - + When configuring WordPress set the this address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. + + + Remove website - + Удалить сайт - + The site with all data will be removed from the tor network. - + Сайт со всеми данными будет удален из сети Tor. - + Continue - Продолжить + Продолжить - + Cancel - + Отменить @@ -757,32 +881,37 @@ Already installed containers were found on the server. All installed containers Settings - + Настройки Servers - + Серверы Connection - + Соединение Application - + Приложение Backup - + Резервное копирование - + About AmneziaVPN - + Об AmneziaVPN + + + + Close application + Закрыть приложение @@ -790,83 +919,87 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - + Поддержите проект пожертвованием - This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app. - + This is a free and open source application. If you like it, support the developers with a donation. + Это бесплатное приложение с открытым исходным кодом. Если, оно вам нравится - поддержите разработчиков пожертвованием. + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + А, если оно вам не нравится, тем более поддержите-пожертвование пойдет на улучшение приложения. Card on Patreon - + Картой на Patreon https://www.patreon.com/amneziavpn - + https://www.patreon.com/amneziavpn Show other methods on Github - + Показать другие способы на Github Contacts - + Контакты Telegram group - + Группа в Telegram To discuss features - + Для обсуждений https://t.me/amnezia_vpn_en - + https://t.me/amnezia_vpn Mail - + Почта For reviews and bug reports - + Для отзывов и сообщений об ошибках Github - + Github https://github.com/amnezia-vpn/amnezia-client - + https://github.com/amnezia-vpn/amnezia-client Website - + Веб-сайт https://amnezia.org - + https://amnezia.org Check for updates - + Проверить обновления @@ -874,82 +1007,77 @@ And if you don't like the app, all the more support it - the donation will Application - + Приложение - Allow application screenshots - - - - Auto start - + Launch the application every time - + starts Start minimized - + Запускать в свернутом виде Launch application minimized - + Запускать приложение в свернутом виде Language - + Язык Logging - + Логирование Enabled - + Включено Disabled - + Отключено Reset settings and remove all data from the application - + Сбросить настройки и удалить все данные из приложения Reset settings and remove all data from the application? - + Сбросить настройки и удалить все данные из приложения? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Все данные из приложения будут удалены, все установленные сервисы AmneziaVPN останутся на сервере. Continue - Продолжить + Продолжить Cancel - + Отменить @@ -957,68 +1085,73 @@ And if you don't like the app, all the more support it - the donation will Backup - + Резервное копирование Settings restored from backup file - + Восстановление настроек из бэкап файла Configuration backup - + Бэкап конфигурация - It will help you instantly restore connection settings at the next installation - + You can save your settings to a backup file to restore them the next time you install the application. + Поможет мгновенно восстановить настройки соединений при следующей установке. Make a backup - + Сделать бэкап Save backup file - + Сохранить бэкап файл - + Backup files (*.backup) - + Файлы резервного копирования (*.backup) - + + Backup file saved + Бэкап файл сохранен + + + Restore from backup - + Восстановить из бэкапа - + Open backup file - - - - - Import settings from a backup file? - + Открыть бэкап файл - All current settings will be reset - + Import settings from a backup file? + Импортировать настройки из бэкап файла? - Continue - Продолжить + All current settings will be reset + Все текущие настройки будут сброшены + Continue + Продолжить + + + Cancel - + Отменить @@ -1026,57 +1159,57 @@ And if you don't like the app, all the more support it - the donation will Connection - + Соединение Auto connect - + Автоподключение Connect to VPN on app start - + Подключение к VPN при запуске приложения Use AmneziaDNS - + Использовать Amnezia DNS If AmneziaDNS is installed on the server - + Если он уставновлен на сервере DNS servers - + DNS сервер If AmneziaDNS is not used or installed - + Эти серверы будут использоваться, если не включен AmneziaDNS - - Split site tunneling - + + Site-based split tunneling + Раздельное туннелирование сайтов - - Allows you to connect to some sites through a secure connection, and to others bypassing it - + + Allows you to select which sites you want to access through the VPN + Позволяет подключаться к одним сайтам через VPN, а к другим в обход него - - Separate application tunneling - + + App-based split tunneling + Раздельное VPN-туннелирование приложений - + Allows you to use the VPN only for certain applications - + Позволяет использовать VPN только для определённых приложений @@ -1084,57 +1217,57 @@ And if you don't like the app, all the more support it - the donation will DNS servers - + DNS сервер - + If AmneziaDNS is not used or installed - + Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS - + Primary DNS - + Первичный DNS - + Secondary DNS - + Вторичный DNS - + Restore default - - - - - Restore default DNS settings? - + Восстановить по умолчанию - Continue - Продолжить + Restore default DNS settings? + Восстановить настройки DNS по умолчанию? + Continue + Продолжить + + + Cancel - + Отменить - + Settings have been reset - + Настройки сброшены - + Save - + Сохранить - + Settings saved - + Сохранить настройки @@ -1142,57 +1275,62 @@ And if you don't like the app, all the more support it - the donation will Logging - + Логирование Save logs - + Сохранять логи Open folder with logs - + Открыть папку с логами Save - + Сохранить Logs files (*.log) - + Logs files (*.log) - + + Logs file saved + Файл с логами сохранен + + + Save logs to file - - - - - Clear logs? - + Сохранить логи в файл - Continue - Продолжить + Clear logs? + Очистить логи? + Continue + Продолжить + + + Cancel - + Отменить - + Logs have been cleaned up - + Логи удалены - + Clear logs - + Удалить логи @@ -1200,27 +1338,27 @@ And if you don't like the app, all the more support it - the donation will All installed containers have been added to the application - + Все установленные протоколы и сервисы были добавлены в приложение Clear Amnezia cache - + Очистить кэш Amnezia на сервере May be needed when changing other settings - + Может понадобиться при изменении других настроек Clear cached profiles? - Очистить закешированные профили + Удалить кэш Amnezia с сервера? No new installed containers found - + Новые установленные протоколы и сервисы не обнаружены @@ -1239,47 +1377,47 @@ And if you don't like the app, all the more support it - the donation will Cancel - + Отменить Check the server for previously installed Amnezia services - + Проверить сервер на наличие ранее установленных сервисов Amnezia Add them to the application if they were not displayed - + Добавить их в приложение, если они не были отображены Remove server from application - + Удалить сервер из приложения Remove server? - + Удалить сервер? All installed AmneziaVPN services will still remain on the server. - + Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере. Clear server from Amnezia software - + Очистить сервер от протоколов и сервисов Amnezia Clear server from Amnezia software? - + Удалить все сервисы и протоколы Amnezia с сервера? All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - + На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты. @@ -1287,27 +1425,27 @@ And if you don't like the app, all the more support it - the donation will Server name - + Имя сервера Save - + Сохранить Protocols - + Протоколы Services - + Сервисы Data - + Данные @@ -1315,131 +1453,135 @@ And if you don't like the app, all the more support it - the donation will settings - + настройки Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? - All users with whom you shared a connection will no longer be able to connect to it - + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. Continue - Продолжить + Продолжить Cancel - + Отменить PageSettingsServersList - + Servers - + Серверы PageSettingsSplitTunneling - - Only the addresses in the list must be opened via VPN - + + Addresses from the list should be accessed via VPN + Только адреса из списка должны открываться через VPN - - Addresses from the list should never be opened via VPN - + + Addresses from the list should not be accessed via VPN + Адреса из списка не должны открываться через VPN - - Split site tunneling - + + Split tunneling + Раздельное VPN-туннелирование - + Mode - - - - - Remove - + Режим - Continue - Продолжить + Remove + Удалить + Continue + Продолжить + + + Cancel - + Отменить - + Site or IP - + Сайт или IP - + Import/Export Sites - + Импорт/экспорт Сайтов - + Import - + Импорт - + Save site list - + Сохранить список сайтов - + Save sites - + Сохранить - - - + + + Sites files (*.json) - + Sites files (*.json) - + Import a list of sites - + Импортировать список с сайтами - + Replace site list - + Заменить список сайтов - - + + Open sites file - + Открыть список с сайтами - + Add imported sites to existing ones - + Добавить импортированные сайты к существующим @@ -1447,97 +1589,109 @@ And if you don't like the app, all the more support it - the donation will Server connection - + Подключение к серверу Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.. + +Всё в порядке, если кодом поделился пользователь, которому вы доверяете. What do you have? - + Выберете что у вас есть File with connection settings - + Файл с настройками подключения File with connection settings or backup - + Файл с настройками подключения или бэкап Open config file - + Открыть файл с конфигурацией QR-code - + QR-код Key as text - + Ключ в виде текста PageSetupWizardCredentials - Server connection - + Подключение к серверу Server IP address [:port] - + Server IP address [:port] 255.255.255.255:88 - + 255.255.255.255:88 Password / SSH private key - + Password / SSH private key Continue - Продолжить + Продолжить - + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим сторонам + + + Enter the address in the format 255.255.255.255:88 - + Введите адрес в формате 255.255.255.255:88 Login to connect via SSH - + Login to connect via SSH - + + Configure your server + Настроить ваш сервер + + + Ip address cannot be empty - + Поле Ip address не может быть пустым - + Login cannot be empty - + Поле Login не может быть пустым - + Password/private key cannot be empty - + Поле Password/private key не может быть пустым @@ -1545,61 +1699,69 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + Какой уровень контроля интеренета в вашем регионе? Set up a VPN yourself - + Настроить VPN самостоятельно I want to choose a VPN protocol - + Выбрать VPN-протокол Continue - Продолжить + Продолжить Set up later - + Настроить позднее PageSetupWizardInstalling - + The server has already been added to the application - + Сервер уже был добавлен в приложение - Amnesia has detected that your server is currently - + Amnesia обнаружила, что ваш сервер в настоящее время - busy installing other software. Amnesia installation - + занят установкой других протоколов или сервисов. Установка Amnesia - + + Amnezia has detected that your server is currently + Amnezia обнаружила, что ваш сервер в настоящее время + + + + busy installing other software. Amnezia installation + занят установкой другого программного обеспечения. Установка Amnezia + + + will pause until the server finishes installing other software - + будет приостановлена до тех пор, пока сервер не завершит установку - + Installing - + Установка - + Usually it takes no more than 5 minutes - + Обычно это занимает не более 5 минут @@ -1607,32 +1769,32 @@ It's okay as long as it's from someone you trust. Installing %1 - + Установить %1 More detailed - + Подробнее - + Close - + Закрыть - + Network protocol - + Сетевой протокол - + Port - + Порт - + Install - + Установить @@ -1640,12 +1802,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + VPN протокол Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + Выберите протокол, который вам больше подходит. В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси, TOR-сайт и SFTP. @@ -1653,7 +1815,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + Наведите камеру на QR-код и удерживайте ее в течение нескольких секунд. @@ -1661,27 +1823,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + Восстановление настроек из бэкап файла Free service for creating a personal VPN on your server. - + Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности. Helps you access blocked content without revealing your privacy, even to VPN providers. - + Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN. I have the data to connect - + У меня есть данные для подключения I have nothing - + У меня ничего нет @@ -1689,27 +1851,27 @@ It's okay as long as it's from someone you trust. Connection key - + Ключ для подключения A line that starts with vpn://... - + Строка, которая начинается с vpn://... Key - + Ключ Insert - + Вставить Continue - Продолжить + Продолжить @@ -1717,27 +1879,27 @@ It's okay as long as it's from someone you trust. New connection - + Новое соединение Do not use connection code from public sources. It could be created to intercept your data. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные. Collapse content - + Свернуть Show content - + Показать содержимое ключа Connect - + Подключиться @@ -1745,99 +1907,101 @@ It's okay as long as it's from someone you trust. OpenVpn native format - + OpenVpn нативный формат WireGuard native format - + WireGuard нативный формат - VPN Access - + VPN-Доступ Connection - + Соединение - VPN access without the ability to manage the server - + Доступ к VPN, без возможности управления сервером - - Full access to server - + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. + Server - + Сервер Accessing - + Доступ - - File with accessing settings to - - - - + Connection to - + Подключение к File with connection settings to - + Файл с настройками доступа к Save OpenVPN config - + Сохранить OpenVPN config Save WireGuard config - + Сохранить WireGuard config For the AmneziaVPN app - + Для AmneziaVPN + + + + Share VPN Access + Поделиться VPN Full access + Полный доступ + + + + Share VPN access without the ability to manage the server + Поделиться доступом к VPN, без возможности управления сервером + + + + Protocols - - Servers - - - - - + Protocol - + Протокол Connection format - + Формат подключения Share - + Поделиться @@ -1845,7 +2009,7 @@ It's okay as long as it's from someone you trust. Close - + Закрыть @@ -1853,38 +2017,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not remove private key from keystore - + Could not remove private key from keystore @@ -1892,12 +2056,12 @@ It's okay as long as it's from someone you trust. Unknown error - + Unknown error Access to keychain denied - + Access to keychain denied @@ -1905,27 +2069,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + Could not store data in settings: access error Could not store data in settings: format error - + Could not store data in settings: format error Could not delete data from settings: access error - + Could not delete data from settings: access error Could not delete data from settings: format error - + Could not delete data from settings: format error Entry not found - + Entry not found @@ -1933,80 +2097,80 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error No keychain service available - + No keychain service available Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Access to keychain denied - + Access to keychain denied Could not determine data type: %1; %2 - + Could not determine data type: %1; %2 Entry not found - + Entry not found Unsupported entry type 'Map' - + Unsupported entry type 'Map' Unknown kwallet entry type '%1' - + Unknown kwallet entry type '%1' Password not found - + Password not found Could not open keystore - + Could not open keystore Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create decryption cipher - + Could not create decryption cipher @@ -2014,73 +2178,73 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + Credential size exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Writing credentials failed: Win32 error code %1 - + Writing credentials failed: Win32 error code %1 Encryption failed - + Encryption failed D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not create private key generator - + Could not create private key generator Could not generate new private key - + Could not generate new private key Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create encryption cipher - + Could not create encryption cipher Could not encrypt data - + Could not encrypt data @@ -2088,367 +2252,499 @@ It's okay as long as it's from someone you trust. No error - + No error Unknown Error - + Unknown Error Function not implemented - + Function not implemented Server check failed - + Server check failed Server port already used. Check for another software - + Server port already used. Check for another software Server error: Docker container missing - + Server error: Docker container missing Server error: Docker failed - + Server error: Docker failed Installation canceled by user - + Installation canceled by user The user does not have permission to use sudo - + The user does not have permission to use sudo Ssh request was denied - + Ssh request was denied Ssh request was interrupted - + Ssh request was interrupted Ssh internal error - + Ssh internal error Invalid private key or invalid passphrase entered - + Invalid private key or invalid passphrase entered The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types Timeout connecting to server - + Timeout connecting to server Sftp error: End-of-file encountered - + Sftp error: End-of-file encountered Sftp error: File does not exist - + Sftp error: File does not exist Sftp error: Permission denied - + Sftp error: Permission denied Sftp error: Generic failure - + Sftp error: Generic failure Sftp error: Garbage received from server - + Sftp error: Garbage received from server Sftp error: No connection has been set up - + Sftp error: No connection has been set up Sftp error: There was a connection, but we lost it - + Sftp error: There was a connection, but we lost it Sftp error: Operation not supported by libssh yet - + Sftp error: Operation not supported by libssh yet Sftp error: Invalid file handle - + Sftp error: Invalid file handle Sftp error: No such file or directory path exists - + Sftp error: No such file or directory path exists Sftp error: An attempt to create an already existing file or directory has been made - + Sftp error: An attempt to create an already existing file or directory has been made Sftp error: Write-protected filesystem - + Sftp error: Write-protected filesystem Sftp error: No media was in remote drive - + Sftp error: No media was in remote drive Failed to save config to disk - + Failed to save config to disk OpenVPN config missing - + OpenVPN config missing OpenVPN management server error - + OpenVPN management server error OpenVPN executable missing - + OpenVPN executable missing ShadowSocks (ss-local) executable missing - + ShadowSocks (ss-local) executable missing Cloak (ck-client) executable missing - + Cloak (ck-client) executable missing Amnezia helper service error - + Amnezia helper service error OpenSSL failed - + OpenSSL failed Can't connect: another VPN connection is active - + Can't connect: another VPN connection is active Can't setup OpenVPN TAP network adapter - + Can't setup OpenVPN TAP network adapter VPN pool error: no available addresses - + VPN pool error: no available addresses - The config does not contain any containers and credentials for connecting to the server + The config does not contain any containers and credentiaks for connecting to the server Internal error - + Internal error - + IPsec - + IPsec - + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2 в сочетании с уровнем шифрования IPSec это современный и стабильный протокол VPN. +Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. +Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. + +* Доступно в AmneziaVPN только для Windows. +* Низкое энергопотребление, на мобильных устройствах +* Минимальная конфигурация +* Распознается системами DPI-анализа +* Работает по сетевому протоколу UDP, порты 500 и 4500. + + + DNS Service - + DNS Сервис Sftp file sharing service - - - - - - Website in Tor network - + Сервис обмена файлами Sftp + + Website in Tor network + Веб-сайт в сети Tor + + + Amnezia DNS - + Amnezia DNS OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + OpenVPN - популярный VPN-протокол, с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - + ShadowSocks - маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - + OpenVPN over Cloak - OpenVPN с маскировкой VPN под web-трафик и защитой от обнаружения active-probbing. Подходит для регионов с самым высоким уровнем цензуры. 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. + AmneziaWG - Специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. + + + 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. - + IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS. - + Deploy a WordPress site on the Tor network in two clicks. - + Разверните сайт на WordPress в сети Tor в два клика. - + Replace the current DNS server with your own. This will increase your privacy level. - + Замените DNS-сервер на Amnezia DNS. Это повысит уровень конфиденциальности. - + Creates a file vault on your server to securely store and transfer files. - - - - - OpenVPN container - - - - - Container with OpenVpn and ShadowSocks - - - - - Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. - WireGuard container - + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN однин из самых популярных и проверенных временем VPN-протоколов. +В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, поддержка OpenVPN множества методов аутентификации делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. + +* Доступность AmneziaVPN для всех платформ +* Нормальное энергопотребление на мобильных устройствах +* Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами +* Распознается системами DPI-анализа и поэтому подвержен блокировке +* Может работать по сетевым протоколам TCP и UDP. - - IPsec container - + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks, создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению. Однако некоторые системы анализа трафика все же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG, или OpenVPN over Cloak. + +* Доступен в AmneziaVPN только на ПК ноутбуках. +* Настраиваемый протокол шифрования +* Обнаруживается некоторыми DPI-системами +* Работает по сетевому протоколу TCP. - + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от блокировок. + +OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. + +Cloak защищает OpenVPN от обнаружения и блокировок. + +Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению + +Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. + +Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak + +* Доступность AmneziaVPN на всех платформах +* Высокое энергопотребление на мобильных устройствах +* Гибкие настройки +* Не распознается системами DPI-анализа +* Работает по сетевому протоколу TCP, 443 порт. + + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + WireGuard - относительно новый популярный VPN-протокол с упрощенной архитектурой. +Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. +WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. + +* Доступность AmneziaVPN для всех платформ +* Низкое энергопотребление +* Минимальное количество настроек +* Легко распознается системами DPI-анализа, подвержен блокировке +* Работает по сетевому протоколу UDP. + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + AmneziaWG - усовершенствованная версия популярного VPN-протокола Wireguard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокопроизводительные возможности работы на разных устройствах. +Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. +Таким образом, AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. + +* Доступность AmneziaVPN на всех платформах +* Низкое энергопотребление +* Минимальное количество настроек +* Не распознается системами DPI-анализа, устойчив к блокировке +* Работает по сетевому протоколу UDP. + + + AmneziaWG container + AmneziaWG протокол + + + Sftp file sharing service - is secure FTP service - + Сервис обмена файлами Sftp - безопасный FTP-сервис - + Sftp service - + Сервис SFTP Entry not found - + Entry not found Access to keychain denied - + Access to keychain denied No keyring daemon - + No keyring daemon Already unlocked - + Already unlocked No such keyring - + No such keyring Bad arguments - + Bad arguments I/O error - + I/O error Cancelled - + Cancelled Keyring already exists - + Keyring already exists No match - + No match Unknown error - + Unknown error error 0x%1: %2 + error 0x%1: %2 + + + + WireGuard Configuration Highlighter + + + + + &Randomize colors @@ -2467,7 +2763,7 @@ It's okay as long as it's from someone you trust. Choose language - + Выберите язык @@ -2475,13 +2771,13 @@ It's okay as long as it's from someone you trust. Server #1 - + Server #1 Server - + Server @@ -2489,22 +2785,22 @@ It's okay as long as it's from someone you trust. Software version - + Версия ПО All settings have been reset to default values - + Все настройки были сброшены к значению "По умолчанию" Cached profiles cleared - + Кэш профиля очищен Backup file is corrupted - + Backup файл поврежден @@ -2513,32 +2809,32 @@ It's okay as long as it's from someone you trust. Save AmneziaVPN config - + Сохранить config AmneziaVPN Share - + Поделиться Copy - + Скопировать Copied - + Скопировано - Show connection settings + Show content - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + Для считывания QR-кода в приложении Amnezia выберите "Добавить сервер" → "У меня есть данные для подключения" → "QR-код, ключ или файл настроек" @@ -2546,42 +2842,42 @@ It's okay as long as it's from someone you trust. Hostname not look like ip adress or domain name - + Имя хоста не похоже на ip-адрес или доменное имя New site added: %1 - + Добавлен новый сайт %1 Site removed: %1 - + Сайт удален %1 Can't open file: %1 - + Невозможно открыть файл: %1 Failed to parse JSON data from file: %1 - + Не удалось разобрать JSON-данные из файла: %1 - The JSON data is not an array in file: - + The JSON data is not an array in file: %1 + Данные JSON не являются массивом в файле: %1 Import completed - + Импорт завершен Export completed - + Экспорт завершен @@ -2590,39 +2886,47 @@ It's okay as long as it's from someone you trust. Show - + Показать Connect - + Подключиться Disconnect - + Отключиться Visit Website - + Посетить сайт Quit - + Закрыть + + + + TextFieldWithHeaderType + + + The field can't be empty + Поле не может быть пустым VpnConnection - + Mbps - + Mbps @@ -2630,75 +2934,95 @@ It's okay as long as it's from someone you trust. Unknown - + Неизвестный Disconnected - + Отключен Preparing - + Подготовка Connecting... - + Подключение... Connected - + Подключено Disconnecting... - + Отключение... Reconnecting... - + Переподключение... Error - + Ошибка amnezia::ContainerProps - + Low - + Низкий + + + + Medium or High + Средний или Высокий + + + + Extreme + Экстремальный + + + + I just want to increase the level of my privacy. + Я просто хочу повысить уровень своей приватности. + + + + I want to bypass censorship. This option recommended in most cases. + Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. + + + + Most VPN protocols are blocked. Recommended if other options are not working. + Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. - High - + Высокий - Medium - + Средний - Many foreign websites and VPN providers are blocked - + Многие иностранные сайты и VPN-провайдеры заблокированы - Some foreign sites are blocked, but VPN providers are not blocked - + Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются - I just want to increase the level of privacy - + Хочу просто повысить уровень приватности @@ -2706,12 +3030,12 @@ It's okay as long as it's from someone you trust. Private key passphrase - + Кодовая фраза для закрытого ключа Save - + Сохранить diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 109ad9c8..c8422faa 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,9 +4,13 @@ AmneziaApplication - Split tunneling for WireGuard is not implemented, the option was disabled - 未启用选项,还未实现基于WireGuard协议的VPN分流 + 未启用选项,还未实现基于WireGuard协议的VPN分离 + + + + Split tunneling for %1 is not implemented, the option was disabled + @@ -14,13 +18,13 @@ AmneziaVPN - + VPN Connected Refers to the app - which is currently running the background and waiting - VPN已连接 + VPN已连接 @@ -35,60 +39,72 @@ ConnectionController - - - + + + Connect - 连接 + 连接 VPN Protocols is not installed. Please install VPN container at first - 不存在VPN协议,请先安装 + 请先安装VPN协议 - + Connection... - 连接中 + 连接中 - + Connected - 已连接 + 已连接 - + Reconnection... - 重连中 + 重连中 - + Disconnection... - 断开中 + 断开中 - + Settings updated successfully, Reconnnection... - 配置已更新,重连中 + 配置已更新,重连中 ConnectionTypeSelectionDrawer - Connection data - 连接数据 + 连接方式 + + + + Add new connection + 添加新连接 + + + + Configure your server + 配置您的服务器 + + + + Open config file, key or QR code + 配置文件,授权码或二维码 - Server IP, login and password - 服务器IP,用户名和密码 + 服务器IP,用户名和密码 - QR code, key or configuration file - 二维码,授权码或者配置文件 + 二维码,授权码或者配置文件 @@ -96,22 +112,22 @@ C&ut - 剪切 + 剪切 &Copy - 拷贝 + 拷贝 &Paste - 粘贴 + 粘贴 &SelectAll - 全选 + 全选 @@ -119,28 +135,32 @@ Access error! - 访问错误 + 访问错误 HomeContainersListView - The selected protocol is not supported on the current platform - 当前平台不支持所选协议 + Unable change protocol while there is an active connection + 已建立连接时无法更改服务器配置 + + + + The selected protocol is not supported on the current platform + 当前平台不支持所选协议 - Reconnect via VPN Procotol: - 重连基于VPN协议: + 重连VPN基于协议: ImportController - + Scanned %1 of %2. - 扫描 %1 of %2. + 扫描 %1 of %2. @@ -154,49 +174,49 @@ 已安装在服务器上 - - + + %1 installed successfully. - %1 安装成功。 + %1 安装成功。 - - + + %1 is already installed on the server. - 服务器上已经安装 %1。 + 服务器上已经安装 %1。 - + Added containers that were already installed on the server - + 添加已安装在服务器上的容器 - + Already installed containers were found on the server. All installed containers have been added to the application - -在服务上发现已经安装协议并添加到应用程序 + +在服务上发现已经安装协议并添加至应用 - + Settings updated successfully - 配置更新成功 + 配置更新成功 - + Server '%1' was removed - 已移除服务器 '%1' + 已移除服务器 '%1' - + All containers from server '%1' have been removed - 服务器 '%1' 的所有容器已移除 + 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' - %1 已从服务器 '%2' 上移除 + %1 已从服务器 '%2' 上移除 1% has been removed from the server '%2' @@ -215,14 +235,14 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user - 请以用户身份登录 + 请以用户身份登录 - + Server added successfully - 服务器添加成功 + 增加服务器成功 @@ -230,17 +250,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - 获取授权码失败: %1 + 获取授权码失败: %1 Write key failed: %1 - 写入授权码失败: %1 + 写入授权码失败: %1 Delete key failed: %1 - 删除授权码失败: %1 + 删除授权码失败: %1 @@ -249,27 +269,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + VPN Connected - 已连接到VPN + 已连接到VPN VPN Disconnected - 已从VPN断开 + 已从VPN断开 AmneziaVPN notification - AmneziaVPN 提示 + AmneziaVPN 提示 Unsecured network detected: - 发现不安全网络 + 发现不安全网络 @@ -277,25 +297,122 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - 正从 %1 移除服务 + 正从 %1 移除服务 Usually it takes no more than 5 minutes - 通常5分钟之内完成 + 大约5分钟之内完成 PageHome - + VPN protocol - VPN协议 + VPN协议 - + Servers - 服务器 + 服务器 + + + + Unable change server while there is an active connection + 已建立连接时无法更改服务器配置 + + + + PageProtocolAwgSettings + + + AmneziaWG settings + AmneziaWG 配置 + + + + Port + 端口 + + + + Junk packet count + 垃圾包数量 + + + + Junk packet minimum size + 垃圾包最小值 + + + + Junk packet maximum size + 垃圾包最大值 + + + + Init packet junk size + 初始化垃圾包大小 + + + + Response packet junk size + 响应垃圾包大小 + + + + Init packet magic header + 初始化数据包魔数头 + + + + Response packet magic header + 响应包魔数头 + + + + Transport packet magic header + 传输包魔数头 + + + + Underload packet magic header + 低负载数据包魔数头 + + + + Remove AmneziaWG + 移除AmneziaWG + + + + Remove AmneziaWG from server? + 从服务上移除AmneziaWG? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Save and Restart Amnezia + 保存并重启Amnezia @@ -303,224 +420,232 @@ Already installed containers were found on the server. All installed containers Cloak settings - Cloak 配置 + Cloak 配置 Disguised as traffic from - 伪装流量来自 + 伪装流量为 Port - 端口 + 端口 Cipher - 解码 + 加密算法 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia PageProtocolOpenVpnSettings - + OpenVPN settings - OpenVPN 配置 + OpenVPN 配置 - + VPN Addresses Subnet - VPN子网掩码 + VPN子网掩码 - + Network protocol - 网络协议 + 网络协议 - + Port - 端口 + 端口 - + Auto-negotiate encryption - 自动协商加密 + 自定义加密方式 - + Hash - - - - - SHA512 - + - SHA384 - + SHA512 + - SHA256 - + SHA384 + - SHA3-512 - + SHA256 + - SHA3-384 - + SHA3-512 + - SHA3-256 - + SHA3-384 + - whirlpool - + SHA3-256 + - BLAKE2b512 - + whirlpool + - BLAKE2s256 - + BLAKE2b512 + + BLAKE2s256 + + + + SHA1 - + - + Cipher - 解码 - - - - AES-256-GCM - + - AES-192-GCM - + AES-256-GCM + - AES-128-GCM - + AES-192-GCM + - AES-256-CBC - + AES-128-GCM + - AES-192-CBC - + AES-256-CBC + - AES-128-CBC - + AES-192-CBC + - ChaCha20-Poly1305 - + AES-128-CBC + - ARIA-256-CBC - + ChaCha20-Poly1305 + - CAMELLIA-256-CBC - + ARIA-256-CBC + + CAMELLIA-256-CBC + + + + none - + - + TLS auth - TLS认证 + TLS认证 - + Block DNS requests outside of VPN - 阻止VPN外的DNS请求 + 阻止VPN外的DNS请求 - + Additional client configuration commands - 附加客户端配置命令 + 附加客户端配置命令 - - + + Commands: - 命令: + 命令: - + Additional server configuration commands - 附加服务器端配置命令 - - - - Remove OpenVPN - 移除OpenVPN - - - - Remove OpenVpn from server? - 从服务器移除OpenVPN吗? - - - - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 附加服务器端配置命令 + Remove OpenVPN + 移除OpenVPN + + + + Remove OpenVpn from server? + 从服务器移除OpenVPN吗? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + Continue - 继续 + 继续 - + Cancel - 取消 + 取消 - + Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -528,46 +653,58 @@ Already installed containers were found on the server. All installed containers settings - 配置 + 配置 Show connection options - 展示连接选项 + 显示连接选项 + + + Connection options + 连接选项 - Connection options - 连接选项 + Connection options %1 + %1 连接选项 - + Remove - 移除 + 移除 - + Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 from server? 从服务器 - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 - + Continue - 继续 + 继续 - + Cancel - 取消 + 取消 @@ -575,23 +712,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - ShadowSocks 配置 + ShadowSocks 配置 Port - 端口 + 端口 Cipher - 解码 + 加密算法 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -600,22 +737,23 @@ 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访问。 + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - DNS地址与您的服务器地址相同。您可以在连接选项卡下的设置中配置 DNS + 其地址与您的服务器地址相同。您可以在 设置 连接 中进行配置。 Remove - 移除 + 移除 Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? from server? @@ -624,12 +762,12 @@ Already installed containers were found on the server. All installed containers Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -637,17 +775,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - 配置更新成功 + 配置更新成功 SFTP settings - SFTP 配置 + SFTP 配置 Host - 主机 + 主机 @@ -655,69 +793,69 @@ Already installed containers were found on the server. All installed containers Copied - 拷贝 + 拷贝 Port - 端口 + 端口 Login - 用户 + 用户 Password - 密码 + 密码 - + Mount folder on device - 在设备上挂载文件夹 + 挂载文件夹 - + In order to mount remote SFTP folder as local drive, perform following steps: <br> - 要将远程 SFTP 文件夹安装为本地驱动器,请执行以下步骤: <br> + 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> - - + + <br>1. Install the latest version of - <br>1. 安装最新版的 + <br>1. 安装最新版的 - - + + <br>2. Install the latest version of - <br>2. 安装最新版的 + <br>2. 安装最新版的 - + Detailed instructions - 详细说明 + 详细说明 - + Remove SFTP and all data stored there - 移除SFTP和其本地所有数据 + 移除SFTP和其本地所有数据 - + Remove SFTP and all data stored there? - 移除SFTP和其本地所有数据? + 移除SFTP和其本地所有数据? - + Continue - 继续 + 继续 - + Cancel - 取消 + 取消 @@ -725,57 +863,61 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - 配置更新成功 + 配置更新成功 Tor website settings - Tor网站配置 + Tor网站配置 Website address - 网址 + 网址 Copied - 拷贝 + 已拷贝 - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 - + After installation it takes several minutes while your onion site will become available in the Tor Network. - 安装几分钟后,洋葱站点才会在 Tor 网络中生效。 + 完成安装几分钟后,洋葱站点才会在 Tor 网络中生效。 + + + + When configuring WordPress set the this onion address as domain. + 配置 WordPress 时,将此洋葱地址设置为域。 - When configuring WordPress set the domain as this onion address. - 配置 WordPress 时,将域设置为此洋葱地址。 + 配置 WordPress 时,将域设置为此洋葱地址。 - + Remove website - 移除网站 + 移除网站 - + The site with all data will be removed from the tor network. - 网站及其所有数据将从 Tor 网络中删除 + 网站及其所有数据将从 Tor 网络中删除 - + Continue - 继续 + 继续 - + Cancel - 取消 + 取消 @@ -783,32 +925,37 @@ Already installed containers were found on the server. All installed containers Settings - 设置 + 设置 Servers - 服务器 + 服务器 Connection - 连接 + 连接 Application - 应用 + 应用 Backup - 备份 + 备份 - + About AmneziaVPN - 关于 + 关于 + + + + Close application + 关闭应用 @@ -816,84 +963,94 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - 捐款 + 捐款 + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 +如果您不喜欢,请捐助支持我们改进它。 - This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app. - 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 -如果您不喜欢,请捐助支持我们改进它。 + This is a free and open source application. If you like it, support the developers with a donation. + 这是一个免费且开源的软件。如果您喜欢它,请捐助开发者们。 + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + 如果您不喜欢,请捐助支持我们改进它。 Card on Patreon - Patreon订阅 + Patreon订阅 https://www.patreon.com/amneziavpn - + Show other methods on Github - 其他捐款途径 + 其他捐款途径 Contacts - 联系方式 + 联系方式 Telegram group - 电报群 + 电报群 To discuss features - 用于功能讨论 + 用于功能讨论 https://t.me/amnezia_vpn_en - + Mail - 邮件 + 邮件 For reviews and bug reports - 用于评论和提交软件的缺陷 + 用于评论和提交软件的缺陷 Github - + https://github.com/amnezia-vpn/amnezia-client - + Website - 官网 + 官网 https://amnezia.org - + Check for updates - 更新 + 检查更新 @@ -901,82 +1058,89 @@ And if you don't like the app, all the more support it - the donation will Application - 应用 + 应用 Allow application screenshots - + 允许截屏 Auto start - 自动运行 + 自动运行 - Launch the application every time - 总是在系统 + 总是在系统 + + + starts + 启动时自动运行运用程序 + + + Launch the application every time %1 starts + 运行应用软件在%1系统启动时 - starts - 启动时自动运行运用程序 + Launch the application every time the device is starts + 每次设备启动时启动应用程序 Start minimized - 最小化 + 最小化 Launch application minimized - 开启应用程序时窗口最小化 + 开启应用软件时窗口最小化 Language - 语言 + 语言 Logging - 日志 + 日志 Enabled - 开启 + 开启 Disabled - 禁用 + 禁用 Reset settings and remove all data from the application - 重置并清理应用的所有数据 + 重置并清理应用的所有数据 Reset settings and remove all data from the application? - 重置并清理应用的所有数据? + 重置并清理应用的所有数据? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 + 所有配置恢复为默认值。服务器已安装的AmneziaVPN服务将被保留。 Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -984,68 +1148,77 @@ And if you don't like the app, all the more support it - the donation will Settings restored from backup file - 从备份文件还原配置 + 从备份文件还原配置 Backup - 备份 + 备份 Configuration backup - 配置备份 + 备份设置 + + + It will help you instantly restore connection settings at the next installation + 帮助您在下次安装时立即恢复连接设置 - It will help you instantly restore connection settings at the next installation - 帮助您在下次安装时立即恢复连接设置 + You can save your settings to a backup file to restore them the next time you install the application. + 您可以将配置信息备份到文件中,以便在下次安装应用软件时恢复配置 Make a backup - 进行备份 + 进行备份 Save backup file - 保存备份 + 保存备份 - + Backup files (*.backup) - + - + + Backup file saved + 备份文件已保存 + + + Restore from backup - 从备份还原 + 从备份还原 - + Open backup file - 打开备份文件 - - - - Import settings from a backup file? - 从备份文件导入设置? + 打开备份文件 - All current settings will be reset - 当前所有设置将重置 + Import settings from a backup file? + 从备份文件导入设置? - Continue - 继续 + All current settings will be reset + 当前所有设置将重置 + Continue + 继续 + + + Cancel - 取消 + 取消 @@ -1053,17 +1226,17 @@ And if you don't like the app, all the more support it - the donation will Connection - 连接 + 连接 Auto connect - 自动连接 + 自动连接 Connect to VPN on app start - 应用开启时连接VPN + 应用开启时连接VPN Use AmneziaDNS if installed on the server @@ -1072,42 +1245,54 @@ And if you don't like the app, all the more support it - the donation will Use AmneziaDNS - 使用AmneziaDNS + 使用AmneziaDNS If AmneziaDNS is installed on the server - 如其已安装至服务器上 + 如果已在服务器安装AmneziaDNS DNS servers - DNS服务器列表 + DNS服务器 If AmneziaDNS is not used or installed - 如果未使用或未安装AmneziaDNS + 如果未使用或未安装AmneziaDNS + + + + Site-based split tunneling + 基于网站的隧道分离 + + + + Allows you to select which sites you want to access through the VPN + 配置想要通过VPN访问网站 + + + + App-based split tunneling + 基于应用的隧道分离 - Split site tunneling - 网站级VPN分流 + 网站级VPN分流 - Allows you to connect to some sites through a secure connection, and to others bypassing it - 使用VPN访问指定网站,其他的则绕过 + 使用VPN访问指定网站,其他的则绕过 - Separate application tunneling - 应用级VPN分流 + 应用级VPN分流 - + Allows you to use the VPN only for certain applications - 仅限指定应用使用VPN + 仅指定应用使用VPN @@ -1115,57 +1300,57 @@ And if you don't like the app, all the more support it - the donation will DNS servers - DNS服务器 + DNS服务器 - + If AmneziaDNS is not used or installed - 如果未使用或未安装Amnezia DNS + 如果未使用或未安装AmneziaDNS - + Primary DNS - 首选 DNS + 首选 DNS - + Secondary DNS - 备用 DNS + 备用 DNS - + Restore default - 恢复默认配置 - - - - Restore default DNS settings? - 是否恢复默认DNS配置? + 恢复默认配置 - Continue - 继续 + Restore default DNS settings? + 是否恢复默认DNS配置? + Continue + 继续 + + + Cancel - 取消 + 取消 - + Settings have been reset - 已重置 + 已重置 - + Save - 保存 + 保存 - + Settings saved - 配置已保存 + 配置已保存 @@ -1173,57 +1358,62 @@ And if you don't like the app, all the more support it - the donation will Logging - 日志 + 日志 Save logs - 记录日志 + 记录日志 Open folder with logs - 打开日志文件夹 + 打开日志文件夹 Save - 保存 + 保存 Logs files (*.log) - + - + + Logs file saved + 日志文件已保存 + + + Save logs to file - 保存日志到文件 - - - - Clear logs? - 清除日志? + 保存日志到文件 - Continue - 继续 + Clear logs? + 清理日志? + Continue + 继续 + + + Cancel - 取消 + 取消 - + Logs have been cleaned up - 已清理日志 + 日志已清理 - + Clear logs - 清理日志 + 清理日志 @@ -1231,27 +1421,27 @@ And if you don't like the app, all the more support it - the donation will All installed containers have been added to the application - 所有已安装的容器已添加到应用程序中 + 所有已安装的容器,已被添加到应用软件 No new installed containers found - 未找到新安装的容器 + 未发现新安装的容器 Clear Amnezia cache - 清除 Amnezia 缓存 + 清除 Amnezia 缓存 May be needed when changing other settings - 更改其他设置时可能需要 + 更改其他设置时可能需要缓存 Clear cached profiles? - 清除缓存的配置文件? + 清除缓存? @@ -1263,54 +1453,54 @@ And if you don't like the app, all the more support it - the donation will Continue - 继续 + 继续 Cancel - 取消 + 取消 Check the server for previously installed Amnezia services - 检查服务器上是否存在 Amnezia 服务 + 检查服务器上,是否存在之前安装的 Amnezia 服务 Add them to the application if they were not displayed - 如果存在且未被显示,则添加到应用程序里 + 如果存在且未显示,则添加到应用软件 Remove server from application - 移除本地服务器信息 + 移除本地服务器信息 Remove server? - 移除本地服务器信息? + 移除本地服务器信息? All installed AmneziaVPN services will still remain on the server. - 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 Clear server from Amnezia software - 移除Amnezia中服务器信息 + 清理Amnezia中服务器信息 Clear server from Amnezia software? - 从Amnezia中清除服务器? + 清理Amnezia中服务器信息 All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - 服务器上的所有容器都将被删除。这意味着配置文件、密钥和证书将被删除。 + 服务器上的所有容器都将被删除。配置文件、密钥和证书也将被删除。 @@ -1318,27 +1508,27 @@ And if you don't like the app, all the more support it - the donation will Server name - 服务器名称 + 服务器名 Save - 保存 + 保存 Protocols - 协议 + 协议 Services - 服务 + 服务 Data - 数据 + 数据 @@ -1346,12 +1536,21 @@ And if you don't like the app, all the more support it - the donation will settings - 配置 + 配置 Remove - 移除 + 移除 + + + + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + + All users who you shared a connection with will no longer be able to connect to it. + 使用此共享连接的所有用户,将无法再连接它。 from server? @@ -1360,121 +1559,132 @@ And if you don't like the app, all the more support it - the donation will Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 Continue - 继续 + 继续 Cancel - 取消 + 取消 PageSettingsServersList - + Servers - 服务器 + 服务器 PageSettingsSplitTunneling - Only the addresses in the list must be opened via VPN - 仅列表中的地址须通过VPN访问 + 仅列表中的地址须通过VPN访问 - Addresses from the list should never be opened via VPN - 勿通过VPN访问列表中的地址 + 勿通过VPN访问列表中的地址 - Split site tunneling - 网站级VPN分流 + 网站级VPN分流 - + + Addresses from the list should be accessed via VPN + 仅使用VPN访问 + + + + Addresses from the list should not be accessed via VPN + 不使用VPN访问 + + + + Split tunneling + 隧道分离 + + + Mode - 方式 - - - - Remove - 移除 + 规则 - Continue - 继续 + Remove + 移除 + Continue + 继续 + + + Cancel - 取消 + 取消 - + Site or IP - 网址或IP地址 + 网站或IP地址 - + Import/Export Sites - 导入/导出网址 + 导入/导出网站 - + Import - 导入 + 导入 - + Save site list - 保存网址 + 保存网址 - + Save sites - 保存网址 + 保存网址 - - - + + + Sites files (*.json) - + - + Import a list of sites - 导入网址列表 + 导入网址列表 - + Replace site list - 替换网址列表 + 替换网址列表 - - + + Open sites file - 打开网址文件 + 打开网址文件 - + Add imported sites to existing ones - 将导入的网址添加到现有网址中 + 将导入的网址添加到现有网址中 @@ -1482,98 +1692,109 @@ And if you don't like the app, all the more support it - the donation will Server connection - 服务器连接 + 服务器连接 Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - 请勿使用公共来源的连接代码。它可能是为了拦截您的数据而创建的。 -最好是来源可信。 + 请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。 +请确保连接码来源可信。 What do you have? - + 你用什么方式创建连接? File with connection settings or backup - 包含连接配置或备份的文件 + 包含连接配置或备份的文件 File with connection settings - 包含连接配置的文件 + 包含连接配置的文件 Open config file - 打开配置文件 + 打开配置文件 QR-code - 二维码 + 二维码 Key as text - 授权码文本 + 授权码文本 PageSetupWizardCredentials - Server connection - 服务器连接 + 连接服务器 + + + + Configure your server + 配置服务器 Server IP address [:port] - 服务器IP [:端口] + 服务器IP [:端口] 255.255.255.255:88 - + Login to connect via SSH - 用户名 + 用户 Password / SSH private key - 密码 或者 私钥 + 密码 或 私钥 Continue - 继续 + 继续 - + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + 您输入的所有数据将严格保密 +不会向 Amnezia 或任何第三方分享或披露 + + + Ip address cannot be empty - IP不能为空 - - - - Enter the address in the format 255.255.255.255:88 - 按照这种格式输入 255.255.255.255:88 - - - - Login cannot be empty - 用户名不能为空 + IP不能为空 + Enter the address in the format 255.255.255.255:88 + 按照这种格式输入 255.255.255.255:88 + + + + Login cannot be empty + 账号不能为空 + + + Password/private key cannot be empty - 密码或者私钥不能为空 + 密码或私钥不能为空 @@ -1581,61 +1802,69 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - 您所在地区的互联网控制力度如何? + 您所在地区的互联网管控力度如何? Set up a VPN yourself - 自己架设VPN + 自己架设VPN I want to choose a VPN protocol - 我想选择VPN协议 + 我想选择VPN协议 Continue - 继续 + 继续 Set up later - 稍后设置 + 稍后设置 PageSetupWizardInstalling - + Usually it takes no more than 5 minutes - 通常不超过5分钟 + 通常不超过5分钟 - + The server has already been added to the application - 服务器已添加到应用程序中 + 服务器已添加到应用软件中 + + + + Amnezia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + + + busy installing other software. Amnezia installation + 正安装其他软件。Amnezia安装 - Amnesia has detected that your server is currently - Amnezia 检测到您的服务器当前 + Amnezia 检测到您的服务器当前 - busy installing other software. Amnesia installation - 正安装其他软件。Amnezia安装 + 正安装其他软件。Amnezia安装 - + will pause until the server finishes installing other software - 将暂停,直到服务器完成安装其他软件。 + 将暂停,直到其他软件安装完成。 - + Installing - 安装中 + 安装中 @@ -1643,32 +1872,32 @@ It's okay as long as it's from someone you trust. Installing %1 - 正在安装 %1 + 正在安装 %1 More detailed - 更多细节 + 更多细节 - + Close - 关闭 + 关闭 - + Network protocol - 网络协议 + 网络协议 - + Port - 端口 + 端口 - + Install - 安装 + 安装 @@ -1676,12 +1905,12 @@ It's okay as long as it's from someone you trust. VPN protocol - VPN 协议 + VPN 协议 Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - 选择最适合您的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 + 选择你认为优先级最高的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 @@ -1689,7 +1918,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - 将相机对准二维码并按住几秒钟 + 将相机对准二维码并按住几秒钟 @@ -1697,27 +1926,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - 从备份文件还原配置 + 从备份文件还原配置 Free service for creating a personal VPN on your server. - + 在您的服务器上架设私人免费VPN服务。 Helps you access blocked content without revealing your privacy, even to VPN providers. - + 帮助您访问受限内容,保护您的隐私,即使是VPN提供商也无法获取。 I have the data to connect - + 我有连接配置 I have nothing - + 我没有 @@ -1725,27 +1954,27 @@ It's okay as long as it's from someone you trust. Connection key - 连接授权码 + 连接授权码 A line that starts with vpn://... - 以 vpn://... 开始的行 + 以 vpn://... 开始的行 Key - 授权码 + 授权码 Insert - 插入 + 插入 Continue - 继续 + 继续 @@ -1753,27 +1982,27 @@ It's okay as long as it's from someone you trust. New connection - 新连接 + 新连接 Do not use connection code from public sources. It could be created to intercept your data. - 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 + 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 Collapse content - + 折叠内容 Show content - 展示内容 + 显示内容 Connect - 连接 + 连接 @@ -1781,77 +2010,97 @@ It's okay as long as it's from someone you trust. Save OpenVPN config - 保存OpenVPN配置 + 保存OpenVPN配置 Save WireGuard config - 保存WireGuard配置 + 保存WireGuard配置 For the AmneziaVPN app - AmneziaVPN 应用 + AmneziaVPN 应用 OpenVpn native format - OpenVPN原生格式 + OpenVPN原生格式 WireGuard native format - WireGuard原生格式 + WireGuard原生格式 + Share VPN Access + 共享 VPN 访问 + + + + Share VPN access without the ability to manage the server + 共享 VPN 访问,无需管理服务器 + + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + 共享服务器管理访问权限。与您共享服务器全部访问权限的用户将可以添加和删除服务器上的任何协议和服务,以及更改设置。 + + VPN Access - 访问VPN + 访问VPN Connection - 连接 + 连接 Full access - 完整授权 + 完全访问 - VPN access without the ability to manage the server - 无权控制服务器 + 访问VPN,但没有权限管理服务。 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 - Full access to server - 获得服务器完整授权 + 获得服务器完整授权 + + + Servers + 服务器 - Servers - 服务器 - - Server - 服务器 + 服务器 Accessing - 访问 + 访问 File with accessing settings to - + 访问配置文件的内容为: File with connection settings to - 连接配置文件的内容为: + 连接配置文件的内容为: Protocols @@ -1861,23 +2110,23 @@ It's okay as long as it's from someone you trust. Protocol - 协议 + 协议 Connection to - 连接到 + 连接到 Connection format - 连接方式 + 连接格式 Share - 共享 + 共享 @@ -1885,7 +2134,7 @@ It's okay as long as it's from someone you trust. Close - 关闭 + 关闭 @@ -1893,38 +2142,38 @@ It's okay as long as it's from someone you trust. Password entry not found - 没有密码输入 + 未发现秘密 Could not decrypt data - 不能加密数据 + 数据无法加密 Unknown error - 位置错误 + 未知错误 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not remove private key from keystore - 无法从密钥库中删除私钥 + 无法从密钥库中删除私钥 @@ -1932,12 +2181,12 @@ It's okay as long as it's from someone you trust. Unknown error - 未知错误 + 未知错误 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 @@ -1945,27 +2194,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - 无法在配置中存储数据:访问错误 + 无法在配置中存储数据:访问错误 Could not store data in settings: format error - 无法在陪置中存储数据:格式错误 + 无法在陪置中存储数据:格式错误 Could not delete data from settings: access error - 无法在配置中删除数据:访问错误 + 无法在配置中删除数据:访问错误 Could not delete data from settings: format error - 无法在配置中删除数据:格式错误 + 无法在配置中删除数据:格式错误 Entry not found - 未找到条目 + 未找到条目 @@ -1973,80 +2222,80 @@ It's okay as long as it's from someone you trust. Password entry not found - 没有密码输入 + 未发现密码 Could not decrypt data - 不能加密数据 + 数据无法加密 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 No keychain service available - + 没有有效的钥匙串服务 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 Could not determine data type: %1; %2 - + 无法确定数据类型: %1; %2 Entry not found - + 未找到记录 Unsupported entry type 'Map' - + 不支持的记录类型 'Map' Unknown kwallet entry type '%1' - + 未知钱包类型 '%1' Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not retrieve private key from keystore - 无法从密钥存储库中检索私钥 + 无法从密钥存储库中检索私钥 Could not create decryption cipher - 无法创建解密密码 + 无法创建解密算法 @@ -2054,276 +2303,276 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + 证书大小超过上限,最大为: %1 Credential key exceeds maximum size of %1 - + 凭证密钥大小超过上限,最大为: %1 Writing credentials failed: Win32 error code %1 - + 写入凭证失败,Win32错误码: %1 Encryption failed - + 加密失败 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not create private key generator - 无法创建私钥生成器 + 无法创建私钥生成器 Could not generate new private key - 无法生成新的私钥 + 无法生成新的私钥 Could not retrieve private key from keystore - 无法从密钥库检索私钥 + 无法从密钥库检索私钥 Could not create encryption cipher - 无法创建加密密码 + 无法创建加密密码 Could not encrypt data - 无法加密数据 + 无法加密数据 QObject - + Sftp service - Sftp 服务 + Sftp 服务 No error - 没有错误 + 没有错误 Unknown Error - 位置错误 + 未知错误 Function not implemented - 功能未实现 + 功能未实现 Server check failed - 服务器检测失败 + 服务器检测失败 Server port already used. Check for another software - 检测服务器该端口是否被其他软件被占用 + 检测服务器该端口是否被其他软件被占用 Server error: Docker container missing - Server error: Docker容器丢失 + 服务器错误: Docker容器丢失 Server error: Docker failed - Server error: Docker失败 + 服务器错误: Docker失败 Installation canceled by user - 用户取消安装 + 用户取消安装 The user does not have permission to use sudo - 用户没有root权限 + 用户没有root权限 Ssh request was denied - ssh请求被拒绝 + ssh请求被拒绝 Ssh request was interrupted - ssh请求中断 + ssh请求中断 Ssh internal error - ssh内部错误 + ssh内部错误 Invalid private key or invalid passphrase entered - 输入的私钥或密码无效 + 输入的私钥或密码无效 The selected private key format is not supported, use openssh ED25519 key types or PEM key types - 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 Timeout connecting to server - 连接服务器超时 + 连接服务器超时 Sftp error: End-of-file encountered - Sftp错误: 遇到文件结尾 + Sftp错误: End-of-file encountered Sftp error: File does not exist - Sftp错误: 文件不存在 + Sftp错误: 文件不存在 Sftp error: Permission denied - Sftp错误: 权限受限 + Sftp错误: 权限不足 Sftp error: Generic failure - Sftp错误: 一般失败 + Sftp错误: 一般失败 Sftp error: Garbage received from server - Sftp错误: 从服务器收到垃圾信息 + Sftp错误: 从服务器收到垃圾信息 Sftp error: No connection has been set up - + Sftp 错误: 未建立连接 Sftp error: There was a connection, but we lost it - + Sftp 错误: 已有连接丢失 Sftp error: Operation not supported by libssh yet - + Sftp error: libssh不支持该操作 Sftp error: Invalid file handle - + Sftp error: 无效的文件句柄 Sftp error: No such file or directory path exists - + Sftp 错误: 文件夹或文件不存在 Sftp error: An attempt to create an already existing file or directory has been made - + Sftp 错误: 文件或目录已存在 Sftp error: Write-protected filesystem - + Sftp 错误: 文件系统写保护 Sftp error: No media was in remote drive - + Sftp 错误: 远程驱动器中没有媒介 Failed to save config to disk - 配置保存到磁盘失败 + 配置保存到磁盘失败 OpenVPN config missing - OpenVPN配置丢失 + OpenVPN配置丢失 OpenVPN management server error - OpenVPN 管理服务器错误 + OpenVPN 管理服务器错误 OpenVPN executable missing - OpenVPN 可执行文件丢失 + OpenVPN 可执行文件丢失 ShadowSocks (ss-local) executable missing - ShadowSocks (ss-local) 执行文件丢失 + ShadowSocks (ss-local) 执行文件丢失 Cloak (ck-client) executable missing - Cloak (ck-client) 执行文件丢失 + Cloak (ck-client) 执行文件丢失 Amnezia helper service error - Amnezia 帮助服务错误 + Amnezia 服务连接失败 OpenSSL failed - OpenSSL失败 + OpenSSL错误 Can't connect: another VPN connection is active - 无法连接:另一个VPN连接处于活动状态 + 无法连接:另一个VPN连接处于活跃状态 Can't setup OpenVPN TAP network adapter - 无法设置 OpenVPN TAP 网络适配器 + 无法设置 OpenVPN TAP 网络适配器 VPN pool error: no available addresses - VPN 池错误:没有可用地址 + VPN 池错误:没有可用地址 @@ -2332,168 +2581,317 @@ It's okay as long as it's from someone you trust. The config does not contain any containers and credentiaks for connecting to the server - 该配置不包含任何用于连接到服务器的容器和凭据。 + 该配置不包含任何用于连接到服务器的容器和凭据。 Internal error - 内部错误 + 内部错误 - + IPsec - - - - - - Website in Tor network - 在 Tor 网络中架设网站 + + + Website in Tor network + 在 Tor 网络中架设网站 + + + Amnezia DNS - + Sftp file sharing service - SFTP文件共享服务 + SFTP文件共享服务 OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 + OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + 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. + AmneziaWG - Amnezia 的特殊协议,基于 WireGuard。它的速度像 WireGuard 一样快,但非常抗堵塞。推荐用于审查较严的地区。 + + + 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. - IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 - + Deploy a WordPress site on the Tor network in two clicks. - 只需点击两次即可架设 WordPress 网站到 Tor 网络 + 只需点击两次即可架设 WordPress 网站到 Tor 网络 - + Replace the current DNS server with your own. This will increase your privacy level. - 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私级别。 + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 - + Creates a file vault on your server to securely store and transfer files. - 在您的服务器上创建文件库以安全地存储和传输文件 - - - - OpenVPN container - OpenVPN容器 - - - - Container with OpenVpn and ShadowSocks - 带有 OpenVpn 和 ShadowSocks 的容器 - - - - Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - 具有 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + 在您的服务器上创建文件仓库,以便安全地存储和传输文件 + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN 是最流行且经过时间考验的 VPN 协议之一。 +它采用其独特的安全协议,利用 SSL/TLS 的优势进行加密和密钥交换。此外,OpenVPN 支持多种身份验证方法,使其具有多功能性和适应性,可适应各种设备和操作系统。由于其开源性质,OpenVPN 受益于全球社区的广泛审查,这不断增强了其安全性。凭借性能、安全性和兼容性的强大平衡,OpenVPN 仍然是注重隐私的个人和企业的首选。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备的正常功耗 +* 灵活定制,满足用户使用不同操作系统和设备的需求 +* 被DPI分析系统识别,因此容易被阻塞 +* 可以通过 TCP 和 UDP 网络协议运行 + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 + +* 仅在桌面平台上的 AmneziaVPN 中可用 +* 移动设备的正常功耗 + +* 可配置的加密协议 +* 可以被某些 DPI 系统检测到 +* 通过 TCP 网络协议工作。 + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + 这是 OpenVPN 协议和专门用于阻止保护的 Cloak 插件的组合。 + +OpenVPN 通过加密客户端和服务器之间的所有 Internet 流量来提供安全的 VPN 连接。 + +Cloak 可保护 OpenVPN 免遭检测和阻止。 + +Cloak 可以修改数据包元数据,以便将 VPN 流量完全屏蔽为正常 Web 流量,并且还可以保护 VPN 免受主动探测的检测。这使得它非常难以被发现 + +收到第一个数据包后,Cloak 立即对传入连接进行身份验证。如果身份验证失败,该插件会将服务器伪装成虚假网站,并且您的 VPN 对分析系统来说将变得不可见。 + +如果您所在地区的互联网审查非常严格,我们建议您在第一次连接时仅使用 OpenVPN over Cloak + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备功耗高 +* 配置灵活 +* 不被 DPI 分析系统识别 +* 通过 TCP 网络协议、443 端口工作。 + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + 一种相对较新的流行 VPN 协议,具有简化的架构。 +在所有设备上提供稳定的 VPN 连接和高性能。使用硬编码的加密设置。 WireGuard 与 OpenVPN 相比具有更低的延迟和更好的数据传输吞吐量。 +由于其独特的数据包签名,WireGuard 非常容易受到阻塞。与其他一些采用混淆技术的 VPN 协议不同,WireGuard 数据包的一致签名模式可以更容易地被高级深度数据包检测 (DPI) 系统和其他网络监控工具识别并阻止。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 容易被DPI分析系统识别,容易被阻塞 +* 通过 UDP 网络协议工作。 + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + AmneziaWG 是流行 VPN 协议的现代迭代,它建立在 WireGuard 的基础上,保留了其简化的架构和跨设备的高性能功能。 +虽然 WireGuard 以其高效而闻名,但由于其独特的数据包签名,它存在容易被检测到的问题。 AmneziaWG 通过使用更好的混淆方法解决了这个问题,使其流量与常规互联网流量融合在一起。 +这意味着 AmneziaWG 保留了原始版本的快速性能,同时添加了额外的隐秘层,使其成为那些想要快速且谨慎的 VPN 连接的人的绝佳选择。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 不被DPI分析系统识别,抗阻塞 +* 通过 UDP 网络协议工作。 + + + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 +其显着特征之一是能够在网络和设备之间快速切换,使其特别适应动态网络环境。 +虽然 IKEv2 兼具安全性、稳定性和速度,但必须注意的是,IKEv2 很容易被检测到,并且容易受到阻止。 + +* 仅在 Windows 上的 AmneziaVPN 中可用 +* 低功耗,在移动设备上 +* 最低配置 +* 获得DPI分析系统认可 +* 通过 UDP 网络协议、端口 500 和 4500 工作。 + + + OpenVPN container + OpenVPN容器 + + + Container with OpenVpn and ShadowSocks + 含 OpenVpn 和 ShadowSocks 的容器 + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + + WireGuard container - WireGuard 容器 + WireGuard 容器 - IPsec container - IPsec 容器 + IPsec 容器 - + DNS Service - DNS 服务 + DNS 服务 - + Sftp file sharing service - is secure FTP service - Sftp 文件共享服务 - 安全的 FTP 服务 + Sftp 文件共享服务 - 安全的 FTP 服务 Entry not found - 未找到记录 + 未找到记录 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 No keyring daemon - 没有密钥环守护进程 + 没有密钥环守护进程 Already unlocked - 已经解锁 + 已经解锁 No such keyring - 没有这样的密钥环 + 没有这样的密钥环 Bad arguments - 错误参数 + 错误参数 I/O error - I/O错误 + I/O错误 Cancelled - 已取消 + 已取消 Keyring already exists - 密匙环已经存在 + 密匙环已经存在 No match - 不匹配 + 不匹配 Unknown error - 未知错误 + 未知错误 error 0x%1: %2 - 错误 0x%1: %2 + 错误 0x%1: %2 + + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + @@ -2511,7 +2909,7 @@ It's okay as long as it's from someone you trust. Choose language - 选择语言 + 选择语言 @@ -2519,13 +2917,13 @@ It's okay as long as it's from someone you trust. Server #1 - + Server - 服务器 + 服务器 @@ -2533,22 +2931,22 @@ It's okay as long as it's from someone you trust. Software version - 软件版本 + 软件版本 Backup file is corrupted - 备份文件已损坏 + 备份文件已损坏 All settings have been reset to default values - 所配置恢复为默认值 + 所配置恢复为默认值 Cached profiles cleared - 缓存的配置文件已清除 + 缓存的配置文件已清除 @@ -2557,36 +2955,36 @@ It's okay as long as it's from someone you trust. Save AmneziaVPN config - 保存配置 + 保存配置 Share - 共享 + 共享 Copy - 拷贝 + 拷贝 Copied - 已拷贝 + 已拷贝 Show connection settings - + 显示连接配置 Show content 展示内容 - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” @@ -2594,42 +2992,42 @@ It's okay as long as it's from someone you trust. Hostname not look like ip adress or domain name - + 请输入有效的域名或IP地址 New site added: %1 - + 已经添加新网站: %1 Site removed: %1 - + 已移除网站: %1 Can't open file: %1 - + 无法打开文件: %1 Failed to parse JSON data from file: %1 - + JSON解析失败,文件: %1 - The JSON data is not an array in file: - + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 Import completed - + 完成导入 Export completed - + 完成导出 @@ -2638,39 +3036,47 @@ It's okay as long as it's from someone you trust. Show - 界面 + 显示 Connect - 连接 + 连接 Disconnect - 断开 + 断开 Visit Website - 官网 + 官网 Quit - 退出 + 退出 + + + + TextFieldWithHeaderType + + + The field can't be empty + 输入不能为空 VpnConnection - + Mbps - + @@ -2678,75 +3084,95 @@ It's okay as long as it's from someone you trust. Unknown - 未知 + 未知 Disconnected - 断开连接 + 连接已断开 Preparing - 准备中 + 准备中 Connecting... - 连接中 + 连接中 Connected - 已连接 + 已连接 Disconnecting... - 断开中 + 断开中 Reconnecting... - 重连中 + 重连中 Error - 错误 + 错误 amnezia::ContainerProps - + Low - + + + + + Medium or High + 中或高 + + + + Extreme + 极度 + + + + I just want to increase the level of my privacy. + 只是想提高隐私保护级别。 + + + + I want to bypass censorship. This option recommended in most cases. + 想要绕过审查制度。大多数情况下推荐使用此选项。 + + + + Most VPN protocols are blocked. Recommended if other options are not working. + 大多数 VPN 协议都被阻止。如果其他选项不起作用,推荐此选项。 - High - + - Medium - + - I just want to increase the level of privacy - 我只是想提高隐私级别 + 我只是想提高隐私保护级别 - Many foreign websites and VPN providers are blocked - 大多国外网站和VPN提供商被屏蔽 + 大多国外网站和VPN提供商被屏蔽 - Some foreign sites are blocked, but VPN providers are not blocked - 一些国外网站被屏蔽,但VPN提供商未被屏蔽 + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 @@ -2754,12 +3180,12 @@ It's okay as long as it's from someone you trust. Private key passphrase - 私钥密码 + 私钥密码 Save - 保存 + 保存 diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 8198a5a1..3750a33c 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -146,8 +146,6 @@ void ImportController::importConfig() || m_config.contains(config_key::configVersion)) { // todo m_serversModel->addServer(m_config); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit importFinished(); } else { qDebug() << "Failed to import profile"; @@ -220,21 +218,75 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) QJsonObject ImportController::extractWireGuardConfig(const QString &data) { + QMap configMap; + auto configByLines = data.split("\n"); + for (const QString &line : configByLines) { + QString trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap[parts.at(0).trimmed()] = parts.at(1).trimmed(); + } + } + } + QJsonObject lastConfig; lastConfig[config_key::config] = data; - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*)(?::([0-9]*))?"); + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); QString hostName; QString port; if (hostNameAndPortMatch.hasCaptured(1)) { hostName = hostNameAndPortMatch.captured(1); - } /*else { - qDebug() << "send error?" - }*/ + } else { + qDebug() << "Failed to import profile"; + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); + } if (hostNameAndPortMatch.hasCaptured(2)) { port = hostNameAndPortMatch.captured(2); + } else { + port = protocols::wireguard::defaultPort; + } + + lastConfig[config_key::hostName] = hostName; + lastConfig[config_key::port] = port.toInt(); + +// if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty() +// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { + lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); + lastConfig[config_key::client_ip] = configMap.value("Address"); + lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); + lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); +// } else { +// qDebug() << "Failed to import profile"; +// emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); +// return QJsonObject(); +// } + + QString protocolName = "wireguard"; + if (!configMap.value(config_key::junkPacketCount).isEmpty() + && !configMap.value(config_key::junkPacketMinSize).isEmpty() + && !configMap.value(config_key::junkPacketMaxSize).isEmpty() + && !configMap.value(config_key::initPacketJunkSize).isEmpty() + && !configMap.value(config_key::responsePacketJunkSize).isEmpty() + && !configMap.value(config_key::initPacketMagicHeader).isEmpty() + && !configMap.value(config_key::responsePacketMagicHeader).isEmpty() + && !configMap.value(config_key::underloadPacketMagicHeader).isEmpty() + && !configMap.value(config_key::transportPacketMagicHeader).isEmpty()) { + lastConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + lastConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + lastConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + lastConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + lastConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + lastConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + lastConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + lastConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + lastConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + protocolName = "awg"; } QJsonObject wireguardConfig; @@ -244,15 +296,15 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) wireguardConfig[config_key::transport_proto] = "udp"; QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); - containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); + containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName)); + containers.insert(protocolName, QJsonValue(wireguardConfig)); QJsonArray arr; arr.push_back(containers); QJsonObject config; config[config_key::containers] = arr; - config[config_key::defaultContainer] = "amnezia-wireguard"; + config[config_key::defaultContainer] = "amnezia-" + protocolName; config[config_key::description] = m_settings->nextAvailableServerName(); const static QRegularExpression dnsRegExp( diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 4885aa80..422d9849 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "core/errorstrings.h" #include "core/controllers/serverController.h" @@ -73,6 +74,38 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol)); + if (container == DockerContainer::Awg) { + QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); + QString junkPacketMinSize = QString::number(50); + QString junkPacketMaxSize = QString::number(1000); + QString initPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + QString responsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + + QSet headersValue; + while (headersValue.size() != 4) { + + auto max = (std::numeric_limits::max)(); + headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, max))); + } + + auto headersValueList = headersValue.values(); + + QString initPacketMagicHeader = headersValueList.at(0); + QString responsePacketMagicHeader = headersValueList.at(1); + QString underloadPacketMagicHeader = headersValueList.at(2); + QString transportPacketMagicHeader = headersValueList.at(3); + + containerConfig[config_key::junkPacketCount] = junkPacketCount; + containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize; + containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; + containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize; + containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; + containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; + containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; + containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; + containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + } + if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::password, Utils::getRandomString(10)); @@ -132,7 +165,6 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(finishMessage); return; @@ -472,8 +504,9 @@ void InstallController::addEmptyServer() server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); + m_serversModel->addServer(server); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(tr("Server added successfully")); } diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index cb500618..ed60500a 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -157,3 +157,8 @@ void PageController::setTriggeredBtConnectButton(bool trigger) { m_isTriggeredByConnectButton = trigger; } + +void PageController::closeApplication() +{ + qApp->quit(); +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 70732bd9..20c3bbed 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -49,6 +49,7 @@ namespace PageLoader PageProtocolShadowSocksSettings, PageProtocolCloakSettings, PageProtocolWireGuardSettings, + PageProtocolAwgSettings, PageProtocolIKev2Settings, PageProtocolRaw }; @@ -84,10 +85,11 @@ public slots: void drawerOpen(); void drawerClose(); - bool isTriggeredByConnectButton(); void setTriggeredBtConnectButton(bool trigger); + void closeApplication(); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 3edfe3d9..78d0dd67 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -22,7 +22,7 @@ SettingsController::SettingsController(const QSharedPointer &serve m_languageModel(languageModel), m_settings(settings) { - m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); #ifdef Q_OS_ANDROID if (!m_settings->isScreenshotsEnabled()) { @@ -193,4 +193,4 @@ void SettingsController::toggleScreenshotsEnabled(bool enable) } }); #endif -} \ No newline at end of file +} diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 4d0391be..8c420899 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -97,7 +97,7 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting) } if (!jsonDocument.isArray()) { - emit errorOccurred(tr("The JSON data is not an array in file: ").arg(fileName)); + emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); return; } diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 0ffdc00b..0521cb28 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -41,7 +41,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return container; case IsInstalledRole: // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: { + case IsDefaultRole: { //todo remove m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); m_defaultContainerIndex = container; emit defaultContainerChanged(); @@ -117,6 +117,14 @@ QString ContainersModel::getDefaultContainerName() return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } +void ContainersModel::setDefaultContainer(int index) +{ + auto container = static_cast(index); + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); +} + int ContainersModel::getCurrentlyProcessedContainerIndex() { return m_currentlyProcessedContainerIndex; @@ -228,6 +236,11 @@ bool ContainersModel::isAnyContainerInstalled() return false; } +void ContainersModel::updateContainersConfig() +{ + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 2cc41cbf..997b21e3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -46,6 +46,7 @@ public: public slots: DockerContainer getDefaultContainer(); QString getDefaultContainerName(); + void setDefaultContainer(int index); void setCurrentlyProcessedServerIndex(const int index); @@ -65,6 +66,8 @@ public slots: bool isAnyContainerInstalled(); + void updateContainersConfig(); + protected: QHash roleNames() const override; diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp new file mode 100644 index 00000000..7d0277b9 --- /dev/null +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -0,0 +1,137 @@ +#include "awgConfigModel.h" + +#include + +#include "protocols/protocols_defs.h" + +AwgConfigModel::AwgConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int AwgConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break; + case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; + case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; + case Roles::InitPacketJunkSizeRole: + m_protocolConfig.insert(config_key::initPacketJunkSize, value.toString()); + break; + case Roles::ResponsePacketJunkSizeRole: + m_protocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); + break; + case Roles::InitPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); + break; + case Roles::ResponsePacketMagicHeaderRole: + m_protocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); + break; + case Roles::UnderloadPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString()); + break; + case Roles::TransportPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); + break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant AwgConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount); + case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize); + case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize); + case Roles::InitPacketJunkSizeRole: return m_protocolConfig.value(config_key::initPacketJunkSize); + case Roles::ResponsePacketJunkSizeRole: return m_protocolConfig.value(config_key::responsePacketJunkSize); + case Roles::InitPacketMagicHeaderRole: return m_protocolConfig.value(config_key::initPacketMagicHeader); + case Roles::ResponsePacketMagicHeaderRole: return m_protocolConfig.value(config_key::responsePacketMagicHeader); + case Roles::UnderloadPacketMagicHeaderRole: return m_protocolConfig.value(config_key::underloadPacketMagicHeader); + case Roles::TransportPacketMagicHeaderRole: return m_protocolConfig.value(config_key::transportPacketMagicHeader); + } + + return QVariant(); +} + +void AwgConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + + QJsonObject protocolConfig = config.value(config_key::awg).toObject(); + + m_protocolConfig[config_key::port] = + protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); + m_protocolConfig[config_key::junkPacketCount] = + protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); + m_protocolConfig[config_key::junkPacketMinSize] = + protocolConfig.value(config_key::junkPacketMinSize) + .toString(protocols::awg::defaultJunkPacketMinSize); + m_protocolConfig[config_key::junkPacketMaxSize] = + protocolConfig.value(config_key::junkPacketMaxSize) + .toString(protocols::awg::defaultJunkPacketMaxSize); + m_protocolConfig[config_key::initPacketJunkSize] = + protocolConfig.value(config_key::initPacketJunkSize) + .toString(protocols::awg::defaultInitPacketJunkSize); + m_protocolConfig[config_key::responsePacketJunkSize] = + protocolConfig.value(config_key::responsePacketJunkSize) + .toString(protocols::awg::defaultResponsePacketJunkSize); + m_protocolConfig[config_key::initPacketMagicHeader] = + protocolConfig.value(config_key::initPacketMagicHeader) + .toString(protocols::awg::defaultInitPacketMagicHeader); + m_protocolConfig[config_key::responsePacketMagicHeader] = + protocolConfig.value(config_key::responsePacketMagicHeader) + .toString(protocols::awg::defaultResponsePacketMagicHeader); + m_protocolConfig[config_key::underloadPacketMagicHeader] = + protocolConfig.value(config_key::underloadPacketMagicHeader) + .toString(protocols::awg::defaultUnderloadPacketMagicHeader); + m_protocolConfig[config_key::transportPacketMagicHeader] = + protocolConfig.value(config_key::transportPacketMagicHeader) + .toString(protocols::awg::defaultTransportPacketMagicHeader); + + endResetModel(); +} + +QJsonObject AwgConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::awg, m_protocolConfig); + return m_fullConfig; +} + +QHash AwgConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[JunkPacketCountRole] = "junkPacketCount"; + roles[JunkPacketMinSizeRole] = "junkPacketMinSize"; + roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize"; + roles[InitPacketJunkSizeRole] = "initPacketJunkSize"; + roles[ResponsePacketJunkSizeRole] = "responsePacketJunkSize"; + roles[InitPacketMagicHeaderRole] = "initPacketMagicHeader"; + roles[ResponsePacketMagicHeaderRole] = "responsePacketMagicHeader"; + roles[UnderloadPacketMagicHeaderRole] = "underloadPacketMagicHeader"; + roles[TransportPacketMagicHeaderRole] = "transportPacketMagicHeader"; + + return roles; +} diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h new file mode 100644 index 00000000..e67a3708 --- /dev/null +++ b/client/ui/models/protocols/awgConfigModel.h @@ -0,0 +1,47 @@ +#ifndef AWGCONFIGMODEL_H +#define AWGCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class AwgConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + JunkPacketCountRole, + JunkPacketMinSizeRole, + JunkPacketMaxSizeRole, + InitPacketJunkSizeRole, + ResponsePacketJunkSizeRole, + InitPacketMagicHeaderRole, + ResponsePacketMagicHeaderRole, + UnderloadPacketMagicHeaderRole, + TransportPacketMagicHeaderRole + }; + + explicit AwgConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // AWGCONFIGMODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 8c999470..5826025e 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -78,12 +78,11 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; - case Proto::L2tp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; // non-vpn - case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; + case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings; default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; } } diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index e0fe0787..c3210f73 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -96,7 +96,7 @@ void ServersModel::setDefaultServerIndex(const int index) { m_settings->setDefaultServer(index); m_defaultServerIndex = m_settings->defaultServerIndex(); - emit defaultServerIndexChanged(); + emit defaultServerIndexChanged(m_defaultServerIndex); } const int ServersModel::getDefaultServerIndex() diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index feff3ec8..f63c585f 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -67,7 +67,7 @@ protected: signals: void currentlyProcessedServerIndexChanged(const int index); - void defaultServerIndexChanged(); + void defaultServerIndexChanged(const int index); void defaultServerNameChanged(); private: diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index 5fd9a38b..f6cb9b13 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -3,7 +3,14 @@ SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings) { - m_currentRouteMode = m_settings->routeMode(); + auto routeMode = m_settings->routeMode(); + if (routeMode == Settings::RouteMode::VpnAllSites) { + m_isSplitTunnelingEnabled = false; + m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites; + } else { + m_isSplitTunnelingEnabled = true; + m_currentRouteMode = routeMode; + } fillSites(); } @@ -93,6 +100,21 @@ void SitesModel::setRouteMode(int routeMode) emit routeModeChanged(); } +bool SitesModel::isSplitTunnelingEnabled() +{ + return m_isSplitTunnelingEnabled; +} + +void SitesModel::toggleSplitTunneling(bool enabled) +{ + if (enabled) { + setRouteMode(m_currentRouteMode); + } else { + m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); + } + m_isSplitTunnelingEnabled = enabled; +} + QVector > SitesModel::getCurrentSites() { return m_sites; diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index 70def0ec..ad16b7a3 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -31,6 +31,9 @@ public slots: int getRouteMode(); void setRouteMode(int routeMode); + bool isSplitTunnelingEnabled(); + void toggleSplitTunneling(bool enabled); + QVector> getCurrentSites(); signals: @@ -44,6 +47,7 @@ private: std::shared_ptr m_settings; + bool m_isSplitTunnelingEnabled; Settings::RouteMode m_currentRouteMode; QVector> m_sites; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 2fdcb9b2..757662ae 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -146,6 +146,7 @@ Button { PageController.setTriggeredBtConnectButton(true) ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardEasy) return diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index ecde1554..1f7b2f29 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -20,16 +20,14 @@ DrawerType { anchors.right: parent.right spacing: 0 - Header2TextType { + Header2Type { Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 - Layout.bottomMargin: 32 - Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 - text: qsTr("Connection data") - wrapMode: Text.WordWrap + headerText: qsTr("Add new connection") } LabelWithButtonType { @@ -37,7 +35,7 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("Server IP, login and password") + text: qsTr("Configure your server") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -51,7 +49,7 @@ DrawerType { LabelWithButtonType { Layout.fillWidth: true - text: qsTr("QR code, key or configuration file") + text: qsTr("Open config file, key or QR code") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 4708128f..f05b90d6 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -50,41 +50,30 @@ ListView { imageSource: "qrc:/images/controls/download.svg" showImage: !isInstalled - checkable: isInstalled + checkable: isInstalled && !ConnectionController.isConnected && isSupported checked: isDefault - onPressed: function(mouse) { - if (!isSupported) { - PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) - } - } - onClicked: { - if (checked) { - var needReconnected = false - if (!isDefault) { - needReconnected = true - } + if (ConnectionController.isConnected && isInstalled) { + PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection")) + return + } + if (checked) { isDefault = true menuContent.currentIndex = index containersDropDown.menuVisible = false - - - if (needReconnected && - (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { - PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) - PageController.goToPageHome() - menu.visible = false - ConnectionController.openConnection() - } } else { + if (!isSupported && isInstalled) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + return + } + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false - menu.visible = false } } diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index a79f9140..16cdcb39 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -17,9 +17,11 @@ DrawerType { property var noButtonFunction width: parent.width - height: parent.height * 0.5 + height: content.implicitHeight + 32 ColumnLayout { + id: content + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index edd96bd7..89eb727e 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -64,6 +64,11 @@ ListView { // goToPage(PageEnum.PageProtocolWireGuardSettings) break } + case ContainerEnum.Awg: { + AwgConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolAwgSettings) + break + } case ContainerEnum.Ipsec: { ProtocolsModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolRaw) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 2419b51a..1158dadc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -213,6 +213,7 @@ DrawerType { Image { anchors.fill: parent + anchors.margins: 2 smooth: false source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index a9edd543..0855101c 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -26,4 +26,16 @@ Item { } return false } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } } diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 8429acb8..32f89122 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -81,6 +81,7 @@ RadioButton { Text { text: root.headerText + wrapMode: Text.WordWrap color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 @@ -110,6 +111,7 @@ RadioButton { Text { text: root.footerText + wrapMode: Text.WordWrap visible: root.footerText !== "" color: "#878B91" font.pixelSize: 13 diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 72765d78..830f59f9 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Controls +import "../Config" + Drawer { id: drawer property bool needCloseButton: true @@ -39,6 +41,18 @@ Drawer { border.color: "#2C2D30" border.width: 1 + + Rectangle { + visible: GC.isMobile() + + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + + width: 20 + height: 2 + color: "#2C2D30" + } } Overlay.modal: Rectangle { diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index bb051f76..8b85d591 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,9 +17,12 @@ Item { property string rightImageSource property string leftImageSource + property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3 property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" property string descriptionColor: "#878B91" + property string descriptionDisabledColor: "#494B50" property real textOpacity: 1.0 property string rightImageColor: "#d7d8db" @@ -42,9 +45,9 @@ Item { visible: leftImageSource ? true : false - Layout.preferredHeight: rightImageSource ? leftImage.implicitHeight : 56 - Layout.preferredWidth: rightImageSource ? leftImage.implicitWidth : 56 - Layout.rightMargin: rightImageSource ? 16 : 0 + Layout.preferredHeight: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource || !isLeftImageHoverEnabled ? 16 : 0 radius: 12 color: "transparent" @@ -70,7 +73,14 @@ Item { ListItemTitleType { text: root.text - color: root.descriptionOnTop ? root.descriptionColor : root.textColor + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.descriptionColor : root.textColor + } else { + return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor + } + } + maximumLineCount: root.textMaximumLineCount elide: root.textElide @@ -95,7 +105,13 @@ Item { id: description text: root.descriptionText - color: root.descriptionOnTop ? root.textColor : root.descriptionColor + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.textColor : root.descriptionColor + } else { + return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor + } + } opacity: root.textOpacity @@ -156,7 +172,7 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - hoverEnabled: true + hoverEnabled: root.enabled onEntered: { if (rightImageSource) { diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index ee7372f5..1dbd0e84 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -30,17 +30,13 @@ Switch { property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" - implicitWidth: content.implicitWidth + switcher.implicitWidth - implicitHeight: content.implicitHeight - hoverEnabled: enabled ? true : false indicator: Rectangle { id: switcher - anchors.left: content.right + anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 4 implicitWidth: 52 implicitHeight: 32 @@ -90,11 +86,11 @@ Switch { contentItem: ColumnLayout { id: content - anchors.fill: parent - anchors.rightMargin: switcher.implicitWidth + anchors.verticalCenter: parent.verticalCenter ListItemTitleType { Layout.fillWidth: true + rightPadding: indicator.width text: root.text color: root.enabled ? root.textColor : root.textDisabledColor @@ -104,6 +100,7 @@ Switch { id: description Layout.fillWidth: true + rightPadding: indicator.width color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index b09ae00d..ac0473cf 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -12,6 +12,7 @@ Item { property string headerTextColor: "#878b91" property alias errorText: errorField.text + property bool checkEmptyText: false property string buttonText property string buttonImageSource @@ -99,6 +100,12 @@ Item { root.errorText = "" } + onActiveFocusChanged: { + if (checkEmptyText && textFieldText === "") { + errorText = qsTr("The field can't be empty") + } + } + MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index 4a738214..e29b0be4 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -5,6 +5,8 @@ import QtQuick.Shapes Popup { id: root + property alias buttonWidth: button.implicitWidth + modal: false closePolicy: Popup.NoAutoClose padding: 4 @@ -20,6 +22,8 @@ Popup { } ImageButtonType { + id: button + image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d395cd22..cc49e4f0 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,6 +26,55 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + buttonContent.state = "expanded" + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + function onForceCloseDrawer() { + buttonContent.state = "collapsed" + } + } + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + updateDescriptions() + } + } + + Connections { + target: ContainersModel + + function onDefaultContainerChanged() { + updateDescriptions() + } + } + + function updateDescriptions() { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName + expandedServersMenuDescription.text = description + root.defaultServerHostName + } + + Component.onCompleted: updateDescriptions() + MouseArea { anchors.fill: parent enabled: buttonContent.state === "expanded" @@ -43,25 +92,11 @@ PageType { } } - Connections { - target: PageController - - function onRestorePageHomeState(isContainerInstalled) { - buttonContent.state = "expanded" - if (isContainerInstalled) { - containersDropDown.menuVisible = true - } - } - function onForceCloseDrawer() { - buttonContent.state = "collapsed" - } - } - MouseArea { id: dragArea anchors.fill: buttonBackground - cursorShape: Qt.PointingHandCursor + cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true drag.target: buttonContent @@ -206,8 +241,18 @@ PageType { } ] + DividerType { + Layout.topMargin: 10 + Layout.fillWidth: false + Layout.preferredWidth: 20 + Layout.preferredHeight: 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) + } + RowLayout { - Layout.topMargin: 24 + Layout.topMargin: 14 Layout.leftMargin: 24 Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter @@ -255,26 +300,10 @@ PageType { } LabelTextType { + id: collapsedServerMenuDescription Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter visible: buttonContent.collapsedVisibility - - text: { - var description = "" - if (ServersModel.isDefaultServerHasWriteAccess()) { - if (SettingsController.isAmneziaDnsEnabled() - && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { - description += "Amnezia DNS | " - } - } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { - description += "Amnezia DNS | " - } - } - - description += root.defaultContainerName + " | " + root.defaultServerHostName - return description - } } ColumnLayout { @@ -286,7 +315,7 @@ PageType { Header1TextType { Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 14 Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -297,10 +326,11 @@ PageType { } LabelTextType { + id: expandedServersMenuDescription Layout.bottomMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - text: root.defaultServerHostName + Layout.fillWidth: true + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter } RowLayout { @@ -365,18 +395,7 @@ PageType { Layout.rightMargin: 16 visible: buttonContent.expandedVisibility - actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: qsTr("Servers") - - actionButtonFunction: function() { - buttonContent.state = "collapsed" - connectionTypeSelection.visible = true - } - } - - ConnectionTypeSelectionDrawer { - id: connectionTypeSelection } } @@ -450,11 +469,11 @@ PageType { if (hasWriteAccess) { if (SettingsController.isAmneziaDnsEnabled() && ContainersModel.isAmneziaDnsContainerInstalled(index)) { - description += "AmneziaDNS | " + description += "Amnezia DNS | " } } else { if (containsAmneziaDns) { - description += "AmneziaDNS | " + description += "Amnezia DNS | " } } @@ -462,10 +481,16 @@ PageType { } checked: index === serversMenuContent.currentIndex + checkable: !ConnectionController.isConnected ButtonGroup.group: serversRadioButtonGroup onClicked: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) + return + } + serversMenuContent.currentIndex = index ServersModel.currentlyProcessedIndex = index diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml new file mode 100644 index 00000000..237a8b46 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -0,0 +1,329 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: AwgConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("AmneziaWG settings") + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketCountTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet count") + textFieldText: junkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + console.log("1") + if (textFieldText === "") { + textFieldText = "0" + } + + if (textFieldText !== junkPacketCount) { + junkPacketCount = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet minimum size") + textFieldText: junkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== junkPacketMinSize) { + junkPacketMinSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet maximum size") + textFieldText: junkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== junkPacketMaxSize) { + junkPacketMaxSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet junk size") + textFieldText: initPacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== initPacketJunkSize) { + initPacketJunkSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet junk size") + textFieldText: responsePacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== responsePacketJunkSize) { + responsePacketJunkSize = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet magic header") + textFieldText: initPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== initPacketMagicHeader) { + initPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet magic header") + textFieldText: responsePacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== responsePacketMagicHeader) { + responsePacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Transport packet magic header") + textFieldText: transportPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== transportPacketMagicHeader) { + transportPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Underload packet magic header") + textFieldText: underloadPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textFieldText !== underloadPacketMagicHeader) { + underloadPacketMagicHeader = textFieldText + } + } + + checkEmptyText: true + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove AmneziaWG") + + onClicked: { + questionDrawer.headerText = qsTr("Remove AmneziaWG from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + enabled: underloadPacketMagicHeaderTextField.errorText === "" && + transportPacketMagicHeaderTextField.errorText === "" && + responsePacketMagicHeaderTextField.errorText === "" && + initPacketMagicHeaderTextField.errorText === "" && + responsePacketJunkSizeTextField.errorText === "" && + initPacketJunkSizeTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" && + portTextField.errorText === "" + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(AwgConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 491bdf31..55cdcf04 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ContainerEnum 1.0 import "./" import "../Controls2" @@ -252,6 +253,8 @@ PageType { ColumnLayout { id: checkboxLayout + + anchors.fill: parent CheckBoxType { Layout.fillWidth: true @@ -351,6 +354,8 @@ PageType { Layout.leftMargin: -8 implicitHeight: 32 + visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.OpenVpn + defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) pressedColor: Qt.rgba(1, 1, 1, 0.12) @@ -360,7 +365,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 34ca4055..967b605b 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -127,7 +127,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - headerText: qsTr("Connection options ") + protocolName + headerText: qsTr("Connection options %1").arg(protocolName) } TextArea { @@ -169,12 +169,14 @@ PageType { width: parent.width + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 61ba663d..b12302dd 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -96,7 +96,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -113,7 +113,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -130,7 +130,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -147,23 +147,11 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } - TextEdit{ - id: clipboard - visible: false - } - - function copyToClipBoard(text) { - clipboard.text = text - clipboard.selectAll() - clipboard.copy() - clipboard.select(0, 0) - } - BasicButtonType { visible: !GC.isMobile() diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 04d7076c..3bfa5bb0 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -78,23 +78,11 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - content.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } - TextEdit{ - id: clipboard - visible: false - } - - function copyToClipBoard(text) { - clipboard.text = text - clipboard.selectAll() - clipboard.copy() - clipboard.select(0, 0) - } - ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 40 @@ -121,7 +109,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("When configuring WordPress set the domain as this onion address.") + text: qsTr("When configuring WordPress set the this onion address as domain.") } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index e020dc2c..92575dda 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -95,6 +95,7 @@ PageType { DividerType {} LabelWithButtonType { + id: about Layout.fillWidth: true text: qsTr("About AmneziaVPN") @@ -107,6 +108,24 @@ PageType { } DividerType {} + + LabelWithButtonType { + visible: GC.isDesktop() + Layout.fillWidth: true + Layout.preferredHeight: about.height + + text: qsTr("Close application") + leftImageSource: "qrc:/images/controls/x-circle.svg" + isLeftImageHoverEnabled: false + + clickedFunction: function() { + PageController.closeApplication() + } + } + + DividerType { + visible: GC.isDesktop() + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index e73ef88f..eaa9eb3d 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -68,8 +68,8 @@ PageType { height: 20 font.pixelSize: 14 - text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app.") + text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. ") + + qsTr("And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application.") color: "#CCCAC8" } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index c5536fdb..05e468f0 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -70,7 +70,7 @@ PageType { Layout.margins: 16 text: qsTr("Auto start") - descriptionText: qsTr("Launch the application every time ") + Qt.platform.os + qsTr(" starts") + descriptionText: qsTr("Launch the application every time the device is starts") checked: SettingsController.isAutoStartEnabled() onCheckedChanged: { diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 7a556dfb..81be0465 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -77,7 +77,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: -12 - text: qsTr("It will help you instantly restore connection settings at the next installation") + text: qsTr("You can save your settings to a backup file to restore them the next time you install the application.") color: "#878B91" } @@ -103,6 +103,7 @@ PageType { PageController.showBusyIndicator(true) SettingsController.backupAppConfig(fileName) PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Backup file saved")) } } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index ae5fd7f4..565ae7db 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -94,10 +94,12 @@ PageType { DividerType {} LabelWithButtonType { + visible: GC.isDesktop() + Layout.fillWidth: true - text: qsTr("Split site tunneling") - descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") + text: qsTr("Site-based split tunneling") + descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -105,12 +107,16 @@ PageType { } } - DividerType {} + DividerType { + visible: GC.isDesktop() + } LabelWithButtonType { + visible: false + Layout.fillWidth: true - text: qsTr("Separate application tunneling") + text: qsTr("App-based split tunneling") descriptionText: qsTr("Allows you to use the VPN only for certain applications") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -118,7 +124,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 58ec0783..5670464f 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -46,6 +46,7 @@ PageType { } ParagraphTextType { + Layout.fillWidth: true text: qsTr("If AmneziaDNS is not used or installed") } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 4141f51f..840c41d4 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -115,6 +115,7 @@ PageType { PageController.showBusyIndicator(true) SettingsController.exportLogsFile(fileName) PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs file saved")) } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 998948d1..a961cf56 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -114,7 +114,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index c0807f35..040aafc3 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -34,20 +34,10 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: qsTr("Servers") - - actionButtonFunction: function() { - connectionTypeSelection.visible = true - } } } - ConnectionTypeSelectionDrawer { - id: connectionTypeSelection - } - FlickableType { anchors.top: header.bottom anchors.topMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index b79d5d22..873ae997 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -20,6 +20,10 @@ import "../Components" PageType { id: root + property bool pageEnabled: { + return !ConnectionController.isConnected + } + Connections { target: SitesController @@ -46,12 +50,12 @@ PageType { QtObject { id: onlyForwardSites - property string name: qsTr("Only the addresses in the list must be opened via VPN") + property string name: qsTr("Addresses from the list should be accessed via VPN") property int type: routeMode.onlyForwardSites } QtObject { id: allExceptSites - property string name: qsTr("Addresses from the list should never be opened via VPN") + property string name: qsTr("Addresses from the list should not be accessed via VPN") property int type: routeMode.allExceptSites } @@ -78,29 +82,26 @@ PageType { RowLayout { HeaderType { + enabled: root.pageEnabled + Layout.fillWidth: true Layout.leftMargin: 16 - headerText: qsTr("Split site tunneling") + headerText: qsTr("Split tunneling") } SwitcherType { id: switcher - property int lastActiveRouteMode: routeMode.onlyForwardSites + enabled: root.pageEnabled Layout.fillWidth: true Layout.rightMargin: 16 - checked: SitesModel.routeMode !== routeMode.allSites - onToggled: { - if (checked) { - SitesModel.routeMode = lastActiveRouteMode - } else { - lastActiveRouteMode = SitesModel.routeMode - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - SitesModel.routeMode = routeMode.allSites - } + checked: SitesModel.isSplitTunnelingEnabled() + onToggled: { + SitesModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } } @@ -115,7 +116,7 @@ PageType { drawerHeight: 0.4375 - enabled: switcher.checked + enabled: root.pageEnabled headerText: qsTr("Mode") @@ -155,9 +156,9 @@ PageType { FlickableType { anchors.top: header.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin + contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin - enabled: switcher.checked + enabled: root.pageEnabled Column { id: col @@ -221,8 +222,17 @@ PageType { } } + Rectangle { + anchors.fill: addSiteButton + anchors.bottomMargin: -24 + color: "#0E0E11" + opacity: 0.8 + } + RowLayout { - id: connectButton + id: addSiteButton + + enabled: root.pageEnabled anchors.bottom: parent.bottom anchors.left: parent.left diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index bc24c196..5c32b0c5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -41,7 +41,7 @@ PageType { HeaderType { Layout.fillWidth: true - headerText: qsTr("Server connection") + headerText: qsTr("Configure your server") } TextFieldWithHeaderType { @@ -107,6 +107,14 @@ PageType { PageController.goToPage(PageEnum.PageSetupWizardEasy) } } + + LabelTextType { + Layout.fillWidth: true + Layout.topMargin: 12 + + text: qsTr("All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties") + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f2919398..a223f646 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,6 +24,10 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { + if (!ConnectionController.isConnected && !isServiceInstall) { + ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex()) + } + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -41,12 +45,12 @@ PageType { } function onInstallServerFinished(finishedMessage) { + if (!ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + PageController.goToStartPage() - if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { - PageController.restorePageHomeState() - } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - PageController.goToPage(PageEnum.PageSettingsServersList, false) - } else { + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() } @@ -63,8 +67,8 @@ PageType { function onServerIsBusy(isBusy) { if (isBusy) { - root.progressBarText = qsTr("Amnesia has detected that your server is currently ") + - qsTr("busy installing other software. Amnesia installation ") + + root.progressBarText = qsTr("Amnezia has detected that your server is currently ") + + qsTr("busy installing other software. Amnezia installation ") + qsTr("will pause until the server finishes installing other software") root.isTimerRunning = false } else { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 7535464a..7698c755 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -144,33 +144,16 @@ PageType { headerText: name } - TextField { - implicitWidth: parent.width + ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 - padding: 0 - leftPadding: 0 - height: 24 - - color: "#D7D8DB" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - text: detailedDescription - - wrapMode: Text.WordWrap - - readOnly: true - background: Rectangle { - anchors.fill: parent - color: "transparent" - } + textFormat: Text.MarkdownText } + Rectangle { Layout.fillHeight: true color: "transparent" @@ -241,7 +224,7 @@ PageType { if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { port.visible = false } else { - port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) + port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto) } transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 9f5e57a5..994ec200 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -62,7 +62,7 @@ PageType { function onInstallationErrorOccurred(errorMessage) { PageController.showErrorMessage(errorMessage) - var currentPageName = tabBarStackView.currentItem.objectName + var currentPageName = stackView.currentItem.objectName if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { PageController.closePage() @@ -134,7 +134,7 @@ PageType { text: qsTr("I have nothing") - onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions") + onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide") } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 2f1fc392..ac35651f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -24,12 +24,12 @@ PageType { } function onImportFinished() { + if (ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + PageController.goToStartPage() - if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { - PageController.restorePageHomeState() - } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - PageController.goToPage(PageEnum.PageSettingsServersList, false) - } else { + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 00b65310..ced7a5ff 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -118,7 +118,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - headerText: qsTr("VPN Access") + headerText: qsTr("Share VPN Access") } Rectangle { @@ -171,8 +171,8 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : - qsTr("Full access to server") + text: accessTypeSelector.currentIndex === 0 ? qsTr("Share VPN access without the ability to manage the server") : + qsTr("Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings.") color: "#878B91" } @@ -187,7 +187,7 @@ PageType { drawerHeight: 0.4375 - descriptionText: qsTr("Servers") + descriptionText: qsTr("Server") headerText: qsTr("Server") listView: ListViewWithRadioButtonType { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 43366af7..ab02ace4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -9,6 +9,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -17,14 +18,14 @@ PageType { target: PageController function onGoToPageHome() { - tabBar.currentIndex = 0 + tabBar.setCurrentIndex(0) tabBarStackView.goToTabBarPage(PageEnum.PageHome) PageController.updateDrawerRootPage(PageEnum.PageHome) } function onGoToPageSettings() { - tabBar.currentIndex = 2 + tabBar.setCurrentIndex(2) tabBarStackView.goToTabBarPage(PageEnum.PageSettings) PageController.updateDrawerRootPage(PageEnum.PageSettings) @@ -43,9 +44,9 @@ PageType { tabBar.enabled = !visible } - function onShowTopCloseButton(visible) { - topCloseButton.visible = visible - } +// function onShowTopCloseButton(visible) { +// topCloseButton.visible = visible +// } function onEnableTabBar(enabled) { tabBar.enabled = enabled @@ -70,6 +71,7 @@ PageType { } function onGoToStartPage() { + connectionTypeSelection.close() while (tabBarStackView.depth > 1) { tabBarStackView.pop() } @@ -120,6 +122,8 @@ PageType { height: root.height - tabBar.implicitHeight function goToTabBarPage(page) { + connectionTypeSelection.close() + var pagePath = PageController.getPagePath(page) tabBarStackView.clear(StackView.Immediate) tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) @@ -132,19 +136,26 @@ PageType { ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) } + +// onWidthChanged: { +// topCloseButton.x = tabBarStackView.x + tabBarStackView.width - +// topCloseButton.buttonWidth - topCloseButton.rightPadding +// } } TabBar { id: tabBar + property int previousIndex: 0 + anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom topPadding: 8 bottomPadding: 8 - leftPadding: shareTabButton.visible ? 96 : 128 - rightPadding: shareTabButton.visible ? 96 : 128 + leftPadding: 96 + rightPadding: 96 background: Shape { width: parent.width @@ -171,8 +182,10 @@ PageType { onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + tabBar.previousIndex = 0 } } + TabImageButtonType { id: shareTabButton @@ -193,13 +206,24 @@ PageType { image: "qrc:/images/controls/share-2.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageShare) + tabBar.previousIndex = 1 } } + TabImageButtonType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + tabBar.previousIndex = 2 + } + } + + TabImageButtonType { + isSelected: tabBar.currentIndex === 3 + image: "qrc:/images/controls/plus.svg" + onClicked: { + connectionTypeSelection.open() } } } @@ -210,9 +234,18 @@ PageType { z: 1 } - TopCloseButtonType { - id: topCloseButton - x: tabBarStackView.width - topCloseButton.width - z: 1 +// TopCloseButtonType { +// id: topCloseButton + +// x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding +// z: 1 +// } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + + onAboutToHide: { + tabBar.setCurrentIndex(tabBar.previousIndex) + } } } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5cc6a6a0..5712a3ae 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -321,6 +321,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede ErrorCode e = ErrorCode::NoError; m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); + emit newVpnConfigurationCreated(); if (e) { emit connectionStateChanged(Vpn::ConnectionState::Error); return; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 20ee14fa..f6b2343c 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -79,6 +79,8 @@ signals: void serviceIsNotReady(); + void newVpnConfigurationCreated(); + protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); void onConnectionStateChanged(Vpn::ConnectionState state); diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh index 700198e7..13214d6d 100755 --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -96,16 +96,16 @@ if [ "${MAC_CERT_PW+x}" ]; then security find-identity -p codesigning echo "Signing App bundle..." - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR /usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true spctl -a -vvvv $BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing App bundle..." /usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $BUNDLE_DIR xcrun stapler validate $BUNDLE_DIR spctl -a -vvvv $BUNDLE_DIR || true @@ -130,15 +130,15 @@ $QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/maco if [ "${MAC_CERT_PW+x}" ]; then echo "Signing installer bundle..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $INSTALLER_BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR /usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing installer bundle..." /usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Installer_bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $INSTALLER_BUNDLE_DIR xcrun stapler validate $INSTALLER_BUNDLE_DIR spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true @@ -151,13 +151,13 @@ hdiutil create -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app if [ "${MAC_CERT_PW+x}" ]; then echo "Signing DMG installer..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $DMG_FILENAME + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME /usr/bin/codesign --verify -vvvv $DMG_FILENAME || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing DMG installer..." - xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD - sleep 600 + xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD + sleep 300 xcrun stapler staple $DMG_FILENAME xcrun stapler validate $DMG_FILENAME fi diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index c4b7b8cf..7ac37f14 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -47,7 +47,7 @@ cd %PROJECT_DIR% call "%QT_BIN_DIR:"=%\qt-cmake" . -B %WORK_DIR% cd %WORK_DIR% -cmake --build . --config release +cmake --build . --config release -- /p:UseMultiToolTask=true /m if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . --target clean