mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-05-17 00:16:01 +03:00
AmneziaWG release (#4)
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/.gradle/
|
||||
/.idea/*.xml
|
||||
/.idea/caches/
|
||||
/.idea/dictionaries/
|
||||
/.idea/libraries/
|
||||
/captures/
|
||||
/local.properties
|
||||
.DS_Store
|
||||
.cxx/
|
||||
Thumbs.db
|
||||
build/
|
||||
*.apk
|
||||
*.class
|
||||
*.dex
|
||||
*.iml
|
||||
*.jks
|
||||
gradlew.bat
|
||||
maint/
|
||||
35
README.md
35
README.md
@@ -1,40 +1,15 @@
|
||||
# Android GUI for [WireGuard](https://www.wireguard.com/)
|
||||
# Android GUI for [AmneziaWG](https://amnezia.org/learn-more/31_amneziawg)
|
||||
|
||||
**[Download from the Play Store](https://play.google.com/store/apps/details?id=com.wireguard.android)**
|
||||
**[Download from the Play Store](https://play.google.com/store/apps/details?id=org.amnezia.awg)**
|
||||
|
||||
This is an Android GUI for [WireGuard](https://www.wireguard.com/). It [opportunistically uses the kernel implementation](https://git.zx2c4.com/android_kernel_wireguard/about/), and falls back to using the non-root [userspace implementation](https://git.zx2c4.com/wireguard-go/about/).
|
||||
This is an Android GUI for [AmneziaWG](https://amnezia.org/learn-more/31_amneziawg).
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
$ git clone --recurse-submodules https://git.zx2c4.com/wireguard-android
|
||||
$ cd wireguard-android
|
||||
$ git clone --recurse-submodules https://github.com/amnezia-vpn/amneziawg-android
|
||||
$ cd amneziawg-android
|
||||
$ ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
macOS users may need [flock(1)](https://github.com/discoteq/flock).
|
||||
|
||||
## Embedding
|
||||
|
||||
The tunnel library is [on Maven Central](https://search.maven.org/artifact/com.wireguard.android/tunnel), alongside [extensive class library documentation](https://javadoc.io/doc/com.wireguard.android/tunnel).
|
||||
|
||||
```
|
||||
implementation 'com.wireguard.android:tunnel:$wireguardTunnelVersion'
|
||||
```
|
||||
|
||||
The library makes use of Java 8 features, so be sure to support those in your gradle configuration with [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring):
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
coreLibraryDesugaringEnabled = true
|
||||
}
|
||||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
}
|
||||
```
|
||||
|
||||
## Translating
|
||||
|
||||
Please help us translate the app into several languages on [our translation platform](https://crowdin.com/project/WireGuard).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
amneziawgVersionCode=1
|
||||
amneziawgVersionName=1.0.0
|
||||
amneziawgPackageName=org.amnezia.vpn
|
||||
amneziawgVersionCode=3
|
||||
amneziawgVersionName=1.1.0
|
||||
amneziawgPackageName=org.amnezia.awg
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
[versions]
|
||||
agp = "8.2.0"
|
||||
agp = "8.3.0"
|
||||
kotlin = "1.9.20"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-ktx = "androidx.activity:activity-ktx:1.7.2"
|
||||
androidx-annotation = "androidx.annotation:annotation:1.6.0"
|
||||
androidx-activity-ktx = "androidx.activity:activity-ktx:1.8.2"
|
||||
androidx-annotation = "androidx.annotation:annotation:1.7.1"
|
||||
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||
androidx-biometric = "androidx.biometric:biometric:1.1.0"
|
||||
androidx-collection = "androidx.collection:collection:1.2.0"
|
||||
androidx-collection = "androidx.collection:collection:1.4.0"
|
||||
androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.10.1"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.12.0"
|
||||
androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.0.0"
|
||||
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.5.7"
|
||||
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
|
||||
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.0"
|
||||
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
google-material = "com.google.android.material:material:1.9.0"
|
||||
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.6.2"
|
||||
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
|
||||
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.1"
|
||||
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||
google-material = "com.google.android.material:material:1.11.0"
|
||||
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
|
||||
junit = "junit:junit:4.13.2"
|
||||
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
|
||||
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
||||
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
|
||||
|
||||
[plugins]
|
||||
|
||||
@@ -18,7 +18,7 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.settings") version "8.2.0"
|
||||
id("com.android.settings") version "8.3.0"
|
||||
}
|
||||
|
||||
rootProject.name = "amneziawg-android"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
curl -Lo - https://crowdin.com/backend/download/project/wireguard.zip | bsdtar -C ui/src/main/res -x -f - --strip-components 5 wireguard-android
|
||||
find ui/src/main/res -name strings.xml -exec bash -c '[[ $(xmllint --xpath "count(//resources/*)" {}) -ne 0 ]] || rm -rf "$(dirname {})"' \;
|
||||
@@ -3,11 +3,10 @@
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
val pkg: String = providers.gradleProperty("amneziawgPackageName").get()
|
||||
val cmakeAndroidPackageName: String = providers.environmentVariable("ANDROID_PACKAGE_NAME").getOrElse(pkg)
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -36,14 +35,14 @@ android {
|
||||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}")
|
||||
arguments("-DANDROID_PACKAGE_NAME=${cmakeAndroidPackageName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}.debug")
|
||||
arguments("-DANDROID_PACKAGE_NAME=${cmakeAndroidPackageName}.debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,12 +51,6 @@ android {
|
||||
disable += "LongLogTag"
|
||||
disable += "NewApi"
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -66,59 +59,3 @@ dependencies {
|
||||
compileOnly(libs.jsr305)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
register<MavenPublication>("release") {
|
||||
groupId = pkg
|
||||
artifactId = "tunnel"
|
||||
version = providers.gradleProperty("amneziawgVersionName").get()
|
||||
afterEvaluate {
|
||||
from(components["release"])
|
||||
}
|
||||
pom {
|
||||
name.set("WireGuard Tunnel Library")
|
||||
description.set("Embeddable tunnel library for WireGuard for Android")
|
||||
url.set("https://www.wireguard.com/")
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name.set("The Apache Software License, Version 2.0")
|
||||
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
developerConnection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
url.set("https://git.zx2c4.com/wireguard-android")
|
||||
}
|
||||
developers {
|
||||
organization {
|
||||
name.set("WireGuard")
|
||||
url.set("https://www.wireguard.com/")
|
||||
}
|
||||
developer {
|
||||
name.set("WireGuard")
|
||||
email.set("team@wireguard.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = providers.environmentVariable("SONATYPE_USER").orNull
|
||||
password = providers.environmentVariable("SONATYPE_PASSWORD").orNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="com.wireguard.android.backend.GoBackend$VpnService"
|
||||
android:name="org.amnezia.awg.backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.crypto.KeyPair;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must
|
||||
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
|
||||
* attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class Interface {
|
||||
private static final int MAX_UDP_PORT = 65535;
|
||||
private static final int MIN_UDP_PORT = 0;
|
||||
|
||||
private final Set<InetNetwork> addresses;
|
||||
private final Set<InetAddress> dnsServers;
|
||||
private final Set<String> dnsSearchDomains;
|
||||
private final Set<String> excludedApplications;
|
||||
private final Set<String> includedApplications;
|
||||
private final KeyPair keyPair;
|
||||
private final Optional<Integer> listenPort;
|
||||
private final Optional<Integer> mtu;
|
||||
|
||||
private Interface(final Builder builder) {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
|
||||
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
|
||||
dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains));
|
||||
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
|
||||
includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
listenPort = builder.listenPort;
|
||||
mtu = builder.mtu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
|
||||
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
|
||||
*
|
||||
* @param lines An iterable sequence of lines, containing at least a private key attribute
|
||||
* @return An {@code Interface} with all of the attributes from {@code lines} set
|
||||
*/
|
||||
public static Interface parse(final Iterable<? extends CharSequence> lines)
|
||||
throws BadConfigException {
|
||||
final Builder builder = new Builder();
|
||||
for (final CharSequence line : lines) {
|
||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() ->
|
||||
new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.SYNTAX_ERROR, line));
|
||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
|
||||
case "address":
|
||||
builder.parseAddresses(attribute.getValue());
|
||||
break;
|
||||
case "dns":
|
||||
builder.parseDnsServers(attribute.getValue());
|
||||
break;
|
||||
case "excludedapplications":
|
||||
builder.parseExcludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "includedapplications":
|
||||
builder.parseIncludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "listenport":
|
||||
builder.parseListenPort(attribute.getValue());
|
||||
break;
|
||||
case "mtu":
|
||||
builder.parseMtu(attribute.getValue());
|
||||
break;
|
||||
case "privatekey":
|
||||
builder.parsePrivateKey(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof Interface))
|
||||
return false;
|
||||
final Interface other = (Interface) obj;
|
||||
return addresses.equals(other.addresses)
|
||||
&& dnsServers.equals(other.dnsServers)
|
||||
&& dnsSearchDomains.equals(other.dnsSearchDomains)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& includedApplications.equals(other.includedApplications)
|
||||
&& keyPair.equals(other.keyPair)
|
||||
&& listenPort.equals(other.listenPort)
|
||||
&& mtu.equals(other.mtu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of IP addresses assigned to the interface.
|
||||
*
|
||||
* @return a set of {@link InetNetwork}s
|
||||
*/
|
||||
public Set<InetNetwork> getAddresses() {
|
||||
// The collection is already immutable.
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS servers associated with the interface.
|
||||
*
|
||||
* @return a set of {@link InetAddress}es
|
||||
*/
|
||||
public Set<InetAddress> getDnsServers() {
|
||||
// The collection is already immutable.
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS search domains associated with the interface.
|
||||
*
|
||||
* @return a set of strings
|
||||
*/
|
||||
public Set<String> getDnsSearchDomains() {
|
||||
// The collection is already immutable.
|
||||
return dnsSearchDomains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications excluded from using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getExcludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return excludedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications included exclusively for using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getIncludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return includedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public/private key pair used by the interface.
|
||||
*
|
||||
* @return a key pair
|
||||
*/
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UDP port number that the WireGuard interface will listen on.
|
||||
*
|
||||
* @return a UDP port number, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MTU used for the WireGuard interface.
|
||||
*
|
||||
* @return the MTU, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 1;
|
||||
hash = 31 * hash + addresses.hashCode();
|
||||
hash = 31 * hash + dnsServers.hashCode();
|
||||
hash = 31 * hash + excludedApplications.hashCode();
|
||||
hash = 31 * hash + includedApplications.hashCode();
|
||||
hash = 31 * hash + keyPair.hashCode();
|
||||
hash = 31 * hash + listenPort.hashCode();
|
||||
hash = 31 * hash + mtu.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
|
||||
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
|
||||
*
|
||||
* @return A concise single-line identifier for the {@code Interface}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("(Interface ");
|
||||
sb.append(keyPair.getPublicKey().toBase64());
|
||||
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for inclusion in a {@code wg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return The {@code Interface} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!addresses.isEmpty())
|
||||
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
|
||||
if (!dnsServers.isEmpty()) {
|
||||
final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
|
||||
dnsServerStrings.addAll(dnsSearchDomains);
|
||||
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
|
||||
}
|
||||
if (!excludedApplications.isEmpty())
|
||||
sb.append("ExcludedApplications = ").append(Attribute.join(excludedApplications)).append('\n');
|
||||
if (!includedApplications.isEmpty())
|
||||
sb.append("IncludedApplications = ").append(Attribute.join(includedApplications)).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
|
||||
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
|
||||
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Interface} for use with the WireGuard cross-platform userspace API.
|
||||
* Note that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
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'));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> dnsSearchDomains = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> excludedApplications = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> includedApplications = new LinkedHashSet<>();
|
||||
// No default; must be provided before building.
|
||||
@Nullable private KeyPair keyPair;
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> listenPort = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> mtu = Optional.empty();
|
||||
|
||||
public Builder addAddress(final InetNetwork address) {
|
||||
addresses.add(address);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAddresses(final Collection<InetNetwork> addresses) {
|
||||
this.addresses.addAll(addresses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServer(final InetAddress dnsServer) {
|
||||
dnsServers.add(dnsServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
|
||||
this.dnsServers.addAll(dnsServers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomain(final String dnsSearchDomain) {
|
||||
dnsSearchDomains.add(dnsSearchDomain);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) {
|
||||
this.dnsSearchDomains.addAll(dnsSearchDomains);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Interface build() throws BadConfigException {
|
||||
if (keyPair == null)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
|
||||
Reason.MISSING_ATTRIBUTE, null);
|
||||
if (!includedApplications.isEmpty() && !excludedApplications.isEmpty())
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INCLUDED_APPLICATIONS,
|
||||
Reason.INVALID_KEY, null);
|
||||
return new Interface(this);
|
||||
}
|
||||
|
||||
public Builder excludeApplication(final String application) {
|
||||
excludedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder excludeApplications(final Collection<String> applications) {
|
||||
excludedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplication(final String application) {
|
||||
includedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplications(final Collection<String> applications) {
|
||||
includedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
|
||||
try {
|
||||
for (final String address : Attribute.split(addresses))
|
||||
addAddress(InetNetwork.parse(address));
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
|
||||
try {
|
||||
for (final String dnsServer : Attribute.split(dnsServers)) {
|
||||
try {
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
} catch (final ParseException e) {
|
||||
if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer))
|
||||
throw e;
|
||||
addDnsSearchDomain(dnsServer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseExcludedApplications(final CharSequence apps) {
|
||||
return excludeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseIncludedApplications(final CharSequence apps) {
|
||||
return includeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseListenPort(final String listenPort) throws BadConfigException {
|
||||
try {
|
||||
return setListenPort(Integer.parseInt(listenPort));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseMtu(final String mtu) throws BadConfigException {
|
||||
try {
|
||||
return setMtu(Integer.parseInt(mtu));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
|
||||
try {
|
||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
|
||||
} catch (final KeyFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setKeyPair(final KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setListenPort(final int listenPort) throws BadConfigException {
|
||||
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT,
|
||||
Reason.INVALID_VALUE, String.valueOf(listenPort));
|
||||
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
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));
|
||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
tunnel/src/main/java/org/amnezia/awg/GoBackend.java
Normal file
18
tunnel/src/main/java/org/amnezia/awg/GoBackend.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.amnezia.awg;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class GoBackend {
|
||||
@Nullable
|
||||
public static native String awgGetConfig(int handle);
|
||||
|
||||
public static native int awgGetSocketV4(int handle);
|
||||
|
||||
public static native int awgGetSocketV6(int handle);
|
||||
|
||||
public static native void awgTurnOff(int handle);
|
||||
|
||||
public static native int awgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
public static native String awgVersion();
|
||||
}
|
||||
@@ -3,24 +3,23 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.backend.BackendException.Reason;
|
||||
import org.amnezia.awg.backend.Tunnel.State;
|
||||
import org.amnezia.awg.util.RootShell;
|
||||
import org.amnezia.awg.util.ToolsInstaller;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -35,27 +34,27 @@ import java.util.Set;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Backend} that uses the kernel module and {@code wg-quick} to provide
|
||||
* WireGuard tunnels.
|
||||
* Implementation of {@link Backend} that uses the kernel module and {@code awg-quick} to provide
|
||||
* AmneziaWG tunnels.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
public final class WgQuickBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/WgQuickBackend";
|
||||
public final class AwgQuickBackend implements Backend {
|
||||
private static final String TAG = "AmneziaWG/AwgQuickBackend";
|
||||
private final File localTemporaryDir;
|
||||
private final RootShell rootShell;
|
||||
private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
|
||||
private final ToolsInstaller toolsInstaller;
|
||||
private boolean multipleTunnels;
|
||||
|
||||
public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||
public AwgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.toolsInstaller = toolsInstaller;
|
||||
}
|
||||
|
||||
public static boolean hasKernelSupport() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
return new File("/sys/module/amneziawg").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,13 +63,13 @@ public final class WgQuickBackend implements Backend {
|
||||
// Don't throw an exception here or nothing will show up in the UI.
|
||||
try {
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
|
||||
if (rootShell.run(output, "awg show interfaces") != 0 || output.isEmpty())
|
||||
return Collections.emptySet();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "Unable to enumerate running tunnels", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// wg puts all interface names on the same line. Split them into separate elements.
|
||||
// awg puts all interface names on the same line. Split them into separate elements.
|
||||
return Set.of(output.get(0).split(" "));
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ public final class WgQuickBackend implements Backend {
|
||||
final Statistics stats = new Statistics();
|
||||
final Collection<String> output = new ArrayList<>();
|
||||
try {
|
||||
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
|
||||
if (rootShell.run(output, String.format("awg show '%s' dump", tunnel.getName())) != 0)
|
||||
return stats;
|
||||
} catch (final Exception ignored) {
|
||||
return stats;
|
||||
@@ -104,7 +103,7 @@ public final class WgQuickBackend implements Backend {
|
||||
@Override
|
||||
public String getVersion() throws Exception {
|
||||
final List<String> output = new ArrayList<>();
|
||||
if (rootShell.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
|
||||
if (rootShell.run(output, "cat /sys/module/amneziawg/version") != 0 || output.isEmpty())
|
||||
throw new BackendException(Reason.UNKNOWN_KERNEL_MODULE_NAME);
|
||||
return output.get(0);
|
||||
}
|
||||
@@ -174,17 +173,17 @@ public final class WgQuickBackend implements Backend {
|
||||
|
||||
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
|
||||
try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
|
||||
stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
|
||||
stream.write(config.toAwgQuickString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
String command = String.format("wg-quick %s '%s'",
|
||||
String command = String.format("awg-quick %s '%s'",
|
||||
state.toString().toLowerCase(Locale.ENGLISH), tempFile.getAbsolutePath());
|
||||
if (state == State.UP)
|
||||
command = "cat /sys/module/wireguard/version && " + command;
|
||||
command = "cat /sys/module/amneziawg/version && " + command;
|
||||
final int result = rootShell.run(null, command);
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
if (result != 0)
|
||||
throw new BackendException(Reason.WG_QUICK_CONFIG_ERROR_CODE, result);
|
||||
throw new BackendException(Reason.AWG_QUICK_CONFIG_ERROR_CODE, result);
|
||||
|
||||
if (state == State.UP)
|
||||
runningConfigs.put(tunnel, config);
|
||||
@@ -3,17 +3,17 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for implementations of the WireGuard secure network tunnel.
|
||||
* Interface for implementations of the AmneziaWG secure network tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* A subclass of {@link Exception} that encapsulates the reasons for a failure originating in
|
||||
@@ -50,7 +50,7 @@ public final class BackendException extends Exception {
|
||||
*/
|
||||
public enum Reason {
|
||||
UNKNOWN_KERNEL_MODULE_NAME,
|
||||
WG_QUICK_CONFIG_ERROR_CODE,
|
||||
AWG_QUICK_CONFIG_ERROR_CODE,
|
||||
TUNNEL_MISSING_CONFIG,
|
||||
VPN_NOT_AUTHORIZED,
|
||||
UNABLE_TO_START_VPN,
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -12,19 +12,18 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.SharedLibraryLoader;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.InetEndpoint;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.backend.BackendException.Reason;
|
||||
import org.amnezia.awg.backend.Tunnel.State;
|
||||
import org.amnezia.awg.util.SharedLibraryLoader;
|
||||
import org.amnezia.awg.config.Config;
|
||||
import org.amnezia.awg.config.InetEndpoint;
|
||||
import org.amnezia.awg.config.InetNetwork;
|
||||
import org.amnezia.awg.config.Peer;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -36,14 +35,16 @@ import java.util.concurrent.TimeoutException;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import static org.amnezia.awg.GoBackend.*;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Backend} that uses the wireguard-go userspace implementation to provide
|
||||
* WireGuard tunnels.
|
||||
* Implementation of {@link Backend} that uses the amneziawg-go userspace implementation to provide
|
||||
* AmneziaWG tunnels.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class GoBackend implements Backend {
|
||||
private static final int DNS_RESOLUTION_RETRIES = 10;
|
||||
private static final String TAG = "WireGuard/GoBackend";
|
||||
private static final String TAG = "AmneziaWG/GoBackend";
|
||||
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
||||
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
|
||||
private final Context context;
|
||||
@@ -71,17 +72,7 @@ public final class GoBackend implements Backend {
|
||||
alwaysOnCallback = cb;
|
||||
}
|
||||
|
||||
@Nullable private static native String wgGetConfig(int handle);
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
private static native int wgGetSocketV6(int handle);
|
||||
|
||||
private static native void wgTurnOff(int handle);
|
||||
|
||||
private static native int wgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
private static native String wgVersion();
|
||||
|
||||
/**
|
||||
* Method to get the names of running tunnels.
|
||||
@@ -120,7 +111,7 @@ public final class GoBackend implements Backend {
|
||||
final Statistics stats = new Statistics();
|
||||
if (tunnel != currentTunnel || currentTunnelHandle == -1)
|
||||
return stats;
|
||||
final String config = wgGetConfig(currentTunnelHandle);
|
||||
final String config = awgGetConfig(currentTunnelHandle);
|
||||
if (config == null)
|
||||
return stats;
|
||||
Key key = null;
|
||||
@@ -179,13 +170,13 @@ public final class GoBackend implements Backend {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the underlying wireguard-go library.
|
||||
* Get the version of the underlying amneziawg-go library.
|
||||
*
|
||||
* @return {@link String} value of the version of the wireguard-go library.
|
||||
* @return {@link String} value of the version of the amneziawg-go library.
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return wgVersion();
|
||||
return awgVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,7 +266,7 @@ public final class GoBackend implements Backend {
|
||||
}
|
||||
|
||||
// Build config
|
||||
final String goConfig = config.toWgUserspaceString();
|
||||
final String goConfig = config.toAwgUserspaceString();
|
||||
|
||||
// Create the vpn tunnel with android API
|
||||
final VpnService.Builder builder = service.getBuilder();
|
||||
@@ -322,8 +313,8 @@ public final class GoBackend implements Backend {
|
||||
try (final ParcelFileDescriptor tun = builder.establish()) {
|
||||
if (tun == null)
|
||||
throw new BackendException(Reason.TUN_CREATION_ERROR);
|
||||
Log.d(TAG, "Go backend " + wgVersion());
|
||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
Log.d(TAG, "Go backend " + awgVersion());
|
||||
currentTunnelHandle = awgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
}
|
||||
if (currentTunnelHandle < 0)
|
||||
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
|
||||
@@ -331,8 +322,8 @@ public final class GoBackend implements Backend {
|
||||
currentTunnel = tunnel;
|
||||
currentConfig = config;
|
||||
|
||||
service.protect(wgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(wgGetSocketV6(currentTunnelHandle));
|
||||
service.protect(awgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(awgGetSocketV6(currentTunnelHandle));
|
||||
} else {
|
||||
if (currentTunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down");
|
||||
@@ -342,7 +333,7 @@ public final class GoBackend implements Backend {
|
||||
currentTunnel = null;
|
||||
currentTunnelHandle = -1;
|
||||
currentConfig = null;
|
||||
wgTurnOff(handleToClose);
|
||||
awgTurnOff(handleToClose);
|
||||
try {
|
||||
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
|
||||
} catch (final TimeoutException ignored) { }
|
||||
@@ -410,7 +401,7 @@ public final class GoBackend implements Backend {
|
||||
final Tunnel tunnel = owner.currentTunnel;
|
||||
if (tunnel != null) {
|
||||
if (owner.currentTunnelHandle != -1)
|
||||
wgTurnOff(owner.currentTunnelHandle);
|
||||
awgTurnOff(owner.currentTunnelHandle);
|
||||
owner.currentTunnel = null;
|
||||
owner.currentTunnelHandle = -1;
|
||||
owner.currentConfig = null;
|
||||
@@ -3,16 +3,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -31,12 +30,12 @@ public class Statistics {
|
||||
/**
|
||||
* Add a peer and its current stats to the internal map.
|
||||
*
|
||||
* @param key A WireGuard public key bound to a particular peer
|
||||
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* @param key An AmneziaWG public key bound to a particular peer
|
||||
* @param rxBytes The received traffic for the {@link org.amnezia.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes
|
||||
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* @param txBytes The transmitted traffic for the {@link org.amnezia.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes.
|
||||
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
|
||||
* @param latestHandshake The timestamp of the latest handshake for the {@link org.amnezia.config.Peer}
|
||||
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
|
||||
*/
|
||||
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
|
||||
@@ -54,9 +53,9 @@ public class Statistics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
|
||||
* Get the statistics for the {@link org.amnezia.config.Peer} referenced by the provided {@link Key}
|
||||
*
|
||||
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
|
||||
* @param peer A {@link Key} representing a {@link org.amnezia.config.Peer}.
|
||||
* @return a {@link PeerStats} representing various statistics about this peer.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -67,8 +66,8 @@ public class Statistics {
|
||||
/**
|
||||
* Get the list of peers being tracked by this instance.
|
||||
*
|
||||
* @return An array of {@link Key} instances representing WireGuard
|
||||
* {@link com.wireguard.config.Peer}s
|
||||
* @return An array of {@link Key} instances representing AmneziaWG
|
||||
* {@link org.amnezia.config.Peer}s
|
||||
*/
|
||||
public Key[] peers() {
|
||||
return stats.keySet().toArray(new Key[0]);
|
||||
@@ -3,14 +3,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
package org.amnezia.awg.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a WireGuard tunnel.
|
||||
* Represents an AmneziaWG tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
@@ -3,10 +3,10 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -78,7 +78,16 @@ public class BadConfigException extends Exception {
|
||||
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
|
||||
PRE_SHARED_KEY("PresharedKey"),
|
||||
PRIVATE_KEY("PrivateKey"),
|
||||
PUBLIC_KEY("PublicKey");
|
||||
PUBLIC_KEY("PublicKey"),
|
||||
JUNK_PACKET_COUNT("JunkPacketCount"),
|
||||
JUNK_PACKET_MIN_SIZE("JunkPacketMinSize"),
|
||||
JUNK_PACKET_MAX_SIZE("JunkPacketMaxSize"),
|
||||
INIT_PACKET_JUNK_SIZE("InitPacketJunkSize"),
|
||||
RESPONSE_PACKET_JUNK_SIZE("ResponsePacketJunkSize"),
|
||||
INIT_PACKET_MAGIC_HEADER("InitPacketMagicHeader"),
|
||||
RESPONSE_PACKET_MAGIC_HEADER("ResponsePacketMagicHeader"),
|
||||
UNDERLOAD_PACKET_MAGIC_HEADER("UnderloadPacketMagicHeader"),
|
||||
TRANSPORT_PACKET_MAGIC_HEADER("TransportPacketMagicHeader");
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -23,7 +23,7 @@ import java.util.Objects;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the contents of a wg-quick configuration file, made up of one or more "Interface"
|
||||
* Represents the contents of a awg-quick configuration file, made up of one or more "Interface"
|
||||
* sections (combined together), and zero or more "Peer" sections (treated individually).
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -44,7 +44,7 @@ public final class Config {
|
||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
|
||||
* be parsed.
|
||||
*
|
||||
* @param stream a stream of UTF-8 text that is interpreted as a WireGuard configuration
|
||||
* @param stream a stream of UTF-8 text that is interpreted as an AmneziaWG configuration
|
||||
* @return a {@code Config} instance representing the supplied configuration
|
||||
*/
|
||||
public static Config parse(final InputStream stream)
|
||||
@@ -57,7 +57,7 @@ public final class Config {
|
||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
|
||||
* be parsed.
|
||||
*
|
||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as a WireGuard configuration
|
||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as an AmneziaWG configuration
|
||||
* @return a {@code Config} instance representing the supplied configuration
|
||||
*/
|
||||
public static Config parse(final BufferedReader reader)
|
||||
@@ -155,30 +155,30 @@ public final class Config {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Config} into a string suitable for use as a {@code wg-quick}
|
||||
* Converts the {@code Config} into a string suitable for use as a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return the {@code Config} represented as one [Interface] and zero or more [Peer] sections
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("[Interface]\n").append(interfaze.toWgQuickString());
|
||||
sb.append("[Interface]\n").append(interfaze.toAwgQuickString());
|
||||
for (final Peer peer : peers)
|
||||
sb.append("\n[Peer]\n").append(peer.toWgQuickString());
|
||||
sb.append("\n[Peer]\n").append(peer.toAwgQuickString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Config} for use with the WireGuard cross-platform userspace API.
|
||||
* Serializes the {@code Config} for use with the AmneziaWG cross-platform userspace API.
|
||||
*
|
||||
* @return the {@code Config} represented as a series of "key=value" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
public String toAwgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(interfaze.toWgUserspaceString());
|
||||
sb.append(interfaze.toAwgUserspaceString());
|
||||
sb.append("replace_peers=true\n");
|
||||
for (final Peer peer : peers)
|
||||
sb.append(peer.toWgUserspaceString());
|
||||
sb.append(peer.toAwgUserspaceString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Inet4Address;
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
@@ -21,7 +21,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* An external endpoint (host and port) used to connect to a WireGuard {@link Peer}.
|
||||
* An external endpoint (host and port) used to connect to an AmneziaWG {@link Peer}.
|
||||
* <p>
|
||||
* Instances of this class are externally immutable.
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ public final class InetEndpoint {
|
||||
throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters");
|
||||
final URI uri;
|
||||
try {
|
||||
uri = new URI("wg://" + endpoint);
|
||||
uri = new URI("awg://" + endpoint);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new ParseException(InetEndpoint.class, endpoint, e);
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
750
tunnel/src/main/java/org/amnezia/awg/config/Interface.java
Normal file
750
tunnel/src/main/java/org/amnezia/awg/config/Interface.java
Normal file
@@ -0,0 +1,750 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.crypto.KeyPair;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the configuration for an AmneziaWG interface (an [Interface] block). Interfaces must
|
||||
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
|
||||
* attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class Interface {
|
||||
private static final int MAX_UDP_PORT = 65535;
|
||||
private static final int MIN_UDP_PORT = 0;
|
||||
|
||||
private final Set<InetNetwork> addresses;
|
||||
private final Set<InetAddress> dnsServers;
|
||||
private final Set<String> dnsSearchDomains;
|
||||
private final Set<String> excludedApplications;
|
||||
private final Set<String> includedApplications;
|
||||
private final KeyPair keyPair;
|
||||
private final Optional<Integer> listenPort;
|
||||
private final Optional<Integer> mtu;
|
||||
private final Optional<Integer> junkPacketCount;
|
||||
private final Optional<Integer> junkPacketMinSize;
|
||||
private final Optional<Integer> junkPacketMaxSize;
|
||||
private final Optional<Integer> initPacketJunkSize;
|
||||
private final Optional<Integer> responsePacketJunkSize;
|
||||
private final Optional<Long> initPacketMagicHeader;
|
||||
private final Optional<Long> responsePacketMagicHeader;
|
||||
private final Optional<Long> underloadPacketMagicHeader;
|
||||
private final Optional<Long> transportPacketMagicHeader;
|
||||
|
||||
private Interface(final Builder builder) {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
|
||||
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
|
||||
dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains));
|
||||
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
|
||||
includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
listenPort = builder.listenPort;
|
||||
mtu = builder.mtu;
|
||||
junkPacketCount = builder.junkPacketCount;
|
||||
junkPacketMinSize = builder.junkPacketMinSize;
|
||||
junkPacketMaxSize = builder.junkPacketMaxSize;
|
||||
initPacketJunkSize = builder.initPacketJunkSize;
|
||||
responsePacketJunkSize = builder.responsePacketJunkSize;
|
||||
initPacketMagicHeader = builder.initPacketMagicHeader;
|
||||
responsePacketMagicHeader = builder.responsePacketMagicHeader;
|
||||
underloadPacketMagicHeader = builder.underloadPacketMagicHeader;
|
||||
transportPacketMagicHeader = builder.transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
|
||||
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
|
||||
*
|
||||
* @param lines An iterable sequence of lines, containing at least a private key attribute
|
||||
* @return An {@code Interface} with all of the attributes from {@code lines} set
|
||||
*/
|
||||
public static Interface parse(final Iterable<? extends CharSequence> lines)
|
||||
throws BadConfigException {
|
||||
final Builder builder = new Builder();
|
||||
for (final CharSequence line : lines) {
|
||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() ->
|
||||
new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.SYNTAX_ERROR, line));
|
||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
|
||||
case "address":
|
||||
builder.parseAddresses(attribute.getValue());
|
||||
break;
|
||||
case "dns":
|
||||
builder.parseDnsServers(attribute.getValue());
|
||||
break;
|
||||
case "excludedapplications":
|
||||
builder.parseExcludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "includedapplications":
|
||||
builder.parseIncludedApplications(attribute.getValue());
|
||||
break;
|
||||
case "listenport":
|
||||
builder.parseListenPort(attribute.getValue());
|
||||
break;
|
||||
case "mtu":
|
||||
builder.parseMtu(attribute.getValue());
|
||||
break;
|
||||
case "privatekey":
|
||||
builder.parsePrivateKey(attribute.getValue());
|
||||
break;
|
||||
case "jc":
|
||||
builder.parseJunkPacketCount(attribute.getValue());
|
||||
break;
|
||||
case "jmin":
|
||||
builder.parseJunkPacketMinSize(attribute.getValue());
|
||||
break;
|
||||
case "jmax":
|
||||
builder.parseJunkPacketMaxSize(attribute.getValue());
|
||||
break;
|
||||
case "s1":
|
||||
builder.parseInitPacketJunkSize(attribute.getValue());
|
||||
break;
|
||||
case "s2":
|
||||
builder.parseResponsePacketJunkSize(attribute.getValue());
|
||||
break;
|
||||
case "h1":
|
||||
builder.parseInitPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h2":
|
||||
builder.parseResponsePacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h3":
|
||||
builder.parseUnderloadPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
case "h4":
|
||||
builder.parseTransportPacketMagicHeader(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
|
||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof Interface))
|
||||
return false;
|
||||
final Interface other = (Interface) obj;
|
||||
return addresses.equals(other.addresses)
|
||||
&& dnsServers.equals(other.dnsServers)
|
||||
&& dnsSearchDomains.equals(other.dnsSearchDomains)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& includedApplications.equals(other.includedApplications)
|
||||
&& keyPair.equals(other.keyPair)
|
||||
&& listenPort.equals(other.listenPort)
|
||||
&& mtu.equals(other.mtu)
|
||||
&& junkPacketCount.equals(other.junkPacketCount)
|
||||
&& junkPacketMinSize.equals(other.junkPacketMinSize)
|
||||
&& junkPacketMaxSize.equals(other.junkPacketMaxSize)
|
||||
&& initPacketJunkSize.equals(other.initPacketJunkSize)
|
||||
&& responsePacketJunkSize.equals(other.responsePacketJunkSize)
|
||||
&& initPacketMagicHeader.equals(other.initPacketMagicHeader)
|
||||
&& responsePacketMagicHeader.equals(other.responsePacketMagicHeader)
|
||||
&& underloadPacketMagicHeader.equals(other.underloadPacketMagicHeader)
|
||||
&& transportPacketMagicHeader.equals(other.transportPacketMagicHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of IP addresses assigned to the interface.
|
||||
*
|
||||
* @return a set of {@link InetNetwork}s
|
||||
*/
|
||||
public Set<InetNetwork> getAddresses() {
|
||||
// The collection is already immutable.
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS servers associated with the interface.
|
||||
*
|
||||
* @return a set of {@link InetAddress}es
|
||||
*/
|
||||
public Set<InetAddress> getDnsServers() {
|
||||
// The collection is already immutable.
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS search domains associated with the interface.
|
||||
*
|
||||
* @return a set of strings
|
||||
*/
|
||||
public Set<String> getDnsSearchDomains() {
|
||||
// The collection is already immutable.
|
||||
return dnsSearchDomains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications excluded from using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getExcludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return excludedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications included exclusively for using the interface.
|
||||
*
|
||||
* @return a set of package names
|
||||
*/
|
||||
public Set<String> getIncludedApplications() {
|
||||
// The collection is already immutable.
|
||||
return includedApplications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public/private key pair used by the interface.
|
||||
*
|
||||
* @return a key pair
|
||||
*/
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UDP port number that the AmneziaWG interface will listen on.
|
||||
*
|
||||
* @return a UDP port number, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MTU used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the MTU, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketCount used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketCount, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketCount() {
|
||||
return junkPacketCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketMinSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketMinSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketMinSize() {
|
||||
return junkPacketMinSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the junkPacketMaxSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the junkPacketMaxSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getJunkPacketMaxSize() {
|
||||
return junkPacketMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initPacketJunkSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the initPacketJunkSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getInitPacketJunkSize() {
|
||||
return initPacketJunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsePacketJunkSize used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the responsePacketJunkSize, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Integer> getResponsePacketJunkSize() {
|
||||
return responsePacketJunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the initPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getInitPacketMagicHeader() {
|
||||
return initPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsePacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the responsePacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getResponsePacketMagicHeader() {
|
||||
return responsePacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underloadPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the underloadPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getUnderloadPacketMagicHeader() {
|
||||
return underloadPacketMagicHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transportPacketMagicHeader used for the AmneziaWG interface.
|
||||
*
|
||||
* @return the transportPacketMagicHeader, or {@code Optional.empty()} if none is configured
|
||||
*/
|
||||
public Optional<Long> getTransportPacketMagicHeader() {
|
||||
return transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 1;
|
||||
hash = 31 * hash + addresses.hashCode();
|
||||
hash = 31 * hash + dnsServers.hashCode();
|
||||
hash = 31 * hash + excludedApplications.hashCode();
|
||||
hash = 31 * hash + includedApplications.hashCode();
|
||||
hash = 31 * hash + keyPair.hashCode();
|
||||
hash = 31 * hash + listenPort.hashCode();
|
||||
hash = 31 * hash + mtu.hashCode();
|
||||
hash = 31 * hash + junkPacketCount.hashCode();
|
||||
hash = 31 * hash + junkPacketMinSize.hashCode();
|
||||
hash = 31 * hash + junkPacketMaxSize.hashCode();
|
||||
hash = 31 * hash + initPacketJunkSize.hashCode();
|
||||
hash = 31 * hash + responsePacketJunkSize.hashCode();
|
||||
hash = 31 * hash + initPacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + responsePacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + underloadPacketMagicHeader.hashCode();
|
||||
hash = 31 * hash + transportPacketMagicHeader.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
|
||||
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
|
||||
*
|
||||
* @return A concise single-line identifier for the {@code Interface}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("(Interface ");
|
||||
sb.append(keyPair.getPublicKey().toBase64());
|
||||
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Interface} into a string suitable for inclusion in a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return The {@code Interface} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!addresses.isEmpty())
|
||||
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
|
||||
if (!dnsServers.isEmpty()) {
|
||||
final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
|
||||
dnsServerStrings.addAll(dnsSearchDomains);
|
||||
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
|
||||
}
|
||||
if (!excludedApplications.isEmpty())
|
||||
sb.append("ExcludedApplications = ").append(Attribute.join(excludedApplications)).append('\n');
|
||||
if (!includedApplications.isEmpty())
|
||||
sb.append("IncludedApplications = ").append(Attribute.join(includedApplications)).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
|
||||
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
|
||||
junkPacketCount.ifPresent(jc -> sb.append("Jc = ").append(jc).append('\n'));
|
||||
junkPacketMinSize.ifPresent(jmin -> sb.append("Jmin = ").append(jmin).append('\n'));
|
||||
junkPacketMaxSize.ifPresent(jmax -> sb.append("Jmax = ").append(jmax).append('\n'));
|
||||
initPacketJunkSize.ifPresent(s1 -> sb.append("S1 = ").append(s1).append('\n'));
|
||||
responsePacketJunkSize.ifPresent(s2 -> sb.append("S2 = ").append(s2).append('\n'));
|
||||
initPacketMagicHeader.ifPresent(h1 -> sb.append("H1 = ").append(h1).append('\n'));
|
||||
responsePacketMagicHeader.ifPresent(h2 -> sb.append("H2 = ").append(h2).append('\n'));
|
||||
underloadPacketMagicHeader.ifPresent(h3 -> sb.append("H3 = ").append(h3).append('\n'));
|
||||
transportPacketMagicHeader.ifPresent(h4 -> sb.append("H4 = ").append(h4).append('\n'));
|
||||
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Interface} for use with the AmneziaWG cross-platform userspace API.
|
||||
* Note that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
|
||||
*/
|
||||
public String toAwgUserspaceString() {
|
||||
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'));
|
||||
junkPacketCount.ifPresent(jc -> sb.append("jc=").append(jc).append('\n'));
|
||||
junkPacketMinSize.ifPresent(jmin -> sb.append("jmin=").append(jmin).append('\n'));
|
||||
junkPacketMaxSize.ifPresent(jmax -> sb.append("jmax=").append(jmax).append('\n'));
|
||||
initPacketJunkSize.ifPresent(s1 -> sb.append("s1=").append(s1).append('\n'));
|
||||
responsePacketJunkSize.ifPresent(s2 -> sb.append("s2=").append(s2).append('\n'));
|
||||
initPacketMagicHeader.ifPresent(h1 -> sb.append("h1=").append(h1).append('\n'));
|
||||
responsePacketMagicHeader.ifPresent(h2 -> sb.append("h2=").append(h2).append('\n'));
|
||||
underloadPacketMagicHeader.ifPresent(h3 -> sb.append("h3=").append(h3).append('\n'));
|
||||
transportPacketMagicHeader.ifPresent(h4 -> sb.append("h4=").append(h4).append('\n'));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> dnsSearchDomains = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> excludedApplications = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> includedApplications = new LinkedHashSet<>();
|
||||
// No default; must be provided before building.
|
||||
@Nullable private KeyPair keyPair;
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> listenPort = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> mtu = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketCount = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketMinSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> junkPacketMaxSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> initPacketJunkSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> responsePacketJunkSize = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> initPacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> responsePacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> underloadPacketMagicHeader = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Long> transportPacketMagicHeader = Optional.empty();
|
||||
|
||||
|
||||
public Builder addAddress(final InetNetwork address) {
|
||||
addresses.add(address);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAddresses(final Collection<InetNetwork> addresses) {
|
||||
this.addresses.addAll(addresses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServer(final InetAddress dnsServer) {
|
||||
dnsServers.add(dnsServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
|
||||
this.dnsServers.addAll(dnsServers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomain(final String dnsSearchDomain) {
|
||||
dnsSearchDomains.add(dnsSearchDomain);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) {
|
||||
this.dnsSearchDomains.addAll(dnsSearchDomains);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Interface build() throws BadConfigException {
|
||||
if (keyPair == null)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
|
||||
Reason.MISSING_ATTRIBUTE, null);
|
||||
if (!includedApplications.isEmpty() && !excludedApplications.isEmpty())
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INCLUDED_APPLICATIONS,
|
||||
Reason.INVALID_KEY, null);
|
||||
return new Interface(this);
|
||||
}
|
||||
|
||||
public Builder excludeApplication(final String application) {
|
||||
excludedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder excludeApplications(final Collection<String> applications) {
|
||||
excludedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplication(final String application) {
|
||||
includedApplications.add(application);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder includeApplications(final Collection<String> applications) {
|
||||
includedApplications.addAll(applications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
|
||||
try {
|
||||
for (final String address : Attribute.split(addresses))
|
||||
addAddress(InetNetwork.parse(address));
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
|
||||
try {
|
||||
for (final String dnsServer : Attribute.split(dnsServers)) {
|
||||
try {
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
} catch (final ParseException e) {
|
||||
if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer))
|
||||
throw e;
|
||||
addDnsSearchDomain(dnsServer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseExcludedApplications(final CharSequence apps) {
|
||||
return excludeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseIncludedApplications(final CharSequence apps) {
|
||||
return includeApplications(List.of(Attribute.split(apps)));
|
||||
}
|
||||
|
||||
public Builder parseListenPort(final String listenPort) throws BadConfigException {
|
||||
try {
|
||||
return setListenPort(Integer.parseInt(listenPort));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseMtu(final String mtu) throws BadConfigException {
|
||||
try {
|
||||
return setMtu(Integer.parseInt(mtu));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketCount(final String junkPacketCount) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketCount(Integer.parseInt(junkPacketCount));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_COUNT, junkPacketCount, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketMinSize(final String junkPacketMinSize) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketMinSize(Integer.parseInt(junkPacketMinSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MIN_SIZE, junkPacketMinSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJunkPacketMaxSize(final String junkPacketMaxSize) throws BadConfigException {
|
||||
try {
|
||||
return setJunkPacketMaxSize(Integer.parseInt(junkPacketMaxSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MAX_SIZE, junkPacketMaxSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseInitPacketJunkSize(final String initPacketJunkSize) throws BadConfigException {
|
||||
try {
|
||||
return setInitPacketJunkSize(Integer.parseInt(initPacketJunkSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_JUNK_SIZE, initPacketJunkSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseResponsePacketJunkSize(final String responsePacketJunkSize) throws BadConfigException {
|
||||
try {
|
||||
return setResponsePacketJunkSize(Integer.parseInt(responsePacketJunkSize));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_JUNK_SIZE, responsePacketJunkSize, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseInitPacketMagicHeader(final String initPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setInitPacketMagicHeader(Long.parseLong(initPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_MAGIC_HEADER, initPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseResponsePacketMagicHeader(final String responsePacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
|
||||
return setResponsePacketMagicHeader(Long.parseLong(responsePacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_MAGIC_HEADER, responsePacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseUnderloadPacketMagicHeader(final String underloadPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setUnderloadPacketMagicHeader(Long.parseLong(underloadPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.UNDERLOAD_PACKET_MAGIC_HEADER, underloadPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseTransportPacketMagicHeader(final String transportPacketMagicHeader) throws BadConfigException {
|
||||
try {
|
||||
return setTransportPacketMagicHeader(Long.parseLong(transportPacketMagicHeader));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TRANSPORT_PACKET_MAGIC_HEADER, transportPacketMagicHeader, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
|
||||
try {
|
||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
|
||||
} catch (final KeyFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setKeyPair(final KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setListenPort(final int listenPort) throws BadConfigException {
|
||||
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT,
|
||||
Reason.INVALID_VALUE, String.valueOf(listenPort));
|
||||
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMtu(final int mtu) throws BadConfigException {
|
||||
if (mtu < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.MTU,
|
||||
Reason.INVALID_VALUE, String.valueOf(mtu));
|
||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketCount(final int junkPacketCount) throws BadConfigException {
|
||||
if (junkPacketCount < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_COUNT,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketCount));
|
||||
this.junkPacketCount = junkPacketCount == 0 ? Optional.empty() : Optional.of(junkPacketCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketMinSize(final int junkPacketMinSize) throws BadConfigException {
|
||||
if (junkPacketMinSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MIN_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketMinSize));
|
||||
this.junkPacketMinSize = junkPacketMinSize == 0 ? Optional.empty() : Optional.of(junkPacketMinSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJunkPacketMaxSize(final int junkPacketMaxSize) throws BadConfigException {
|
||||
if (junkPacketMaxSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JUNK_PACKET_MAX_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(junkPacketMaxSize));
|
||||
this.junkPacketMaxSize = junkPacketMaxSize == 0 ? Optional.empty() : Optional.of(junkPacketMaxSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setInitPacketJunkSize(final int initPacketJunkSize) throws BadConfigException {
|
||||
if (initPacketJunkSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_JUNK_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(initPacketJunkSize));
|
||||
this.initPacketJunkSize = initPacketJunkSize == 0 ? Optional.empty() : Optional.of(initPacketJunkSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResponsePacketJunkSize(final int responsePacketJunkSize) throws BadConfigException {
|
||||
if (responsePacketJunkSize < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_JUNK_SIZE,
|
||||
Reason.INVALID_VALUE, String.valueOf(responsePacketJunkSize));
|
||||
this.responsePacketJunkSize = responsePacketJunkSize == 0 ? Optional.empty() : Optional.of(responsePacketJunkSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setInitPacketMagicHeader(final long initPacketMagicHeader) throws BadConfigException {
|
||||
if (initPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.INIT_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(initPacketMagicHeader));
|
||||
this.initPacketMagicHeader = initPacketMagicHeader == 0 ? Optional.empty() : Optional.of(initPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResponsePacketMagicHeader(final long responsePacketMagicHeader) throws BadConfigException {
|
||||
if (responsePacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.RESPONSE_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(responsePacketMagicHeader));
|
||||
this.responsePacketMagicHeader = responsePacketMagicHeader == 0 ? Optional.empty() : Optional.of(responsePacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnderloadPacketMagicHeader(final long underloadPacketMagicHeader) throws BadConfigException {
|
||||
if (underloadPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.UNDERLOAD_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(underloadPacketMagicHeader));
|
||||
this.underloadPacketMagicHeader = underloadPacketMagicHeader == 0 ? Optional.empty() : Optional.of(underloadPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTransportPacketMagicHeader(final long transportPacketMagicHeader) throws BadConfigException {
|
||||
if (transportPacketMagicHeader < 0)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.TRANSPORT_PACKET_MAGIC_HEADER,
|
||||
Reason.INVALID_VALUE, String.valueOf(transportPacketMagicHeader));
|
||||
this.transportPacketMagicHeader = transportPacketMagicHeader == 0 ? Optional.empty() : Optional.of(transportPacketMagicHeader);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.crypto.Key;
|
||||
import org.amnezia.awg.crypto.KeyFormatException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -23,7 +23,7 @@ import java.util.Set;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key,
|
||||
* Represents the configuration for an AmneziaWG peer (a [Peer] block). Peers must have a public key,
|
||||
* and may optionally have several other attributes.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -168,12 +168,12 @@ public final class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code Peer} into a string suitable for inclusion in a {@code wg-quick}
|
||||
* Converts the {@code Peer} into a string suitable for inclusion in a {@code awg-quick}
|
||||
* configuration file.
|
||||
*
|
||||
* @return the {@code Peer} represented as a series of "Key = Value" lines
|
||||
*/
|
||||
public String toWgQuickString() {
|
||||
public String toAwgQuickString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!allowedIps.isEmpty())
|
||||
sb.append("AllowedIPs = ").append(Attribute.join(allowedIps)).append('\n');
|
||||
@@ -185,12 +185,12 @@ public final class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@code Peer} for use with the WireGuard cross-platform userspace API. Note
|
||||
* Serializes the {@code Peer} for use with the AmneziaWG cross-platform userspace API. Note
|
||||
* that not all attributes are included in this representation.
|
||||
*
|
||||
* @return the {@code Peer} represented as a series of "key=value" lines
|
||||
*/
|
||||
public String toWgUserspaceString() {
|
||||
public String toAwgUserspaceString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
// The order here is important: public_key signifies the beginning of a new peer.
|
||||
sb.append("public_key=").append(publicKey.toHex()).append('\n');
|
||||
@@ -204,7 +204,7 @@ public final class Peer {
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static final class Builder {
|
||||
// See wg(8)
|
||||
// See awg(8)
|
||||
private static final int MAX_PERSISTENT_KEEPALIVE = 65535;
|
||||
|
||||
// Defaults to an empty set.
|
||||
@@ -4,9 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
|
||||
/**
|
||||
* Implementation of Curve25519 ECDH.
|
||||
* <p>
|
||||
* This implementation was imported to WireGuard from noise-java:
|
||||
* This implementation was imported to AmneziaWG from noise-java:
|
||||
* https://github.com/rweather/noise-java
|
||||
* <p>
|
||||
* This implementation is based on that from arduinolibs:
|
||||
@@ -3,17 +3,17 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.crypto.KeyFormatException.Type;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.crypto.KeyFormatException.Type;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents a WireGuard public or private key. This class uses specialized constant-time base64
|
||||
* Represents an AmneziaWG public or private key. This class uses specialized constant-time base64
|
||||
* and hexadecimal codec implementations that resist side-channel attacks.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
@@ -83,10 +83,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a WireGuard public or private key from its base64 string representation. This
|
||||
* Decodes an AmneziaWG public or private key from its base64 string representation. This
|
||||
* function throws a {@link KeyFormatException} if the source string is not well-formed.
|
||||
*
|
||||
* @param str the base64 string representation of a WireGuard key
|
||||
* @param str the base64 string representation of an AmneziaWG key
|
||||
* @return the decoded key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromBase64(final String str) throws KeyFormatException {
|
||||
@@ -120,10 +120,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a WireGuard public or private key in an immutable container. This function throws a
|
||||
* Wraps an AmneziaWG public or private key in an immutable container. This function throws a
|
||||
* {@link KeyFormatException} if the source data is not the correct length.
|
||||
*
|
||||
* @param bytes an array of bytes containing a WireGuard key in binary format
|
||||
* @param bytes an array of bytes containing an AmneziaWG key in binary format
|
||||
* @return the key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromBytes(final byte[] bytes) throws KeyFormatException {
|
||||
@@ -133,10 +133,10 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a WireGuard public or private key from its hexadecimal string representation. This
|
||||
* Decodes an AmneziaWG public or private key from its hexadecimal string representation. This
|
||||
* function throws a {@link KeyFormatException} if the source string is not well-formed.
|
||||
*
|
||||
* @param str the hexadecimal string representation of a WireGuard key
|
||||
* @param str the hexadecimal string representation of an AmneziaWG key
|
||||
* @return the decoded key encapsulated in an immutable container
|
||||
*/
|
||||
public static Key fromHex(final String str) throws KeyFormatException {
|
||||
@@ -269,7 +269,7 @@ public final class Key {
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported formats for encoding a WireGuard key.
|
||||
* The supported formats for encoding an AmneziaWG key.
|
||||
*/
|
||||
public enum Format {
|
||||
BASE64(44),
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* An exception thrown when attempting to parse an invalid key (too short, too long, or byte
|
||||
@@ -3,12 +3,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
package org.amnezia.awg.crypto;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* Represents a Curve25519 key pair as used by WireGuard.
|
||||
* Represents a Curve25519 key pair as used by AmneziaWG.
|
||||
* <p>
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -3,13 +3,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException.Reason;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.RootShell.RootShellException.Reason;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -29,7 +29,7 @@ import androidx.annotation.Nullable;
|
||||
@NonNullForAll
|
||||
public class RootShell {
|
||||
private static final String SU = "su";
|
||||
private static final String TAG = "WireGuard/RootShell";
|
||||
private static final String TAG = "AmneziaWG/RootShell";
|
||||
|
||||
private final File localBinaryDir;
|
||||
private final File localTemporaryDir;
|
||||
@@ -3,13 +3,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -27,7 +27,7 @@ import androidx.annotation.RestrictTo.Scope;
|
||||
@NonNullForAll
|
||||
@RestrictTo(Scope.LIBRARY_GROUP)
|
||||
public final class SharedLibraryLoader {
|
||||
private static final String TAG = "WireGuard/SharedLibraryLoader";
|
||||
private static final String TAG = "AmneziaWG/SharedLibraryLoader";
|
||||
|
||||
private SharedLibraryLoader() {
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
package org.amnezia.awg.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
import org.amnezia.awg.util.RootShell.RootShellException;
|
||||
import org.amnezia.awg.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -23,7 +23,7 @@ import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.RestrictTo.Scope;
|
||||
|
||||
/**
|
||||
* Helper to install WireGuard tools to the system partition.
|
||||
* Helper to install AmneziaWG tools to the system partition.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -33,13 +33,13 @@ public final class ToolsInstaller {
|
||||
public static final int NO = 0x2;
|
||||
public static final int SYSTEM = 0x8;
|
||||
public static final int YES = 0x1;
|
||||
private static final String[] EXECUTABLES = {"wg", "wg-quick"};
|
||||
private static final String[] EXECUTABLES = {"awg", "awg-quick"};
|
||||
private static final File[] INSTALL_DIRS = {
|
||||
new File("/system/xbin"),
|
||||
new File("/system/bin"),
|
||||
};
|
||||
@Nullable private static final File INSTALL_DIR = getInstallDir();
|
||||
private static final String TAG = "WireGuard/ToolsInstaller";
|
||||
private static final String TAG = "AmneziaWG/ToolsInstaller";
|
||||
|
||||
private final Context context;
|
||||
private final File localBinaryDir;
|
||||
@@ -100,7 +100,7 @@ public final class ToolsInstaller {
|
||||
"Tools were already extracted into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "The wg and wg-quick tools are not available", e);
|
||||
Log.e(TAG, "The awg and awg-quick tools are not available", e);
|
||||
areToolsAvailable = false;
|
||||
}
|
||||
}
|
||||
@@ -134,8 +134,8 @@ public final class ToolsInstaller {
|
||||
|
||||
@RestrictTo(Scope.LIBRARY_GROUP)
|
||||
public int install() throws RootShellException, IOException {
|
||||
if (!context.getPackageName().startsWith("com.wireguard."))
|
||||
throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app.");
|
||||
if (!context.getPackageName().startsWith("org.amnezia."))
|
||||
throw new SecurityException("The tools may only be installed system-wide from the main AmneziaWG app.");
|
||||
return willInstallAsMagiskModule() ? installMagisk() : installSystem();
|
||||
}
|
||||
|
||||
@@ -143,12 +143,12 @@ public final class ToolsInstaller {
|
||||
extract();
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
|
||||
script.append("trap 'rm -rf /data/adb/modules/wireguard' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /data/adb/modules/wireguard/; mkdir -p /data/adb/modules/wireguard%s; ", INSTALL_DIR));
|
||||
script.append("printf 'id=wireguard\nname=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; ");
|
||||
script.append("touch /data/adb/modules/wireguard/auto_mount; ");
|
||||
script.append("trap 'rm -rf /data/adb/modules/amneziawg' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /data/adb/modules/amneziawg/; mkdir -p /data/adb/modules/amneziawg%s; ", INSTALL_DIR));
|
||||
script.append("printf 'id=amneziawg\nname=AmneziaWG Command Line Tools\nversion=1.0\nversionCode=1\nauthor=amnezia\ndescription=Command line tools for AmneziaWG\nminMagisk=1500\n' > /data/adb/modules/amneziawg/module.prop; ");
|
||||
script.append("touch /data/adb/modules/amneziawg/auto_mount; ");
|
||||
for (final String name : EXECUTABLES) {
|
||||
final File destination = new File("/data/adb/modules/wireguard" + INSTALL_DIR, name);
|
||||
final File destination = new File("/data/adb/modules/amneziawg" + INSTALL_DIR, name);
|
||||
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ",
|
||||
new File(localBinaryDir, name), destination, destination, destination));
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.BadConfigException.Reason;
|
||||
import com.wireguard.config.BadConfigException.Section;
|
||||
import org.amnezia.awg.config.BadConfigException.Location;
|
||||
import org.amnezia.awg.config.BadConfigException.Reason;
|
||||
import org.amnezia.awg.config.BadConfigException.Section;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
package org.amnezia.awg.config;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -9,7 +9,7 @@ add_link_options(LINKER:--build-id=none)
|
||||
add_compile_options(-Wall -Werror)
|
||||
|
||||
add_executable(libwg-quick.so amneziawg-tools/src/wg-quick/android.c ndk-compat/compat.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
target_compile_options(libwg-quick.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DAWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
target_link_libraries(libwg-quick.so -ldl)
|
||||
|
||||
file(GLOB WG_SOURCES amneziawg-tools/src/*.c ndk-compat/compat.c)
|
||||
|
||||
Submodule tunnel/tools/amneziawg-tools updated: af245c000e...d33c4b6936
@@ -20,12 +20,12 @@ export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
|
||||
export GOOS := android
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
GO_VERSION := 1.20.3
|
||||
GO_VERSION := 1.21.3
|
||||
GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m))
|
||||
GO_TARBALL := go$(GO_VERSION).$(GO_PLATFORM).tar.gz
|
||||
GO_HASH_darwin-amd64 := c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565
|
||||
GO_HASH_darwin-arm64 := 86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95
|
||||
GO_HASH_linux-amd64 := 979694c2c25c735755bf26f4f45e19e64e4811d661dd07b8c010f7a8e18adfca
|
||||
GO_HASH_darwin-amd64 := 27014fc69e301d7588a169ca239b3cc609f0aa1abf38528bf0d20d3b259211eb
|
||||
GO_HASH_darwin-arm64 := 65302a7a9f7a4834932b3a7a14cb8be51beddda757b567a2f9e0cbd0d7b5a6ab
|
||||
GO_HASH_linux-amd64 := 1241381b2843fae5a9707eec1f8fb2ef94d827990582c7c7c32f5bdfbfd420c8
|
||||
|
||||
default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ func init() {
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
|
||||
//export awgTurnOn
|
||||
func awgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
|
||||
tag := cstring("AmneziaWG/" + interfaceName)
|
||||
logger := &device.Logger{
|
||||
Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
|
||||
@@ -146,8 +146,8 @@ func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
|
||||
return i
|
||||
}
|
||||
|
||||
//export wgTurnOff
|
||||
func wgTurnOff(tunnelHandle int32) {
|
||||
//export awgTurnOff
|
||||
func awgTurnOff(tunnelHandle int32) {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
@@ -159,8 +159,8 @@ func wgTurnOff(tunnelHandle int32) {
|
||||
handle.device.Close()
|
||||
}
|
||||
|
||||
//export wgGetSocketV4
|
||||
func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
//export awgGetSocketV4
|
||||
func awgGetSocketV4(tunnelHandle int32) int32 {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
@@ -176,8 +176,8 @@ func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
return int32(fd)
|
||||
}
|
||||
|
||||
//export wgGetSocketV6
|
||||
func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
//export awgGetSocketV6
|
||||
func awgGetSocketV6(tunnelHandle int32) int32 {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
@@ -193,8 +193,8 @@ func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
return int32(fd)
|
||||
}
|
||||
|
||||
//export wgGetConfig
|
||||
func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
//export awgGetConfig
|
||||
func awgGetConfig(tunnelHandle int32) *C.char {
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -206,8 +206,8 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
return C.CString(settings)
|
||||
}
|
||||
|
||||
//export wgVersion
|
||||
func wgVersion() *C.char {
|
||||
//export awgVersion
|
||||
func awgVersion() *C.char {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return C.CString("unknown")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module github.com/amnezia-vpn/amneziawg-android
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.1
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.5
|
||||
golang.org/x/sys v0.17.0
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.1 h1:cYcHr/3PgtrQhwueE+qZh3i01ZO0jpgJMcwvsAwoKW8=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.1/go.mod h1:12g0XRbFeGbpXvuCmBOV21YxLWSFnUFJnwgrzyHBUyk=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.5 h1:V5uvzWhxyPLuhWeh52VPG4sAOU7TpUMwQbkofADtJdI=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.5/go.mod h1:12g0XRbFeGbpXvuCmBOV21YxLWSFnUFJnwgrzyHBUyk=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
|
||||
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
@@ -10,6 +11,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
#include <string.h>
|
||||
|
||||
struct go_string { const char *str; long n; };
|
||||
extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings);
|
||||
extern void wgTurnOff(int handle);
|
||||
extern int wgGetSocketV4(int handle);
|
||||
extern int wgGetSocketV6(int handle);
|
||||
extern char *wgGetConfig(int handle);
|
||||
extern char *wgVersion();
|
||||
extern int awgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings);
|
||||
extern void awgTurnOff(int handle);
|
||||
extern int awgGetSocketV4(int handle);
|
||||
extern int awgGetSocketV6(int handle);
|
||||
extern char *awgGetConfig(int handle);
|
||||
extern char *awgVersion();
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
|
||||
{
|
||||
const char *ifname_str = (*env)->GetStringUTFChars(env, ifname, 0);
|
||||
size_t ifname_len = (*env)->GetStringUTFLength(env, ifname);
|
||||
const char *settings_str = (*env)->GetStringUTFChars(env, settings, 0);
|
||||
size_t settings_len = (*env)->GetStringUTFLength(env, settings);
|
||||
int ret = wgTurnOn((struct go_string){
|
||||
int ret = awgTurnOn((struct go_string){
|
||||
.str = ifname_str,
|
||||
.n = ifname_len
|
||||
}, tun_fd, (struct go_string){
|
||||
@@ -33,25 +33,25 @@ JNIEXPORT jint JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgTurnO
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgTurnOff(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT void JNICALL Java_org_amnezia_awg_GoBackend_awgTurnOff(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
wgTurnOff(handle);
|
||||
awgTurnOff(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgGetSocketV4(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgGetSocketV4(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
return wgGetSocketV4(handle);
|
||||
return awgGetSocketV4(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgGetSocketV6(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jint JNICALL Java_org_amnezia_awg_GoBackend_awgGetSocketV6(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
return wgGetSocketV6(handle);
|
||||
return awgGetSocketV6(handle);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgGetConfig(JNIEnv *env, jclass c, jint handle)
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_awg_GoBackend_awgGetConfig(JNIEnv *env, jclass c, jint handle)
|
||||
{
|
||||
jstring ret;
|
||||
char *config = wgGetConfig(handle);
|
||||
char *config = awgGetConfig(handle);
|
||||
if (!config)
|
||||
return NULL;
|
||||
ret = (*env)->NewStringUTF(env, config);
|
||||
@@ -59,10 +59,10 @@ JNIEXPORT jstring JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgGe
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_vpn_protocol_wireguard_GoBackend_wgVersion(JNIEnv *env, jclass c)
|
||||
JNIEXPORT jstring JNICALL Java_org_amnezia_awg_GoBackend_awgVersion(JNIEnv *env, jclass c)
|
||||
{
|
||||
jstring ret;
|
||||
char *version = wgVersion();
|
||||
char *version = awgVersion();
|
||||
if (!version)
|
||||
return NULL;
|
||||
ret = (*env)->NewStringUTF(env, version);
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
"names": [
|
||||
{
|
||||
"names": [
|
||||
{ "name": "wg0" },
|
||||
{ "name": "wg1" },
|
||||
{ "name": "wg2" },
|
||||
{ "name": "wg3" },
|
||||
{ "name": "wg4" },
|
||||
{ "name": "wg5" },
|
||||
{ "name": "wg6" },
|
||||
{ "name": "wg7" },
|
||||
{ "name": "wg8" },
|
||||
{ "name": "wg9" },
|
||||
{ "name": "wg10" },
|
||||
{ "name": "wg11" }
|
||||
{ "name": "awg0" },
|
||||
{ "name": "awg1" },
|
||||
{ "name": "awg2" },
|
||||
{ "name": "awg3" },
|
||||
{ "name": "awg4" },
|
||||
{ "name": "awg5" },
|
||||
{ "name": "awg6" },
|
||||
{ "name": "awg7" },
|
||||
{ "name": "awg8" },
|
||||
{ "name": "awg9" },
|
||||
{ "name": "awg10" },
|
||||
{ "name": "awg11" }
|
||||
],
|
||||
"checked": [
|
||||
{ "checked": true },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">WireGuard β</string>
|
||||
<string name="app_name" translatable="false">AmneziaWG β</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
|
||||
tools:node="remove" />
|
||||
</manifest>
|
||||
@@ -6,7 +6,9 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.SYSTEM_ALERT_WINDOW"
|
||||
android:minSdkVersion="34" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
@@ -111,22 +113,14 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".updater.Updater$AppUpdatedReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".model.TunnelManager$IntentReceiver"
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||
<intent-filter>
|
||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_DOWN" />
|
||||
<action android:name="org.amnezia.awg.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="org.amnezia.awg.action.SET_TUNNEL_UP" />
|
||||
<action android:name="org.amnezia.awg.action.SET_TUNNEL_DOWN" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.updater.Updater
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
override fun getSummary() = context.getString(R.string.donate_summary)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.donate_title)
|
||||
|
||||
override fun onClick() {
|
||||
/* Google Play Store forbids links to our donation page. */
|
||||
if (Updater.installerIsGooglePlay(context)) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.donate_title)
|
||||
.setMessage(R.string.donate_google_play_disappointment)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("https://www.wireguard.com/donations/")
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class SnackbarUpdateShower(private val fragment: Fragment) {
|
||||
private var lastUserIntervention: Updater.Progress.NeedsUserIntervention? = null
|
||||
private val intentLauncher = fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
lastUserIntervention?.markAsDone()
|
||||
}
|
||||
|
||||
private class SwapableSnackbar(fragment: Fragment, view: View, anchor: View?) {
|
||||
private val actionSnackbar = makeSnackbar(fragment, view, anchor)
|
||||
private val statusSnackbar = makeSnackbar(fragment, view, anchor)
|
||||
private var showingAction: Boolean = false
|
||||
private var showingStatus: Boolean = false
|
||||
|
||||
private fun makeSnackbar(fragment: Fragment, view: View, anchor: View?): Snackbar {
|
||||
val snackbar = Snackbar.make(fragment.requireContext(), view, "", Snackbar.LENGTH_INDEFINITE)
|
||||
if (anchor != null)
|
||||
snackbar.anchorView = anchor
|
||||
snackbar.setTextMaxLines(6)
|
||||
snackbar.behavior = object : BaseTransientBottomBar.Behavior() {
|
||||
override fun canSwipeDismissView(child: View): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(snackbar: Snackbar?, @DismissEvent event: Int) {
|
||||
super.onDismissed(snackbar, event)
|
||||
if (event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_ACTION ||
|
||||
(snackbar == actionSnackbar && !showingAction) || (snackbar == statusSnackbar && !showingStatus)
|
||||
)
|
||||
return
|
||||
fragment.lifecycleScope.launch {
|
||||
delay(5.seconds)
|
||||
snackbar?.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
return snackbar
|
||||
}
|
||||
|
||||
fun showAction(text: String, action: String, listener: View.OnClickListener) {
|
||||
if (showingStatus) {
|
||||
showingStatus = false
|
||||
statusSnackbar.dismiss()
|
||||
}
|
||||
actionSnackbar.setText(text)
|
||||
actionSnackbar.setAction(action, listener)
|
||||
if (!showingAction) {
|
||||
actionSnackbar.show()
|
||||
showingAction = true
|
||||
}
|
||||
}
|
||||
|
||||
fun showText(text: String) {
|
||||
if (showingAction) {
|
||||
showingAction = false
|
||||
actionSnackbar.dismiss()
|
||||
}
|
||||
statusSnackbar.setText(text)
|
||||
if (!showingStatus) {
|
||||
statusSnackbar.show()
|
||||
showingStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
actionSnackbar.dismiss()
|
||||
statusSnackbar.dismiss()
|
||||
showingAction = false
|
||||
showingStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
fun attach(view: View, anchor: View?) {
|
||||
val snackbar = SwapableSnackbar(fragment, view, anchor)
|
||||
val context = fragment.requireContext()
|
||||
|
||||
Updater.state.onEach { progress ->
|
||||
when (progress) {
|
||||
is Updater.Progress.Complete ->
|
||||
snackbar.dismiss()
|
||||
|
||||
is Updater.Progress.Available ->
|
||||
snackbar.showAction(context.getString(R.string.updater_avalable), context.getString(R.string.updater_action)) {
|
||||
progress.update()
|
||||
}
|
||||
|
||||
is Updater.Progress.NeedsUserIntervention -> {
|
||||
lastUserIntervention = progress
|
||||
intentLauncher.launch(progress.intent)
|
||||
}
|
||||
|
||||
is Updater.Progress.Installing ->
|
||||
snackbar.showText(context.getString(R.string.updater_installing))
|
||||
|
||||
is Updater.Progress.Rechecking ->
|
||||
snackbar.showText(context.getString(R.string.updater_rechecking))
|
||||
|
||||
is Updater.Progress.Downloading -> {
|
||||
if (progress.bytesTotal != 0UL) {
|
||||
snackbar.showText(
|
||||
context.getString(
|
||||
R.string.updater_download_progress,
|
||||
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong()),
|
||||
QuantityFormatter.formatBytes(progress.bytesTotal.toLong()),
|
||||
progress.bytesDownloaded.toFloat() * 100.0 / progress.bytesTotal.toFloat()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
snackbar.showText(
|
||||
context.getString(
|
||||
R.string.updater_download_progress_nototal,
|
||||
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is Updater.Progress.Failure -> {
|
||||
snackbar.showText(context.getString(R.string.updater_failure, ErrorMessages[progress.error]))
|
||||
delay(5.seconds)
|
||||
progress.retry()
|
||||
}
|
||||
|
||||
is Updater.Progress.Corrupt -> {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.updater_corrupt_title)
|
||||
.setMessage(R.string.updater_corrupt_message)
|
||||
.setPositiveButton(R.string.updater_corrupt_navigate) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(progress.downloadUrl)
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}.setCancelable(false).setOnDismissListener {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
System.exit(0)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}.launchIn(fragment.lifecycleScope)
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.Manifest
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.activity.MainActivity
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.InvalidParameterException
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object Updater {
|
||||
private const val TAG = "WireGuard/Updater"
|
||||
private const val UPDATE_URL_FMT = "https://download.wireguard.com/android-client/%s"
|
||||
private const val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID + "-"
|
||||
private const val APK_NAME_SUFFIX = ".apk"
|
||||
private const val LATEST_FILE = "latest.sig"
|
||||
private const val RELEASE_PUBLIC_KEY_BASE64 = "RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
|
||||
private val CURRENT_VERSION by lazy { Version(BuildConfig.VERSION_NAME) }
|
||||
|
||||
private val updaterScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
private fun installer(context: Context): String = try {
|
||||
val packageName = context.packageName
|
||||
val pm = context.packageManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getInstallerPackageName(packageName) ?: ""
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
""
|
||||
}
|
||||
|
||||
fun installerIsGooglePlay(context: Context): Boolean = installer(context) == "com.android.vending"
|
||||
|
||||
sealed class Progress {
|
||||
object Complete : Progress()
|
||||
class Available(val version: String) : Progress() {
|
||||
fun update() {
|
||||
applicationScope.launch {
|
||||
UserKnobs.setUpdaterNewerVersionConsented(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Rechecking : Progress()
|
||||
class Downloading(val bytesDownloaded: ULong, val bytesTotal: ULong) : Progress()
|
||||
object Installing : Progress()
|
||||
class NeedsUserIntervention(val intent: Intent, private val id: Int) : Progress() {
|
||||
|
||||
private suspend fun installerActive(): Boolean {
|
||||
if (mutableState.firstOrNull() != this@NeedsUserIntervention)
|
||||
return true
|
||||
try {
|
||||
if (Application.get().packageManager.packageInstaller.getSessionInfo(id)?.isActive == true)
|
||||
return true
|
||||
} catch (_: SecurityException) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun markAsDone() {
|
||||
applicationScope.launch {
|
||||
if (installerActive())
|
||||
return@launch
|
||||
delay(7.seconds)
|
||||
if (installerActive())
|
||||
return@launch
|
||||
emitProgress(Failure(Exception("Ignored by user")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Failure(val error: Throwable) : Progress() {
|
||||
fun retry() {
|
||||
updaterScope.launch {
|
||||
downloadAndUpdateWrapErrors()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Corrupt(private val betterFile: String?) : Progress() {
|
||||
val downloadUrl: String
|
||||
get() = UPDATE_URL_FMT.format(betterFile ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
private val mutableState = MutableStateFlow<Progress>(Progress.Complete)
|
||||
val state = mutableState.asStateFlow()
|
||||
|
||||
private suspend fun emitProgress(progress: Progress, force: Boolean = false) {
|
||||
if (force || mutableState.firstOrNull()?.javaClass != progress.javaClass)
|
||||
mutableState.emit(progress)
|
||||
}
|
||||
|
||||
private class Sha256Digest(hex: String) {
|
||||
val bytes: ByteArray
|
||||
|
||||
init {
|
||||
if (hex.length != 64)
|
||||
throw InvalidParameterException("SHA256 hashes must be 32 bytes long")
|
||||
bytes = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
private class Version(version: String) : Comparable<Version> {
|
||||
val parts: ULongArray
|
||||
|
||||
init {
|
||||
val strParts = version.split(".")
|
||||
if (strParts.isEmpty())
|
||||
throw InvalidParameterException("Version has no parts")
|
||||
parts = ULongArray(strParts.size)
|
||||
for (i in parts.indices) {
|
||||
parts[i] = strParts[i].toULong()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return parts.joinToString(".")
|
||||
}
|
||||
|
||||
override fun compareTo(other: Version): Int {
|
||||
for (i in 0 until max(parts.size, other.parts.size)) {
|
||||
val lhsPart = if (i < parts.size) parts[i] else 0UL
|
||||
val rhsPart = if (i < other.parts.size) other.parts[i] else 0UL
|
||||
if (lhsPart > rhsPart)
|
||||
return 1
|
||||
else if (lhsPart < rhsPart)
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private class Update(val fileName: String, val version: Version, val hash: Sha256Digest)
|
||||
|
||||
private fun versionOfFile(name: String): Version? {
|
||||
if (!name.startsWith(APK_NAME_PREFIX) || !name.endsWith(APK_NAME_SUFFIX))
|
||||
return null
|
||||
return try {
|
||||
Version(name.substring(APK_NAME_PREFIX.length, name.length - APK_NAME_SUFFIX.length))
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifySignedFileList(signifyDigest: String): List<Update> {
|
||||
val updates = ArrayList<Update>(1)
|
||||
val publicKeyBytes = Base64.decode(RELEASE_PUBLIC_KEY_BASE64, Base64.DEFAULT)
|
||||
if (publicKeyBytes == null || publicKeyBytes.size != 32 + 10 || publicKeyBytes[0] != 'E'.code.toByte() || publicKeyBytes[1] != 'd'.code.toByte())
|
||||
throw InvalidKeyException("Invalid public key")
|
||||
val lines = signifyDigest.split("\n", limit = 3)
|
||||
if (lines.size != 3)
|
||||
throw InvalidParameterException("Invalid signature format: too few lines")
|
||||
if (!lines[0].startsWith("untrusted comment: "))
|
||||
throw InvalidParameterException("Invalid signature format: missing comment")
|
||||
val signatureBytes = Base64.decode(lines[1], Base64.DEFAULT)
|
||||
if (signatureBytes == null || signatureBytes.size != 64 + 10)
|
||||
throw InvalidParameterException("Invalid signature format: wrong sized or missing signature")
|
||||
for (i in 0..9) {
|
||||
if (signatureBytes[i] != publicKeyBytes[i])
|
||||
throw InvalidParameterException("Invalid signature format: wrong signer")
|
||||
}
|
||||
if (!Ed25519.verify(
|
||||
lines[2].toByteArray(StandardCharsets.UTF_8),
|
||||
signatureBytes.sliceArray(10 until 10 + 64),
|
||||
publicKeyBytes.sliceArray(10 until 10 + 32)
|
||||
)
|
||||
)
|
||||
throw SecurityException("Invalid signature")
|
||||
for (line in lines[2].split("\n").dropLastWhile { it.isEmpty() }) {
|
||||
val components = line.split(" ", limit = 2)
|
||||
if (components.size != 2)
|
||||
throw InvalidParameterException("Invalid file list format: too few components")
|
||||
/* If version is null, it's not a file we understand, but still a legitimate entry, so don't throw. */
|
||||
val version = versionOfFile(components[1]) ?: continue
|
||||
updates.add(Update(components[1], version, Sha256Digest(components[0])))
|
||||
}
|
||||
return updates
|
||||
}
|
||||
|
||||
private fun checkForUpdates(): Update? {
|
||||
val connection = URL(UPDATE_URL_FMT.format(LATEST_FILE)).openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
|
||||
connection.connect()
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK)
|
||||
throw IOException(connection.responseMessage)
|
||||
var fileListBytes = ByteArray(1024 * 512 /* 512 KiB */)
|
||||
connection.inputStream.use {
|
||||
val len = it.read(fileListBytes)
|
||||
if (len <= 0)
|
||||
throw IOException("File list is empty")
|
||||
fileListBytes = fileListBytes.sliceArray(0 until len)
|
||||
}
|
||||
return verifySignedFileList(fileListBytes.decodeToString()).maxByOrNull { it.version }
|
||||
}
|
||||
|
||||
private suspend fun downloadAndUpdate() = withContext(Dispatchers.IO) {
|
||||
val receiver = InstallReceiver()
|
||||
val context = Application.get().applicationContext
|
||||
val pendingIntent = withContext(Dispatchers.Main) {
|
||||
ContextCompat.registerReceiver(context, receiver, IntentFilter(receiver.sessionId), ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(receiver.sessionId).setPackage(context.packageName),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
emitProgress(Progress.Rechecking)
|
||||
val update = checkForUpdates()
|
||||
if (update == null || update.version <= CURRENT_VERSION) {
|
||||
emitProgress(Progress.Complete)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
emitProgress(Progress.Downloading(0UL, 0UL), true)
|
||||
val connection = URL(UPDATE_URL_FMT.format(update.fileName)).openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
|
||||
connection.connect()
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK)
|
||||
throw IOException("Update could not be fetched: ${connection.responseCode}")
|
||||
|
||||
var downloadedByteLen: ULong = 0UL
|
||||
val totalByteLen = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) connection.contentLengthLong else connection.contentLength).toLong().toULong()
|
||||
val fileBytes = ByteArray(1024 * 32 /* 32 KiB */)
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
|
||||
|
||||
val installer = context.packageManager.packageInstaller
|
||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
params.setAppPackageName(context.packageName) /* Enforces updates; disallows new apps. */
|
||||
val session = installer.openSession(installer.createSession(params))
|
||||
var sessionFailure = true
|
||||
try {
|
||||
val installDest = session.openWrite(receiver.sessionId, 0, -1)
|
||||
|
||||
installDest.use { dest ->
|
||||
connection.inputStream.use { src ->
|
||||
while (true) {
|
||||
val readLen = src.read(fileBytes)
|
||||
if (readLen <= 0)
|
||||
break
|
||||
|
||||
digest.update(fileBytes, 0, readLen)
|
||||
dest.write(fileBytes, 0, readLen)
|
||||
|
||||
downloadedByteLen += readLen.toUInt()
|
||||
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
|
||||
|
||||
if (downloadedByteLen >= 1024UL * 1024UL * 100UL /* 100 MiB */)
|
||||
throw IOException("File too large")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitProgress(Progress.Installing)
|
||||
if (!digest.digest().contentEquals(update.hash.bytes))
|
||||
throw SecurityException("Update has invalid hash")
|
||||
sessionFailure = false
|
||||
} finally {
|
||||
if (sessionFailure) {
|
||||
session.abandon()
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
session.commit(pendingIntent.intentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
private var updating = false
|
||||
private suspend fun downloadAndUpdateWrapErrors() {
|
||||
if (updating)
|
||||
return
|
||||
updating = true
|
||||
try {
|
||||
downloadAndUpdate()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
updating = false
|
||||
}
|
||||
|
||||
private class InstallReceiver : BroadcastReceiver() {
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (sessionId != intent.action)
|
||||
return
|
||||
|
||||
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE_INVALID)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
|
||||
val userIntervention = IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java)!!
|
||||
applicationScope.launch {
|
||||
emitProgress(Progress.NeedsUserIntervention(userIntervention, id))
|
||||
}
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
applicationScope.launch {
|
||||
emitProgress(Progress.Complete)
|
||||
}
|
||||
context.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
|
||||
try {
|
||||
context.applicationContext.packageManager.packageInstaller.abandonSession(id)
|
||||
} catch (_: SecurityException) {
|
||||
}
|
||||
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "Installation error $status"
|
||||
applicationScope.launch {
|
||||
val e = Exception(message)
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
context.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun monitorForUpdates() {
|
||||
if (BuildConfig.DEBUG)
|
||||
return
|
||||
|
||||
val context = Application.get()
|
||||
|
||||
if (installerIsGooglePlay(context))
|
||||
return
|
||||
|
||||
if (!if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
|
||||
} else {
|
||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
||||
}.requestedPermissions.contains(Manifest.permission.REQUEST_INSTALL_PACKAGES)
|
||||
) {
|
||||
if (installer(context).isNotEmpty()) {
|
||||
updaterScope.launch {
|
||||
val update = try {
|
||||
checkForUpdates()
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
emitProgress(Progress.Corrupt(update?.fileName))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
updaterScope.launch {
|
||||
if (UserKnobs.updaterNewerVersionSeen.firstOrNull()?.let { Version(it) > CURRENT_VERSION } == true)
|
||||
return@launch
|
||||
|
||||
var waitTime = 15
|
||||
while (true) {
|
||||
try {
|
||||
val update = checkForUpdates() ?: continue
|
||||
if (update.version > CURRENT_VERSION) {
|
||||
Log.i(TAG, "Update available: ${update.version}")
|
||||
UserKnobs.setUpdaterNewerVersionSeen(update.version.toString())
|
||||
return@launch
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
delay(waitTime.minutes)
|
||||
waitTime = 45
|
||||
}
|
||||
}
|
||||
|
||||
UserKnobs.updaterNewerVersionSeen.onEach { ver ->
|
||||
if (
|
||||
ver != null &&
|
||||
Version(ver) > CURRENT_VERSION &&
|
||||
UserKnobs.updaterNewerVersionConsented.firstOrNull()?.let { Version(it) > CURRENT_VERSION } != true
|
||||
)
|
||||
emitProgress(Progress.Available(ver))
|
||||
}.launchIn(applicationScope)
|
||||
|
||||
UserKnobs.updaterNewerVersionConsented.onEach { ver ->
|
||||
if (ver != null && Version(ver) > CURRENT_VERSION)
|
||||
updaterScope.launch {
|
||||
downloadAndUpdateWrapErrors()
|
||||
}
|
||||
}.launchIn(applicationScope)
|
||||
}
|
||||
|
||||
class AppUpdatedReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED)
|
||||
return
|
||||
|
||||
if (installer(context) != context.packageName)
|
||||
return
|
||||
|
||||
/* TODO: does not work because of restrictions placed on broadcast receivers. */
|
||||
val start = Intent(context, MainActivity::class.java)
|
||||
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
start.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(start)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.viewmodel
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableList
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Interface
|
||||
import com.wireguard.crypto.Key
|
||||
import com.wireguard.crypto.KeyFormatException
|
||||
import com.wireguard.crypto.KeyPair
|
||||
|
||||
class InterfaceProxy : BaseObservable, Parcelable {
|
||||
@get:Bindable
|
||||
val excludedApplications: ObservableList<String> = ObservableArrayList()
|
||||
|
||||
@get:Bindable
|
||||
val includedApplications: ObservableList<String> = ObservableArrayList()
|
||||
|
||||
@get:Bindable
|
||||
var addresses: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.addresses)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var dnsServers: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.dnsServers)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var listenPort: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.listenPort)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var mtu: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.mtu)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var privateKey: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
val publicKey: String
|
||||
get() = try {
|
||||
KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64()
|
||||
} catch (ignored: KeyFormatException) {
|
||||
""
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
addresses = parcel.readString() ?: ""
|
||||
dnsServers = parcel.readString() ?: ""
|
||||
parcel.readStringList(excludedApplications)
|
||||
parcel.readStringList(includedApplications)
|
||||
listenPort = parcel.readString() ?: ""
|
||||
mtu = parcel.readString() ?: ""
|
||||
privateKey = parcel.readString() ?: ""
|
||||
}
|
||||
|
||||
constructor(other: Interface) {
|
||||
addresses = Attribute.join(other.addresses)
|
||||
val dnsServerStrings = other.dnsServers.map { it.hostAddress }.plus(other.dnsSearchDomains)
|
||||
dnsServers = Attribute.join(dnsServerStrings)
|
||||
excludedApplications.addAll(other.excludedApplications)
|
||||
includedApplications.addAll(other.includedApplications)
|
||||
listenPort = other.listenPort.map { it.toString() }.orElse("")
|
||||
mtu = other.mtu.map { it.toString() }.orElse("")
|
||||
val keyPair = other.keyPair
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
fun generateKeyPair() {
|
||||
val keyPair = KeyPair()
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@Throws(BadConfigException::class)
|
||||
fun resolve(): Interface {
|
||||
val builder = Interface.Builder()
|
||||
if (addresses.isNotEmpty()) builder.parseAddresses(addresses)
|
||||
if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers)
|
||||
if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications)
|
||||
if (includedApplications.isNotEmpty()) builder.includeApplications(includedApplications)
|
||||
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
|
||||
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
|
||||
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(addresses)
|
||||
dest.writeString(dnsServers)
|
||||
dest.writeStringList(excludedApplications)
|
||||
dest.writeStringList(includedApplications)
|
||||
dest.writeString(listenPort)
|
||||
dest.writeString(mtu)
|
||||
dest.writeString(privateKey)
|
||||
}
|
||||
|
||||
private class InterfaceProxyCreator : Parcelable.Creator<InterfaceProxy> {
|
||||
override fun createFromParcel(parcel: Parcel): InterfaceProxy {
|
||||
return InterfaceProxy(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<InterfaceProxy?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<InterfaceProxy> = InterfaceProxyCreator()
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
package org.amnezia.awg
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -17,16 +17,15 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.configStore.FileConfigStore
|
||||
import com.wireguard.android.model.TunnelManager
|
||||
import com.wireguard.android.updater.Updater
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.configStore.FileConfigStore
|
||||
import org.amnezia.awg.model.TunnelManager
|
||||
import org.amnezia.awg.util.RootShell
|
||||
import org.amnezia.awg.util.ToolsInstaller
|
||||
import org.amnezia.awg.util.UserKnobs
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -63,14 +62,14 @@ class Application : android.app.Application() {
|
||||
|
||||
private suspend fun determineBackend(): Backend {
|
||||
var backend: Backend? = null
|
||||
if (UserKnobs.enableKernelModule.first() && WgQuickBackend.hasKernelSupport()) {
|
||||
if (UserKnobs.enableKernelModule.first() && AwgQuickBackend.hasKernelSupport()) {
|
||||
try {
|
||||
rootShell.start()
|
||||
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
|
||||
wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
|
||||
backend = wgQuickBackend
|
||||
val awgQuickBackend = AwgQuickBackend(applicationContext, rootShell, toolsInstaller)
|
||||
awgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
|
||||
backend = awgQuickBackend
|
||||
UserKnobs.multipleTunnels.onEach {
|
||||
wgQuickBackend.setMultipleTunnels(it)
|
||||
awgQuickBackend.setMultipleTunnels(it)
|
||||
}.launchIn(coroutineScope)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
@@ -116,7 +115,6 @@ class Application : android.app.Application() {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
}
|
||||
Updater.monitorForUpdates()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
|
||||
@@ -130,8 +128,8 @@ class Application : android.app.Application() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT)
|
||||
private const val TAG = "WireGuard/Application"
|
||||
val USER_AGENT = String.format(Locale.ENGLISH, "AmneziaWG/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT)
|
||||
private const val TAG = "AmneziaWG/Application"
|
||||
private lateinit var weakSelf: WeakReference<Application>
|
||||
|
||||
fun get(): Application {
|
||||
@@ -2,21 +2,21 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
package org.amnezia.awg
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class BootShutdownReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
applicationScope.launch {
|
||||
if (Application.getBackend() !is WgQuickBackend) return@launch
|
||||
if (Application.getBackend() !is AwgQuickBackend) return@launch
|
||||
val tunnelManager = Application.getTunnelManager()
|
||||
if (Intent.ACTION_BOOT_COMPLETED == action) {
|
||||
Log.i(TAG, "Broadcast receiver restoring state (boot)")
|
||||
@@ -29,6 +29,6 @@ class BootShutdownReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/BootShutdownReceiver"
|
||||
private const val TAG = "AmneziaWG/BootShutdownReceiver"
|
||||
}
|
||||
}
|
||||
@@ -2,27 +2,29 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
package org.amnezia.awg
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.Observable.OnPropertyChangedCallback
|
||||
import com.wireguard.android.activity.MainActivity
|
||||
import com.wireguard.android.activity.TunnelToggleActivity
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import com.wireguard.android.widget.SlashDrawable
|
||||
import org.amnezia.awg.activity.MainActivity
|
||||
import org.amnezia.awg.activity.TunnelToggleActivity
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import org.amnezia.awg.widget.SlashDrawable
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -55,7 +57,7 @@ class QuickTileService : TileService() {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityAndCollapse(intent)
|
||||
@@ -68,6 +70,12 @@ class QuickTileService : TileService() {
|
||||
tunnel.setStateAsync(Tunnel.State.TOGGLE)
|
||||
updateTile()
|
||||
} catch (_: Throwable) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !Settings.canDrawOverlays(this@QuickTileService)) {
|
||||
val permissionIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
|
||||
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivityAndCollapse(PendingIntent.getActivity(this@QuickTileService, 0, permissionIntent, PendingIntent.FLAG_IMMUTABLE))
|
||||
return@launch
|
||||
}
|
||||
val toggleIntent = Intent(this@QuickTileService, TunnelToggleActivity::class.java)
|
||||
toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(toggleIntent)
|
||||
@@ -172,7 +180,7 @@ class QuickTileService : TileService() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/QuickTileService"
|
||||
private const val TAG = "AmneziaWG/QuickTileService"
|
||||
var isAdded: Boolean = false
|
||||
private set
|
||||
}
|
||||
@@ -2,15 +2,15 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.CallbackRegistry
|
||||
import androidx.databinding.CallbackRegistry.NotifierCallback
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.content.ClipDescription.compareMimeTypes
|
||||
import android.content.ContentProvider
|
||||
@@ -36,13 +36,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.LogViewerActivityBinding
|
||||
import com.wireguard.android.util.DownloadsFileSaver
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.resolveAttribute
|
||||
import com.wireguard.crypto.KeyPair
|
||||
import org.amnezia.awg.BuildConfig
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.databinding.LogViewerActivityBinding
|
||||
import org.amnezia.awg.util.DownloadsFileSaver
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.resolveAttribute
|
||||
import org.amnezia.awg.crypto.KeyPair
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -171,7 +171,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
var outputFile: DownloadsFileSaver.DownloadsFile? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
outputFile = downloadsFileSaver.save("wireguard-log.txt", "text/plain", true)
|
||||
outputFile = downloadsFileSaver.save("amneziawg-log.txt", "text/plain", true)
|
||||
outputFile?.outputStream?.write(rawLogBytes())
|
||||
} catch (e: Throwable) {
|
||||
outputFile?.delete()
|
||||
@@ -223,7 +223,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
} else {
|
||||
if (bufferedLogLines.isNotEmpty()) {
|
||||
bufferedLogLines.last().msg += "\n$line"
|
||||
} else if (!logLines.isEmpty) {
|
||||
} else if (!logLines.isEmpty()) {
|
||||
logLines[logLines.size() - 1].msg += "\n$line"
|
||||
priorModified = true
|
||||
}
|
||||
@@ -294,7 +294,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
private val THREADTIME_LINE: Pattern =
|
||||
Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$")
|
||||
private val LOGS: MutableMap<String, ByteArray> = ConcurrentHashMap()
|
||||
private const val TAG = "WireGuard/LogViewerActivity"
|
||||
private const val TAG = "AmneziaWG/LogViewerActivity"
|
||||
}
|
||||
|
||||
private inner class LogEntryAdapter : RecyclerView.Adapter<LogEntryAdapter.ViewHolder>() {
|
||||
@@ -353,7 +353,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? =
|
||||
logForUri(uri)?.let {
|
||||
val m = MatrixCursor(arrayOf(android.provider.OpenableColumns.DISPLAY_NAME, android.provider.OpenableColumns.SIZE), 1)
|
||||
m.addRow(arrayOf("wireguard-log.txt", it.size.toLong()))
|
||||
m.addRow(arrayOf("amneziawg-log.txt", it.size.toLong()))
|
||||
m
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
@@ -15,15 +15,15 @@ import androidx.appcompat.app.ActionBar
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.fragment.TunnelDetailFragment
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.fragment.TunnelDetailFragment
|
||||
import org.amnezia.awg.fragment.TunnelEditorFragment
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
|
||||
/**
|
||||
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
||||
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
||||
* editing the configuration and interface state of WireGuard tunnels.
|
||||
* CRUD interface for AmneziaWG tunnels. This activity serves as the main entry point to the
|
||||
* AmneziaWG application, and contains several fragments for listing, viewing details of, and
|
||||
* editing the configuration and interface state of AmneziaWG tunnels.
|
||||
*/
|
||||
class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
|
||||
private var actionBar: ActionBar? = null
|
||||
@@ -2,25 +2,23 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.service.quicksettings.TileService
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.preference.PreferencesPreferenceDataStore
|
||||
import com.wireguard.android.util.AdminKnobs
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.QuickTileService
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.preference.PreferencesPreferenceDataStore
|
||||
import org.amnezia.awg.util.AdminKnobs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -62,26 +60,22 @@ class SettingsActivity : AppCompatActivity() {
|
||||
darkTheme?.parent?.removePreference(darkTheme)
|
||||
--preferenceScreen.initialExpandedChildrenCount
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
val remoteApps = preferenceManager.findPreference<Preference>("allow_remote_control_intents")
|
||||
remoteApps?.parent?.removePreference(remoteApps)
|
||||
}
|
||||
if (AdminKnobs.disableConfigExport) {
|
||||
val zipExporter = preferenceManager.findPreference<Preference>("zip_exporter")
|
||||
zipExporter?.parent?.removePreference(zipExporter)
|
||||
}
|
||||
val wgQuickOnlyPrefs = arrayOf(
|
||||
val awgQuickOnlyPrefs = arrayOf(
|
||||
preferenceManager.findPreference("tools_installer"),
|
||||
preferenceManager.findPreference("restore_on_boot"),
|
||||
preferenceManager.findPreference<Preference>("multiple_tunnels")
|
||||
).filterNotNull()
|
||||
wgQuickOnlyPrefs.forEach { it.isVisible = false }
|
||||
awgQuickOnlyPrefs.forEach { it.isVisible = false }
|
||||
lifecycleScope.launch {
|
||||
if (Application.getBackend() is WgQuickBackend) {
|
||||
if (Application.getBackend() is AwgQuickBackend) {
|
||||
++preferenceScreen.initialExpandedChildrenCount
|
||||
wgQuickOnlyPrefs.forEach { it.isVisible = true }
|
||||
awgQuickOnlyPrefs.forEach { it.isVisible = true }
|
||||
} else {
|
||||
wgQuickOnlyPrefs.forEach { it.parent?.removePreference(it) }
|
||||
awgQuickOnlyPrefs.forEach { it.parent?.removePreference(it) }
|
||||
}
|
||||
}
|
||||
preferenceManager.findPreference<Preference>("log_viewer")?.setOnPreferenceClickListener {
|
||||
@@ -89,9 +83,9 @@ class SettingsActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
val kernelModuleEnabler = preferenceManager.findPreference<Preference>("kernel_module_enabler")
|
||||
if (WgQuickBackend.hasKernelSupport()) {
|
||||
if (AwgQuickBackend.hasKernelSupport()) {
|
||||
lifecycleScope.launch {
|
||||
if (Application.getBackend() !is WgQuickBackend) {
|
||||
if (Application.getBackend() !is AwgQuickBackend) {
|
||||
try {
|
||||
withContext(Dispatchers.IO) { Application.getRootShell().start() }
|
||||
} catch (_: Throwable) {
|
||||
@@ -2,12 +2,12 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.commit
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import org.amnezia.awg.fragment.TunnelEditorFragment
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
|
||||
/**
|
||||
* Standalone activity for creating tunnels.
|
||||
@@ -2,27 +2,24 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.service.quicksettings.TileService
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.QuickTileService
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class TunnelToggleActivity : AppCompatActivity() {
|
||||
private val permissionActivityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() }
|
||||
@@ -61,6 +58,6 @@ class TunnelToggleActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TunnelToggleActivity"
|
||||
private const val TAG = "AmneziaWG/TunnelToggleActivity"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity
|
||||
package org.amnezia.awg.activity
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
@@ -34,22 +34,22 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter
|
||||
import com.wireguard.android.databinding.TvActivityBinding
|
||||
import com.wireguard.android.databinding.TvFileListItemBinding
|
||||
import com.wireguard.android.databinding.TvTunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import com.wireguard.android.util.TunnelImporter
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.databinding.Keyed
|
||||
import org.amnezia.awg.databinding.ObservableKeyedArrayList
|
||||
import org.amnezia.awg.databinding.ObservableKeyedRecyclerViewAdapter
|
||||
import org.amnezia.awg.databinding.TvActivityBinding
|
||||
import org.amnezia.awg.databinding.TvFileListItemBinding
|
||||
import org.amnezia.awg.databinding.TvTunnelListItemBinding
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.QuantityFormatter
|
||||
import org.amnezia.awg.util.TunnelImporter
|
||||
import org.amnezia.awg.util.UserKnobs
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -263,31 +263,14 @@ class TvMainActivity : AppCompatActivity() {
|
||||
private suspend fun makeStorageRoots(): Collection<KeyedFile> = withContext(Dispatchers.IO) {
|
||||
cachedRoots?.let { return@withContext it }
|
||||
val list = HashSet<KeyedFile>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val storageManager: StorageManager = getSystemService() ?: return@withContext list
|
||||
list.addAll(storageManager.storageVolumes.mapNotNull { volume ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
volume.directory?.let { KeyedFile(it, volume.getDescription(this@TvMainActivity)) }
|
||||
} else {
|
||||
KeyedFile((StorageVolume::class.java.getMethod("getPathFile").invoke(volume) as File), volume.getDescription(this@TvMainActivity))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
list.add(KeyedFile(Environment.getExternalStorageDirectory()))
|
||||
try {
|
||||
File("/storage").listFiles()?.forEach {
|
||||
if (!it.isDirectory) return@forEach
|
||||
try {
|
||||
if (Environment.isExternalStorageRemovable(it)) {
|
||||
list.add(KeyedFile(it))
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
val storageManager: StorageManager = getSystemService() ?: return@withContext list
|
||||
list.addAll(storageManager.storageVolumes.mapNotNull { volume ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
volume.directory?.let { KeyedFile(it, volume.getDescription(this@TvMainActivity)) }
|
||||
} else {
|
||||
KeyedFile((StorageVolume::class.java.getMethod("getPathFile").invoke(volume) as File), volume.getDescription(this@TvMainActivity))
|
||||
}
|
||||
}
|
||||
})
|
||||
cachedRoots = list
|
||||
list
|
||||
}
|
||||
@@ -440,6 +423,6 @@ class TvMainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TvMainActivity"
|
||||
private const val TAG = "AmneziaWG/TvMainActivity"
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.configStore
|
||||
package org.amnezia.awg.configStore
|
||||
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.config.Config
|
||||
|
||||
/**
|
||||
* Interface for persistent storage providers for WireGuard configurations.
|
||||
* Interface for persistent storage providers for AmneziaWG configurations.
|
||||
*/
|
||||
interface ConfigStore {
|
||||
/**
|
||||
@@ -2,13 +2,13 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.configStore
|
||||
package org.amnezia.awg.configStore
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Config
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
@@ -17,7 +17,7 @@ import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/**
|
||||
* Configuration store that uses a `wg-quick`-style file for each configured tunnel.
|
||||
* Configuration store that uses a `awg-quick`-style file for each configured tunnel.
|
||||
*/
|
||||
class FileConfigStore(private val context: Context) : ConfigStore {
|
||||
@Throws(IOException::class)
|
||||
@@ -26,7 +26,7 @@ class FileConfigStore(private val context: Context) : ConfigStore {
|
||||
val file = fileFor(name)
|
||||
if (!file.createNewFile())
|
||||
throw IOException(context.getString(R.string.config_file_exists_error, file.name))
|
||||
FileOutputStream(file, false).use { it.write(config.toWgQuickString().toByteArray(StandardCharsets.UTF_8)) }
|
||||
FileOutputStream(file, false).use { it.write(config.toAwgQuickString().toByteArray(StandardCharsets.UTF_8)) }
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -72,11 +72,11 @@ class FileConfigStore(private val context: Context) : ConfigStore {
|
||||
val file = fileFor(name)
|
||||
if (!file.isFile)
|
||||
throw FileNotFoundException(context.getString(R.string.config_not_found_error, file.name))
|
||||
FileOutputStream(file, false).use { stream -> stream.write(config.toWgQuickString().toByteArray(StandardCharsets.UTF_8)) }
|
||||
FileOutputStream(file, false).use { stream -> stream.write(config.toAwgQuickString().toByteArray(StandardCharsets.UTF_8)) }
|
||||
return config
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/FileConfigStore"
|
||||
private const val TAG = "AmneziaWG/FileConfigStore"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.view.LayoutInflater
|
||||
@@ -18,14 +18,14 @@ import androidx.databinding.adapters.ListenerUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import com.wireguard.android.widget.ToggleSwitch
|
||||
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener
|
||||
import com.wireguard.android.widget.TvCardView
|
||||
import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.InetNetwork
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import org.amnezia.awg.widget.ToggleSwitch
|
||||
import org.amnezia.awg.widget.ToggleSwitch.OnBeforeCheckedChangeListener
|
||||
import org.amnezia.awg.widget.TvCardView
|
||||
import org.amnezia.awg.config.Attribute
|
||||
import org.amnezia.awg.config.InetNetwork
|
||||
import java.net.InetAddress
|
||||
import java.util.Optional
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -11,7 +11,7 @@ import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ObservableList
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.wireguard.android.BR
|
||||
import org.amnezia.awg.BR
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
/**
|
||||
* Interface for objects that have a identifying key of the given type.
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
import androidx.databinding.ObservableArrayList
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
@@ -11,7 +11,7 @@ import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ObservableList
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.wireguard.android.BR
|
||||
import org.amnezia.awg.BR
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
package org.amnezia.awg.databinding
|
||||
|
||||
import java.util.AbstractList
|
||||
import java.util.Collections
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
@@ -17,8 +17,8 @@ import androidx.fragment.app.setFragmentResult
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.resolveAttribute
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.util.resolveAttribute
|
||||
|
||||
class AddTunnelsSheet : BottomSheetDialogFragment() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Dialog
|
||||
@@ -21,12 +21,12 @@ import androidx.fragment.app.setFragmentResult
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
||||
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
import com.wireguard.android.model.ApplicationData
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.databinding.AppListDialogFragmentBinding
|
||||
import org.amnezia.awg.databinding.ObservableKeyedArrayList
|
||||
import org.amnezia.awg.model.ApplicationData
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
@@ -14,16 +14,16 @@ import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.BaseActivity
|
||||
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.activity.BaseActivity
|
||||
import org.amnezia.awg.activity.BaseActivity.OnSelectedTunnelChangedListener
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.databinding.TunnelDetailFragmentBinding
|
||||
import org.amnezia.awg.databinding.TunnelListItemBinding
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -109,6 +109,6 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/BaseFragment"
|
||||
private const val TAG = "AmneziaWG/BaseFragment"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
@@ -10,11 +10,11 @@ import android.view.WindowManager
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.databinding.ConfigNamingDialogFragmentBinding
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Config
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -15,12 +15,12 @@ import androidx.core.view.MenuProvider
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelDetailPeerBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.databinding.TunnelDetailFragmentBinding
|
||||
import org.amnezia.awg.databinding.TunnelDetailPeerBinding
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.QuantityFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
@@ -23,20 +23,20 @@ import androidx.core.view.MenuProvider
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.AdminKnobs
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.viewmodel.ConfigProxy
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.databinding.TunnelEditorFragmentBinding
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.AdminKnobs
|
||||
import org.amnezia.awg.util.BiometricAuthenticator
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.viewmodel.ConfigProxy
|
||||
import org.amnezia.awg.config.Config
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
* Fragment for editing an AmneziaWG configuration.
|
||||
*/
|
||||
class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
private var haveShownKeys = false
|
||||
@@ -328,6 +328,6 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
companion object {
|
||||
private const val KEY_LOCAL_CONFIG = "local_config"
|
||||
private const val KEY_ORIGINAL_NAME = "original_name"
|
||||
private const val TAG = "WireGuard/TunnelEditorFragment"
|
||||
private const val TAG = "AmneziaWG/TunnelEditorFragment"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
package org.amnezia.awg.fragment
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
@@ -26,25 +26,24 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.zxing.qrcode.QRCodeReader
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.updater.SnackbarUpdateShower
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QrCodeFromFileScanner
|
||||
import com.wireguard.android.util.TunnelImporter
|
||||
import com.wireguard.android.widget.MultiselectableRelativeLayout
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.activity.TunnelCreatorActivity
|
||||
import org.amnezia.awg.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import org.amnezia.awg.databinding.TunnelListFragmentBinding
|
||||
import org.amnezia.awg.databinding.TunnelListItemBinding
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.QrCodeFromFileScanner
|
||||
import org.amnezia.awg.util.TunnelImporter
|
||||
import org.amnezia.awg.widget.MultiselectableRelativeLayout
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
|
||||
* Fragment containing a list of known AmneziaWG tunnels. It allows creating and deleting tunnels.
|
||||
*/
|
||||
class TunnelListFragment : BaseFragment() {
|
||||
private val actionModeListener = ActionModeListener()
|
||||
@@ -81,8 +80,6 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val snackbarUpdateShower = SnackbarUpdateShower(this)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (savedInstanceState != null) {
|
||||
@@ -127,7 +124,6 @@ class TunnelListFragment : BaseFragment() {
|
||||
bottomSheet.showNow(childFragmentManager, "BOTTOM_SHEET")
|
||||
}
|
||||
executePendingBindings()
|
||||
snackbarUpdateShower.attach(mainContainer, createFab)
|
||||
}
|
||||
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) { actionMode?.finish() }
|
||||
backPressedCallback?.isEnabled = false
|
||||
@@ -337,6 +333,6 @@ class TunnelListFragment : BaseFragment() {
|
||||
|
||||
companion object {
|
||||
private const val CHECKED_ITEMS = "CHECKED_ITEMS"
|
||||
private const val TAG = "WireGuard/TunnelListFragment"
|
||||
private const val TAG = "AmneziaWG/TunnelListFragment"
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,13 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
package org.amnezia.awg.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.databinding.Keyed
|
||||
|
||||
class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isSelected: Boolean) : BaseObservable(), Keyed<String> {
|
||||
override val key = name
|
||||
@@ -2,23 +2,23 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
package org.amnezia.awg.model
|
||||
|
||||
import android.util.Log
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.backend.Statistics
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.databinding.Keyed
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import org.amnezia.awg.config.Config
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
|
||||
* Encapsulates the volatile and nonvolatile state of an AmneziaWG tunnel.
|
||||
*/
|
||||
class ObservableTunnel internal constructor(
|
||||
private val manager: TunnelManager,
|
||||
@@ -141,6 +141,6 @@ class ObservableTunnel internal constructor(
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/ObservableTunnel"
|
||||
private const val TAG = "AmneziaWG/ObservableTunnel"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.model
|
||||
package org.amnezia.awg.model
|
||||
|
||||
object TunnelComparator : Comparator<String> {
|
||||
private class NaturalSortString(originalString: String) {
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
package org.amnezia.awg.model
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@@ -12,19 +12,19 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.Application.Companion.get
|
||||
import com.wireguard.android.Application.Companion.getBackend
|
||||
import com.wireguard.android.Application.Companion.getTunnelManager
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.configStore.ConfigStore
|
||||
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.Application.Companion.get
|
||||
import org.amnezia.awg.Application.Companion.getBackend
|
||||
import org.amnezia.awg.Application.Companion.getTunnelManager
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.Statistics
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.configStore.ConfigStore
|
||||
import org.amnezia.awg.databinding.ObservableSortedKeyedArrayList
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.UserKnobs
|
||||
import org.amnezia.awg.util.applicationScope
|
||||
import org.amnezia.awg.config.Config
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -35,7 +35,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Maintains and mediates changes to the set of available WireGuard tunnels,
|
||||
* Maintains and mediates changes to the set of available AmneziaWG tunnels,
|
||||
*/
|
||||
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
private val tunnels = CompletableDeferred<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
|
||||
@@ -217,7 +217,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
val manager = getTunnelManager()
|
||||
if (intent == null) return@launch
|
||||
val action = intent.action ?: return@launch
|
||||
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
|
||||
if ("org.amnezia.awg.action.REFRESH_TUNNEL_STATES" == action) {
|
||||
manager.refreshTunnelStates()
|
||||
return@launch
|
||||
}
|
||||
@@ -225,8 +225,8 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
return@launch
|
||||
val state: Tunnel.State
|
||||
state = when (action) {
|
||||
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
|
||||
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
|
||||
"org.amnezia.awg.action.SET_TUNNEL_UP" -> Tunnel.State.UP
|
||||
"org.amnezia.awg.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
|
||||
else -> return@launch
|
||||
}
|
||||
val tunnelName = intent.getStringExtra("tunnel") ?: return@launch
|
||||
@@ -250,6 +250,6 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TunnelManager"
|
||||
private const val TAG = "AmneziaWG/TunnelManager"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -10,14 +10,14 @@ import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.activity
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.activity.SettingsActivity
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.util.UserKnobs
|
||||
import org.amnezia.awg.util.activity
|
||||
import org.amnezia.awg.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
@@ -32,7 +32,7 @@ class KernelModuleEnablerPreference(context: Context, attrs: AttributeSet?) : Pr
|
||||
init {
|
||||
isVisible = false
|
||||
lifecycleScope.launch {
|
||||
setState(if (Application.getBackend() is WgQuickBackend) State.ENABLED else State.DISABLED)
|
||||
setState(if (Application.getBackend() is AwgQuickBackend) State.ENABLED else State.DISABLED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,6 @@ class KernelModuleEnablerPreference(context: Context, attrs: AttributeSet?) : Pr
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/KernelModuleEnablerPreference"
|
||||
private const val TAG = "AmneziaWG/KernelModuleEnablerPreference"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import android.app.StatusBarManager
|
||||
import android.content.ComponentName
|
||||
@@ -14,8 +14,8 @@ import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import org.amnezia.awg.QuickTileService
|
||||
import org.amnezia.awg.R
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
class QuickTilePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
@@ -2,15 +2,15 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.util.ToolsInstaller
|
||||
import org.amnezia.awg.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -10,14 +10,14 @@ import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.BuildConfig
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.AwgQuickBackend
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -31,7 +31,7 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
|
||||
|
||||
override fun onClick() {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("https://www.wireguard.com/")
|
||||
intent.data = Uri.parse("https://amnezia.org/")
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Throwable) {
|
||||
@@ -41,7 +41,7 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
|
||||
|
||||
companion object {
|
||||
private fun getBackendPrettyName(context: Context, backend: Backend) = when (backend) {
|
||||
is WgQuickBackend -> context.getString(R.string.type_name_kernel_module)
|
||||
is AwgQuickBackend -> context.getString(R.string.type_name_kernel_module)
|
||||
is GoBackend -> context.getString(R.string.type_name_go_userspace)
|
||||
else -> ""
|
||||
}
|
||||
@@ -2,21 +2,21 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
package org.amnezia.awg.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.AdminKnobs
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.DownloadsFileSaver
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.activity
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.util.AdminKnobs
|
||||
import org.amnezia.awg.util.BiometricAuthenticator
|
||||
import org.amnezia.awg.util.DownloadsFileSaver
|
||||
import org.amnezia.awg.util.ErrorMessages
|
||||
import org.amnezia.awg.util.activity
|
||||
import org.amnezia.awg.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
@@ -43,7 +43,7 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
if (configs.isEmpty()) {
|
||||
throw IllegalArgumentException(context.getString(R.string.no_tunnels_error))
|
||||
}
|
||||
val outputFile = downloadsFileSaver.save("wireguard-export.zip", "application/zip", true)
|
||||
val outputFile = downloadsFileSaver.save("amneziawg-export.zip", "application/zip", true)
|
||||
if (outputFile == null) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
isEnabled = true
|
||||
@@ -54,7 +54,7 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
ZipOutputStream(outputFile.outputStream).use { zip ->
|
||||
for (i in configs.indices) {
|
||||
zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
|
||||
zip.write(configs[i].toWgQuickString().toByteArray(StandardCharsets.UTF_8))
|
||||
zip.write(configs[i].toAwgQuickString().toByteArray(StandardCharsets.UTF_8))
|
||||
}
|
||||
zip.closeEntry()
|
||||
}
|
||||
@@ -108,6 +108,6 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/ZipExporterPreference"
|
||||
private const val TAG = "AmneziaWG/ZipExporterPreference"
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.RestrictionsManager
|
||||
import androidx.core.content.getSystemService
|
||||
import com.wireguard.android.Application
|
||||
import org.amnezia.awg.Application
|
||||
|
||||
object AdminKnobs {
|
||||
private val restrictions: RestrictionsManager? = Application.get().getSystemService()
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -13,11 +13,11 @@ import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.wireguard.android.R
|
||||
import org.amnezia.awg.R
|
||||
|
||||
|
||||
object BiometricAuthenticator {
|
||||
private const val TAG = "WireGuard/BiometricAuthenticator"
|
||||
private const val TAG = "AmneziaWG/BiometricAuthenticator"
|
||||
|
||||
// Not all devices support strong biometric auth so we're allowing both device credentials as
|
||||
// well as weak biometrics.
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@@ -12,7 +12,7 @@ import android.widget.TextView
|
||||
import androidx.core.content.getSystemService
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.wireguard.android.R
|
||||
import org.amnezia.awg.R
|
||||
|
||||
/**
|
||||
* Standalone utilities for interacting with the system clipboard.
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
@@ -17,7 +17,7 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.wireguard.android.R
|
||||
import org.amnezia.awg.R
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -2,22 +2,22 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.RemoteException
|
||||
import com.google.zxing.ChecksumException
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.util.RootShell.RootShellException
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.InetEndpoint
|
||||
import com.wireguard.config.InetNetwork
|
||||
import com.wireguard.config.ParseException
|
||||
import com.wireguard.crypto.Key
|
||||
import com.wireguard.crypto.KeyFormatException
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.backend.BackendException
|
||||
import org.amnezia.awg.util.RootShell.RootShellException
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.InetEndpoint
|
||||
import org.amnezia.awg.config.InetNetwork
|
||||
import org.amnezia.awg.config.ParseException
|
||||
import org.amnezia.awg.crypto.Key
|
||||
import org.amnezia.awg.crypto.KeyFormatException
|
||||
import java.net.InetAddress
|
||||
|
||||
object ErrorMessages {
|
||||
@@ -33,7 +33,7 @@ object ErrorMessages {
|
||||
)
|
||||
private val BE_REASON_MAP = mapOf(
|
||||
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error,
|
||||
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
|
||||
BackendException.Reason.AWG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
|
||||
BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error,
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
|
||||
@@ -3,15 +3,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.activity.SettingsActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
fun Context.resolveAttribute(@AttrRes attrRes: Int): Int {
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.graphics.Bitmap
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.icu.text.ListFormatter
|
||||
import android.icu.text.MeasureFormat
|
||||
@@ -11,8 +11,8 @@ import android.icu.text.RelativeDateTimeFormatter
|
||||
import android.icu.util.Measure
|
||||
import android.icu.util.MeasureUnit
|
||||
import android.os.Build
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -31,9 +31,6 @@ object QuantityFormatter {
|
||||
fun formatEpochAgo(epochMillis: Long): String {
|
||||
var span = (System.currentTimeMillis() - epochMillis) / 1000
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
|
||||
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, span.seconds.toString())
|
||||
|
||||
if (span <= 0L)
|
||||
return RelativeDateTimeFormatter.getInstance().format(RelativeDateTimeFormatter.Direction.PLAIN, RelativeDateTimeFormatter.AbsoluteUnit.NOW)
|
||||
val measureFormat = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
|
||||
@@ -3,18 +3,18 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.fragment.ConfigNamingDialogFragment
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.config.Config
|
||||
import org.amnezia.awg.Application
|
||||
import org.amnezia.awg.R
|
||||
import org.amnezia.awg.fragment.ConfigNamingDialogFragment
|
||||
import org.amnezia.awg.model.ObservableTunnel
|
||||
import org.amnezia.awg.config.Config
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -148,5 +148,5 @@ object TunnelImporter {
|
||||
messageCallback(message)
|
||||
}
|
||||
|
||||
private const val TAG = "WireGuard/TunnelImporter"
|
||||
private const val TAG = "AmneziaWG/TunnelImporter"
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
package org.amnezia.awg.util
|
||||
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import com.wireguard.android.Application
|
||||
import org.amnezia.awg.Application
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -88,34 +88,4 @@ object UserKnobs {
|
||||
it[RUNNING_TUNNELS] = runningTunnels
|
||||
}
|
||||
}
|
||||
|
||||
private val UPDATER_NEWER_VERSION_SEEN = stringPreferencesKey("updater_newer_version_seen")
|
||||
val updaterNewerVersionSeen: Flow<String?>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[UPDATER_NEWER_VERSION_SEEN]
|
||||
}
|
||||
|
||||
suspend fun setUpdaterNewerVersionSeen(newerVersionSeen: String?) {
|
||||
Application.getPreferencesDataStore().edit {
|
||||
if (newerVersionSeen == null)
|
||||
it.remove(UPDATER_NEWER_VERSION_SEEN)
|
||||
else
|
||||
it[UPDATER_NEWER_VERSION_SEEN] = newerVersionSeen
|
||||
}
|
||||
}
|
||||
|
||||
private val UPDATER_NEWER_VERSION_CONSENTED = stringPreferencesKey("updater_newer_version_consented")
|
||||
val updaterNewerVersionConsented: Flow<String?>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[UPDATER_NEWER_VERSION_CONSENTED]
|
||||
}
|
||||
|
||||
suspend fun setUpdaterNewerVersionConsented(newerVersionConsented: String?) {
|
||||
Application.getPreferencesDataStore().edit {
|
||||
if (newerVersionConsented == null)
|
||||
it.remove(UPDATER_NEWER_VERSION_CONSENTED)
|
||||
else
|
||||
it[UPDATER_NEWER_VERSION_CONSENTED] = newerVersionConsented
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.viewmodel
|
||||
package org.amnezia.awg.viewmodel
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
@@ -10,9 +10,9 @@ import android.os.Parcelable
|
||||
import androidx.core.os.ParcelCompat
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableList
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Config
|
||||
import com.wireguard.config.Peer
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Config
|
||||
import org.amnezia.awg.config.Peer
|
||||
|
||||
class ConfigProxy : Parcelable {
|
||||
val `interface`: InterfaceProxy
|
||||
241
ui/src/main/java/org/amnezia/awg/viewmodel/InterfaceProxy.kt
Normal file
241
ui/src/main/java/org/amnezia/awg/viewmodel/InterfaceProxy.kt
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.amnezia.awg.viewmodel
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableList
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.config.Attribute
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Interface
|
||||
import org.amnezia.awg.crypto.Key
|
||||
import org.amnezia.awg.crypto.KeyFormatException
|
||||
import org.amnezia.awg.crypto.KeyPair
|
||||
|
||||
class InterfaceProxy : BaseObservable, Parcelable {
|
||||
@get:Bindable
|
||||
val excludedApplications: ObservableList<String> = ObservableArrayList()
|
||||
|
||||
@get:Bindable
|
||||
val includedApplications: ObservableList<String> = ObservableArrayList()
|
||||
|
||||
@get:Bindable
|
||||
var addresses: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.addresses)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var dnsServers: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.dnsServers)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var listenPort: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.listenPort)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var mtu: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.mtu)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var junkPacketCount: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.junkPacketCount)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var junkPacketMinSize: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.junkPacketMinSize)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var junkPacketMaxSize: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.junkPacketMaxSize)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var initPacketJunkSize: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.initPacketJunkSize)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var responsePacketJunkSize: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.responsePacketJunkSize)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var initPacketMagicHeader: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.initPacketMagicHeader)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var responsePacketMagicHeader: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.responsePacketMagicHeader)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var underloadPacketMagicHeader: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.underloadPacketMagicHeader)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var transportPacketMagicHeader: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.transportPacketMagicHeader)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var privateKey: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
val publicKey: String
|
||||
get() = try {
|
||||
KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64()
|
||||
} catch (ignored: KeyFormatException) {
|
||||
""
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
addresses = parcel.readString() ?: ""
|
||||
dnsServers = parcel.readString() ?: ""
|
||||
parcel.readStringList(excludedApplications)
|
||||
parcel.readStringList(includedApplications)
|
||||
listenPort = parcel.readString() ?: ""
|
||||
mtu = parcel.readString() ?: ""
|
||||
junkPacketCount = parcel.readString() ?: ""
|
||||
junkPacketMinSize = parcel.readString() ?: ""
|
||||
junkPacketMaxSize = parcel.readString() ?: ""
|
||||
initPacketJunkSize = parcel.readString() ?: ""
|
||||
responsePacketJunkSize = parcel.readString() ?: ""
|
||||
initPacketMagicHeader = parcel.readString() ?: ""
|
||||
responsePacketMagicHeader = parcel.readString() ?: ""
|
||||
underloadPacketMagicHeader = parcel.readString() ?: ""
|
||||
transportPacketMagicHeader = parcel.readString() ?: ""
|
||||
privateKey = parcel.readString() ?: ""
|
||||
}
|
||||
|
||||
constructor(other: Interface) {
|
||||
addresses = Attribute.join(other.addresses)
|
||||
val dnsServerStrings = other.dnsServers.map { it.hostAddress }.plus(other.dnsSearchDomains)
|
||||
dnsServers = Attribute.join(dnsServerStrings)
|
||||
excludedApplications.addAll(other.excludedApplications)
|
||||
includedApplications.addAll(other.includedApplications)
|
||||
listenPort = other.listenPort.map { it.toString() }.orElse("")
|
||||
mtu = other.mtu.map { it.toString() }.orElse("")
|
||||
junkPacketCount = other.junkPacketCount.map { it.toString() }.orElse("")
|
||||
junkPacketMinSize = other.junkPacketMinSize.map { it.toString() }.orElse("")
|
||||
junkPacketMaxSize = other.junkPacketMaxSize.map { it.toString() }.orElse("")
|
||||
initPacketJunkSize = other.initPacketJunkSize.map { it.toString() }.orElse("")
|
||||
responsePacketJunkSize = other.responsePacketJunkSize.map { it.toString() }.orElse("")
|
||||
initPacketMagicHeader = other.initPacketMagicHeader.map { it.toString() }.orElse("")
|
||||
responsePacketMagicHeader = other.responsePacketMagicHeader.map { it.toString() }.orElse("")
|
||||
underloadPacketMagicHeader = other.underloadPacketMagicHeader.map { it.toString() }.orElse("")
|
||||
transportPacketMagicHeader = other.transportPacketMagicHeader.map { it.toString() }.orElse("")
|
||||
val keyPair = other.keyPair
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
fun generateKeyPair() {
|
||||
val keyPair = KeyPair()
|
||||
privateKey = keyPair.privateKey.toBase64()
|
||||
notifyPropertyChanged(BR.privateKey)
|
||||
notifyPropertyChanged(BR.publicKey)
|
||||
}
|
||||
|
||||
@Throws(BadConfigException::class)
|
||||
fun resolve(): Interface {
|
||||
val builder = Interface.Builder()
|
||||
if (addresses.isNotEmpty()) builder.parseAddresses(addresses)
|
||||
if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers)
|
||||
if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications)
|
||||
if (includedApplications.isNotEmpty()) builder.includeApplications(includedApplications)
|
||||
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
|
||||
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
|
||||
if (junkPacketCount.isNotEmpty()) builder.parseJunkPacketCount(junkPacketCount)
|
||||
if (junkPacketMinSize.isNotEmpty()) builder.parseJunkPacketMinSize(junkPacketMinSize)
|
||||
if (junkPacketMaxSize.isNotEmpty()) builder.parseJunkPacketMaxSize(junkPacketMaxSize)
|
||||
if (initPacketJunkSize.isNotEmpty()) builder.parseInitPacketJunkSize(initPacketJunkSize)
|
||||
if (responsePacketJunkSize.isNotEmpty()) builder.parseResponsePacketJunkSize(responsePacketJunkSize)
|
||||
if (initPacketMagicHeader.isNotEmpty()) builder.parseInitPacketMagicHeader(initPacketMagicHeader)
|
||||
if (responsePacketMagicHeader.isNotEmpty()) builder.parseResponsePacketMagicHeader(responsePacketMagicHeader)
|
||||
if (underloadPacketMagicHeader.isNotEmpty()) builder.parseUnderloadPacketMagicHeader(underloadPacketMagicHeader)
|
||||
if (transportPacketMagicHeader.isNotEmpty()) builder.parseTransportPacketMagicHeader(transportPacketMagicHeader)
|
||||
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(addresses)
|
||||
dest.writeString(dnsServers)
|
||||
dest.writeStringList(excludedApplications)
|
||||
dest.writeStringList(includedApplications)
|
||||
dest.writeString(listenPort)
|
||||
dest.writeString(mtu)
|
||||
dest.writeString(junkPacketCount)
|
||||
dest.writeString(junkPacketMinSize)
|
||||
dest.writeString(junkPacketMaxSize)
|
||||
dest.writeString(initPacketJunkSize)
|
||||
dest.writeString(responsePacketJunkSize)
|
||||
dest.writeString(initPacketMagicHeader)
|
||||
dest.writeString(responsePacketMagicHeader)
|
||||
dest.writeString(underloadPacketMagicHeader)
|
||||
dest.writeString(transportPacketMagicHeader)
|
||||
dest.writeString(privateKey)
|
||||
}
|
||||
|
||||
private class InterfaceProxyCreator : Parcelable.Creator<InterfaceProxy> {
|
||||
override fun createFromParcel(parcel: Parcel): InterfaceProxy {
|
||||
return InterfaceProxy(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<InterfaceProxy?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<InterfaceProxy> = InterfaceProxyCreator()
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.viewmodel
|
||||
package org.amnezia.awg.viewmodel
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
@@ -11,10 +11,10 @@ import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.Observable.OnPropertyChangedCallback
|
||||
import androidx.databinding.ObservableList
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Peer
|
||||
import org.amnezia.awg.BR
|
||||
import org.amnezia.awg.config.Attribute
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Peer
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class PeerProxy : BaseObservable, Parcelable {
|
||||
@@ -2,15 +2,15 @@
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.widget
|
||||
package org.amnezia.awg.widget
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import com.wireguard.crypto.Key
|
||||
import org.amnezia.awg.crypto.Key
|
||||
|
||||
/**
|
||||
* InputFilter for entering WireGuard private/public keys encoded with base64.
|
||||
* InputFilter for entering AmneziaWG private/public keys encoded with base64.
|
||||
*/
|
||||
class KeyInputFilter : InputFilter {
|
||||
override fun filter(
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user