summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.idea/codeStyles/Project.xml3
-rw-r--r--build.gradle62
-rw-r--r--gradle.properties3
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin59536 -> 61608 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties5
-rwxr-xr-xgradlew18
-rw-r--r--settings.gradle5
-rw-r--r--tunnel/build.gradle68
-rw-r--r--tunnel/src/main/AndroidManifest.xml7
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Backend.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/BackendException.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Bgp.java281
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Dhcp.java31
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java562
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java325
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Statistics.java48
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java19
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java59
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java4
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/RootShell.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Attribute.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/BadConfigException.java11
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Config.java14
-rw-r--r--tunnel/src/main/java/com/wireguard/config/HttpProxy.java78
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetAddresses.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetEndpoint.java51
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetNetwork.java4
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Interface.java35
-rw-r--r--tunnel/src/main/java/com/wireguard/config/ParseException.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Peer.java15
-rw-r--r--tunnel/src/main/java/com/wireguard/crypto/Curve25519.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/crypto/Key.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/crypto/KeyPair.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/util/NonNullForAll.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/util/Resolver.java138
-rw-r--r--tunnel/src/main/proto/libwg.proto125
-rw-r--r--tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java2
-rw-r--r--tunnel/src/test/java/com/wireguard/config/ConfigTest.java2
-rw-r--r--tunnel/tools/CMakeLists.txt16
-rw-r--r--tunnel/tools/libwg-go/.gitignore3
-rw-r--r--tunnel/tools/libwg-go/Makefile42
-rw-r--r--tunnel/tools/libwg-go/api-android.go56
-rw-r--r--tunnel/tools/libwg-go/dhcp.go192
-rw-r--r--tunnel/tools/libwg-go/go.mod25
-rw-r--r--tunnel/tools/libwg-go/go.sum183
-rw-r--r--tunnel/tools/libwg-go/goruntime-boottime-over-monotonic.diff67
-rw-r--r--tunnel/tools/libwg-go/http-proxy.go716
-rw-r--r--tunnel/tools/libwg-go/jni.c19
-rw-r--r--tunnel/tools/libwg-go/service.go319
-rw-r--r--tunnel/tools/ndk-compat/compat.c6
-rw-r--r--tunnel/tools/ndk-compat/compat.h6
m---------tunnel/tools/wireguard-tools0
-rw-r--r--ui/build.gradle26
-rw-r--r--ui/src/main/AndroidManifest.xml28
-rw-r--r--ui/src/main/java/com/wireguard/android/Application.kt8
-rw-r--r--ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/QuickTileService.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt12
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/MainActivity.kt23
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TvMainActivity.kt18
-rw-r--r--ui/src/main/java/com/wireguard/android/configStore/ConfigStore.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/configStore/FileConfigStore.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/Keyed.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt21
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt53
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt54
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt57
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ApplicationData.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt135
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelComparator.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelManager.kt7
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/DonatePreference.kt38
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/KernelModuleEnablerPreference.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/AdminKnobs.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ClipboardUtils.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt4
-rw-r--r--ui/src/main/java/com/wireguard/android/util/Extensions.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt60
-rw-r--r--ui/src/main/java/com/wireguard/android/util/TunnelImporter.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/UserKnobs.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt22
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt4
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt74
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt85
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt2
-rw-r--r--ui/src/main/res/drawable/ic_action_add_white.xml7
-rw-r--r--ui/src/main/res/drawable/ic_action_delete.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_edit.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_edit_white.xml9
-rw-r--r--ui/src/main/res/drawable/ic_action_generate.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_open.xml (renamed from ui/src/main/res/drawable/ic_action_open_white.xml)3
-rw-r--r--ui/src/main/res/drawable/ic_action_save.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_scan_qr_code.xml (renamed from ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml)3
-rw-r--r--ui/src/main/res/drawable/ic_action_select_all.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_share_white.xml1
-rw-r--r--ui/src/main/res/drawable/ic_arrow_back.xml5
-rw-r--r--ui/src/main/res/drawable/ic_settings.xml3
-rw-r--r--ui/src/main/res/drawable/tv_logo_banner.xml2
-rw-r--r--ui/src/main/res/layout/add_tunnels_bottom_sheet.xml4
-rw-r--r--ui/src/main/res/layout/config_naming_dialog_fragment.xml5
-rw-r--r--ui/src/main/res/layout/http_proxy_menu_item.xml8
-rw-r--r--ui/src/main/res/layout/log_viewer_activity.xml2
-rw-r--r--ui/src/main/res/layout/log_viewer_entry.xml2
-rw-r--r--ui/src/main/res/layout/tunnel_detail_fragment.xml120
-rw-r--r--ui/src/main/res/layout/tunnel_detail_peer.xml34
-rw-r--r--ui/src/main/res/layout/tunnel_editor_fragment.xml105
-rw-r--r--ui/src/main/res/values-ca-rES/strings.xml6
-rw-r--r--ui/src/main/res/values-cs-rCZ/strings.xml6
-rw-r--r--ui/src/main/res/values-de/strings.xml8
-rw-r--r--ui/src/main/res/values-es-rES/strings.xml8
-rw-r--r--ui/src/main/res/values-fa-rIR/strings.xml9
-rw-r--r--ui/src/main/res/values-fr/strings.xml8
-rw-r--r--ui/src/main/res/values-hi-rIN/strings.xml8
-rw-r--r--ui/src/main/res/values-in/strings.xml8
-rw-r--r--ui/src/main/res/values-it/strings.xml8
-rw-r--r--ui/src/main/res/values-ja/strings.xml8
-rw-r--r--ui/src/main/res/values-ko-rKR/strings.xml8
-rw-r--r--ui/src/main/res/values-no-rNO/strings.xml8
-rw-r--r--ui/src/main/res/values-pa-rIN/strings.xml8
-rw-r--r--ui/src/main/res/values-pl-rPL/strings.xml8
-rw-r--r--ui/src/main/res/values-pt-rPT/strings.xml8
-rw-r--r--ui/src/main/res/values-ro-rRO/strings.xml8
-rw-r--r--ui/src/main/res/values-ru/strings.xml8
-rw-r--r--ui/src/main/res/values-sk-rSK/strings.xml2
-rw-r--r--ui/src/main/res/values-sl/strings.xml8
-rw-r--r--ui/src/main/res/values-sv-rSE/strings.xml8
-rw-r--r--ui/src/main/res/values-tr-rTR/strings.xml8
-rw-r--r--ui/src/main/res/values-uk-rUA/strings.xml8
-rw-r--r--ui/src/main/res/values-vi-rVN/strings.xml5
-rw-r--r--ui/src/main/res/values-zh-rCN/strings.xml8
-rw-r--r--ui/src/main/res/values-zh-rTW/strings.xml4
-rw-r--r--ui/src/main/res/values/strings.xml15
-rw-r--r--ui/src/main/res/values/styles.xml17
-rw-r--r--ui/src/main/res/values/tv_styles.xml18
-rw-r--r--ui/src/main/res/xml/app_restrictions.xml2
-rw-r--r--ui/src/main/res/xml/preferences.xml2
-rw-r--r--version.gradle5
165 files changed, 4437 insertions, 592 deletions
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 3eb23b6b..d19645e3 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -66,9 +66,6 @@
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="10" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
- <XML>
- <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
- </XML>
<codeStyleSettings language="JAVA">
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
diff --git a/build.gradle b/build.gradle
index 02647c0c..b151d045 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,69 +1,35 @@
buildscript {
ext {
- activityVersion = '1.4.0'
- annotationsVersion = '1.3.0'
- appcompatVersion = '1.4.1'
+ activityVersion = '1.6.1'
+ annotationsVersion = '1.6.0'
+ appcompatVersion = '1.6.1'
biometricVersion = '1.1.0'
collectionVersion = '1.2.0'
- constraintLayoutVersion = '2.1.3'
+ constraintLayoutVersion = '2.1.4'
coordinatorLayoutVersion = '1.2.0'
- coreKtxVersion = '1.7.0'
- coroutinesVersion = '1.6.1'
+ coreKtxVersion = '1.9.0'
+ coroutinesVersion = '1.6.4'
datastoreVersion = '1.0.0'
desugarVersion = '1.1.5'
- fragmentVersion = '1.4.1'
+ fragmentVersion = '1.5.5'
+ grpcVersion = '1.53.0'
jsr305Version = '3.0.2'
junitVersion = '4.13.2'
- lifecycleRuntimeKtxVersion = '2.4.1'
- materialComponentsVersion = '1.5.0'
+ lifecycleRuntimeKtxVersion = '2.6.0'
+ materialComponentsVersion = '1.8.0'
preferenceVersion = '1.2.0'
+ protobufGradleVersion = '0.9.0'
+ protocVersion = '3.22.2'
zxingEmbeddedVersion = '4.3.0'
groupName = 'com.wireguard.android'
}
}
-plugins {
- id "de.undercouch.download" version "5.0.4"
-}
-
-task downloadCrowdin(type: Download) {
- src 'https://crowdin.com/backend/download/project/wireguard.zip'
- dest file('build/translations.zip')
- overwrite true
-}
-
-task cleanCrowdin(type: Delete) {
- delete 'ui/src/main/res/values-*/strings.xml'
-}
-
-task extractCrowdin(type: Copy, dependsOn: ['downloadCrowdin', 'cleanCrowdin']) {
- mustRunAfter 'downloadCrowdin'
- from zipTree(file('build/translations.zip'))
- into file('build/translations')
- doFirst {
- delete 'build/translations'
- }
-}
-
-task crowdin(type: Copy, dependsOn: ['extractCrowdin']) {
- mustRunAfter 'extractCrowdin'
- from 'build/translations/wireguard-android/ui/src/main/res'
- into 'ui/src/main/res/'
- doLast {
- delete 'build/translations'
- delete 'build/translations.zip'
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
-
tasks {
wrapper {
- gradleVersion = "7.3.3"
- distributionSha256Sum = "b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302"
+ gradleVersion = "8.0.2"
+ distributionSha256Sum = "ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7"
}
}
diff --git a/gradle.properties b/gradle.properties
index b91f5409..3efc9b38 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,9 +18,6 @@ org.gradle.jvmargs=-Xmx1536m
# Turn off AP discovery in compile path to enable compile avoidance
kapt.include.compile.classpath=false
-# Enable AndroidX
-android.useAndroidX=true
-
# Enable non-transitive R class namespacing where each library only contains
# references to the resources it declares instead of declarations plus all
# transitive dependency references.
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180f..ccebba77 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 59250647..19acfb4e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionSha256Sum=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
+networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c7873..79a61d42 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/settings.gradle b/settings.gradle
index 574688e0..cde735b8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,6 @@
pluginManagement {
- def agpVersion = "7.1.3"
- def kotlinVersion = "1.6.20"
+ def agpVersion = '7.4.2'
+ def kotlinVersion = "1.8.0"
repositories {
gradlePluginPortal()
google()
@@ -25,4 +25,5 @@ dependencyResolutionManagement {
rootProject.name = "wireguard-android"
include ':tunnel'
+include ':bgp-java'
include ':ui'
diff --git a/tunnel/build.gradle b/tunnel/build.gradle
index 0c18e05c..2a1b2103 100644
--- a/tunnel/build.gradle
+++ b/tunnel/build.gradle
@@ -1,19 +1,27 @@
+buildscript {
+ dependencies {
+ classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufGradleVersion"
+ }
+}
+
plugins {
id 'com.android.library'
+ id 'com.google.protobuf' version "$protobufGradleVersion"
}
version wireguardVersionName
group groupName
android {
- compileSdkVersion 31
+ compileSdk 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+ namespace 'com.wireguard.android.tunnel'
defaultConfig {
minSdkVersion 21
- targetSdkVersion 31
+ targetSdkVersion 33
versionCode wireguardVersionCode
versionName wireguardVersionName
}
@@ -43,9 +51,8 @@ android {
}
}
}
- lintOptions {
- disable('LongLogTag')
- disable('NewApi') // Desugaring!
+ lint {
+ disable 'LongLogTag', 'NewApi'
}
splits {
abi {
@@ -58,10 +65,61 @@ android {
}
dependencies {
+ implementation project(":bgp-java")
implementation "androidx.annotation:annotation:$annotationsVersion"
implementation "androidx.collection:collection:$collectionVersion"
+ implementation "io.grpc:grpc-okhttp:$grpcVersion"
+ implementation "io.grpc:grpc-protobuf-lite:$grpcVersion"
+ implementation "io.grpc:grpc-stub:$grpcVersion"
compileOnly "com.google.code.findbugs:jsr305:$jsr305Version"
+ compileOnly "javax.annotation:javax.annotation-api:1.2"
testImplementation "junit:junit:$junitVersion"
}
+protobuf {
+ protoc {
+ // You still need protoc like in the non-Android case
+ artifact = "com.google.protobuf:protoc:$protocVersion"
+ }
+ plugins {
+ grpc {
+ artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion"
+ }
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option 'lite'
+ }
+ }
+ task.plugins {
+ grpc {
+ option 'lite'
+ }
+ }
+ }
+ }
+}
+
+afterEvaluate({ project ->
+ // All custom configurations created by the protobuf plugin,
+ // are only available at this point.
+ def protoc = configurations.getByName('protobufToolsLocator_protoc')
+
+ task copyProtoc(type: Copy) {
+ // Used by tunnel/tools/libwg-go/Makefile run in tools/CMakeLists.txt
+ from protoc
+ into "${gradle.gradleUserHomeDir}/caches/protoc-${protocVersion}"
+ rename 'protoc-.*', 'protoc'
+ fileMode 0775
+ }
+
+ preBuild.dependsOn copyProtoc
+
+ // Extract duration.proto used by external library in libwg.proto
+ preDebugBuild.dependsOn extractIncludeDebugProto
+ preReleaseBuild.dependsOn extractIncludeReleaseProto
+})
+
apply from: "publish.gradle"
diff --git a/tunnel/src/main/AndroidManifest.xml b/tunnel/src/main/AndroidManifest.xml
index dc0d6c7f..99509a20 100644
--- a/tunnel/src/main/AndroidManifest.xml
+++ b/tunnel/src/main/AndroidManifest.xml
@@ -1,16 +1,15 @@
<!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.wireguard.android.tunnel">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<service
android:name="com.wireguard.android.backend.GoBackend$VpnService"
android:permission="android.permission.BIND_VPN_SERVICE"
- android:exported="true">
+ android:exported="false">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Backend.java b/tunnel/src/main/java/com/wireguard/android/backend/Backend.java
index 5aaad826..edf98b9e 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Backend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Backend.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java
index cfa5ce0d..af966ec1 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java b/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java
new file mode 100644
index 00000000..12668621
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java
@@ -0,0 +1,281 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.backend;
+
+import android.net.TrafficStats;
+import android.util.Log;
+
+import com.lumaserv.bgp.BGPListener;
+import com.lumaserv.bgp.BGPServer;
+import com.lumaserv.bgp.BGPSession;
+import com.lumaserv.bgp.BGPSessionConfiguration;
+import com.lumaserv.bgp.protocol.AFI;
+import com.lumaserv.bgp.protocol.BGPPacket;
+import com.lumaserv.bgp.protocol.IPPrefix;
+import com.lumaserv.bgp.protocol.SAFI;
+import com.lumaserv.bgp.protocol.attribute.ASPathAttribute;
+import com.lumaserv.bgp.protocol.attribute.MPReachableNLRIAttribute;
+import com.lumaserv.bgp.protocol.attribute.NextHopAttribute;
+import com.lumaserv.bgp.protocol.attribute.OriginAttribute;
+import com.lumaserv.bgp.protocol.attribute.PathAttribute;
+import com.lumaserv.bgp.protocol.attribute.TunnelEncapsAttribute;
+import com.lumaserv.bgp.protocol.message.BGPUpdate;
+
+import com.wireguard.config.InetEndpoint;
+import com.wireguard.config.InetNetwork;
+import com.wireguard.crypto.Key;
+import com.wireguard.crypto.KeyFormatException;
+
+import io.grpc.ManagedChannel;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.SocketFactory;
+
+public class Bgp implements BGPListener {
+ private static final String TAG = "WireGuard/Bgp";
+ private static final String SESSION = "demosession";
+ private static final int MY_ASN = (int)4200000201L;
+ private static final int REMOTE_ASN = (int)4200000010L;
+ private static final String REMOTE_ADDR = "10.49.32.1";
+ private static final String REMOTE_ID = "10.49.160.1";
+ private static final String LOCAL_ID = "10.49.33.218";
+ private static final int PORT = 0;
+ private static final int STATS_TAG = 1; // FIXME
+
+ private ManagedChannel channel;
+ private Tunnel tunnel;
+ private int tunnelHandle;
+ private BGPServer server;
+
+ public Bgp(ManagedChannel channel, Tunnel tunnel, int tunnelHandle) {
+ this.channel = channel;
+ this.tunnel = tunnel;
+ this.tunnelHandle = tunnelHandle;
+ }
+
+ @Override
+ public void onOpen(BGPSession session) {
+ // DO WHAT YOU WANT
+ Log.i(TAG, "onOpen");
+
+ // BGPUpdate update;
+ // {
+ // List<IPPrefix> prefixes = new ArrayList<>(1);
+ // prefixes.add(new IPPrefix(new byte[]{10, 49, 124, 105}, 32));
+ // List<PathAttribute> attrs = new ArrayList<>();
+ // attrs.add(new OriginAttribute(OriginAttribute.Origin.IGP));
+ // attrs.add(new NextHopAttribute().setAddress(new byte[]{10, 49, 125, 105}));
+ // ASPathAttribute.Segment seg = new ASPathAttribute.Segment();
+ // seg.setType(ASPathAttribute.Segment.Type.SEQUENCE);
+ // seg.getAsns().add(MY_ASN);
+ // ASPathAttribute asPath = new ASPathAttribute(session);
+ // asPath.getSegments().add(seg);
+ // attrs.add(asPath);
+ // update = new BGPUpdate().setAttributes(attrs).setPrefixes(prefixes);
+ // }
+
+ // BGPUpdate update2;
+ // try {
+ // List<PathAttribute> attrs = new ArrayList<>();
+ // attrs.add(new OriginAttribute(OriginAttribute.Origin.IGP));
+ // ASPathAttribute.Segment seg = new ASPathAttribute.Segment();
+ // seg.setType(ASPathAttribute.Segment.Type.SEQUENCE);
+ // seg.getAsns().add(MY_ASN);
+ // ASPathAttribute asPath = new ASPathAttribute(session);
+ // List<IPPrefix> prefixes = new ArrayList<>(1);
+ // prefixes.add(new IPPrefix(new byte[]{0x20, 0x1, 0x04, 0x70, (byte)0xdf, (byte)0xae, 0x63, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x05}, 128));
+ // MPReachableNLRIAttribute mpr = new MPReachableNLRIAttribute();
+ // mpr.setAfi(AFI.IPV6).setSafi(SAFI.UNICAST).setNextHop(InetAddress.getByName("2001:470:dfae:6300::1:105")).setNlriPrefixes(prefixes);
+ // attrs.add(mpr);
+ // asPath.getSegments().add(seg);
+ // attrs.add(asPath);
+ // update2 = new BGPUpdate().setAttributes(attrs);
+ // } catch (UnknownHostException ex) {
+ // throw new RuntimeException(ex);
+ // }
+
+ // try {
+ // session.sendUpdate(update);
+ // session.sendUpdate(update2);
+ // } catch (IOException ex) {
+ // throw new RuntimeException(ex);
+ // }
+ }
+
+ @Override
+ public void onUpdate(BGPSession session, BGPUpdate update) {
+ // DO WHAT YOU WANT
+ Log.i(TAG, "onUpdate: " + update.getPrefixes() + ",-" + update.getWithdrawnPrefixes() + "," + update.getAttributes());
+ MPReachableNLRIAttribute mpr = null;
+ TunnelEncapsAttribute te = null;
+
+ for (PathAttribute attr: update.getAttributes()) {
+ if (attr instanceof TunnelEncapsAttribute) {
+ te = (TunnelEncapsAttribute)attr;
+ } else if (attr instanceof MPReachableNLRIAttribute) {
+ mpr = (MPReachableNLRIAttribute)attr;
+ }
+ }
+
+ if (te == null) {
+ return;
+ }
+
+ TunnelEncapsAttribute.WireGuard wg = null;
+ TunnelEncapsAttribute.Color col = null;
+ TunnelEncapsAttribute.EgressEndpoint ep = null;
+ TunnelEncapsAttribute.UDPDestinationPort port = null;
+
+ for (TunnelEncapsAttribute.Tunnel t: te.getTunnels()) {
+ if (t.getType() != 51820) {
+ continue;
+ }
+
+ for (TunnelEncapsAttribute.SubTlv st: t.getSubTlvs()) {
+ if (st instanceof TunnelEncapsAttribute.WireGuard) {
+ wg = (TunnelEncapsAttribute.WireGuard)st;
+ } else if (st instanceof TunnelEncapsAttribute.Color) {
+ col = (TunnelEncapsAttribute.Color)st;
+ } else if (st instanceof TunnelEncapsAttribute.EgressEndpoint) {
+ ep = (TunnelEncapsAttribute.EgressEndpoint)st;
+ } else if (st instanceof TunnelEncapsAttribute.UDPDestinationPort) {
+ port = (TunnelEncapsAttribute.UDPDestinationPort)st;
+ }
+ }
+ }
+
+ if (wg == null) {
+ return;
+ }
+
+ try {
+ Key publicKey = Key.fromBytes(wg.getPublicKey());
+ InetEndpoint endpoint = null;
+
+ if (ep != null && port != null) {
+ endpoint = InetEndpoint.fromAddress(ep.getAddress(), port.getPort());
+ }
+
+ tunnel.onEndpointChange(publicKey, endpoint);
+
+ List<InetNetwork> addNetworks = new ArrayList<>();
+ List<InetNetwork> removeNetworks = new ArrayList<>();
+
+ for (IPPrefix prefix: update.getPrefixes()) {
+ try {
+ addNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength()));
+ } catch (UnknownHostException ignore) {
+ }
+ }
+
+ for (IPPrefix prefix: update.getWithdrawnPrefixes()) {
+ try {
+ removeNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength()));
+ } catch (UnknownHostException ignore) {
+ }
+ }
+
+ if (mpr != null && (mpr.getAfi() == AFI.IPV6 || mpr.getAfi() == AFI.IPV4) && mpr.getSafi() == SAFI.UNICAST) {
+ for (IPPrefix prefix: mpr.getNlriPrefixes()) {
+ try {
+ addNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength()));
+ } catch (UnknownHostException ignore) {
+ }
+ }
+ }
+
+ tunnel.onAllowedIpsChange(publicKey, addNetworks, removeNetworks);
+
+ } catch (KeyFormatException ex) {
+ Log.w(TAG, "Key.fromBytes " + ex);
+ }
+ }
+
+ @Override
+ public void onClose(BGPSession session) {
+ // NOT YET IMPLEMENTED
+ Log.i(TAG, "onClose");
+ }
+
+ public boolean startServer() {
+ stopServer();
+ try {
+ SocketFactory factory = new SocketFactory() {
+ private Socket taggedSocket(Socket sock) throws SocketException {
+ TrafficStats.tagSocket(sock);
+ return sock;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ return taggedSocket(new Socket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ return taggedSocket(new Socket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ return taggedSocket(new Socket(address, port, localAddress, localPort));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ return taggedSocket(new Socket(host, port, localHost, localPort));
+ }
+ };
+
+ BGPSessionConfiguration config =
+ new BGPSessionConfiguration(SESSION,
+ MY_ASN,
+ ip(LOCAL_ID),
+ REMOTE_ASN,
+ ip(REMOTE_ID),
+ null, // Remote address
+ factory,
+ this);
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ ServerSocket socket = new ServerSocket(PORT);
+ //TrafficStats.tagSocket(socket);
+ // Set
+ server = new BGPServer(socket);
+ server.getSessionConfigurations().add(config);
+ server.connect(config, REMOTE_ADDR);
+ return true;
+ } catch (IOException ex) {
+ return false;
+ }
+ }
+
+ public void stopServer() {
+ if (server != null) {
+ server.shutdown();
+ server = null;
+ }
+ }
+
+ private static byte[] ip(String s) throws UnknownHostException {
+ InetAddress addr = InetAddress.getByName(s);
+ byte[] data = addr.getAddress();
+ if (data.length != 4)
+ throw new UnknownHostException(s + ": Not an IPv4 address");
+ return data;
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Dhcp.java b/tunnel/src/main/java/com/wireguard/android/backend/Dhcp.java
new file mode 100644
index 00000000..59a3e69c
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Dhcp.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.backend;
+
+import com.wireguard.config.InetNetwork;
+import com.wireguard.util.NonNullForAll;
+
+import java.util.Set;
+
+/**
+ * Class representing DHCP info for a {@link Tunnel} instance.
+ */
+@NonNullForAll
+public class Dhcp {
+ private Set<InetNetwork> addresses;
+
+ Dhcp(Set<InetNetwork> addresses) {
+ this.addresses = addresses;
+ }
+
+ public Set<InetNetwork> getAddresses() {
+ return addresses;
+ }
+
+ public String toString() {
+ return "DHCP";
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
index 3d0886cf..67451533 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -7,30 +7,90 @@ package com.wireguard.android.backend;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.LocalSocketAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.ProxyInfo;
+import android.net.TrafficStats;
+import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.system.OsConstants;
import android.util.Log;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Empty;
+
import com.wireguard.android.backend.BackendException.Reason;
import com.wireguard.android.backend.Tunnel.State;
+import com.wireguard.android.backend.gen.DhcpRequest;
+import com.wireguard.android.backend.gen.DhcpResponse;
+import com.wireguard.android.backend.gen.GetConnectionOwnerUidResponse;
+import com.wireguard.android.backend.gen.IpcSetRequest;
+import com.wireguard.android.backend.gen.IpcSetResponse;
+import com.wireguard.android.backend.gen.Lease;
+import com.wireguard.android.backend.gen.LibwgGrpc;
+import com.wireguard.android.backend.gen.ReverseRequest;
+import com.wireguard.android.backend.gen.ReverseResponse;
+import com.wireguard.android.backend.gen.StartHttpProxyRequest;
+import com.wireguard.android.backend.gen.StartHttpProxyResponse;
+import com.wireguard.android.backend.gen.StopHttpProxyRequest;
+import com.wireguard.android.backend.gen.StopHttpProxyResponse;
+import com.wireguard.android.backend.gen.TunnelHandle;
+import com.wireguard.android.backend.gen.VersionRequest;
+import com.wireguard.android.backend.gen.VersionResponse;
import com.wireguard.android.util.SharedLibraryLoader;
import com.wireguard.config.Config;
+import com.wireguard.config.HttpProxy;
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 com.wireguard.util.Resolver;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.grpc.stub.StreamObserver;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.net.URL;
+import java.nio.ByteOrder;
import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.net.SocketFactory;
import androidx.annotation.Nullable;
import androidx.collection.ArraySet;
@@ -43,12 +103,20 @@ import androidx.collection.ArraySet;
public final class GoBackend implements Backend {
private static final int DNS_RESOLUTION_RETRIES = 10;
private static final String TAG = "WireGuard/GoBackend";
+ private static final int STATS_TAG = 2;
@Nullable private static AlwaysOnCallback alwaysOnCallback;
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
private final Context context;
@Nullable private Config currentConfig;
@Nullable private Tunnel currentTunnel;
private int currentTunnelHandle = -1;
+ private ManagedChannel channel;
+ private ConnectivityManager connectivityManager;
+ private ConnectivityManager.NetworkCallback myNetworkCallback = new MyNetworkCallback();
+ private ConnectivityManager.NetworkCallback vpnNetworkCallback;
+ @Nullable private Network activeNetwork;
+ private boolean obtainDhcpLease = false;
+ @Nullable private Bgp bgp;
/**
* Public constructor for GoBackend.
@@ -58,6 +126,15 @@ public final class GoBackend implements Backend {
public GoBackend(final Context context) {
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
this.context = context;
+ connectivityManager = context.getSystemService(ConnectivityManager.class);
+ File socketFile = new File(context.getCacheDir(), "libwg.sock");
+ String socketName = socketFile.getAbsolutePath();
+ Log.i(TAG, "wgStartGrpc: " + wgStartGrpc(socketName));
+ ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress("localhost", 10000).usePlaintext();
+ LocalSocketAddress address = new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM);
+ SocketFactory socketFactory = new UnixDomainSocketFactory(address);
+ ((OkHttpChannelBuilder) channelBuilder).socketFactory(socketFactory);
+ channel = channelBuilder.build();
}
/**
@@ -76,12 +153,16 @@ public final class GoBackend implements Backend {
private static native int wgGetSocketV6(int handle);
+ private static native void wgSetFd(int handle, int tunFd);
+
private static native void wgTurnOff(int handle);
private static native int wgTurnOn(String ifName, int tunFd, String settings);
private static native String wgVersion();
+ private static native int wgStartGrpc(String sockName);
+
/**
* Method to get the names of running tunnels.
*
@@ -125,10 +206,17 @@ public final class GoBackend implements Backend {
Key key = null;
long rx = 0;
long tx = 0;
+ long handshakeSec = 0;
+ int handshakeNSec = 0;
for (final String line : config.split("\\n")) {
if (line.startsWith("public_key=")) {
- if (key != null)
- stats.add(key, rx, tx);
+ if (key != null) {
+ LocalDateTime handshake = null;
+ if (handshakeSec > 0) {
+ handshake = LocalDateTime.ofEpochSecond(handshakeSec, handshakeNSec, ZoneOffset.UTC);
+ }
+ stats.add(key, rx, tx, handshake);
+ }
rx = 0;
tx = 0;
try {
@@ -152,10 +240,31 @@ public final class GoBackend implements Backend {
} catch (final NumberFormatException ignored) {
tx = 0;
}
+ } else if (line.startsWith("last_handshake_time_sec=")) {
+ if (key == null)
+ continue;
+ try {
+ handshakeSec = Long.parseLong(line.substring(24));
+ } catch (final NumberFormatException ignored) {
+ handshakeSec = 0;
+ }
+ } else if (line.startsWith("last_handshake_time_nsec=")) {
+ if (key == null)
+ continue;
+ try {
+ handshakeNSec = Integer.parseInt(line.substring(25));
+ } catch (final NumberFormatException ignored) {
+ handshakeNSec = 0;
+ }
}
}
- if (key != null)
- stats.add(key, rx, tx);
+ if (key != null) {
+ LocalDateTime handshake = null;
+ if (handshakeSec > 0) {
+ handshake = LocalDateTime.ofEpochSecond(handshakeSec, handshakeNSec, ZoneOffset.UTC);
+ }
+ stats.add(key, rx, tx, handshake);
+ }
return stats;
}
@@ -166,7 +275,10 @@ public final class GoBackend implements Backend {
*/
@Override
public String getVersion() {
- return wgVersion();
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ VersionRequest request = VersionRequest.newBuilder().build();
+ VersionResponse resp = stub.version(request);
+ return resp.getVersion();
}
/**
@@ -205,78 +317,251 @@ public final class GoBackend implements Backend {
return getState(tunnel);
}
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
- throws Exception {
- Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state);
+ private static String downloadPacFile(Network network, Uri pacFileUrl) {
+ HttpURLConnection urlConnection = null;
+ StringBuffer buf = new StringBuffer();
+ try {
+ URL url = new URL(pacFileUrl.toString());
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ urlConnection = (HttpURLConnection) network.openConnection(url);
+
+ InputStream in = urlConnection.getInputStream();
+ InputStreamReader isw = new InputStreamReader(in);
+
+ int data = isw.read();
+ while (data != -1) {
+ char current = (char) data;
+ data = isw.read();
+ buf.append(current);
+ }
+ } catch (Exception e) {
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
- if (state == State.UP) {
- if (config == null)
- throw new BackendException(Reason.TUNNEL_MISSING_CONFIG);
+ return buf.toString();
+ }
- if (VpnService.prepare(context) != null)
- throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
+ private int startHttpProxy(String pacFile) {
+ LibwgGrpc.LibwgStub asyncStub = LibwgGrpc.newStub(channel);
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ StartHttpProxyRequest.Builder reqBuilder = StartHttpProxyRequest.newBuilder();
+ if (pacFile != null && pacFile != "") {
+ reqBuilder.setPacFileContent(pacFile);
+ }
- final VpnService service;
- if (!vpnService.isDone()) {
- Log.d(TAG, "Requesting to start VpnService");
- context.startService(new Intent(context, VpnService.class));
+ Thread streamer = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Log.i(TAG, "Before streamReverse");
+ streamReverse(asyncStub);
+ Log.i(TAG, "After streamReverse");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
}
+ });
- try {
- service = vpnService.get(2, TimeUnit.SECONDS);
- } catch (final TimeoutException e) {
- final Exception be = new BackendException(Reason.UNABLE_TO_START_VPN);
- be.initCause(e);
- throw be;
+ StartHttpProxyRequest req = reqBuilder.build();
+ StartHttpProxyResponse resp = stub.startHttpProxy(req);
+ Log.i(TAG, "Start http proxy listen_port:" + resp.getListenPort() + ", error:" + resp.getError().getMessage());
+ streamer.start();
+ return resp.getListenPort();
+ }
+
+ private void stopHttpProxy() {
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ StopHttpProxyRequest req = StopHttpProxyRequest.newBuilder().build();
+ StopHttpProxyResponse resp = stub.stopHttpProxy(req);
+ Log.i(TAG, "Stop http proxy: " + resp.getError().getMessage());
+ }
+
+ private static InetSocketAddress toInetSocketAddress(com.wireguard.android.backend.gen.InetSocketAddress sockAddr) {
+ try {
+ return new InetSocketAddress(InetAddress.getByAddress(sockAddr.getAddress().getAddress().toByteArray()), sockAddr.getPort());
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void Dhcp(VpnService service) throws Exception{
+ obtainDhcpLease = false;
+
+ // Heuristics: Use first ULA address as client address
+ com.wireguard.android.backend.gen.InetAddress source = null;
+
+ for (final InetNetwork net : currentConfig.getInterface().getAddresses()) {
+ InetAddress addr = net.getAddress();
+ if (addr instanceof Inet6Address) {
+ if (Resolver.isULA((Inet6Address)addr)) {
+ source = com.wireguard.android.backend.gen.InetAddress.newBuilder().setAddress(ByteString.copyFrom(addr.getAddress())).build();
+ }
}
- service.setOwner(this);
+ }
- if (currentTunnelHandle != -1) {
- Log.w(TAG, "Tunnel already up");
- return;
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ DhcpRequest request = DhcpRequest.newBuilder().setSource(source).build();
+ DhcpResponse resp = stub.dhcp(request);
+ Log.i(TAG, "Dhcp: " + resp.getError().getMessage());
+
+ Set<InetNetwork> addresses = new LinkedHashSet<>();
+ if (resp.getLeasesList() != null) {
+ for (final Lease lease: resp.getLeasesList()) {
+ try {
+ InetAddress addr = InetAddress.getByAddress(lease.getAddress().getAddress().toByteArray());
+ Log.i(TAG, "Lease: " + addr);
+ addresses.add(new InetNetwork(addr, 128));
+ } catch (UnknownHostException ex) {
+ // Ignore
+ }
}
+ }
+ Dhcp dhcp = new Dhcp(addresses);
- dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) {
- // Pre-resolve IPs so they're cached when building the userspace string
- for (final Peer peer : config.getPeers()) {
- final InetEndpoint ep = peer.getEndpoint().orElse(null);
- if (ep == null)
- continue;
- if (ep.getResolved().orElse(null) == null) {
- if (i < DNS_RESOLUTION_RETRIES - 1) {
- Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again");
- Thread.sleep(1000);
- continue dnsRetry;
- } else
- throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost());
+ // Replace the vpn tunnel
+ final VpnService.Builder builder = getBuilder(currentTunnel.getName(), currentConfig, service, dhcp.getAddresses());
+
+ Log.i(TAG, "Builder: " + builder);
+
+ try (final ParcelFileDescriptor tun = builder.establish()) {
+ if (tun == null)
+ throw new BackendException(Reason.TUN_CREATION_ERROR);
+ Log.d(TAG, "Go backend " + wgVersion());
+ // SetFd
+ wgSetFd(currentTunnelHandle, tun.detachFd());
+ }
+ if (currentTunnelHandle < 0)
+ throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
+
+ service.protect(wgGetSocketV4(currentTunnelHandle));
+ service.protect(wgGetSocketV6(currentTunnelHandle));
+ Log.i(TAG, "Dhcp done");
+
+ bgp = new Bgp(channel, currentTunnel, currentTunnelHandle);
+ bgp.startServer();
+
+ currentTunnel.onDhcpChange(dhcp);
+ }
+
+ private int getConnectionOwnerUid(int protocol, InetSocketAddress local, InetSocketAddress remote) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ return connectivityManager.getConnectionOwnerUid(protocol, local, remote);
+ else
+ return Process.INVALID_UID;
+ }
+
+ private void streamReverse(LibwgGrpc.LibwgStub asyncStub) throws InterruptedException {
+ Log.i(TAG, "In streamReverse");
+ final CountDownLatch finishLatch = new CountDownLatch(1);
+ final AtomicReference<StreamObserver<ReverseRequest>> atomicRequestObserver = new AtomicReference<StreamObserver<ReverseRequest>>();
+ // Throwable failed = null;
+
+ StreamObserver<ReverseResponse> responseObserver = new StreamObserver<ReverseResponse>() {
+ @Override
+ public void onNext(ReverseResponse resp) {
+ try {
+ String pkg = "";
+ int uid = getConnectionOwnerUid(resp.getUid().getProtocol(), toInetSocketAddress(resp.getUid().getLocal()), toInetSocketAddress(resp.getUid().getRemote()));
+ if (uid != Process.INVALID_UID) {
+ PackageManager pm = context.getPackageManager();
+ pkg = pm.getNameForUid(uid);
+ String[] pkgs = pm.getPackagesForUid(uid);
+ Log.i(TAG, "reverse onNext uid:" + uid + " package:" + pkg);
+ if (pkgs != null) {
+ for (int i=0; i < pkgs.length; i++) {
+ Log.i(TAG, "getPackagesForUid() = " + pkgs[i]);
+ }
+ }
+ } else {
+ Log.i(TAG, "Connection not found");
+ }
+
+ ReverseRequest req = ReverseRequest.newBuilder()
+ .setUid(GetConnectionOwnerUidResponse.newBuilder()
+ .setUid(uid)
+ .setPackage(pkg != null ? pkg: "")
+ .build())
+ .build();
+
+ io.grpc.Context.current().fork().run(new Runnable() {
+ public void run() {
+ atomicRequestObserver.get().onNext(req);
+ }
+ });
+ } catch (RuntimeException ex) {
+ Log.i(TAG, "onNext " + ex);
+ throw ex;
}
}
- break;
- }
- // Build config
- final String goConfig = config.toWgUserspaceString();
+ @Override
+ public void onError(Throwable t) {
+ // failed = t;
+ Log.i(TAG, "streamReverse error: " + t);
+ finishLatch.countDown();
+ }
- // Create the vpn tunnel with android API
+ @Override
+ public void onCompleted() {
+ Log.i(TAG, "streamReverse completed");
+ finishLatch.countDown();
+ }
+ };
+ StreamObserver<ReverseRequest> requestObserver = asyncStub.reverse(responseObserver);
+ atomicRequestObserver.set(requestObserver);
+
+ // Mark the end of requests
+ //requestObserver.onCompleted();
+
+ //requestObserver.onNext(ReverseRequest.getDefaultInstance());
+
+ Log.i(TAG, "Waiting streamReverse");
+ // Receiving happens asynchronously
+ finishLatch.await();
+
+ // if (failed != null) {
+ // throw new RuntimeException(failed);
+ // }
+ Log.i(TAG, "Exit streamReverse");
+ }
+
+ private VpnService.Builder getBuilder(final String name, @Nullable final Config config, final VpnService service, @Nullable final Set<InetNetwork> leases) throws PackageManager.NameNotFoundException {
+ Log.i(TAG, "Builder 1");
final VpnService.Builder builder = service.getBuilder();
- builder.setSession(tunnel.getName());
+ Log.i(TAG, "Builder 2");
+ builder.setSession(name);
+ Log.i(TAG, "Builder 3");
for (final String excludedApplication : config.getInterface().getExcludedApplications())
builder.addDisallowedApplication(excludedApplication);
+ Log.i(TAG, "Builder 4");
for (final String includedApplication : config.getInterface().getIncludedApplications())
builder.addAllowedApplication(includedApplication);
+ Log.i(TAG, "Builder 5");
+ if (leases != null) {
+ for (final InetNetwork lease: leases) {
+ builder.addAddress(lease.getAddress(), lease.getMask());
+ }
+ }
+
+ Log.i(TAG, "Builder 6");
for (final InetNetwork addr : config.getInterface().getAddresses())
builder.addAddress(addr.getAddress(), addr.getMask());
+ Log.i(TAG, "Builder 7");
for (final InetAddress addr : config.getInterface().getDnsServers())
builder.addDnsServer(addr.getHostAddress());
+ Log.i(TAG, "Builder 8");
for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains())
builder.addSearchDomain(dnsSearchDomain);
+ Log.i(TAG, "Builder 9");
boolean sawDefaultRoute = false;
for (final Peer peer : config.getPeers()) {
for (final InetNetwork addr : peer.getAllowedIps()) {
@@ -286,20 +571,125 @@ public final class GoBackend implements Backend {
}
}
+ Log.i(TAG, "Builder 10");
// "Kill-switch" semantics
if (!(sawDefaultRoute && config.getPeers().size() == 1)) {
builder.allowFamily(OsConstants.AF_INET);
builder.allowFamily(OsConstants.AF_INET6);
}
+ Log.i(TAG, "Builder 11");
builder.setMtu(config.getInterface().getMtu().orElse(1280));
+ Log.i(TAG, "Builder 12");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
builder.setMetered(false);
+ Log.i(TAG, "Builder 13");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
service.setUnderlyingNetworks(null);
+ Log.i(TAG, "Builder 14");
+ Optional<HttpProxy> proxy = config.getInterface().getHttpProxy();
+ Log.i(TAG, "Builder 14.1");
+ if (proxy.isPresent()) {
+ Log.i(TAG, "Builder 14.2");
+ ProxyInfo pi = proxy.get().getProxyInfo();
+ Log.i(TAG, "Builder 14.3");
+ Uri pacFileUrl = pi.getPacFileUrl();
+ Log.i(TAG, "Builder 14.4");
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Log.i(TAG, "Builder 14.5");
+ if (pacFileUrl != null && pacFileUrl != Uri.EMPTY) {
+ Log.i(TAG, "Builder 14.6");
+ String pacFile = downloadPacFile(activeNetwork, pacFileUrl);
+ int listenPort = startHttpProxy(pacFile);
+ Log.i(TAG, "Builder 14.7");
+ ProxyInfo localPi = ProxyInfo.buildDirectProxy("localhost", listenPort);
+ Log.i(TAG, "Builder 14.8");
+ builder.setHttpProxy(localPi);
+ Log.i(TAG, "Builder 14.9");
+ } else {
+ Log.i(TAG, "Builder 14.10");
+ builder.setHttpProxy(pi);
+ Log.i(TAG, "Builder 14.11");
+ }
+ Log.i(TAG, "Builder 14.12");
+ }
+ Log.i(TAG, "Builder 14.13");
+ }
+ Log.i(TAG, "Builder 14.14");
+
+ Log.i(TAG, "Builder 15");
builder.setBlocking(true);
+ return builder;
+ }
+
+ private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
+ throws Exception {
+ Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state);
+
+ if (state == State.UP) {
+ if (config == null)
+ throw new BackendException(Reason.TUNNEL_MISSING_CONFIG);
+
+ if (VpnService.prepare(context) != null)
+ throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
+
+ final VpnService service;
+ if (!vpnService.isDone()) {
+ Log.d(TAG, "Requesting to start VpnService");
+ context.startService(new Intent(context, VpnService.class));
+ }
+
+ try {
+ service = vpnService.get(2, TimeUnit.SECONDS);
+ } catch (final TimeoutException e) {
+ final Exception be = new BackendException(Reason.UNABLE_TO_START_VPN);
+ be.initCause(e);
+ throw be;
+ }
+ service.setOwner(this);
+
+ if (currentTunnelHandle != -1) {
+ Log.w(TAG, "Tunnel already up");
+ return;
+ }
+
+
+ activeNetwork = connectivityManager.getActiveNetwork();
+ if (!connectivityManager.getNetworkCapabilities(activeNetwork).hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
+ Log.w(TAG, "VPN network is active, null activeNetwork");
+ activeNetwork = null;
+ }
+ final Resolver resolver = new Resolver(activeNetwork, connectivityManager.getLinkProperties(activeNetwork));
+ dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) {
+ // Pre-resolve IPs so they're cached when building the userspace string
+ for (final Peer peer : config.getPeers()) {
+ final InetEndpoint ep = peer.getEndpoint().orElse(null);
+ if (ep == null)
+ continue;
+ // FIXME
+ tunnel.onEndpointChange(peer.getPublicKey(), ep);
+ Log.i(TAG, "onEndpointChange " + peer.getPublicKey() + ", " + ep);
+ if (ep.getResolved(resolver, true).orElse(null) == null) {
+ if (i < DNS_RESOLUTION_RETRIES - 1) {
+ Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again");
+ Thread.sleep(1000);
+ continue dnsRetry;
+ } else
+ throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost());
+ }
+ }
+ break;
+ }
+
+ // Build config
+ final String goConfig = config.toWgUserspaceString(resolver);
+
+ // Create the vpn tunnel with android API
+ final VpnService.Builder builder = getBuilder(tunnel.getName(), config, service, null);
+
try (final ParcelFileDescriptor tun = builder.establish()) {
if (tun == null)
throw new BackendException(Reason.TUN_CREATION_ERROR);
@@ -314,6 +704,15 @@ public final class GoBackend implements Backend {
service.protect(wgGetSocketV4(currentTunnelHandle));
service.protect(wgGetSocketV6(currentTunnelHandle));
+
+ obtainDhcpLease = true;
+
+ NetworkRequest req = new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).build();
+ connectivityManager.requestNetwork(req, myNetworkCallback);
+
+ NetworkRequest vpnReq = new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_VPN).removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).build();
+ vpnNetworkCallback = new VpnNetworkCallback(service);
+ connectivityManager.requestNetwork(vpnReq, vpnNetworkCallback);
} else {
if (currentTunnelHandle == -1) {
Log.w(TAG, "Tunnel already down");
@@ -323,6 +722,16 @@ public final class GoBackend implements Backend {
currentTunnel = null;
currentTunnelHandle = -1;
currentConfig = null;
+ if (bgp != null) {
+ bgp.stopServer();
+ bgp = null;
+ }
+ stopHttpProxy();
+ if (vpnNetworkCallback != null)
+ connectivityManager.unregisterNetworkCallback(vpnNetworkCallback);
+ vpnNetworkCallback = null;
+ connectivityManager.unregisterNetworkCallback(myNetworkCallback);
+ activeNetwork = null;
wgTurnOff(handleToClose);
}
@@ -385,10 +794,21 @@ public final class GoBackend implements Backend {
@Override
public void onDestroy() {
if (owner != null) {
+ if (owner.bgp != null) {
+ owner.bgp.stopServer();
+ owner.bgp = null;
+ }
+ owner.stopHttpProxy();
final Tunnel tunnel = owner.currentTunnel;
if (tunnel != null) {
- if (owner.currentTunnelHandle != -1)
+ if (owner.currentTunnelHandle != -1) {
+ if (owner.vpnNetworkCallback != null)
+ owner.connectivityManager.unregisterNetworkCallback(owner.vpnNetworkCallback);
+ owner.vpnNetworkCallback = null;
+ owner.connectivityManager.unregisterNetworkCallback(owner.myNetworkCallback);
+ owner.activeNetwork = null;
wgTurnOff(owner.currentTunnelHandle);
+ }
owner.currentTunnel = null;
owner.currentTunnelHandle = -1;
owner.currentConfig = null;
@@ -414,4 +834,48 @@ public final class GoBackend implements Backend {
this.owner = owner;
}
}
+
+ private class VpnNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private VpnService service;
+ public VpnNetworkCallback(VpnService service) {
+ this.service = service;
+ }
+ @Override
+ public void onAvailable(Network network) {
+ Log.w(TAG, "VPN onAvailable: " + network);
+ if (obtainDhcpLease) {
+ Log.w(TAG, "Obtaindhcplease");
+ try {
+ Log.w(TAG, "Before Dhcp");
+ Dhcp(service);
+ Log.w(TAG, "After Dhcp");
+ } catch (Exception ex) {
+ Log.e(TAG, "DHCP failed: " + ex);
+ }
+ }
+ }
+ }
+
+ private class MyNetworkCallback extends ConnectivityManager.NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ activeNetwork = network;
+ Log.w(TAG, "onAvailable: " + activeNetwork);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+ Log.w(TAG, "onLinkPropertiesChanged: " + network + " is default:" + (network.equals(activeNetwork)));
+ if (network.equals(activeNetwork) && currentConfig != null && currentTunnelHandle > -1) {
+ final Resolver resolver = new Resolver(network, linkProperties);
+ final String goConfig = currentConfig.toWgUserspaceStringWithChangedEndpoints(resolver);
+ Log.w(TAG, "is default network, config:" + goConfig);
+
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ TunnelHandle tunnel = TunnelHandle.newBuilder().setHandle(currentTunnelHandle).build();
+ IpcSetRequest request = IpcSetRequest.newBuilder().setTunnel(tunnel).setConfig(goConfig).build();
+ IpcSetResponse resp = stub.ipcSet(request);
+ }
+ }
+ }
}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java b/tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java
new file mode 100644
index 00000000..bf027ae1
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java
@@ -0,0 +1,325 @@
+/*
+ */
+package com.wireguard.android.backend;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImplFactory;
+import java.net.SocketOptions;
+import java.nio.channels.SocketChannel;
+import java.util.Vector;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+
+/**
+ * Adaptor allows using a LocalSocket as a Socket.
+ */
+final class LocalSocketAdapter extends Socket {
+ private final LocalSocketAddress address;
+ private final LocalSocket unix;
+ private final SocketAddress localAddress;
+ private InetSocketAddress inetSocketAddress;
+ private InputStream is;
+ private OutputStream os;
+
+ LocalSocketAdapter(LocalSocketAddress address) {
+ this.address = address;
+ this.localAddress = new InetSocketAddress(0);
+ unix = new LocalSocket();
+ }
+
+ LocalSocketAdapter(LocalSocketAddress address, InetSocketAddress inetAddress) {
+ this(address);
+ this.inetSocketAddress = inetAddress;
+ }
+
+ private void throwUnsupportedOperationException() {
+ Log.i("helloworld", "Unsupported: " + Log.getStackTraceString(new Exception()));
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void bind (SocketAddress bindpoint) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void close() throws IOException {
+ unix.close();
+ }
+
+ @Override public void connect(SocketAddress endpoint) throws IOException {
+ this.inetSocketAddress = (InetSocketAddress) endpoint;
+ try {
+ unix.connect(address);
+ } catch (IOException e) {
+ Log.i("helloworld", "Error: " + e.toString());
+ throw e;
+ }
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint, int timeout) throws IOException {
+ this.inetSocketAddress = (InetSocketAddress) endpoint;
+ unix.connect(address, timeout);
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ throwUnsupportedOperationException();
+ return null;
+ }
+
+ @Override public InetAddress getInetAddress() {
+ return inetSocketAddress.getAddress();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ is = unix.getInputStream();
+ return is;
+ }
+
+ @Override
+ public boolean getKeepAlive() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ throwUnsupportedOperationException();
+ return null;
+ }
+
+ @Override
+ public int getLocalPort() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ //throwUnsupportedOperationException();
+ return localAddress;
+ }
+
+ @Override
+ public boolean getOOBInline() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ if (os != null)
+ return os;
+
+ OutputStream unixOs = unix.getOutputStream();
+ os = new OutputStream() {
+ @Override
+ public void close() throws IOException {
+ // LocalSocket's default implementation closes the socket,
+ // which leaves readers of thes InputStream hanging.
+ // Instead shutdown input (and output) to release readers.
+ LocalSocketAdapter.this.shutdownInput();
+ LocalSocketAdapter.this.shutdownOutput();
+ }
+
+ @Override
+ public void write (byte[] b) throws IOException {
+ unixOs.write(b);
+ }
+
+ @Override
+ public void write (byte[] b, int off, int len) throws IOException {
+ unixOs.write(b, off, len);
+ }
+
+ @Override
+ public void write (int b) throws IOException {
+ unixOs.write(b);
+ }
+ };
+ return os;
+ }
+
+ @Override
+ public int getPort() {
+ return inetSocketAddress.getPort();
+ }
+
+ @Override
+ public int getReceiveBufferSize() throws SocketException {
+ try {
+ return unix.getReceiveBufferSize();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return inetSocketAddress;
+ }
+
+ @Override
+ public boolean getReuseAddress() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public int getSendBufferSize() throws SocketException {
+ try {
+ return unix.getSendBufferSize();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int getSoLinger() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public int getSoTimeout() throws SocketException {
+ try {
+ return unix.getSoTimeout();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean getTcpNoDelay() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public int getTrafficClass() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public boolean isBound() {
+ return unix.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return unix.isClosed();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return unix.isConnected();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return unix.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return unix.isOutputShutdown();
+ }
+
+ @Override
+ public void sendUrgentData (int data) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setKeepAlive (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setOOBInline (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setPerformancePreferences (int connectionTime,
+ int latency,
+ int bandwidth) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setReceiveBufferSize(int size) throws SocketException {
+ try {
+ unix.setReceiveBufferSize(size);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setReuseAddress (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setSendBufferSize(int size) throws SocketException {
+ try {
+ unix.setSendBufferSize(size);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setSoLinger (boolean on,
+ int linger) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setSoTimeout(int timeout) throws SocketException {
+ try {
+ unix.setSoTimeout(timeout);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setTcpNoDelay (boolean on) {
+ // Not relevant for local sockets.
+ }
+
+ @Override
+ public void setTrafficClass (int tc) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ unix.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ unix.shutdownOutput();
+ }
+
+ @Override
+ public String toString() {
+ return unix.toString();
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java
index 5d658019..ffec25f1 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,7 @@ import android.util.Pair;
import com.wireguard.crypto.Key;
import com.wireguard.util.NonNullForAll;
+import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@@ -19,7 +20,19 @@ import java.util.Map;
*/
@NonNullForAll
public class Statistics {
- private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
+ private static class Stat {
+ long rx;
+ long tx;
+ LocalDateTime lastHandshake;
+
+ Stat(long rx, long tx, LocalDateTime lastHandshake) {
+ this.rx = rx;
+ this.tx = tx;
+ this.lastHandshake = lastHandshake;
+ }
+ }
+
+ private final Map<Key, Stat> peerBytes = new HashMap<>();
private long lastTouched = SystemClock.elapsedRealtime();
Statistics() {
@@ -34,8 +47,8 @@ public class Statistics {
* @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes.
*/
- void add(final Key key, final long rx, final long tx) {
- peerBytes.put(key, Pair.create(rx, tx));
+ void add(final Key key, final long rx, final long tx, final LocalDateTime lastHandshake) {
+ peerBytes.put(key, new Stat(rx, tx, lastHandshake));
lastTouched = SystemClock.elapsedRealtime();
}
@@ -56,10 +69,10 @@ public class Statistics {
* @return a long representing the number of bytes received by this peer.
*/
public long peerRx(final Key peer) {
- final Pair<Long, Long> rxTx = peerBytes.get(peer);
- if (rxTx == null)
+ final Stat stat = peerBytes.get(peer);
+ if (stat == null)
return 0;
- return rxTx.first;
+ return stat.rx;
}
/**
@@ -70,10 +83,17 @@ public class Statistics {
* @return a long representing the number of bytes transmitted by this peer.
*/
public long peerTx(final Key peer) {
- final Pair<Long, Long> rxTx = peerBytes.get(peer);
- if (rxTx == null)
+ final Stat stat = peerBytes.get(peer);
+ if (stat == null)
return 0;
- return rxTx.second;
+ return stat.tx;
+ }
+
+ public LocalDateTime peerLastHandshake(final Key peer) {
+ final Stat stat = peerBytes.get(peer);
+ if (stat == null)
+ return null;
+ return stat.lastHandshake;
}
/**
@@ -93,8 +113,8 @@ public class Statistics {
*/
public long totalRx() {
long rx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- rx += val.first;
+ for (final Stat val : peerBytes.values()) {
+ rx += val.rx;
}
return rx;
}
@@ -106,8 +126,8 @@ public class Statistics {
*/
public long totalTx() {
long tx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- tx += val.second;
+ for (final Stat val : peerBytes.values()) {
+ tx += val.tx;
}
return tx;
}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java
index 9564bbd1..fc94375b 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java
@@ -1,12 +1,18 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.backend;
+import androidx.annotation.Nullable;
+
+import com.wireguard.config.InetEndpoint;
+import com.wireguard.config.InetNetwork;
+import com.wireguard.crypto.Key;
import com.wireguard.util.NonNullForAll;
+import java.util.List;
import java.util.regex.Pattern;
/**
@@ -54,4 +60,15 @@ public interface Tunnel {
return running ? UP : DOWN;
}
}
+
+ /**
+ * React to a change of DHCP of the tunnel. Should only be directly called by Backend.
+ *
+ * @param newDhcp The new DHCP info of the tunnel.
+ */
+ void onDhcpChange(Dhcp newDhcp);
+
+ void onEndpointChange(Key publicKey, @Nullable InetEndpoint newEndpoint);
+
+ void onAllowedIpsChange(Key publicKey, @Nullable List<InetNetwork> addNetworks, @Nullable List<InetNetwork> removeNetworks);
}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java b/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java
new file mode 100644
index 00000000..427a19f1
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.wireguard.android.backend;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import javax.net.SocketFactory;
+import android.net.LocalSocketAddress;
+
+/** Impersonate TCP-style SocketFactory over UNIX domain sockets. */
+public final class UnixDomainSocketFactory extends SocketFactory {
+ private final LocalSocketAddress address;
+
+ public UnixDomainSocketFactory(LocalSocketAddress address) {
+ this.address = address;
+ }
+
+ @Override public Socket createSocket() throws IOException {
+ return new LocalSocketAdapter(address);
+ }
+
+ @Override public Socket createSocket(String host, int port) throws IOException {
+ Socket result = createSocket();
+ result.connect(new InetSocketAddress(host, port));
+ return result;
+ }
+
+ @Override public Socket createSocket(
+ String host, int port, InetAddress localHost, int localPort) throws IOException {
+ return createSocket(host, port);
+ }
+
+ @Override public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket result = createSocket();
+ result.connect(new InetSocketAddress(host, port));
+ return result;
+ }
+
+ @Override public Socket createSocket(
+ InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocket(host, port);
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
index 3121c996..123e5a45 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -93,7 +93,7 @@ public final class WgQuickBackend implements Backend {
if (parts.length != 3)
continue;
try {
- stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
+ stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]), null);
} catch (final Exception ignored) {
}
}
diff --git a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java
index d9f02b34..e123733b 100644
--- a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java
+++ b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
index cf4fb584..15a00467 100644
--- a/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
+++ b/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java
index 04a4a948..65d23eab 100644
--- a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java
+++ b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/Attribute.java b/tunnel/src/main/java/com/wireguard/config/Attribute.java
index 969a6aa2..1b7bc368 100644
--- a/tunnel/src/main/java/com/wireguard/config/Attribute.java
+++ b/tunnel/src/main/java/com/wireguard/config/Attribute.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
index 8766ce51..0d41cc05 100644
--- a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
+++ b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,6 +8,8 @@ package com.wireguard.config;
import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
+import java.net.MalformedURLException;
+
import androidx.annotation.Nullable;
@NonNullForAll
@@ -44,6 +46,12 @@ public class BadConfigException extends Exception {
}
public BadConfigException(final Section section, final Location location,
+ @Nullable final CharSequence text,
+ final MalformedURLException cause) {
+ this(section, location, Reason.INVALID_VALUE, text, cause);
+ }
+
+ public BadConfigException(final Section section, final Location location,
final ParseException cause) {
this(section, location, Reason.INVALID_VALUE, cause.getText(), cause);
}
@@ -73,6 +81,7 @@ public class BadConfigException extends Exception {
ENDPOINT("Endpoint"),
EXCLUDED_APPLICATIONS("ExcludedApplications"),
INCLUDED_APPLICATIONS("IncludedApplications"),
+ HTTP_PROXY("HttpProxy"),
LISTEN_PORT("ListenPort"),
MTU("MTU"),
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
diff --git a/tunnel/src/main/java/com/wireguard/config/Config.java b/tunnel/src/main/java/com/wireguard/config/Config.java
index 807ebec8..5cd03fc0 100644
--- a/tunnel/src/main/java/com/wireguard/config/Config.java
+++ b/tunnel/src/main/java/com/wireguard/config/Config.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,6 +9,7 @@ import com.wireguard.config.BadConfigException.Location;
import com.wireguard.config.BadConfigException.Reason;
import com.wireguard.config.BadConfigException.Section;
import com.wireguard.util.NonNullForAll;
+import com.wireguard.util.Resolver;
import java.io.BufferedReader;
import java.io.IOException;
@@ -173,12 +174,19 @@ public final class Config {
*
* @return the {@code Config} represented as a series of "key=value" lines
*/
- public String toWgUserspaceString() {
+ public String toWgUserspaceString(Resolver resolver) {
final StringBuilder sb = new StringBuilder();
sb.append(interfaze.toWgUserspaceString());
sb.append("replace_peers=true\n");
for (final Peer peer : peers)
- sb.append(peer.toWgUserspaceString());
+ sb.append(peer.toWgUserspaceString(resolver));
+ return sb.toString();
+ }
+
+ public String toWgUserspaceStringWithChangedEndpoints(Resolver resolver) {
+ final StringBuilder sb = new StringBuilder();
+ for (final Peer peer : peers)
+ sb.append(peer.toWgUserspaceStringWithChangedEndpoint(resolver));
return sb.toString();
}
diff --git a/tunnel/src/main/java/com/wireguard/config/HttpProxy.java b/tunnel/src/main/java/com/wireguard/config/HttpProxy.java
new file mode 100644
index 00000000..d45914f8
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/config/HttpProxy.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2022 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.Section;
+import com.wireguard.util.NonNullForAll;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import android.net.ProxyInfo;
+import android.net.Uri;
+
+@NonNullForAll
+public final class HttpProxy {
+ public static final int DEFAULT_PROXY_PORT = 8080;
+
+ private ProxyInfo pi;
+
+ protected HttpProxy(ProxyInfo pi) {
+ this.pi = pi;
+ }
+
+ public ProxyInfo getProxyInfo() {
+ return pi;
+ }
+
+ public String getHost() {
+ return pi.getHost();
+ }
+
+ public Uri getPacFileUrl() {
+ return pi.getPacFileUrl();
+ }
+
+ public int getPort() {
+ return pi.getPort();
+ }
+
+ public static HttpProxy parse(final String httpProxy) throws BadConfigException {
+ try {
+ if (httpProxy.startsWith("pac:")) {
+ return new HttpProxy(ProxyInfo.buildPacProxy(Uri.parse(httpProxy.substring(4))));
+ } else {
+ final String urlStr;
+ if (!httpProxy.contains("://")) {
+ urlStr = "http://" + httpProxy;
+ } else {
+ urlStr = httpProxy;
+ }
+ URL url = new URL(urlStr);
+ return new HttpProxy(ProxyInfo.buildDirectProxy(url.getHost(), url.getPort() <= 0 ? DEFAULT_PROXY_PORT : url.getPort()));
+ }
+ } catch (final MalformedURLException e) {
+ throw new BadConfigException(Section.INTERFACE, Location.HTTP_PROXY, httpProxy, e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ if (pi.getPacFileUrl() != null && pi.getPacFileUrl() != Uri.EMPTY)
+ sb.append("pac:").append(pi.getPacFileUrl());
+ else {
+ sb.append("http://").append(pi.getHost()).append(':');
+ if (pi.getPort() <= 0)
+ sb.append(DEFAULT_PROXY_PORT);
+ else
+ sb.append(pi.getPort());
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java
index c1305e84..e4d697c5 100644
--- a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java
+++ b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
index 66855f11..dffd534b 100644
--- a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
+++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
@@ -1,11 +1,12 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.util.NonNullForAll;
+import com.wireguard.util.Resolver;
import java.net.Inet4Address;
import java.net.InetAddress;
@@ -64,6 +65,10 @@ public final class InetEndpoint {
}
}
+ public static InetEndpoint fromAddress(final InetAddress address, final int port) {
+ return new InetEndpoint(address.getHostAddress(), true, port);
+ }
+
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof InetEndpoint))
@@ -80,6 +85,14 @@ public final class InetEndpoint {
return port;
}
+ public Optional<InetEndpoint> getResolved() {
+ if (isResolved) {
+ return Optional.of(this);
+ } else {
+ return Optional.ofNullable(resolved);
+ }
+ }
+
/**
* Generate an {@code InetEndpoint} instance with the same port and the host resolved using DNS
* to a numeric address. If the host is already numeric, the existing instance may be returned.
@@ -87,24 +100,34 @@ public final class InetEndpoint {
*
* @return the resolved endpoint, or {@link Optional#empty()}
*/
- public Optional<InetEndpoint> getResolved() {
- if (isResolved)
+ public Optional<InetEndpoint> getResolved(Resolver resolver) {
+ return getResolved(resolver, false, false);
+ }
+
+ public Optional<InetEndpoint> getResolved(Resolver resolver, Boolean force) {
+ return getResolved(resolver, force, false);
+ }
+
+ public Optional<InetEndpoint> getResolvedIfChanged(Resolver resolver) {
+ return getResolved(resolver, true, true);
+ }
+
+ public Optional<InetEndpoint> getResolved(Resolver resolver, Boolean force, Boolean ifChanged) {
+ if (!force && isResolved)
return Optional.of(this);
synchronized (lock) {
//TODO(zx2c4): Implement a real timeout mechanism using DNS TTL
- if (Duration.between(lastResolution, Instant.now()).toMinutes() > 1) {
+ if (force || Duration.between(lastResolution, Instant.now()).toMinutes() > 1) {
try {
- // Prefer v4 endpoints over v6 to work around DNS64 and IPv6 NAT issues.
- final InetAddress[] candidates = InetAddress.getAllByName(host);
- InetAddress address = candidates[0];
- for (final InetAddress candidate : candidates) {
- if (candidate instanceof Inet4Address) {
- address = candidate;
- break;
- }
- }
- resolved = new InetEndpoint(address.getHostAddress(), true, port);
+ InetAddress address = resolver.resolve(host);
+ InetEndpoint resolvedNow = new InetEndpoint(address.getHostAddress(), true, port);
lastResolution = Instant.now();
+
+ if (ifChanged && resolvedNow.equals(resolved)) {
+ return Optional.empty();
+ }
+
+ resolved = resolvedNow;
} catch (final UnknownHostException e) {
resolved = null;
}
diff --git a/tunnel/src/main/java/com/wireguard/config/InetNetwork.java b/tunnel/src/main/java/com/wireguard/config/InetNetwork.java
index 8abf79aa..02ccd946 100644
--- a/tunnel/src/main/java/com/wireguard/config/InetNetwork.java
+++ b/tunnel/src/main/java/com/wireguard/config/InetNetwork.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -20,7 +20,7 @@ public final class InetNetwork {
private final InetAddress address;
private final int mask;
- private InetNetwork(final InetAddress address, final int mask) {
+ public InetNetwork(final InetAddress address, final int mask) {
this.address = address;
this.mask = mask;
}
diff --git a/tunnel/src/main/java/com/wireguard/config/Interface.java b/tunnel/src/main/java/com/wireguard/config/Interface.java
index 694f313a..e47e0be3 100644
--- a/tunnel/src/main/java/com/wireguard/config/Interface.java
+++ b/tunnel/src/main/java/com/wireguard/config/Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -46,6 +46,7 @@ public final class Interface {
private final KeyPair keyPair;
private final Optional<Integer> listenPort;
private final Optional<Integer> mtu;
+ private final Optional<HttpProxy> httpProxy;
private Interface(final Builder builder) {
// Defensively copy to ensure immutability even if the Builder is reused.
@@ -57,6 +58,7 @@ public final class Interface {
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
listenPort = builder.listenPort;
mtu = builder.mtu;
+ httpProxy = builder.httpProxy;
}
/**
@@ -92,6 +94,9 @@ public final class Interface {
case "mtu":
builder.parseMtu(attribute.getValue());
break;
+ case "httpproxy":
+ builder.parseHttpProxy(attribute.getValue());
+ break;
case "privatekey":
builder.parsePrivateKey(attribute.getValue());
break;
@@ -115,7 +120,8 @@ public final class Interface {
&& includedApplications.equals(other.includedApplications)
&& keyPair.equals(other.keyPair)
&& listenPort.equals(other.listenPort)
- && mtu.equals(other.mtu);
+ && mtu.equals(other.mtu)
+ && httpProxy.equals(other.httpProxy);
}
/**
@@ -195,6 +201,15 @@ public final class Interface {
return mtu;
}
+ /**
+ * Returns the HTTP proxy used for the WireGuard interface.
+ *
+ * @return the HTTP proxy, or {@code Optional.empty()} if none is configured
+ */
+ public Optional<HttpProxy> getHttpProxy() {
+ return httpProxy;
+ }
+
@Override
public int hashCode() {
int hash = 1;
@@ -205,6 +220,7 @@ public final class Interface {
hash = 31 * hash + keyPair.hashCode();
hash = 31 * hash + listenPort.hashCode();
hash = 31 * hash + mtu.hashCode();
+ hash = 31 * hash + httpProxy.hashCode();
return hash;
}
@@ -244,6 +260,7 @@ public final class Interface {
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'));
+ httpProxy.ifPresent(p -> sb.append("HttpProxy = ").append(p).append('\n'));
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
return sb.toString();
}
@@ -279,6 +296,8 @@ public final class Interface {
private Optional<Integer> listenPort = Optional.empty();
// Defaults to not present.
private Optional<Integer> mtu = Optional.empty();
+ // Defaults to not present.
+ private Optional<HttpProxy> httpProxy = Optional.empty();
public Builder addAddress(final InetNetwork address) {
addresses.add(address);
@@ -391,6 +410,10 @@ public final class Interface {
}
}
+ public Builder parseHttpProxy(final String httpProxy) throws BadConfigException {
+ return setHttpProxy(HttpProxy.parse(httpProxy));
+ }
+
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
try {
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
@@ -419,5 +442,13 @@ public final class Interface {
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
return this;
}
+
+ public Builder setHttpProxy(final HttpProxy httpProxy) throws BadConfigException {
+ if (httpProxy == null)
+ throw new BadConfigException(Section.INTERFACE, Location.HTTP_PROXY,
+ Reason.INVALID_VALUE, String.valueOf(httpProxy));
+ this.httpProxy = httpProxy == null ? Optional.empty() : Optional.of(httpProxy);
+ return this;
+ }
}
}
diff --git a/tunnel/src/main/java/com/wireguard/config/ParseException.java b/tunnel/src/main/java/com/wireguard/config/ParseException.java
index 8766d995..e72ef3d7 100644
--- a/tunnel/src/main/java/com/wireguard/config/ParseException.java
+++ b/tunnel/src/main/java/com/wireguard/config/ParseException.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/Peer.java b/tunnel/src/main/java/com/wireguard/config/Peer.java
index 9b87b397..d7b75f16 100644
--- a/tunnel/src/main/java/com/wireguard/config/Peer.java
+++ b/tunnel/src/main/java/com/wireguard/config/Peer.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,7 @@ import com.wireguard.config.BadConfigException.Section;
import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
+import com.wireguard.util.Resolver;
import java.util.Collection;
import java.util.Collections;
@@ -190,18 +191,26 @@ public final class Peer {
*
* @return the {@code Peer} represented as a series of "key=value" lines
*/
- public String toWgUserspaceString() {
+ public String toWgUserspaceString(Resolver resolver) {
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');
for (final InetNetwork allowedIp : allowedIps)
sb.append("allowed_ip=").append(allowedIp).append('\n');
- endpoint.flatMap(InetEndpoint::getResolved).ifPresent(ep -> sb.append("endpoint=").append(ep).append('\n'));
+ endpoint.flatMap(ep -> ep.getResolved(resolver)).ifPresent(ep -> sb.append("endpoint=").append(ep).append('\n'));
persistentKeepalive.ifPresent(pk -> sb.append("persistent_keepalive_interval=").append(pk).append('\n'));
preSharedKey.ifPresent(psk -> sb.append("preshared_key=").append(psk.toHex()).append('\n'));
return sb.toString();
}
+ public String toWgUserspaceStringWithChangedEndpoint(Resolver resolver) {
+ 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');
+ endpoint.flatMap(ep -> ep.getResolved(resolver, true)).ifPresent(ep -> sb.append("endpoint=").append(ep).append('\n'));
+ return sb.toString();
+ }
+
@SuppressWarnings("UnusedReturnValue")
public static final class Builder {
// See wg(8)
diff --git a/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java b/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java
index 3104345e..aa66d1ca 100644
--- a/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java
+++ b/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java
@@ -1,6 +1,6 @@
/*
* Copyright © 2016 Southern Storm Software, Pty Ltd.
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/crypto/Key.java b/tunnel/src/main/java/com/wireguard/crypto/Key.java
index ab5e13e0..5fa93a46 100644
--- a/tunnel/src/main/java/com/wireguard/crypto/Key.java
+++ b/tunnel/src/main/java/com/wireguard/crypto/Key.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java b/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java
index dda6d31f..c64b3dc8 100644
--- a/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java
+++ b/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java b/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java
index 1c0f0795..7a564689 100644
--- a/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java
+++ b/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java b/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java
index ed914a20..1c395621 100644
--- a/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java
+++ b/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/util/Resolver.java b/tunnel/src/main/java/com/wireguard/util/Resolver.java
new file mode 100644
index 00000000..654e01f5
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/util/Resolver.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2023 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.util;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import android.net.IpPrefix;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.TrafficStats;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+@NonNullForAll
+public class Resolver {
+ private static final String TAG = "WireGuard/Resolver";
+ private static final int STATS_TAG = 3; // FIXME
+ @Nullable private final Network network;
+ @Nullable private final LinkProperties linkProps;
+ @Nullable private IpPrefix nat64Prefix;
+
+ public Resolver(Network network, LinkProperties linkProps) {
+ this.network = network;
+ this.linkProps = linkProps;
+ if (linkProps != null) {
+ this.nat64Prefix = linkProps.getNat64Prefix();
+ }
+ }
+
+ public static boolean isULA(Inet6Address addr) {
+ byte[] raw = addr.getAddress();
+ return ((raw[0] & 0xfe) == 0xfc);
+ }
+
+ boolean isWithinNAT64Prefix(Inet6Address address) {
+ if (nat64Prefix == null)
+ return false;
+
+ int prefixLength = nat64Prefix.getPrefixLength();
+ byte[] rawAddr = address.getAddress();
+ byte[] rawPrefix = nat64Prefix.getRawAddress();
+
+ for (int i=0; i < prefixLength/8; i++) {
+ if (rawAddr[i] != rawPrefix[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ boolean isPreferredIPv6(Inet6Address local, Inet6Address remote) {
+ if (linkProps == null) {
+ // Prefer IPv4 if there are not link properties that can
+ // be tested.
+ return false;
+ }
+
+ // * Prefer IPv4 if local or remote address is ULA
+ // * Prefer IPv4 if remote IPv6 is within NAT64 prefix.
+ // * Otherwise prefer IPv6
+ boolean isLocalULA = isULA(local);
+ boolean isRemoteULA = isULA(remote);
+
+ if (isLocalULA || isRemoteULA) {
+ return false;
+ }
+
+ if (isWithinNAT64Prefix(remote)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public InetAddress resolve(String host) throws UnknownHostException {
+ TrafficStats.setThreadStatsTag(STATS_TAG);
+ final InetAddress[] candidates = network != null ? network.getAllByName(host) : InetAddress.getAllByName(host);
+ InetAddress address = candidates[0];
+ for (final InetAddress candidate : candidates) {
+ DatagramSocket sock;
+
+ try {
+ sock = new DatagramSocket();
+ TrafficStats.tagDatagramSocket(sock);
+ if (network != null) {
+ network.bindSocket(sock);
+ }
+ } catch (SocketException e) {
+ // Return first candidate as fallback
+ Log.w(TAG, "DatagramSocket failed, fallback to: \"" + address);
+ return address;
+ } catch (IOException e) {
+ // Return first candidate as fallback
+ Log.w(TAG, "BindSocket failed, fallback to: \"" + address);
+ return address;
+ }
+
+ sock.connect(candidate, 51820);
+
+ if (sock.getLocalAddress().isAnyLocalAddress()) {
+ // Connect didn't find a local address.
+ Log.w(TAG, "No local address");
+ continue;
+ }
+
+ Log.w(TAG, "Local address: " + sock.getLocalAddress());
+
+ if (candidate instanceof Inet4Address) {
+ // Accept IPv4 as preferred address.
+ address = candidate;
+ break;
+ }
+
+ Inet6Address local = (Inet6Address)sock.getLocalAddress();
+ InetSocketAddress remoteSockAddr = (InetSocketAddress)sock.getRemoteSocketAddress();
+ Inet6Address remote = (Inet6Address)remoteSockAddr.getAddress();
+ sock.close();
+
+ if (isPreferredIPv6(local, remote)) {
+ address = candidate;
+ break;
+ }
+ }
+ Log.w(TAG, "Resolved \"" + host + "\" to: " + address);
+ return address;
+ }
+}
diff --git a/tunnel/src/main/proto/libwg.proto b/tunnel/src/main/proto/libwg.proto
new file mode 100644
index 00000000..4fa4468b
--- /dev/null
+++ b/tunnel/src/main/proto/libwg.proto
@@ -0,0 +1,125 @@
+syntax = "proto3";
+
+import "google/protobuf/duration.proto";
+
+option java_multiple_files = true;
+option java_package = 'com.wireguard.android.backend.gen';
+option java_outer_classname = "LibwgProto";
+option java_generic_services = true;
+option go_package = 'golang.zx2c4.com/wireguard/android/gen';
+
+package api;
+
+service Libwg {
+ rpc StopGrpc(StopGrpcRequest) returns (StopGrpcResponse);
+ rpc Version(VersionRequest) returns (VersionResponse);
+ rpc StartHttpProxy(StartHttpProxyRequest) returns (StartHttpProxyResponse);
+ rpc StopHttpProxy(StopHttpProxyRequest) returns (StopHttpProxyResponse);
+ rpc Reverse(stream ReverseRequest) returns (stream ReverseResponse);
+ rpc IpcSet(IpcSetRequest) returns (IpcSetResponse);
+ rpc Dhcp(DhcpRequest) returns (DhcpResponse);
+}
+
+message TunnelHandle { int32 handle = 1; }
+
+message Error {
+ enum Code {
+ NO_ERROR = 0;
+ UNSPECIFIED = 1;
+ INVALID_PROTOCOL_BUFFER = 2;
+ INVALID_RESPONSE = 3;
+ }
+ Code code = 1;
+ string message = 2;
+}
+
+message InetAddress {
+ bytes address = 1;
+}
+
+message InetSocketAddress {
+ InetAddress address = 1;
+ uint32 port = 2;
+}
+
+message StopGrpcRequest {
+}
+
+message StopGrpcResponse {
+}
+
+message VersionRequest {
+}
+
+message VersionResponse {
+ string version = 1;
+}
+
+message StartHttpProxyRequest {
+ oneof pacFile {
+ string pacFileUrl = 1;
+ string pacFileContent = 2;
+ }
+}
+
+message StartHttpProxyResponse {
+ uint32 listen_port = 1;
+ Error error = 2;
+}
+
+message StopHttpProxyRequest {
+}
+
+message StopHttpProxyResponse {
+ Error error = 1;
+}
+
+message ReverseRequest {
+ oneof response {
+ GetConnectionOwnerUidResponse uid = 1;
+ }
+}
+
+message ReverseResponse {
+ oneof request {
+ GetConnectionOwnerUidRequest uid = 1;
+ }
+}
+
+message GetConnectionOwnerUidRequest {
+ // ConnectivityManager.getConnectionOwnerUid(int protocol,
+ // InetSocketAddress local, InetSocketAddress remote)
+ int32 protocol = 1;
+ InetSocketAddress local = 2;
+ InetSocketAddress remote = 3;
+}
+
+message GetConnectionOwnerUidResponse {
+ int32 uid = 1;
+ string package = 2; // context.getPackageManager().getNameForUid()
+}
+
+message IpcSetRequest {
+ TunnelHandle tunnel = 1;
+ string config = 2;
+}
+
+message IpcSetResponse {
+ Error error = 1;
+}
+
+message Lease {
+ InetAddress address = 1;
+ google.protobuf.Duration preferred_lifetime = 2;
+ google.protobuf.Duration valid_lifetime = 3;
+}
+
+message DhcpRequest {
+ InetAddress relay = 1;
+ InetAddress source = 2;
+}
+
+message DhcpResponse {
+ Error error = 1;
+ repeated Lease leases = 2;
+}
diff --git a/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java b/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
index ad5b4cf8..a24e049e 100644
--- a/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
+++ b/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/test/java/com/wireguard/config/ConfigTest.java b/tunnel/src/test/java/com/wireguard/config/ConfigTest.java
index 1893cfa6..92143e72 100644
--- a/tunnel/src/test/java/com/wireguard/config/ConfigTest.java
+++ b/tunnel/src/test/java/com/wireguard/config/ConfigTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/tools/CMakeLists.txt b/tunnel/tools/CMakeLists.txt
index a4af72aa..76191ca4 100644
--- a/tunnel/tools/CMakeLists.txt
+++ b/tunnel/tools/CMakeLists.txt
@@ -1,14 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
#
-# Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+# Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
cmake_minimum_required(VERSION 3.4.1)
project("WireGuard")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
-# Work around https://github.com/android-ndk/ndk/issues/602
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
-
add_executable(libwg-quick.so wireguard-tools/src/wg-quick/android.c ndk-compat/compat.c)
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_link_libraries(libwg-quick.so -ldl)
@@ -20,14 +17,13 @@ target_compile_options(libwg.so PUBLIC -O3 -std=gnu11 -D_GNU_SOURCE -include ${C
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
- ANDROID_C_COMPILER=${ANDROID_C_COMPILER}
- ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT}
- ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE}
- ANDROID_SYSROOT=${ANDROID_SYSROOT}
ANDROID_PACKAGE_NAME=${ANDROID_PACKAGE_NAME}
GRADLE_USER_HOME=${GRADLE_USER_HOME}
- CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument
- LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold
+ CC=${CMAKE_C_COMPILER}
+ CFLAGS=${CMAKE_C_FLAGS}
+ LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
+ SYSROOT=${CMAKE_SYSROOT}
+ TARGET=${CMAKE_C_COMPILER_TARGET}
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
BUILDDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../generated-src
)
diff --git a/tunnel/tools/libwg-go/.gitignore b/tunnel/tools/libwg-go/.gitignore
index d1638636..d69ee823 100644
--- a/tunnel/tools/libwg-go/.gitignore
+++ b/tunnel/tools/libwg-go/.gitignore
@@ -1 +1,2 @@
-build/ \ No newline at end of file
+build/
+/gen/
diff --git a/tunnel/tools/libwg-go/Makefile b/tunnel/tools/libwg-go/Makefile
index a2aefef7..90dbd6b9 100644
--- a/tunnel/tools/libwg-go/Makefile
+++ b/tunnel/tools/libwg-go/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
#
-# Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+# Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
BUILDDIR ?= $(CURDIR)/build
DESTDIR ?= $(CURDIR)/out
@@ -12,20 +12,27 @@ NDK_GO_ARCH_MAP_arm64 := arm64
NDK_GO_ARCH_MAP_mips := mipsx
NDK_GO_ARCH_MAP_mips64 := mips64x
-CLANG_FLAGS := --target=$(ANDROID_LLVM_TRIPLE) --gcc-toolchain=$(ANDROID_TOOLCHAIN_ROOT) --sysroot=$(ANDROID_SYSROOT)
-export CGO_CFLAGS := $(CLANG_FLAGS) $(CFLAGS)
+CLANG_FLAGS := --target=$(TARGET) --sysroot=$(SYSROOT)
+export CGO_CFLAGS := $(CLANG_FLAGS) $(subst -mthumb,-marm,$(CFLAGS))
export CGO_LDFLAGS := $(CLANG_FLAGS) $(LDFLAGS) -Wl,-soname=libwg-go.so
-export CC := $(ANDROID_C_COMPILER)
export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
export GOOS := android
export CGO_ENABLED := 1
+export GOPATH ?= $(HOME)/go
-GO_VERSION := 1.18.2
+GO_VERSION := 1.20.2
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 := 1f5f539ce0baa8b65f196ee219abf73a7d9cf558ba9128cc0fe4833da18b04f2
-GO_HASH_darwin-arm64 := 6c7df9a2405f09aa9bab55c93c9c4ce41d3e58127d626bc1825ba5d0a0045d5c
-GO_HASH_linux-amd64 := e54bec97a1a5d230fc2f9ad0880fcbabb5888f30ed9666eca4a91c5a32e86cbc
+GO_HASH_darwin-amd64 := c93b8ced9517d07e1cd4c362c6e2d5242cb139e29b417a328fbf19aded08764c
+GO_HASH_darwin-arm64 := 7343c87f19e79c0063532e82e1c4d6f42175a32d99f7a4d15e658e88bf97f885
+GO_HASH_linux-amd64 := 4eaea32f59cde4dc635fbc42161031d13e1c780b87097f4b4234cfce671f1768
+
+PROTOC_VERSION := 3.20.1
+PROTOC_GEN_GO := $(GOPATH)/bin/protoc-gen-go
+PROTOC := $(GRADLE_USER_HOME)/caches/protoc-$(PROTOC_VERSION)/protoc
+PROTODIR = $(CURDIR)/../../src/main/proto
+PROTO_INCLUDEDIR = $(CURDIR)/../../build/extracted-include-protos/debug
+PBDIR = $(GOPATH)/pkg/mod
default: $(DESTDIR)/libwg-go.so
@@ -45,8 +52,25 @@ $(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_T
patch -p1 -f -N -r- -d "$(dir $@)" < goruntime-boottime-over-monotonic.diff && \
touch "$@"'
+$(PROTOC_GEN_GO): export GOARCH :=
+$(PROTOC_GEN_GO): export GOOS :=
+$(PROTOC_GEN_GO): export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
+$(PROTOC_GEN_GO): $(BUILDDIR)/go-$(GO_VERSION)/.prepared Makefile
+ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
+ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
+
+gen/%.pb.go: export PATH := $(GOPATH)/bin:$(PATH)
+gen/%.pb.go: $(PROTODIR)/%.proto $(BUILDDIR)/go-$(GO_VERSION)/.prepared $(PROTOC_GEN_GO)
+ test -d gen || mkdir gen
+ $(PROTOC) -I $(PROTODIR) -I $(PROTO_INCLUDEDIR) --go_out=paths=source_relative:./gen $<
+
+gen/%_grpc.pb.go: export PATH := $(GOPATH)/bin:$(PATH)
+gen/%_grpc.pb.go: $(PROTODIR)/%.proto $(BUILDDIR)/go-$(GO_VERSION)/.prepared $(PROTOC_GEN_GO)
+ test -d gen || mkdir gen
+ $(PROTOC) -I $(PROTODIR) -I $(PROTO_INCLUDEDIR) --go-grpc_out=./gen --go-grpc_opt=paths=source_relative $<
+
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
-$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod
+$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod api-android.go dhcp.go http-proxy.go service.go gen/libwg.pb.go gen/libwg_grpc.pb.go jni.c
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -trimpath -o "$@" -buildmode c-shared
.DELETE_ON_ERROR:
diff --git a/tunnel/tools/libwg-go/api-android.go b/tunnel/tools/libwg-go/api-android.go
index d47c5d76..78ee7f54 100644
--- a/tunnel/tools/libwg-go/api-android.go
+++ b/tunnel/tools/libwg-go/api-android.go
@@ -48,10 +48,17 @@ func (l AndroidLogger) Printf(format string, args ...interface{}) {
type TunnelHandle struct {
device *device.Device
uapi net.Listener
+ logger *device.Logger
+ tun *tun.NativeTun
}
var tunnelHandles map[int32]TunnelHandle
+func GetTunnel(handle int32) (tunnelHandle TunnelHandle, ok bool) {
+ tunnelHandle, ok = tunnelHandles[handle]
+ return
+}
+
func init() {
tunnelHandles = make(map[int32]TunnelHandle)
signals := make(chan os.Signal)
@@ -142,10 +149,29 @@ func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
device.Close()
return -1
}
- tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi}
+ tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi, tun: tun}
return i
}
+//export wgSetFd
+func wgSetFd(tunnelHandle int32, tunFd int32) {
+ tag := cstring(fmt.Sprintf("WireGuard/GoBackend/%x", tunnelHandle))
+ logger := &device.Logger{
+ Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
+ Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
+ }
+
+ handle, ok := tunnelHandles[tunnelHandle]
+ if !ok {
+ unix.Close(int(tunFd))
+ logger.Errorf("Tunnel not found")
+ return
+ }
+
+ handle.tun.SetFd(int(tunFd))
+ logger.Verbosef("wgSetFd: %v", tunFd)
+}
+
//export wgTurnOff
func wgTurnOff(tunnelHandle int32) {
handle, ok := tunnelHandles[tunnelHandle]
@@ -208,20 +234,40 @@ func wgGetConfig(tunnelHandle int32) *C.char {
//export wgVersion
func wgVersion() *C.char {
+ return C.CString(Version())
+}
+
+func Version() string {
info, ok := debug.ReadBuildInfo()
if !ok {
- return C.CString("unknown")
+ return "unknown"
}
for _, dep := range info.Deps {
if dep.Path == "golang.zx2c4.com/wireguard" {
parts := strings.Split(dep.Version, "-")
if len(parts) == 3 && len(parts[2]) == 12 {
- return C.CString(parts[2][:7])
+ return parts[2][:7]
}
- return C.CString(dep.Version)
+ return dep.Version
}
}
- return C.CString("unknown")
+ return "unknown"
+}
+
+//export wgStartGrpc
+func wgStartGrpc(sock_path string) C.int{
+ tag := cstring("WireGuard/GoBackend/gRPC")
+ logger := &device.Logger{
+ Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
+ Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
+ }
+
+ res, errmsg := StartGrpc(sock_path, logger)
+ if res < 0 {
+ logger.Verbosef("wgStartGrpc: %v (%v)", errmsg, res)
+ }
+
+ return C.int(res)
}
func main() {}
diff --git a/tunnel/tools/libwg-go/dhcp.go b/tunnel/tools/libwg-go/dhcp.go
new file mode 100644
index 00000000..b7919151
--- /dev/null
+++ b/tunnel/tools/libwg-go/dhcp.go
@@ -0,0 +1,192 @@
+package main
+
+import (
+ "context"
+ "net"
+ "net/netip"
+ "time"
+
+ "github.com/insomniacslk/dhcp/dhcpv4"
+ "github.com/insomniacslk/dhcp/dhcpv6"
+ "github.com/insomniacslk/dhcp/dhcpv6/nclient6"
+ "github.com/insomniacslk/dhcp/iana"
+
+ gen "golang.zx2c4.com/wireguard/android/gen"
+)
+
+const (
+ ENABLE_PD = true
+)
+
+type dhcp struct {
+ fqdn string
+ hwAddr net.HardwareAddr
+ conn *net.UDPConn
+ serverAddr net.UDPAddr
+ client *nclient6.Client
+ linkAddr net.IP
+ peerAddr net.IP
+}
+
+func newClientIDOpt(duid dhcpv6.DUID) dhcpv4.Option {
+ iaid := []byte{0, 0, 0, 3}
+ ident := []byte{255} // Type IAID+DUID
+ ident = append(ident, iaid...) // IAID
+ ident = append(ident, duid.ToBytes()...) // DUID
+ return dhcpv4.OptClientIdentifier(ident)
+}
+
+func getDuid(hwAddr net.HardwareAddr) dhcpv6.DUID {
+ duid := &dhcpv6.DUIDLLT{
+ HWType: iana.HWTypeEthernet,
+ Time: uint32(time.Now().Unix()),
+ LinkLayerAddr: hwAddr,
+ }
+
+ return duid
+}
+
+func (d *dhcp) encapSendAndRead(ctx context.Context, msg *dhcpv6.Message, match nclient6.Matcher) (*dhcpv6.Message, error) {
+ packet, err := dhcpv6.EncapsulateRelay(msg, dhcpv6.MessageTypeRelayForward, d.linkAddr, d.peerAddr)
+ if err != nil {
+ return nil, err
+ }
+
+ relay, err := d.client.SendAndReadRelay(ctx, &d.serverAddr, packet, match)
+ if err != nil {
+ return nil, err
+ }
+
+ inner, err := relay.GetInnerMessage()
+ if err != nil {
+ return nil, err
+ }
+
+ return inner, nil
+}
+
+// isRelayMessageType returns a matcher that checks for the message type.
+func isRelayMessageType(t dhcpv6.MessageType, tt ...dhcpv6.MessageType) nclient6.Matcher {
+ return func(p dhcpv6.DHCPv6) bool {
+ inner, err := p.GetInnerMessage()
+ if err != nil {
+ return false
+ }
+ if inner.Type() == t {
+ return true
+ }
+ for _, mt := range tt {
+ if inner.Type() == mt {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+// func New() *dhcp {
+// }
+
+func RunDhcp(ctx context.Context, laddr, raddr netip.Addr) ([]*gen.Lease, error) {
+ d := &dhcp{}
+
+ d.linkAddr = net.ParseIP("fe80::101")
+ d.peerAddr = net.ParseIP("::1")
+
+ // TODO generate hostname and hwAddr from public key
+ hostName := "foobar"
+ d.fqdn = hostName + ".m7n.se"
+ d.hwAddr = []byte{41, 42, 43, 44, 45, 46}
+
+
+ src := net.UDPAddr{IP: laddr.AsSlice(),
+ Port: 0, // Use non-restrict UDP source port
+ }
+
+
+ d.serverAddr = net.UDPAddr{IP: raddr.AsSlice(),
+ Port: 547,
+ }
+
+ err := d.Start(&src)
+ if err != nil {
+ return nil, err
+ }
+
+ defer d.Close()
+
+ reply, err := d.ObtainLease(ctx) // Use reply
+ if err != nil {
+ return nil, err
+ }
+
+ return getAddressesFromReply(reply), nil
+}
+
+func getAddressesFromReply(reply *dhcpv6.Message) []*gen.Lease{
+ var leases []*gen.Lease = make([]*gen.Lease, 0, 4)
+
+ if opt := reply.GetOneOption(dhcpv6.OptionIANA); opt != nil {
+ iana := opt.(*dhcpv6.OptIANA)
+ ianaOpts := iana.Options.Get(dhcpv6.OptionIAAddr)
+
+ for _, opt := range ianaOpts {
+ addr :=opt.(*dhcpv6.OptIAAddress).IPv6Addr
+ lease := &gen.Lease{
+ Address: &gen.InetAddress{
+ Address: addr,
+ },
+ // PreferredLifetime: ,
+ // ValidLifetime: ,
+ }
+ leases = append(leases, lease)
+ }
+ }
+
+ return leases
+}
+
+func (d *dhcp) Start(localAddr *net.UDPAddr) error {
+ conn, err := net.ListenUDP("udp6", localAddr)
+ if err != nil {
+ return err
+ }
+
+ d.client, err = nclient6.NewWithConn(conn, d.hwAddr, nclient6.WithDebugLogger())
+ return err
+}
+
+func (d *dhcp) Close() error {
+ err := d.client.Close()
+
+ d.client = nil
+ return err
+}
+
+func (d *dhcp) ObtainLease(ctx context.Context) (*dhcpv6.Message, error){
+ duidOpt := dhcpv6.WithClientID(getDuid(d.hwAddr))
+ fqdnOpt := dhcpv6.WithFQDN(0x1, d.fqdn)
+
+ solicit, err := dhcpv6.NewSolicit(d.hwAddr, duidOpt, fqdnOpt, dhcpv6.WithRapidCommit)
+ if err != nil {
+ return nil, err
+ }
+
+ msg, err := d.encapSendAndRead(ctx, solicit, isRelayMessageType(dhcpv6.MessageTypeReply, dhcpv6.MessageTypeAdvertise))
+ if err != nil {
+ return nil, err
+ }
+
+ if msg.Type() == dhcpv6.MessageTypeReply {
+ // We got RapidCommitted.
+ return msg, nil
+ }
+
+ // We didn't get RapidCommitted. Request regular lease.
+ req, err := dhcpv6.NewRequestFromAdvertise(msg, fqdnOpt)
+ if err != nil {
+ return nil, err
+ }
+
+ return d.encapSendAndRead(ctx, req, nil)
+}
diff --git a/tunnel/tools/libwg-go/go.mod b/tunnel/tools/libwg-go/go.mod
index e64d2fb1..8f17923c 100644
--- a/tunnel/tools/libwg-go/go.mod
+++ b/tunnel/tools/libwg-go/go.mod
@@ -1,14 +1,29 @@
module golang.zx2c4.com/wireguard/android
-go 1.18
+go 1.20
require (
- golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a
+ github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b
+ github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
+ golang.org/x/net v0.7.0
+ golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
+ google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
+ google.golang.org/protobuf v1.27.1
+ gopkg.in/olebedev/go-duktape.v3 v3.0.0-20210326210528-650f7c854440
)
require (
- golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 // indirect
- golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect
- golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/josharian/native v1.1.0 // indirect
+ github.com/pierrec/lz4/v4 v4.1.14 // indirect
+ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
+ golang.org/x/crypto v0.6.0 // indirect
+ golang.org/x/text v0.7.0 // indirect
+ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
+ google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
)
+
+replace github.com/insomniacslk/dhcp => /src/insomniacslk-dhcp
+
+replace golang.zx2c4.com/wireguard => /src/wireguard-go
diff --git a/tunnel/tools/libwg-go/go.sum b/tunnel/tools/libwg-go/go.sum
index 633bbf7a..dfd31072 100644
--- a/tunnel/tools/libwg-go/go.sum
+++ b/tunnel/tools/libwg-go/go.sum
@@ -1,10 +1,173 @@
-golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
-golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
-golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
-golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
-golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
-golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
-golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b h1:1XqENn2YoYZd6w3Awx+7oa+aR87DFIZJFLF2n1IojA0=
+github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
+github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
+github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
+github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
+github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
+github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
+github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89 h1:260HNjMTPDya+jq5AM1zZLgG9pv9GASPAGiEEJUbRg4=
+golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+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=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ=
+google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84 h1:hZAzgyItS2MPyqvdC8wQZI99ZLGP9Vwijyfr0dmYWc4=
+google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20210326210528-650f7c854440 h1:SxFAMd+8zfpL/Rk4pgdb8leeZDiL3M/gCWCbBvmLkoE=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20210326210528-650f7c854440/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/tunnel/tools/libwg-go/goruntime-boottime-over-monotonic.diff b/tunnel/tools/libwg-go/goruntime-boottime-over-monotonic.diff
index 5cbc2256..7ae0999e 100644
--- a/tunnel/tools/libwg-go/goruntime-boottime-over-monotonic.diff
+++ b/tunnel/tools/libwg-go/goruntime-boottime-over-monotonic.diff
@@ -1,7 +1,8 @@
-From b83553d9f260ba20c6faaa52e6fe6f74309eb41a Mon Sep 17 00:00:00 2001
+From 729c58cb1c0496497dac6de3d0bf540f6149618f Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
-Date: Mon, 22 Feb 2021 02:36:03 +0100
-Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
+Date: Tue, 21 Mar 2023 15:33:56 +0100
+Subject: [PATCH] [release-branch.go1.20] runtime: use CLOCK_BOOTTIME in
+ nanotime on Linux
This makes timers account for having expired while a computer was
asleep, which is quite common on mobile devices. Note that BOOTTIME is
@@ -28,10 +29,10 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
-index 1e3a834812..78b6021fc7 100644
+index 12a294153d..17e3524b40 100644
--- a/src/runtime/sys_linux_386.s
+++ b/src/runtime/sys_linux_386.s
-@@ -337,13 +337,13 @@ noswitch:
+@@ -352,13 +352,13 @@ noswitch:
LEAL 8(SP), BX // &ts (struct timespec)
MOVL BX, 4(SP)
@@ -48,10 +49,10 @@ index 1e3a834812..78b6021fc7 100644
INVOKE_SYSCALL
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
-index 37cb8dad03..e8b730bcaa 100644
+index c7a89ba536..01f0a6a26e 100644
--- a/src/runtime/sys_linux_amd64.s
+++ b/src/runtime/sys_linux_amd64.s
-@@ -302,7 +302,7 @@ noswitch:
+@@ -255,7 +255,7 @@ noswitch:
SUBQ $16, SP // Space for results
ANDQ $~15, SP // Align for C code
@@ -61,7 +62,7 @@ index 37cb8dad03..e8b730bcaa 100644
MOVQ runtime·vdsoClockgettimeSym(SB), AX
CMPQ AX, $0
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
-index 475f52344c..bb567abcf4 100644
+index 7b8c4f0e04..9798a1334e 100644
--- a/src/runtime/sys_linux_arm.s
+++ b/src/runtime/sys_linux_arm.s
@@ -11,7 +11,7 @@
@@ -73,20 +74,20 @@ index 475f52344c..bb567abcf4 100644
// for EABI, as we don't support OABI
#define SYS_BASE 0x0
-@@ -366,7 +366,7 @@ noswitch:
- SUB $24, R13 // Space for results
- BIC $0x7, R13 // Align for C code
+@@ -374,7 +374,7 @@ finish:
+ // func nanotime1() int64
+ TEXT runtime·nanotime1(SB),NOSPLIT,$12-8
- MOVW $CLOCK_MONOTONIC, R0
+ MOVW $CLOCK_BOOTTIME, R0
- MOVW $8(R13), R1 // timespec
- MOVW runtime·vdsoClockgettimeSym(SB), R2
- CMP $0, R2
+ MOVW $spec-12(SP), R1 // timespec
+
+ MOVW runtime·vdsoClockgettimeSym(SB), R4
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
-index 198a5bacef..9715387f36 100644
+index 38ff6ac330..6b819c5441 100644
--- a/src/runtime/sys_linux_arm64.s
+++ b/src/runtime/sys_linux_arm64.s
-@@ -13,7 +13,7 @@
+@@ -14,7 +14,7 @@
#define AT_FDCWD -100
#define CLOCK_REALTIME 0
@@ -95,7 +96,7 @@ index 198a5bacef..9715387f36 100644
#define SYS_exit 93
#define SYS_read 63
-@@ -319,7 +319,7 @@ noswitch:
+@@ -338,7 +338,7 @@ noswitch:
BIC $15, R1
MOVD R1, RSP
@@ -105,10 +106,10 @@ index 198a5bacef..9715387f36 100644
CBZ R2, fallback
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
-index c3e9f37694..e3879acd38 100644
+index 47f2da524d..6c1a9a2801 100644
--- a/src/runtime/sys_linux_mips64x.s
+++ b/src/runtime/sys_linux_mips64x.s
-@@ -312,7 +312,7 @@ noswitch:
+@@ -326,7 +326,7 @@ noswitch:
AND $~15, R1 // Align for C code
MOVV R1, R29
@@ -118,10 +119,10 @@ index c3e9f37694..e3879acd38 100644
MOVV runtime·vdsoClockgettimeSym(SB), R25
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
-index fab2ab3892..f9af103594 100644
+index 5e6b6c1504..7f5fd2a80e 100644
--- a/src/runtime/sys_linux_mipsx.s
+++ b/src/runtime/sys_linux_mipsx.s
-@@ -238,7 +238,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$8-12
+@@ -243,7 +243,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$8-12
RET
TEXT runtime·nanotime1(SB),NOSPLIT,$8-8
@@ -131,11 +132,11 @@ index fab2ab3892..f9af103594 100644
MOVW $SYS_clock_gettime, R2
SYSCALL
diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s
-index fd69ee70a5..ff6bc8355b 100644
+index d0427a4807..05ee9fede9 100644
--- a/src/runtime/sys_linux_ppc64x.s
+++ b/src/runtime/sys_linux_ppc64x.s
-@@ -249,7 +249,7 @@ fallback:
- JMP finish
+@@ -298,7 +298,7 @@ fallback:
+ JMP return
TEXT runtime·nanotime1(SB),NOSPLIT,$16-8
- MOVD $1, R3 // CLOCK_MONOTONIC
@@ -144,18 +145,18 @@ index fd69ee70a5..ff6bc8355b 100644
MOVD R1, R15 // R15 is unchanged by C code
MOVD g_m(g), R21 // R21 = m
diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s
-index c15a1d5364..f52c4d5098 100644
+index 1448670b91..7d2ee3231c 100644
--- a/src/runtime/sys_linux_s390x.s
+++ b/src/runtime/sys_linux_s390x.s
-@@ -207,7 +207,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$16
+@@ -296,7 +296,7 @@ fallback:
RET
- TEXT runtime·nanotime1(SB),NOSPLIT,$16
-- MOVW $1, R2 // CLOCK_MONOTONIC
-+ MOVW $7, R2 // CLOCK_BOOTTIME
- MOVD $tp-16(SP), R3
- MOVW $SYS_clock_gettime, R1
- SYSCALL
+ TEXT runtime·nanotime1(SB),NOSPLIT,$32-8
+- MOVW $1, R2 // CLOCK_MONOTONIC
++ MOVW $7, R2 // CLOCK_BOOTTIME
+
+ MOVD R15, R7 // Backup stack pointer
+
--
-2.30.1
+2.40.0
diff --git a/tunnel/tools/libwg-go/http-proxy.go b/tunnel/tools/libwg-go/http-proxy.go
new file mode 100644
index 00000000..9c457286
--- /dev/null
+++ b/tunnel/tools/libwg-go/http-proxy.go
@@ -0,0 +1,716 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/netip"
+ "net/url"
+ "strings"
+ "sync"
+
+ "github.com/elazarl/goproxy"
+
+ "golang.zx2c4.com/wireguard/device"
+
+ net_proxy "golang.org/x/net/proxy"
+
+ "gopkg.in/olebedev/go-duktape.v3"
+)
+
+const (
+ // Imported from Firefox' ProxyAutoConfig.cpp
+ // https://searchfox.org/mozilla-central/source/netwerk/base/ProxyAutoConfig.cpp
+ // This Source Code Form is subject to the terms of the Mozilla Public
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
+ // file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ ASCII_PAC_UTILS = "function dnsDomainIs(host, domain) {\n" +
+ " return (host.length >= domain.length &&\n" +
+ " host.substring(host.length - domain.length) == domain);\n" +
+ "}\n" +
+ "" +
+ "function dnsDomainLevels(host) {\n" +
+ " return host.split('.').length - 1;\n" +
+ "}\n" +
+ "" +
+ "function isValidIpAddress(ipchars) {\n" +
+ " var matches = " +
+ "/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipchars);\n" +
+ " if (matches == null) {\n" +
+ " return false;\n" +
+ " } else if (matches[1] > 255 || matches[2] > 255 || \n" +
+ " matches[3] > 255 || matches[4] > 255) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " return true;\n" +
+ "}\n" +
+ "" +
+ "function convert_addr(ipchars) {\n" +
+ " var bytes = ipchars.split('.');\n" +
+ " var result = ((bytes[0] & 0xff) << 24) |\n" +
+ " ((bytes[1] & 0xff) << 16) |\n" +
+ " ((bytes[2] & 0xff) << 8) |\n" +
+ " (bytes[3] & 0xff);\n" +
+ " return result;\n" +
+ "}\n" +
+ "" +
+ "function isInNet(ipaddr, pattern, maskstr) {\n" +
+ " if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " if (!isValidIpAddress(ipaddr)) {\n" +
+ " ipaddr = dnsResolve(ipaddr);\n" +
+ " if (ipaddr == null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " }\n" +
+ " var host = convert_addr(ipaddr);\n" +
+ " var pat = convert_addr(pattern);\n" +
+ " var mask = convert_addr(maskstr);\n" +
+ " return ((host & mask) == (pat & mask));\n" +
+ " \n" +
+ "}\n" +
+ "" +
+ "function isPlainHostName(host) {\n" +
+ " return (host.search('(\\\\.)|:') == -1);\n" +
+ "}\n" +
+ "" +
+ "function isResolvable(host) {\n" +
+ " var ip = dnsResolve(host);\n" +
+ " return (ip != null);\n" +
+ "}\n" +
+ "" +
+ "function localHostOrDomainIs(host, hostdom) {\n" +
+ " return (host == hostdom) ||\n" +
+ " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" +
+ "}\n" +
+ "" +
+ "function shExpMatch(url, pattern) {\n" +
+ " pattern = pattern.replace(/\\./g, '\\\\.');\n" +
+ " pattern = pattern.replace(/\\*/g, '.*');\n" +
+ " pattern = pattern.replace(/\\?/g, '.');\n" +
+ " var newRe = new RegExp('^'+pattern+'$');\n" +
+ " return newRe.test(url);\n" +
+ "}\n" +
+ "" +
+ "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" +
+ "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, " +
+ "AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" +
+ "" +
+ "function weekdayRange() {\n" +
+ " function getDay(weekday) {\n" +
+ " if (weekday in wdays) {\n" +
+ " return wdays[weekday];\n" +
+ " }\n" +
+ " return -1;\n" +
+ " }\n" +
+ " var date = new Date();\n" +
+ " var argc = arguments.length;\n" +
+ " var wday;\n" +
+ " if (argc < 1)\n" +
+ " return false;\n" +
+ " if (arguments[argc - 1] == 'GMT') {\n" +
+ " argc--;\n" +
+ " wday = date.getUTCDay();\n" +
+ " } else {\n" +
+ " wday = date.getDay();\n" +
+ " }\n" +
+ " var wd1 = getDay(arguments[0]);\n" +
+ " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" +
+ " return (wd1 == -1 || wd2 == -1) ? false\n" +
+ " : (wd1 <= wd2) ? (wd1 <= wday && wday " +
+ "<= wd2)\n" +
+ " : (wd2 >= wday || wday " +
+ ">= wd1);\n" +
+ "}\n" +
+ "" +
+ "function dateRange() {\n" +
+ " function getMonth(name) {\n" +
+ " if (name in months) {\n" +
+ " return months[name];\n" +
+ " }\n" +
+ " return -1;\n" +
+ " }\n" +
+ " var date = new Date();\n" +
+ " var argc = arguments.length;\n" +
+ " if (argc < 1) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " var isGMT = (arguments[argc - 1] == 'GMT');\n" +
+ "\n" +
+ " if (isGMT) {\n" +
+ " argc--;\n" +
+ " }\n" +
+ " // function will work even without explict handling of this case\n" +
+ " if (argc == 1) {\n" +
+ " var tmp = parseInt(arguments[0]);\n" +
+ " if (isNaN(tmp)) {\n" +
+ " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" +
+ " getMonth(arguments[0]));\n" +
+ " } else if (tmp < 32) {\n" +
+ " return ((isGMT ? date.getUTCDate() : date.getDate()) == " +
+ "tmp);\n" +
+ " } else { \n" +
+ " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) " +
+ "==\n" +
+ " tmp);\n" +
+ " }\n" +
+ " }\n" +
+ " var year = date.getFullYear();\n" +
+ " var date1, date2;\n" +
+ " date1 = new Date(year, 0, 1, 0, 0, 0);\n" +
+ " date2 = new Date(year, 11, 31, 23, 59, 59);\n" +
+ " var adjustMonth = false;\n" +
+ " for (var i = 0; i < (argc >> 1); i++) {\n" +
+ " var tmp = parseInt(arguments[i]);\n" +
+ " if (isNaN(tmp)) {\n" +
+ " var mon = getMonth(arguments[i]);\n" +
+ " date1.setMonth(mon);\n" +
+ " } else if (tmp < 32) {\n" +
+ " adjustMonth = (argc <= 2);\n" +
+ " date1.setDate(tmp);\n" +
+ " } else {\n" +
+ " date1.setFullYear(tmp);\n" +
+ " }\n" +
+ " }\n" +
+ " for (var i = (argc >> 1); i < argc; i++) {\n" +
+ " var tmp = parseInt(arguments[i]);\n" +
+ " if (isNaN(tmp)) {\n" +
+ " var mon = getMonth(arguments[i]);\n" +
+ " date2.setMonth(mon);\n" +
+ " } else if (tmp < 32) {\n" +
+ " date2.setDate(tmp);\n" +
+ " } else {\n" +
+ " date2.setFullYear(tmp);\n" +
+ " }\n" +
+ " }\n" +
+ " if (adjustMonth) {\n" +
+ " date1.setMonth(date.getMonth());\n" +
+ " date2.setMonth(date.getMonth());\n" +
+ " }\n" +
+ " if (isGMT) {\n" +
+ " var tmp = date;\n" +
+ " tmp.setFullYear(date.getUTCFullYear());\n" +
+ " tmp.setMonth(date.getUTCMonth());\n" +
+ " tmp.setDate(date.getUTCDate());\n" +
+ " tmp.setHours(date.getUTCHours());\n" +
+ " tmp.setMinutes(date.getUTCMinutes());\n" +
+ " tmp.setSeconds(date.getUTCSeconds());\n" +
+ " date = tmp;\n" +
+ " }\n" +
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" +
+ " : (date2 >= date) || (date >= date1);\n" +
+ "}\n" +
+ "" +
+ "function timeRange() {\n" +
+ " var argc = arguments.length;\n" +
+ " var date = new Date();\n" +
+ " var isGMT= false;\n" +
+ "" +
+ " if (argc < 1) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " if (arguments[argc - 1] == 'GMT') {\n" +
+ " isGMT = true;\n" +
+ " argc--;\n" +
+ " }\n" +
+ "\n" +
+ " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" +
+ " var date1, date2;\n" +
+ " date1 = new Date();\n" +
+ " date2 = new Date();\n" +
+ "\n" +
+ " if (argc == 1) {\n" +
+ " return (hour == arguments[0]);\n" +
+ " } else if (argc == 2) {\n" +
+ " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" +
+ " } else {\n" +
+ " switch (argc) {\n" +
+ " case 6:\n" +
+ " date1.setSeconds(arguments[2]);\n" +
+ " date2.setSeconds(arguments[5]);\n" +
+ " case 4:\n" +
+ " var middle = argc >> 1;\n" +
+ " date1.setHours(arguments[0]);\n" +
+ " date1.setMinutes(arguments[1]);\n" +
+ " date2.setHours(arguments[middle]);\n" +
+ " date2.setMinutes(arguments[middle + 1]);\n" +
+ " if (middle == 2) {\n" +
+ " date2.setSeconds(59);\n" +
+ " }\n" +
+ " break;\n" +
+ " default:\n" +
+ " throw 'timeRange: bad number of arguments'\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " if (isGMT) {\n" +
+ " date.setFullYear(date.getUTCFullYear());\n" +
+ " date.setMonth(date.getUTCMonth());\n" +
+ " date.setDate(date.getUTCDate());\n" +
+ " date.setHours(date.getUTCHours());\n" +
+ " date.setMinutes(date.getUTCMinutes());\n" +
+ " date.setSeconds(date.getUTCSeconds());\n" +
+ " }\n" +
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" +
+ " : (date2 >= date) || (date >= date1);\n" +
+ "\n" +
+ "}\n"
+)
+
+type HttpProxy struct {
+ listener net.Listener
+ tlsListener net.Listener
+ logger *device.Logger
+ addrPort netip.AddrPort
+ tlsAddrPort netip.AddrPort
+ ctx *duktape.Context
+ defaultProxy *goproxy.ProxyHttpServer
+ uidRequest chan UidRequest
+ handlers []*HttpHandler
+}
+
+func NewHttpProxy(uidRequest chan UidRequest, logger *device.Logger) *HttpProxy {
+ logger.Verbosef("NewHttpProxy")
+ return &HttpProxy{
+ listener: nil,
+ logger: logger,
+ uidRequest: uidRequest,
+ handlers: make([]*HttpHandler, 0, 2),
+ }
+}
+
+var (
+ ASCII_PAC_UTILS_NAMES = []string{"dnsDomainIs", "dnsDomainLevels", "isValidIpAddress", "convert_addr", "isInNet", "isPlainHostName", "isResolvable", "localHostOrDomainIs", "shExpMatch", "weekdayRange", "dateRange", "timeRange"}
+)
+
+func (p *HttpProxy) GetAddrPort() netip.AddrPort {
+ return p.addrPort
+}
+
+type Logger struct {
+ logger *device.Logger
+}
+
+func (l *Logger) Printf(format string, v ...interface{}) {
+ l.logger.Verbosef(format, v...)
+}
+
+func (p *HttpProxy) newGoProxy(cat, proxyUrl string) (*goproxy.ProxyHttpServer, error) {
+ proxy := goproxy.NewProxyHttpServer()
+ proxy.Logger = &Logger{logger: p.logger}
+ proxy.Verbose = true
+ if cat == "SOCKS5" {
+ socksDialer, err := net_proxy.SOCKS5("tcp", proxyUrl, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ proxy.Tr.Dial = socksDialer.Dial
+ }
+ proxy.NonproxyHandler = http.HandlerFunc(func (w http.ResponseWriter, req *http.Request) {
+ if req.Host == "" {
+ fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0")
+ return
+ }
+ req.URL.Scheme = "http"
+ req.URL.Host = req.Host
+ proxy.ServeHTTP(w, req)
+ })
+
+ if cat == "PROXY" {
+ proxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
+ return url.Parse(proxyUrl)
+ }
+ proxy.ConnectDial = proxy.NewConnectDialToProxy(proxyUrl)
+ }
+
+ return proxy, nil
+}
+
+func FindProxyForURL(ctx *duktape.Context, url, host string, logger *device.Logger) (res string, err error) {
+ if !ctx.GetGlobalString("FindProxyForURL") {
+ ctx.Pop()
+ return "", fmt.Errorf("FindProxyForURL not found")
+ }
+
+ ctx.PushString(url)
+ ctx.PushString(host)
+
+ res = "DIRECT"
+ logger.Verbosef("Before pcall")
+ r := ctx.Pcall(2)
+ logger.Verbosef("After pcall")
+ if r == 0 {
+ res = ctx.GetString(-1)
+ } else if ctx.IsError(-1) {
+ ctx.GetPropString(-1, "stack")
+ err = fmt.Errorf("Error: %v", ctx.SafeToString(-1))
+ } else {
+ err = fmt.Errorf("Error: %v", ctx.SafeToString(-1))
+ }
+ ctx.Pop()
+ return
+}
+
+
+func FindProxyForPkg(ctx *duktape.Context, pkg string, logger *device.Logger) (res string, err error) {
+ if !ctx.GetGlobalString("FindProxyForPkg") {
+ ctx.Pop()
+ return "", fmt.Errorf("FindProxyForPkg not found")
+ }
+
+ ctx.PushString(pkg)
+
+ res = "DIRECT"
+ logger.Verbosef("Before pcall")
+ r := ctx.Pcall(1)
+ logger.Verbosef("After pcall")
+ if r == 0 {
+ res = ctx.GetString(-1)
+ } else if ctx.IsError(-1) {
+ ctx.GetPropString(-1, "stack")
+ err = fmt.Errorf("Error: %v", ctx.SafeToString(-1))
+ } else {
+ err = fmt.Errorf("Error: %v", ctx.SafeToString(-1))
+ }
+ ctx.Pop()
+ return
+}
+
+func newPacBodyCtx(body string, logger *device.Logger) *duktape.Context {
+ ctx := duktape.New()
+ ctx.PushGlobalGoFunction("dnsResolve",
+ func(ctx *duktape.Context) int {
+ // Check stack
+ host := ctx.GetString(-1)
+ ctx.Pop()
+ ips, err := net.LookupIP(host)
+ if err != nil {
+ return 0
+ }
+
+ for _, ip := range(ips) {
+ ipStr := string(ip)
+ if strings.Contains(ipStr, ".") {
+ // Found IPv4
+ ctx.PushString(ipStr)
+ return 1
+ }
+ }
+ // No IPv4
+ return 0
+ })
+
+ ctx.PevalString(ASCII_PAC_UTILS)
+ logger.Verbosef("ASCII_PAC_UTILS result is: %v stack:%v", ctx.GetType(-1), ctx.GetTop())
+ ctx.Pop()
+
+ ctx.PevalString(string(body))
+ logger.Verbosef("result is: %v stack:%v", ctx.GetType(-1), ctx.GetTop())
+ ctx.Pop()
+ FindProxyForURL(ctx, "http://www.jabra.se/", "www.jabra.se", logger)
+ return ctx
+}
+
+func (p *HttpProxy) newPacFileCtx(pacFileUrl *url.URL) (*duktape.Context, error) {
+ var pacFileBody = ""
+ if pacFileUrl == nil {
+ return nil, nil
+ }
+
+ resp, err := http.Get(pacFileUrl.String())
+ p.logger.Verbosef("pacFile: %v, %v", resp, err)
+
+ if err == nil {
+ defer resp.Body.Close()
+ ct, ok := resp.Header["Content-Type"]
+ if ok && len(ct) == 1 && ct[0] == "application/x-ns-proxy-autoconfig" {
+ body, err := io.ReadAll(resp.Body)
+ if err == nil {
+ pacFileBody = string(body)
+ return newPacBodyCtx(pacFileBody, p.logger), nil
+ }
+ }
+ }
+
+ return nil, err
+}
+
+func (p *HttpProxy) SetPacFileUrl(pacFileUrl *url.URL) error {
+ ctx, err := p.newPacFileCtx(pacFileUrl)
+ if err != nil {
+ return err
+ }
+
+ for _, handler := range(p.handlers) {
+ handler.setPacCtx(ctx)
+ }
+
+ if p.ctx != nil {
+ p.ctx.Destroy()
+ }
+ p.ctx = ctx
+ return nil
+}
+
+func (p *HttpProxy) SetPacFileContent(pacFile string) error {
+ ctx := newPacBodyCtx(pacFile, p.logger)
+
+ for _, handler := range(p.handlers) {
+ handler.setPacCtx(ctx)
+ }
+
+ if p.ctx != nil {
+ p.ctx.Destroy()
+ }
+ p.ctx = ctx
+ return nil
+}
+
+func (p *HttpProxy) Start() (listen_port uint16, err error) {
+ p.logger.Verbosef("HttpProxy.Start()")
+ listen_port = 0
+
+
+ proxyMap := make(map[string]*goproxy.ProxyHttpServer)
+
+ p.defaultProxy, err = p.newGoProxy("", "")
+ if err != nil {
+ return
+ }
+ proxyMap[""] = p.defaultProxy
+
+ listen_port, handler, err := p.startRegularProxy(proxyMap)
+ if err != nil {
+ return
+ }
+ p.handlers = append(p.handlers, handler)
+
+ return
+}
+
+func (p *HttpProxy) startRegularProxy(proxyMap map[string]*goproxy.ProxyHttpServer) (listen_port uint16, handler *HttpHandler, err error) {
+ p.listener, err = net.Listen("tcp", "[::]:")
+ if err != nil {
+ return
+ }
+
+ p.addrPort, err = netip.ParseAddrPort(p.listener.Addr().String())
+ if err != nil {
+ return
+ }
+
+ listen_port = p.addrPort.Port()
+
+ handler = NewHttpHandler(p, p.defaultProxy, proxyMap, p.logger)
+
+ go http.Serve(NewListener(p.listener, handler, p.logger), handler)
+ return
+}
+
+func (p *HttpProxy) Stop() {
+ if p.listener != nil {
+ p.logger.Verbosef("Close: %v", p.listener)
+ p.listener.Close()
+// p.listener = nil
+ }
+ if p.tlsListener != nil {
+ p.logger.Verbosef("Close: %v", p.tlsListener)
+ p.tlsListener.Close()
+// p.tlsListener = nil
+ }
+ if p.ctx != nil {
+ p.ctx.DestroyHeap()
+ p.ctx = nil
+ }
+}
+
+type HttpHandler struct {
+ p *HttpProxy
+ defaultProxy *goproxy.ProxyHttpServer
+ logger *device.Logger
+ remoteAddrPkgMapMutex sync.RWMutex
+ remoteAddrPkgMap map[string]*goproxy.ProxyHttpServer
+ uidRequest chan UidRequest
+ ctx *duktape.Context
+ proxyMap map[string]*goproxy.ProxyHttpServer // FIXME include type in map key
+}
+
+func NewHttpHandler(p *HttpProxy, defaultProxy *goproxy.ProxyHttpServer, proxyMap map[string]*goproxy.ProxyHttpServer, logger *device.Logger) *HttpHandler{
+ h := &HttpHandler{
+ p: p,
+ defaultProxy: defaultProxy,
+ logger: logger,
+ remoteAddrPkgMapMutex: sync.RWMutex{},
+ remoteAddrPkgMap: make(map[string]*goproxy.ProxyHttpServer),
+ uidRequest: p.uidRequest,
+ proxyMap: proxyMap,
+ }
+
+ return h
+}
+
+// TODO fix multi-threading
+func (h *HttpHandler) setPacCtx(ctx *duktape.Context) {
+ h.ctx = ctx
+}
+
+func (h *HttpHandler) addConnToProxyMap(c net.Conn) error {
+ h.logger.Verbosef("Accept: %v -> %v", c.RemoteAddr().String(), c.LocalAddr().String())
+ local, err := netip.ParseAddrPort(c.RemoteAddr().String())
+ if err != nil {
+ return fmt.Errorf("Bad local address (%v): %v", c.RemoteAddr(), err)
+ }
+ // Remove IPv6 zone, NOOP on IPv4
+ local = netip.AddrPortFrom(local.Addr().WithZone(""), local.Port())
+
+ remote, err := netip.ParseAddrPort(c.LocalAddr().String())
+ if err != nil {
+ return fmt.Errorf("Bad remote address (%v): %v", c.LocalAddr(), err)
+ }
+
+ h.logger.Verbosef("uidRequest: %v -> %v", local, remote)
+ addrPortPair := AddrPortPair{local: local, remote: remote}
+ retCh := make(chan string)
+ h.uidRequest <- UidRequest{Data: addrPortPair, RetCh: retCh}
+
+ select {
+ case pkg := <-retCh:
+ h.logger.Verbosef("uidResponse: '%v'", pkg)
+
+ proxy, ok := h.proxyMap[pkg]
+ if !ok {
+ proxy, err = h.findProxyForPkg(pkg)
+ if err != nil {
+ return err
+ }
+ }
+
+ if proxy != nil {
+ h.remoteAddrPkgMapMutex.Lock()
+ h.remoteAddrPkgMap[c.RemoteAddr().String()] = proxy
+ h.remoteAddrPkgMapMutex.Unlock()
+ }
+ }
+
+ return nil
+}
+
+func (h *HttpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ h.remoteAddrPkgMapMutex.Lock()
+ proxy, ok := h.remoteAddrPkgMap[req.RemoteAddr]
+ if ok && proxy != nil {
+ delete(h.remoteAddrPkgMap, req.RemoteAddr)
+ h.remoteAddrPkgMapMutex.Unlock()
+
+ proxyStr := "nil"
+ proxyUrl, err := proxy.Tr.Proxy(req)
+ if err == nil && proxyUrl != nil {
+ proxyStr = proxyUrl.String()
+ }
+ h.logger.Verbosef("ServeHTTP remote:%s proxy:%v", req.RemoteAddr, proxyStr)
+
+ proxy.ServeHTTP(rw, req)
+ } else {
+ h.remoteAddrPkgMapMutex.Unlock()
+ h.defaultProxy.ServeHTTP(rw, req)
+ }
+}
+
+func (h *HttpHandler) findProxyForPkg(pkg string) (*goproxy.ProxyHttpServer, error) {
+ if h.ctx == nil {
+ return nil, fmt.Errorf("Null context")
+ }
+
+ find := func() (cat string, res string, err error) {
+ h.logger.Verbosef("Call FindProxyForPkg %v %v", pkg)
+ res, err = FindProxyForPkg(h.ctx, pkg, h.logger)
+ h.logger.Verbosef("FindProxyForPkg res %v %v", res, err)
+ if err != nil {
+ h.logger.Verbosef("FindProxyForPkg result is: %v stack:%v", res, h.ctx.GetTop())
+ return "", "", err
+ } else if res == "" {
+ return "DIRECT", "", nil
+ } else {
+ values := strings.Split(strings.Trim(res, " "), ";")
+ for _, v := range values {
+ value := strings.Trim(v, " ")
+ parts := strings.SplitN(value, " ", 2)
+ if parts[0] == "PROXY" || parts[0] == "HTTP" {
+ return "PROXY", "http://" + parts[1], nil
+ } else if parts[0] == "HTTPS" {
+ return "PROXY", "https://" + parts[1], nil
+ } else if parts[0] == "DIRECT" {
+ return parts[0], "", nil
+ } else if parts[0] == "SOCKS" || parts[0] == "SOCKS5" {
+ return "SOCKS5", parts[1], nil
+ }
+ }
+ }
+ return "", "", fmt.Errorf("No result")
+ }
+ cat, res, err := find()
+ if err != nil {
+ return nil, err
+ }
+
+ var proxy *goproxy.ProxyHttpServer
+ if cat == "DIRECT" {
+ proxy = h.defaultProxy
+ } else {
+ proxy, err = h.p.newGoProxy(cat, res)
+ if err != nil {
+ return nil, err
+ }
+ }
+ h.proxyMap[pkg] = proxy
+
+ return proxy, nil
+}
+
+type AddrPortPair struct {
+ local netip.AddrPort
+ remote netip.AddrPort
+}
+
+// Listener
+type Listener struct {
+ l net.Listener
+ handler *HttpHandler
+ logger *device.Logger
+}
+
+func NewListener(listener net.Listener, handler *HttpHandler, logger *device.Logger) *Listener{
+ l := &Listener{
+ l: listener,
+ handler: handler,
+ logger: logger,
+ }
+
+ return l
+}
+
+func (l *Listener) Accept() (net.Conn, error) {
+ c, err := l.l.Accept()
+ if err != nil {
+ l.logger.Verbosef("Accept failed: %v", err)
+ return c, err
+ }
+
+ if err := l.handler.addConnToProxyMap(c); err != nil {
+ err = fmt.Errorf("Reject connection (%v): %v", c, err)
+ c.Close()
+ return nil, err
+ }
+
+ return c, nil
+}
+
+func (l *Listener) Close() error {
+ return l.l.Close()
+}
+
+func (l *Listener) Addr() net.Addr {
+ return l.l.Addr()
+}
diff --git a/tunnel/tools/libwg-go/jni.c b/tunnel/tools/libwg-go/jni.c
index 7ad94d35..20bfb332 100644
--- a/tunnel/tools/libwg-go/jni.c
+++ b/tunnel/tools/libwg-go/jni.c
@@ -14,6 +14,8 @@ extern int wgGetSocketV4(int handle);
extern int wgGetSocketV6(int handle);
extern char *wgGetConfig(int handle);
extern char *wgVersion();
+extern int wgStartGrpc();
+extern int wgSetFd(int handle, int tun_fd);
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
{
@@ -69,3 +71,20 @@ JNIEXPORT jstring JNICALL Java_com_wireguard_android_backend_GoBackend_wgVersion
free(version);
return ret;
}
+
+JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgStartGrpc(JNIEnv *env, jclass c, jstring sockname)
+{
+ const char *sockname_str = (*env)->GetStringUTFChars(env, sockname, 0);
+ size_t sockname_len = (*env)->GetStringUTFLength(env, sockname);
+ jint res = wgStartGrpc((struct go_string){
+ .str = sockname_str,
+ .n = sockname_len
+ });
+ (*env)->ReleaseStringUTFChars(env, sockname, sockname_str);
+ return res;
+}
+
+JNIEXPORT void JNICALL Java_com_wireguard_android_backend_GoBackend_wgSetFd(JNIEnv *env, jclass c, jint handle, jint tun_fd)
+{
+ wgSetFd(handle, tun_fd);
+}
diff --git a/tunnel/tools/libwg-go/service.go b/tunnel/tools/libwg-go/service.go
new file mode 100644
index 00000000..38601f14
--- /dev/null
+++ b/tunnel/tools/libwg-go/service.go
@@ -0,0 +1,319 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "net/netip"
+// "net/url"
+ "os"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+
+ gen "golang.zx2c4.com/wireguard/android/gen"
+ "golang.zx2c4.com/wireguard/device"
+)
+
+const (
+ IPPROTO_TCP = 6
+)
+
+type UidRequest struct {
+ Data AddrPortPair
+ RetCh chan string
+}
+
+type LibwgServiceImpl struct {
+ gen.UnimplementedLibwgServer
+ logger *device.Logger
+ httpProxy *HttpProxy
+ uidRequest chan UidRequest
+ stopReverse chan bool
+}
+
+var service *LibwgServiceImpl
+var server *grpc.Server
+
+func NewLibwgService(logger *device.Logger) gen.LibwgServer {
+ return &LibwgServiceImpl{
+ logger: logger,
+ uidRequest: make(chan UidRequest),
+ stopReverse: make(chan bool),
+ }
+}
+
+func StartGrpc(sock_path string, logger *device.Logger) (int, string) {
+ if server != nil {
+ return -1, "Already started"
+ }
+
+ if _, err := os.Stat(sock_path); err == nil {
+ if err := os.RemoveAll(sock_path); err != nil {
+ return -1, fmt.Sprintf("Cleanup failed: %v %v", sock_path, err)
+ }
+ }
+
+ listener, err := net.Listen("unix", sock_path)
+ if err != nil {
+ return -1, fmt.Sprintf("Listen failed: %v %v", sock_path, err)
+ }
+
+ server = grpc.NewServer()
+ service = NewLibwgService(logger).(*LibwgServiceImpl)
+
+ gen.RegisterLibwgServer(server, service)
+
+ go func() {
+ server.Serve(listener)
+ }()
+
+ logger.Verbosef("gRPC started")
+
+ return 0, ""
+}
+
+func (e *LibwgServiceImpl) Version(ctx context.Context, req *gen.VersionRequest) (*gen.VersionResponse, error) {
+
+ r := &gen.VersionResponse{
+ Version: Version(),
+ }
+
+ return r, nil
+}
+
+func (e *LibwgServiceImpl) StopGrpc(ctx context.Context, req *gen.StopGrpcRequest) (*gen.StopGrpcResponse, error) {
+ if server != nil {
+ server.Stop()
+ server = nil
+ service = nil
+ }
+
+ r := &gen.StopGrpcResponse{
+ }
+
+ return r, nil
+}
+
+func buildStartHttpProxyError(message string) (*gen.StartHttpProxyResponse, error) {
+ r := &gen.StartHttpProxyResponse{
+ Error: &gen.Error{
+ Message: message,
+ },
+ }
+ return r, nil
+}
+
+func (e *LibwgServiceImpl) StartHttpProxy(ctx context.Context, req *gen.StartHttpProxyRequest) (*gen.StartHttpProxyResponse, error) {
+ e.logger.Verbosef("StartHttpProxy 1")
+ var listenPort uint16
+ if e.httpProxy == nil {
+ e.logger.Verbosef("StartHttpProxy 2")
+ e.httpProxy = NewHttpProxy(e.uidRequest, e.logger)
+ e.logger.Verbosef("StartHttpProxy 3")
+ var err error
+ listenPort, err = e.httpProxy.Start()
+ e.logger.Verbosef("StartHttpProxy 4")
+
+ if err != nil {
+ e.httpProxy = nil
+ e.logger.Verbosef("StartHttpProxy 5")
+ return buildStartHttpProxyError(fmt.Sprintf("Http proxy start failed: %v", err))
+ }
+ } else {
+ listenPort = e.httpProxy.GetAddrPort().Port()
+ }
+
+ e.logger.Verbosef("StartHttpProxy 6")
+ // pacFileUrl, err := url.Parse(req.PacFileUrl)
+ // if err != nil {
+ // e.logger.Verbosef("StartHttpProxy 7")
+ // return buildStartHttpProxyError(fmt.Sprintf("Bad pacFileUrl: %v (%s)", err, req.PacFileUrl))
+ // }
+
+ e.logger.Verbosef("StartHttpProxy 8")
+ err := e.httpProxy.SetPacFileContent(req.GetPacFileContent())
+ if err != nil {
+ e.logger.Verbosef("StartHttpProxy 9")
+ return buildStartHttpProxyError(fmt.Sprintf("Bad pacFile: %v (%s)", req.GetPacFileContent()))
+ }
+
+ r := &gen.StartHttpProxyResponse{
+ ListenPort: uint32(listenPort),
+ }
+ e.logger.Verbosef("StartHttpProxy 10")
+ return r, nil
+}
+
+func (e *LibwgServiceImpl) StopHttpProxy(ctx context.Context, req *gen.StopHttpProxyRequest) (*gen.StopHttpProxyResponse, error) {
+ if e.httpProxy == nil {
+ r := &gen.StopHttpProxyResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("Http proxy not running"),
+ },
+ }
+ return r, nil
+ }
+
+ e.httpProxy.Stop()
+ e.httpProxy = nil
+ e.stopReverse <- true
+ r := &gen.StopHttpProxyResponse{}
+ return r, nil
+}
+
+func (e *LibwgServiceImpl) Reverse(stream gen.Libwg_ReverseServer) error {
+ e.logger.Verbosef("Reverse enter loop")
+ for e.httpProxy != nil {
+ var err error
+
+ // err := contextError(stream.Context())
+ err = stream.Context().Err()
+ if err != nil {
+ e.logger.Verbosef("Reverse: context: %v", err)
+ return err
+ }
+
+ select {
+ case <-e.stopReverse:
+ e.logger.Verbosef("Reverse: stop")
+ break
+ case uidReq := <-e.uidRequest:
+ addrPortPair := uidReq.Data
+ local := addrPortPair.local
+ remote := addrPortPair.remote
+ r := &gen.ReverseResponse{
+ Request: &gen.ReverseResponse_Uid{
+ Uid: &gen.GetConnectionOwnerUidRequest{
+ Protocol: IPPROTO_TCP,
+ Local: &gen.InetSocketAddress{
+ Address: &gen.InetAddress{
+ Address: local.Addr().AsSlice(),
+ },
+ Port: uint32(local.Port()),
+ },
+ Remote: &gen.InetSocketAddress{
+ Address: &gen.InetAddress{
+ Address: remote.Addr().AsSlice(),
+ },
+ Port: uint32(remote.Port()),
+ },
+ },
+ },
+ }
+
+ stream.Send(r)
+
+ req, err := stream.Recv()
+ if err == io.EOF {
+ e.logger.Verbosef("no more data")
+ uidReq.RetCh <- ""
+ break
+ }
+ if err != nil {
+ err = status.Errorf(codes.Unknown, "cannot receive stream request: %v", err)
+ e.logger.Verbosef("Reverse: %v", err)
+ uidReq.RetCh <- ""
+ return err
+ }
+
+ e.logger.Verbosef("Reverse: received, wait: %v", req)
+ uidReq.RetCh <- req.GetUid().GetPackage()
+ }
+ }
+
+
+ e.logger.Verbosef("Reverse returns")
+ return nil
+}
+
+func (e *LibwgServiceImpl) IpcSet(ctx context.Context, req *gen.IpcSetRequest) (*gen.IpcSetResponse, error) {
+ tunnel, ok := GetTunnel(req.GetTunnel().GetHandle())
+ if !ok {
+ r := &gen.IpcSetResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("Invalid tunnel"),
+ },
+ }
+ return r, nil
+ }
+
+ err := tunnel.device.IpcSet(req.GetConfig())
+ if err != nil {
+ r := &gen.IpcSetResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("IpcSet failed: %v", err),
+ },
+ }
+ return r, nil
+ }
+
+ r := &gen.IpcSetResponse{
+ }
+
+ return r, nil
+}
+
+func (e *LibwgServiceImpl) Dhcp(ctx context.Context, req *gen.DhcpRequest) (*gen.DhcpResponse, error) {
+ var relayAddr netip.Addr
+ var sourceAddr netip.Addr
+
+ source := req.GetSource()
+ if source != nil {
+ sourceAddr, _ = netip.AddrFromSlice(source.GetAddress())
+ }
+
+ if !sourceAddr.IsValid() || !sourceAddr.Is6() {
+ r := &gen.DhcpResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("DHCPv6 source address missing"),
+ },
+ }
+ return r, nil
+ }
+
+ relay := req.GetRelay()
+ if relay != nil {
+ relayAddr, _ = netip.AddrFromSlice(relay.GetAddress())
+ } else {
+ // Construct relay address from source prefix
+ relayRaw := source.GetAddress()[:8]
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 0)
+ relayRaw = append(relayRaw, 1)
+ relayAddr, _ = netip.AddrFromSlice(relayRaw)
+ }
+
+ if !relayAddr.IsValid() || !relayAddr.Is6() {
+ r := &gen.DhcpResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("DHCPv6 relay address calculation failed"),
+ },
+ }
+ return r, nil
+ }
+
+ e.logger.Verbosef("RunDhcp %v %v", sourceAddr, relayAddr)
+
+ leases, err := RunDhcp(ctx, sourceAddr, relayAddr)
+ if err != nil {
+ r := &gen.DhcpResponse{
+ Error: &gen.Error{
+ Message: fmt.Sprintf("RunDhcp failed: %v", err),
+ },
+ }
+ return r, nil
+ }
+
+ r := &gen.DhcpResponse{
+ Leases: leases,
+ }
+ return r, nil
+}
diff --git a/tunnel/tools/ndk-compat/compat.c b/tunnel/tools/ndk-compat/compat.c
index 596dbd81..2c54c086 100644
--- a/tunnel/tools/ndk-compat/compat.c
+++ b/tunnel/tools/ndk-compat/compat.c
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: BSD
*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
*
*/
#define FILE_IS_EMPTY
-#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
+#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#undef FILE_IS_EMPTY
#include <stdio.h>
#include <stdlib.h>
@@ -58,7 +58,7 @@ ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
}
#endif
-#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
+#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
#undef FILE_IS_EMPTY
#include <string.h>
diff --git a/tunnel/tools/ndk-compat/compat.h b/tunnel/tools/ndk-compat/compat.h
index 820f1015..32f6d799 100644
--- a/tunnel/tools/ndk-compat/compat.h
+++ b/tunnel/tools/ndk-compat/compat.h
@@ -1,16 +1,16 @@
/* SPDX-License-Identifier: BSD
*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
*
*/
-#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
+#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#include <stdio.h>
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
#endif
-#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
+#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
char *strchrnul(const char *s, int c);
#endif
diff --git a/tunnel/tools/wireguard-tools b/tunnel/tools/wireguard-tools
-Subproject c0b68d2eafaf2b44df9377ba0844bc315163247
+Subproject b4f6b4f229d291daf7c35c6f1e7f4841cc6d69b
diff --git a/ui/build.gradle b/ui/build.gradle
index 1ac42e58..61c06f2a 100644
--- a/ui/build.gradle
+++ b/ui/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
@@ -12,16 +14,17 @@ group groupName
final def keystorePropertiesFile = rootProject.file("keystore.properties")
android {
- compileSdkVersion 31
+ compileSdk 33
buildFeatures {
buildConfig = true
dataBinding = true
viewBinding = true
}
+ namespace = 'com.wireguard.android'
defaultConfig {
applicationId 'eu.m7n.wireguard.android'
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 33
versionCode wireguardVersionCode
versionName wireguardVersionName
buildConfigField 'int', 'MIN_SDK_VERSION', "$minSdkVersion.apiLevel"
@@ -59,6 +62,9 @@ android {
}
}
buildTypes {
+ all {
+ buildConfigField("boolean", "IS_GOOGLE_PLAY", (System.getenv("WG_BUILD_FOR_GOOGLE_PLAY") == "true").toString())
+ }
release {
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
minifyEnabled true
@@ -69,10 +75,9 @@ android {
versionNameSuffix "-debug"
}
}
- lintOptions {
- disable('LongLogTag')
- warning('MissingTranslation')
- warning('ImpliedQuantity')
+ lint {
+ disable 'LongLogTag'
+ warning 'MissingTranslation', 'ImpliedQuantity'
}
}
@@ -96,14 +101,11 @@ dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarVersion"
}
-tasks.withType(JavaCompile) {
+tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
options.deprecation = true
}
-tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8
- useIR = true
- }
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
}
diff --git a/ui/src/main/AndroidManifest.xml b/ui/src/main/AndroidManifest.xml
index 4dd38cb2..2f83ba86 100644
--- a/ui/src/main/AndroidManifest.xml
+++ b/ui/src/main/AndroidManifest.xml
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.wireguard.android"
android:installLocation="internalOnly">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission
@@ -21,6 +22,9 @@
<uses-feature
android:name="android.hardware.camera.any"
android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera"
+ android:required="false" />
<permission
android:name="${applicationId}.permission.CONTROL_TUNNELS"
@@ -33,6 +37,7 @@
android:name=".Application"
android:allowBackup="false"
android:banner="@mipmap/banner"
+ android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
@@ -44,7 +49,7 @@
android:theme="@style/NoBackgroundTheme"
android:excludeFromRecents="true"/>
- <activity android:name=".activity.MainActivity">
+ <activity android:name=".activity.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -58,7 +63,8 @@
<activity
android:name=".activity.TvMainActivity"
- android:theme="@style/TvTheme">
+ android:theme="@style/TvTheme"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
@@ -82,7 +88,8 @@
<activity
android:name=".activity.LogViewerActivity"
- android:label="@string/log_viewer_title">
+ android:label="@string/log_viewer_title"
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -94,7 +101,7 @@
android:exported="false"
android:grantUriPermissions="true" />
- <receiver android:name=".BootShutdownReceiver">
+ <receiver android:name=".BootShutdownReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -103,7 +110,8 @@
<receiver
android:name=".model.TunnelManager$IntentReceiver"
- android:permission="${applicationId}.permission.CONTROL_TUNNELS">
+ android:permission="${applicationId}.permission.CONTROL_TUNNELS"
+ android:exported="true">
<intent-filter>
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
@@ -114,7 +122,8 @@
<service
android:name=".QuickTileService"
android:icon="@drawable/ic_tile"
- android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -135,5 +144,10 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
+
+ <intent>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+ </intent>
</queries>
</manifest>
diff --git a/ui/src/main/java/com/wireguard/android/Application.kt b/ui/src/main/java/com/wireguard/android/Application.kt
index d9dcb637..cac1f8d8 100644
--- a/ui/src/main/java/com/wireguard/android/Application.kt
+++ b/ui/src/main/java/com/wireguard/android/Application.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android
@@ -16,6 +16,7 @@ import androidx.datastore.core.DataStore
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
@@ -64,11 +65,9 @@ class Application : android.app.Application() {
private suspend fun determineBackend(): Backend {
var backend: Backend? = null
- var didStartRootShell = false
if (UserKnobs.enableKernelModule.first() && WgQuickBackend.hasKernelSupport()) {
try {
- if (!didStartRootShell)
- rootShell.start()
+ rootShell.start()
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
backend = wgQuickBackend
@@ -88,6 +87,7 @@ class Application : android.app.Application() {
override fun onCreate() {
Log.i(TAG, USER_AGENT)
super.onCreate()
+ DynamicColors.applyToActivitiesIfAvailable(this)
rootShell = RootShell(applicationContext)
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
diff --git a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
index b8bb4f6a..23775f44 100644
--- a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
+++ b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android
diff --git a/ui/src/main/java/com/wireguard/android/QuickTileService.kt b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
index 8b35f9b4..ed208c50 100644
--- a/ui/src/main/java/com/wireguard/android/QuickTileService.kt
+++ b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android
diff --git a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
index bdf0f8d4..8f0855ea 100644
--- a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
@@ -10,6 +10,7 @@ import androidx.databinding.CallbackRegistry.NotifierCallback
import androidx.lifecycle.lifecycleScope
import com.wireguard.android.Application
import com.wireguard.android.model.ObservableTunnel
+import kotlinx.coroutines.launch
/**
* Base class for activities that need to remember the currently-selected tunnel.
@@ -30,6 +31,8 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
// Restore the saved tunnel if there is one; otherwise grab it from the arguments.
val savedTunnelName = when {
savedInstanceState != null -> savedInstanceState.getString(KEY_SELECTED_TUNNEL)
@@ -37,10 +40,9 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
else -> null
}
if (savedTunnelName != null)
- lifecycleScope.launchWhenCreated { selectedTunnel = Application.getTunnelManager().getTunnels()[savedTunnelName] }
-
- // The selected tunnel must be set before the superclass method recreates fragments.
- super.onCreate(savedInstanceState)
+ lifecycleScope.launch {
+ selectedTunnel = Application.getTunnelManager().getTunnels()[savedTunnelName]
+ }
}
override fun onSaveInstanceState(outState: Bundle) {
diff --git a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
index dd100cf9..fe75be35 100644
--- a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt b/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt
index aac08e18..1566c129 100644
--- a/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
@@ -9,6 +9,8 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.addCallback
import androidx.appcompat.app.ActionBar
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
@@ -26,27 +28,29 @@ import com.wireguard.android.model.ObservableTunnel
class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
private var actionBar: ActionBar? = null
private var isTwoPaneLayout = false
+ private var backPressedCallback: OnBackPressedCallback? = null
- override fun onBackPressed() {
+ private fun handleBackPressed() {
val backStackEntries = supportFragmentManager.backStackEntryCount
// If the two-pane layout does not have an editor open, going back should exit the app.
if (isTwoPaneLayout && backStackEntries <= 1) {
finish()
return
}
- // Deselect the current tunnel on navigating back from the detail pane to the one-pane list.
- if (!isTwoPaneLayout && backStackEntries == 1) {
+
+ if (backStackEntries >= 1)
supportFragmentManager.popBackStack()
+
+ // Deselect the current tunnel on navigating back from the detail pane to the one-pane list.
+ if (backStackEntries == 1)
selectedTunnel = null
- return
- }
- super.onBackPressed()
}
override fun onBackStackChanged() {
+ val backStackEntries = supportFragmentManager.backStackEntryCount
+ backPressedCallback?.isEnabled = backStackEntries >= 1
if (actionBar == null) return
// Do not show the home menu when the two-pane layout is at the detail view (see above).
- val backStackEntries = supportFragmentManager.backStackEntryCount
val minBackStackEntries = if (isTwoPaneLayout) 2 else 1
actionBar!!.setDisplayHomeAsUpEnabled(backStackEntries >= minBackStackEntries)
}
@@ -57,6 +61,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
actionBar = supportActionBar
isTwoPaneLayout = findViewById<View?>(R.id.master_detail_wrapper) != null
supportFragmentManager.addOnBackStackChangedListener(this)
+ backPressedCallback = onBackPressedDispatcher.addCallback(this) { handleBackPressed() }
onBackStackChanged()
}
@@ -69,7 +74,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
return when (item.itemId) {
android.R.id.home -> {
// The back arrow in the action bar should act the same as the back button.
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
true
}
R.id.menu_action_edit -> {
diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
index df025163..aa768e0b 100644
--- a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
diff --git a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt
index 2158858b..06f621c4 100644
--- a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt
index 28d5da3a..b3fccee3 100644
--- a/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
index ebf059a5..ee95ce40 100644
--- a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.activity
diff --git a/ui/src/main/java/com/wireguard/android/activity/TvMainActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TvMainActivity.kt
index 7b0737e8..ae98f442 100644
--- a/ui/src/main/java/com/wireguard/android/activity/TvMainActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/TvMainActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -16,12 +16,14 @@ import android.os.storage.StorageVolume
import android.util.Log
import android.view.View
import android.widget.Toast
+import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.forEach
import androidx.databinding.DataBindingUtil
+import androidx.databinding.Observable
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.lifecycle.lifecycleScope
@@ -185,6 +187,17 @@ class TvMainActivity : AppCompatActivity() {
binding.tunnelList.requestFocus()
}
}
+
+ val backPressedCallback = onBackPressedDispatcher.addCallback(this) { handleBackPressed() }
+ val updateBackPressedCallback = object : Observable.OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
+ backPressedCallback.isEnabled = isDeleting.get() || filesRoot.get()?.isNotEmpty() == true
+ }
+ }
+ isDeleting.addOnPropertyChangedCallback(updateBackPressedCallback)
+ filesRoot.addOnPropertyChangedCallback(updateBackPressedCallback)
+ backPressedCallback.isEnabled = false
+
binding.executePendingBindings()
setContentView(binding.root)
@@ -298,7 +311,7 @@ class TvMainActivity : AppCompatActivity() {
}
}
- override fun onBackPressed() {
+ private fun handleBackPressed() {
when {
isDeleting.get() -> {
isDeleting.set(false)
@@ -313,7 +326,6 @@ class TvMainActivity : AppCompatActivity() {
binding.tunnelList.requestFocus()
}
}
- else -> super.onBackPressed()
}
}
diff --git a/ui/src/main/java/com/wireguard/android/configStore/ConfigStore.kt b/ui/src/main/java/com/wireguard/android/configStore/ConfigStore.kt
index 5b66f830..7336e78f 100644
--- a/ui/src/main/java/com/wireguard/android/configStore/ConfigStore.kt
+++ b/ui/src/main/java/com/wireguard/android/configStore/ConfigStore.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.configStore
diff --git a/ui/src/main/java/com/wireguard/android/configStore/FileConfigStore.kt b/ui/src/main/java/com/wireguard/android/configStore/FileConfigStore.kt
index 1099382d..30a2674f 100644
--- a/ui/src/main/java/com/wireguard/android/configStore/FileConfigStore.kt
+++ b/ui/src/main/java/com/wireguard/android/configStore/FileConfigStore.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.configStore
diff --git a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
index 9c8a0dc2..d823fa8d 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt b/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt
index d1a1352b..93333cb6 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt b/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
index 1122f552..f91581d0 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt
index 9963255a..947644b3 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt
index 003ff74e..91223ad1 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
index a8738627..a09d726f 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.databinding
diff --git a/ui/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt b/ui/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
index c56462d6..f54f88f2 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
diff --git a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
index f6f57ddb..627a9728 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -1,11 +1,15 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
import android.Manifest
import android.app.Dialog
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PackageInfoFlags
+import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
@@ -15,6 +19,7 @@ import androidx.databinding.Observable
import androidx.fragment.app.DialogFragment
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
@@ -40,7 +45,7 @@ class AppListDialogFragment : DialogFragment() {
try {
val applicationData: MutableList<ApplicationData> = ArrayList()
withContext(Dispatchers.IO) {
- val packageInfos = pm.getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET), 0)
+ val packageInfos = getPackagesHoldingPermissions(pm, arrayOf(Manifest.permission.INTERNET))
packageInfos.forEach {
val packageName = it.packageName
val appInfo = it.applicationInfo
@@ -59,6 +64,7 @@ class AppListDialogFragment : DialogFragment() {
appData.clear()
appData.addAll(applicationData)
}
+ setButtonText()
} catch (e: Throwable) {
withContext(Dispatchers.Main.immediate) {
val error = ErrorMessages[e]
@@ -76,6 +82,15 @@ class AppListDialogFragment : DialogFragment() {
initiallyExcluded = arguments?.getBoolean(KEY_IS_EXCLUDED) ?: true
}
+ private fun getPackagesHoldingPermissions(pm: PackageManager, permissions: Array<String>): List<PackageInfo> {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ pm.getPackagesHoldingPermissions(permissions, PackageInfoFlags.of(0L))
+ } else {
+ @Suppress("DEPRECATION")
+ pm.getPackagesHoldingPermissions(permissions, 0)
+ }
+ }
+
private fun setButtonText() {
val numSelected = appData.count { it.isSelected }
button?.text = if (numSelected == 0)
@@ -88,7 +103,7 @@ class AppListDialogFragment : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val alertDialogBuilder = AlertDialog.Builder(requireActivity())
+ val alertDialogBuilder = MaterialAlertDialogBuilder(requireActivity())
val binding = AppListDialogFragmentBinding.inflate(requireActivity().layoutInflater, null, false)
binding.executePendingBindings()
alertDialogBuilder.setView(binding.root)
diff --git a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
index 783f5722..569a6217 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
diff --git a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
index d20f21c6..97f2ec73 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
@@ -1,18 +1,15 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
import android.app.Dialog
-import android.content.DialogInterface
import android.os.Bundle
-import android.view.inputmethod.InputMethodManager
-import androidx.appcompat.app.AlertDialog
-import androidx.core.content.getSystemService
+import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
-import com.google.android.material.textfield.TextInputEditText
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding
@@ -26,7 +23,6 @@ import java.nio.charset.StandardCharsets
class ConfigNamingDialogFragment : DialogFragment() {
private var binding: ConfigNamingDialogFragmentBinding? = null
private var config: Config? = null
- private var imm: InputMethodManager? = null
private fun createTunnelAndDismiss() {
val binding = binding ?: return
@@ -41,12 +37,6 @@ class ConfigNamingDialogFragment : DialogFragment() {
}
}
}
-
- override fun dismiss() {
- setKeyboardVisible(false)
- super.dismiss()
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val configText = requireArguments().getString(KEY_CONFIG_TEXT)
@@ -63,45 +53,18 @@ class ConfigNamingDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val activity = requireActivity()
- imm = activity.getSystemService()
- val alertDialogBuilder = AlertDialog.Builder(activity)
+ val alertDialogBuilder = MaterialAlertDialogBuilder(activity)
alertDialogBuilder.setTitle(R.string.import_from_qr_code)
binding = ConfigNamingDialogFragmentBinding.inflate(activity.layoutInflater, null, false)
binding?.apply {
executePendingBindings()
alertDialogBuilder.setView(root)
}
- alertDialogBuilder.setPositiveButton(R.string.create_tunnel, null)
+ alertDialogBuilder.setPositiveButton(R.string.create_tunnel) { _, _ -> createTunnelAndDismiss() }
alertDialogBuilder.setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
- return alertDialogBuilder.create().apply {
- setOnShowListener {
- findViewById<TextInputEditText>(R.id.tunnel_name_text)?.apply {
- setOnFocusChangeListener { v, _ ->
- v.post {
- imm?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT)
- }
- }
- requestFocus()
- }
- }
- }
- }
-
- override fun onResume() {
- super.onResume()
- val dialog = dialog as AlertDialog?
- if (dialog != null) {
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { createTunnelAndDismiss() }
- setKeyboardVisible(true)
- }
- }
-
- private fun setKeyboardVisible(visible: Boolean) {
- if (visible) {
- imm!!.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
- } else if (binding != null) {
- imm!!.hideSoftInputFromWindow(binding!!.tunnelNameText.windowToken, 0)
- }
+ val dialog = alertDialogBuilder.create()
+ dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
+ return dialog
}
companion object {
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
index 7046cb96..e7c1262e 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
@@ -1,16 +1,20 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
import android.os.Bundle
+import android.util.Log;
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
+import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
+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
@@ -18,24 +22,27 @@ 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 com.wireguard.android.viewmodel.ConfigDetail
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
/**
* Fragment that shows details about a specific tunnel.
*/
-class TunnelDetailFragment : BaseFragment() {
+class TunnelDetailFragment : BaseFragment(), MenuProvider {
private var binding: TunnelDetailFragmentBinding? = null
private var lastState = Tunnel.State.TOGGLE
private var timerActive = true
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ return false
}
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.tunnel_detail, menu)
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.tunnel_detail, menu)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
@@ -46,6 +53,11 @@ class TunnelDetailFragment : BaseFragment() {
return binding?.root
}
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
+ }
+
override fun onDestroyView() {
binding = null
super.onDestroyView()
@@ -70,7 +82,9 @@ class TunnelDetailFragment : BaseFragment() {
} else {
lifecycleScope.launch {
try {
- binding.config = newTunnel.getConfigAsync()
+ var config = newTunnel.getConfigDetailAsync()
+ binding.config = config
+ Log.i(TAG, "onSelectedTunnelChanged " + config + ", " + config.config)
} catch (_: Throwable) {
binding.config = null
}
@@ -99,6 +113,7 @@ class TunnelDetailFragment : BaseFragment() {
val state = tunnel.state
if (state != Tunnel.State.UP && lastState == state) return
lastState = state
+ var now = LocalDateTime.now(ZoneId.of("UTC"))
try {
val statistics = tunnel.getStatisticsAsync()
for (i in 0 until binding.peersLayout.childCount) {
@@ -110,11 +125,20 @@ class TunnelDetailFragment : BaseFragment() {
if (rx == 0L && tx == 0L) {
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
- continue
+ } else {
+ peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
+ peer.transferLabel.visibility = View.VISIBLE
+ peer.transferText.visibility = View.VISIBLE
+ }
+ val lastHandshake:LocalDateTime? = statistics.peerLastHandshake(publicKey)
+ if (lastHandshake == null) {
+ peer.lastHandshakeLabel.visibility = View.GONE
+ peer.lastHandshakeText.visibility = View.GONE
+ } else {
+ peer.lastHandshakeText.text = getString(R.string.last_handshake_ago, QuantityFormatter.formatDuration(Duration.between(lastHandshake, now)))
+ peer.lastHandshakeLabel.visibility = View.VISIBLE
+ peer.lastHandshakeText.visibility = View.VISIBLE
}
- peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
- peer.transferLabel.visibility = View.VISIBLE
- peer.transferText.visibility = View.VISIBLE
}
} catch (e: Throwable) {
for (i in 0 until binding.peersLayout.childCount) {
@@ -122,7 +146,13 @@ class TunnelDetailFragment : BaseFragment() {
?: continue
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
+ peer.lastHandshakeLabel.visibility = View.GONE
+ peer.lastHandshakeText.visibility = View.GONE
}
}
}
+
+ companion object {
+ private const val TAG = "WireGuard/TunnelDetailFragment"
+ }
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
index 6c6f53f9..f37475c2 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
@@ -1,10 +1,11 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
import android.content.Context
+import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Log
@@ -16,10 +17,16 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
+import android.widget.ArrayAdapter
+import android.widget.AutoCompleteTextView
import android.widget.EditText
+import android.widget.Filter
import android.widget.Toast
+import androidx.core.view.MenuProvider
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.textfield.TextInputLayout
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.backend.Tunnel
@@ -29,17 +36,33 @@ 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.android.viewmodel.Constants
import com.wireguard.config.Config
import kotlinx.coroutines.launch
/**
* Fragment for editing a WireGuard configuration.
*/
-class TunnelEditorFragment : BaseFragment() {
+class TunnelEditorFragment : BaseFragment(), MenuProvider {
private var haveShownKeys = false
private var binding: TunnelEditorFragmentBinding? = null
private var tunnel: ObservableTunnel? = null
+ private class MaterialSpinnerAdapter<T>(context: Context, resource: Int, private val objects: List<T>) : ArrayAdapter<T>(context, resource, objects) {
+ private val _filter: Filter by lazy {
+ object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ return FilterResults()
+ }
+
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ }
+ }
+ }
+
+ override fun getFilter(): Filter = _filter
+ }
+
private fun onConfigLoaded(config: Config) {
binding?.config = ConfigProxy(config)
}
@@ -65,11 +88,10 @@ class TunnelEditorFragment : BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
}
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.config_editor, menu)
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.config_editor, menu)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
@@ -80,9 +102,21 @@ class TunnelEditorFragment : BaseFragment() {
executePendingBindings()
privateKeyTextLayout.setEndIconOnClickListener { config?.`interface`?.generateKeyPair() }
}
+
+ var httpProxyMenu = binding?.root?.findViewById<TextInputLayout>(R.id.http_proxy_menu)
+ var httpProxyItems = listOf(Constants.HTTP_PROXY_NONE, Constants.HTTP_PROXY_MANUAL, Constants.HTTP_PROXY_PAC)
+ var httpProxyAdapter = MaterialSpinnerAdapter(requireContext(), R.layout.http_proxy_menu_item, httpProxyItems)
+ var httpProxyMenuText = httpProxyMenu?.editText as? AutoCompleteTextView
+ httpProxyMenuText?.setAdapter(httpProxyAdapter)
+
return binding?.root
}
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
+ }
+
override fun onDestroyView() {
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
binding = null
@@ -105,8 +139,8 @@ class TunnelEditorFragment : BaseFragment() {
selectedTunnel = tunnel
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.menu_action_save) {
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ if (menuItem.itemId == R.id.menu_action_save) {
binding ?: return false
val newConfig = try {
binding!!.config!!.resolve()
@@ -152,7 +186,7 @@ class TunnelEditorFragment : BaseFragment() {
}
return true
}
- return super.onOptionsItemSelected(item)
+ return false
}
@Suppress("UNUSED_PARAMETER")
@@ -265,7 +299,12 @@ class TunnelEditorFragment : BaseFragment() {
onSelectedTunnelChanged(null, selectedTunnel)
} else {
tunnel = selectedTunnel
- val config: ConfigProxy = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG)!!
+ val config: ConfigProxy = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ savedInstanceState.getParcelable(KEY_LOCAL_CONFIG, ConfigProxy::class.java)!!
+ } else {
+ @Suppress("DEPRECATION")
+ savedInstanceState.getParcelable(KEY_LOCAL_CONFIG)!!
+ }
val originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME)
if (tunnel != null && tunnel!!.name != originalName) onSelectedTunnelChanged(null, tunnel) else binding!!.config = config
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
index 390a6396..53098eb8 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment
diff --git a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
index c4cb168b..f3892424 100644
--- a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model
diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
index 252e8759..e38e473f 100644
--- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model
@@ -7,12 +7,20 @@ package com.wireguard.android.model
import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
+import com.wireguard.android.Application
import com.wireguard.android.BR
+import com.wireguard.android.backend.Dhcp
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.android.viewmodel.ConfigDetail
+import com.wireguard.android.viewmodel.PeerDetail
import com.wireguard.config.Config
+import com.wireguard.config.InetEndpoint
+import com.wireguard.config.InetNetwork
+import com.wireguard.crypto.Key
+import java.util.Optional
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -55,7 +63,18 @@ class ObservableTunnel internal constructor(
}
fun onStateChanged(state: Tunnel.State): Tunnel.State {
- if (state != Tunnel.State.UP) onStatisticsChanged(null)
+ if (state != Tunnel.State.UP) {
+ onStatisticsChanged(null)
+ onDhcpChanged(null)
+ Application.getCoroutineScope().launch {
+ onPeersReset()
+ }
+ } else {
+ configDetail?.peers?.forEach {
+ var endpoint: InetEndpoint? = it.peer?.endpoint?.orElse(null)
+ it.endpoint = Optional.ofNullable(endpoint?.getResolved()?.get())
+ }
+ }
this.state = state
notifyPropertyChanged(BR.state)
return state
@@ -68,6 +87,7 @@ class ObservableTunnel internal constructor(
this@ObservableTunnel.state
}
+ private var configDetail: ConfigDetail? = if (config != null) ConfigDetail(config) else null
@get:Bindable
var config = config
@@ -86,7 +106,11 @@ class ObservableTunnel internal constructor(
private set
suspend fun getConfigAsync(): Config = withContext(Dispatchers.Main.immediate) {
- config ?: manager.getTunnelConfig(this@ObservableTunnel)
+ config ?: manager.getTunnelConfig(this@ObservableTunnel).config!!
+ }
+
+ suspend fun getConfigDetailAsync(): ConfigDetail = withContext(Dispatchers.Main.immediate) {
+ configDetail ?: manager.getTunnelConfig(this@ObservableTunnel)
}
suspend fun setConfigAsync(config: Config): Config = withContext(Dispatchers.Main.immediate) {
@@ -98,10 +122,11 @@ class ObservableTunnel internal constructor(
}
}
- fun onConfigChanged(config: Config?): Config? {
+ fun onConfigChanged(config: Config?): ConfigDetail? {
+ this.configDetail = ConfigDetail(config)
this.config = config
notifyPropertyChanged(BR.config)
- return config
+ return configDetail
}
@@ -137,6 +162,106 @@ class ObservableTunnel internal constructor(
}
+ @get:Bindable
+ var dhcp: Dhcp? = null
+ private set
+
+ override fun onDhcpChange(newDhcp: Dhcp) {
+ onDhcpChanged(newDhcp)
+ }
+
+ fun onDhcpChanged(dhcp: Dhcp?): Dhcp? {
+ this.dhcp = dhcp
+ notifyPropertyChanged(BR.dhcp)
+ return dhcp
+ }
+
+ // Remove dynamic peers, and reset static peers
+ fun onPeersReset() {
+ Log.i(TAG, "ObservableTunnel onPeersReset")
+ var toRemove: MutableList<PeerDetail> = ArrayList()
+
+ configDetail?.peers?.forEach {
+ if (it.peer == null) {
+ toRemove.add(it)
+ } else {
+ it.endpoint = Optional.empty()
+ }
+ }
+
+ toRemove.forEach {
+ Log.i(TAG, "ObservableTunnel remove " + it)
+ configDetail?.peers?.remove(it)
+ }
+ }
+
+ override fun onEndpointChange(publicKey: Key, newEndpoint: InetEndpoint?) {
+ Application.getCoroutineScope().launch {
+ onEndpointChanged(publicKey, newEndpoint)
+ }
+ }
+
+ private fun onEndpointChanged(publicKey: Key, newEndpoint: InetEndpoint?) {
+
+ Log.i(TAG, "ObservableTunnel onEndpointChange " + newEndpoint)
+ var peer: PeerDetail? = null
+
+ configDetail?.peers?.forEach {
+ if (it.publicKey.equals(publicKey) == true) {
+ Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer)
+ peer = it;
+ }
+ }
+
+ if (peer == null) {
+ Log.i(TAG, "ObservableTunnel create peer " + publicKey)
+ peer = PeerDetail(publicKey)
+ configDetail?.peers?.add(peer)
+ }
+
+ var peer2: PeerDetail = peer!!
+
+ if (newEndpoint != null) {
+ peer2.endpoint = newEndpoint.getResolved()
+ } else {
+ var peer3 = peer2.peer
+ peer2.endpoint = if (peer3 != null) peer3.endpoint else Optional.empty()
+ }
+ }
+
+ fun lookupPeer(publicKey: Key): PeerDetail {
+ configDetail?.peers?.forEach {
+ if (it.publicKey.equals(publicKey) == true) {
+ Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer)
+ return it
+ }
+ }
+
+ Log.i(TAG, "ObservableTunnel create peer " + publicKey)
+ var peer: PeerDetail = PeerDetail(publicKey)
+ configDetail?.peers?.add(peer)
+
+ return peer
+ }
+
+ override fun onAllowedIpsChange(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) {
+ Application.getCoroutineScope().launch {
+ onAllowedIpsChanged(publicKey, addNetworks, removeNetworks)
+ }
+ }
+
+ private fun onAllowedIpsChanged(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) {
+ var peer: PeerDetail = lookupPeer(publicKey)
+
+ removeNetworks?.let() {
+ peer.allowedIps.removeAll(removeNetworks)
+ }
+ addNetworks?.let() {
+ peer.allowedIps.addAll(addNetworks)
+ }
+ }
+
+
suspend fun deleteAsync() = manager.delete(this)
diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelComparator.kt b/ui/src/main/java/com/wireguard/android/model/TunnelComparator.kt
index 0baa44e8..e6b46a54 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelComparator.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelComparator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
index ec796164..77d69f70 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.model
@@ -24,6 +24,7 @@ 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.android.viewmodel.ConfigDetail
import com.wireguard.config.Config
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
@@ -94,7 +95,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
applicationScope.launch { UserKnobs.setLastUsedTunnel(value?.name) }
}
- suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
+ suspend fun getTunnelConfig(tunnel: ObservableTunnel): ConfigDetail = withContext(Dispatchers.Main.immediate) {
tunnel.onConfigChanged(withContext(Dispatchers.IO) { configStore.load(tunnel.name) })!!
}
@@ -155,7 +156,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
tunnel.onConfigChanged(withContext(Dispatchers.IO) {
getBackend().setState(tunnel, tunnel.state, config)
configStore.save(tunnel.name, config)
- })!!
+ })!!.config!!
}
suspend fun setTunnelName(tunnel: ObservableTunnel, name: String): String = withContext(Dispatchers.Main.immediate) {
diff --git a/ui/src/main/java/com/wireguard/android/preference/DonatePreference.kt b/ui/src/main/java/com/wireguard/android/preference/DonatePreference.kt
new file mode 100644
index 00000000..57202c81
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/DonatePreference.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference
+
+import android.app.AlertDialog
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.util.AttributeSet
+import androidx.preference.Preference
+import com.wireguard.android.BuildConfig
+import com.wireguard.android.R
+
+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() {
+ if (BuildConfig.IS_GOOGLE_PLAY) {
+ AlertDialog.Builder(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 (ignored: ActivityNotFoundException) {
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/src/main/java/com/wireguard/android/preference/KernelModuleEnablerPreference.kt b/ui/src/main/java/com/wireguard/android/preference/KernelModuleEnablerPreference.kt
index 15f1dcec..20de8e93 100644
--- a/ui/src/main/java/com/wireguard/android/preference/KernelModuleEnablerPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/KernelModuleEnablerPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference
diff --git a/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt b/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt
index 3f1a7223..1a491684 100644
--- a/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
index 7c6283ba..dac80e88 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference
diff --git a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
index 27065d28..31c751d2 100644
--- a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
index 65880303..ced95b69 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference
diff --git a/ui/src/main/java/com/wireguard/android/util/AdminKnobs.kt b/ui/src/main/java/com/wireguard/android/util/AdminKnobs.kt
index c7a683a3..430e904d 100644
--- a/ui/src/main/java/com/wireguard/android/util/AdminKnobs.kt
+++ b/ui/src/main/java/com/wireguard/android/util/AdminKnobs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt b/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt
index d187a4c8..fe36898f 100644
--- a/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt
+++ b/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/util/ClipboardUtils.kt b/ui/src/main/java/com/wireguard/android/util/ClipboardUtils.kt
index 65fdf078..6a0d54ba 100644
--- a/ui/src/main/java/com/wireguard/android/util/ClipboardUtils.kt
+++ b/ui/src/main/java/com/wireguard/android/util/ClipboardUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util
diff --git a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
index 6a7256e0..8538e75e 100644
--- a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
+++ b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util
diff --git a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
index 60c6b878..466009f0 100644
--- a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
+++ b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util
@@ -114,6 +114,8 @@ object ErrorMessages {
return resources.getString(R.string.bad_config_explanation_udp_port)
} else if (bce.location == BadConfigException.Location.MTU) {
return resources.getString(R.string.bad_config_explanation_positive_number)
+ } else if (bce.location == BadConfigException.Location.HTTP_PROXY) {
+ return resources.getString(R.string.bad_config_explanation_http_proxy)
} else if (bce.location == BadConfigException.Location.PERSISTENT_KEEPALIVE) {
return resources.getString(R.string.bad_config_explanation_pka)
}
diff --git a/ui/src/main/java/com/wireguard/android/util/Extensions.kt b/ui/src/main/java/com/wireguard/android/util/Extensions.kt
index f653cb61..98f94af9 100644
--- a/ui/src/main/java/com/wireguard/android/util/Extensions.kt
+++ b/ui/src/main/java/com/wireguard/android/util/Extensions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt b/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt
index 3e54a52a..135fc1f3 100644
--- a/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt
+++ b/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2022 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt b/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
index 4a9ffed4..f9abd590 100644
--- a/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
+++ b/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,6 +8,8 @@ package com.wireguard.android.util
import com.wireguard.android.Application
import com.wireguard.android.R
+import java.time.Duration
+
object QuantityFormatter {
fun formatBytes(bytes: Long): String {
val context = Application.get().applicationContext
@@ -19,4 +21,58 @@ object QuantityFormatter {
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
}
}
-} \ No newline at end of file
+
+ fun formatDuration(duration: Duration): String {
+ val context = Application.get().applicationContext
+ val str = formatHours(duration.getSeconds())
+ return when {
+ str != "" -> str
+ else -> context.getString(R.string.duration_seconds, 0)
+ }
+ }
+
+ fun formatHours(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val hours = seconds / 3600
+ val restSeconds = seconds % 3600
+ val str = formatMinutes(restSeconds)
+
+ return when {
+ hours > 0 -> context.getString(R.string.duration_hours, hours) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatDays(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val days = seconds / 3600 / 24
+ val restSeconds = seconds % (3600 * 24)
+ val str = formatHours(restSeconds)
+
+ return when {
+ days > 0 -> context.getString(R.string.duration_days, days) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatMinutes(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val minutes = seconds / 60
+ val restSeconds = seconds % 60
+ val str = formatSeconds(restSeconds)
+
+ return when {
+ minutes > 0 -> context.getString(R.string.duration_minutes, minutes) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatSeconds(seconds: Long): String {
+ val context = Application.get().applicationContext
+
+ return when {
+ seconds > 0 -> context.getString(R.string.duration_seconds, seconds)
+ else -> ""
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/util/TunnelImporter.kt b/ui/src/main/java/com/wireguard/android/util/TunnelImporter.kt
index 64993cda..e66691e8 100644
--- a/ui/src/main/java/com/wireguard/android/util/TunnelImporter.kt
+++ b/ui/src/main/java/com/wireguard/android/util/TunnelImporter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt b/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt
index 224c1e1a..52672e45 100644
--- a/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt
+++ b/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt
new file mode 100644
index 00000000..af95a86a
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt
@@ -0,0 +1,22 @@
+package com.wireguard.android.viewmodel
+
+import androidx.databinding.ObservableArrayList
+import androidx.databinding.ObservableList
+
+import com.wireguard.config.Config
+
+class ConfigDetail {
+ val config: Config?
+ val peers: ObservableList<PeerDetail> = ObservableArrayList()
+
+ constructor(other: Config?) {
+ config = other
+ if (other != null) {
+ other.peers.forEach {
+ val detail = PeerDetail(it)
+ peers.add(detail)
+ detail.bind(this)
+ }
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt
index ccfbce34..e29de2dd 100644
--- a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.viewmodel
@@ -18,7 +18,7 @@ class ConfigProxy : Parcelable {
val peers: ObservableList<PeerProxy> = ObservableArrayList()
private constructor(parcel: Parcel) {
- `interface` = parcel.readParcelable(InterfaceProxy::class.java.classLoader)!!
+ `interface` = InterfaceProxy.CREATOR.createFromParcel(parcel)
parcel.readTypedList(peers, PeerProxy.CREATOR)
peers.forEach { it.bind(this) }
}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
index 2792f749..16c3e6a3 100644
--- a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
@@ -1,9 +1,11 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.viewmodel
+import android.net.Uri
+import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.databinding.BaseObservable
@@ -18,6 +20,12 @@ import com.wireguard.crypto.Key
import com.wireguard.crypto.KeyFormatException
import com.wireguard.crypto.KeyPair
+object Constants {
+ const val HTTP_PROXY_NONE = "None"
+ const val HTTP_PROXY_MANUAL = "Manual"
+ const val HTTP_PROXY_PAC = "Proxy Auto-Config"
+}
+
class InterfaceProxy : BaseObservable, Parcelable {
@get:Bindable
val excludedApplications: ObservableList<String> = ObservableArrayList()
@@ -54,6 +62,44 @@ class InterfaceProxy : BaseObservable, Parcelable {
}
@get:Bindable
+ var httpProxyMenu: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyMenu)
+ notifyPropertyChanged(BR.httpProxyManualVisibility)
+ notifyPropertyChanged(BR.httpProxyPacVisibility)
+ }
+
+ @get:Bindable
+ var httpProxyManualVisibility: Int = 0
+ get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) android.view.View.GONE else (if (httpProxyMenu == Constants.HTTP_PROXY_MANUAL) android.view.View.VISIBLE else android.view.View.GONE)
+
+ @get:Bindable
+ var httpProxyHostname: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyHostname)
+ }
+
+ @get:Bindable
+ var httpProxyPort: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyPort)
+ }
+
+ @get:Bindable
+ var httpProxyPac: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyPac)
+ }
+
+ @get:Bindable
+ var httpProxyPacVisibility: Int = 0
+ get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) android.view.View.GONE else (if (httpProxyMenu == Constants.HTTP_PROXY_PAC) android.view.View.VISIBLE else android.view.View.GONE)
+
+ @get:Bindable
var privateKey: String = ""
set(value) {
field = value
@@ -76,6 +122,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
parcel.readStringList(includedApplications)
listenPort = parcel.readString() ?: ""
mtu = parcel.readString() ?: ""
+ httpProxyMenu = parcel.readString() ?: ""
+ httpProxyHostname = parcel.readString() ?: ""
+ httpProxyPort = parcel.readString() ?: ""
+ httpProxyPac = parcel.readString() ?: ""
privateKey = parcel.readString() ?: ""
}
@@ -87,6 +137,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
includedApplications.addAll(other.includedApplications)
listenPort = other.listenPort.map { it.toString() }.orElse("")
mtu = other.mtu.map { it.toString() }.orElse("")
+ httpProxyHostname = other.httpProxy.map { if (it.getHost().startsWith('[') && it.getHost().endsWith(']')) it.getHost().substring(1, it.getHost().length-1) else it.getHost() }.orElse("")
+ httpProxyPort = other.httpProxy.map { if (it.getPort() <= 0) "8080" else it.getPort().toString() }.orElse("")
+ httpProxyPac = other.httpProxy.map { it.getPacFileUrl().toString() }.orElse("")
+ httpProxyMenu = other.httpProxy.map { if (it.getPacFileUrl() != null && it.getPacFileUrl() != Uri.EMPTY) Constants.HTTP_PROXY_PAC else if (it.getHost() != "") Constants.HTTP_PROXY_MANUAL else Constants.HTTP_PROXY_NONE }.orElse(Constants.HTTP_PROXY_NONE)
val keyPair = other.keyPair
privateKey = keyPair.privateKey.toBase64()
}
@@ -111,6 +165,20 @@ class InterfaceProxy : BaseObservable, Parcelable {
if (includedApplications.isNotEmpty()) builder.includeApplications(includedApplications)
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
+ if (Constants.HTTP_PROXY_MANUAL.equals(httpProxyMenu) && httpProxyHostname.isNotEmpty()) {
+ var httpProxy: String
+ if (httpProxyHostname.contains(":")) {
+ httpProxy = "[" + httpProxyHostname + "]"
+ } else {
+ httpProxy = httpProxyHostname
+ }
+ if (httpProxyPort.isNotEmpty()) {
+ httpProxy += ":" + httpProxyPort;
+ }
+ builder.parseHttpProxy(httpProxy)
+ } else if (Constants.HTTP_PROXY_PAC.equals(httpProxyMenu) && httpProxyPac.isNotEmpty()) {
+ builder.parseHttpProxy("pac:" + httpProxyPac)
+ }
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
return builder.build()
}
@@ -122,6 +190,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
dest.writeStringList(includedApplications)
dest.writeString(listenPort)
dest.writeString(mtu)
+ dest.writeString(httpProxyMenu)
+ dest.writeString(httpProxyHostname)
+ dest.writeString(httpProxyPort)
+ dest.writeString(httpProxyPac)
dest.writeString(privateKey)
}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt
new file mode 100644
index 00000000..80b32fd5
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt
@@ -0,0 +1,85 @@
+package com.wireguard.android.viewmodel
+
+import android.util.Log
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import androidx.databinding.Observable
+import androidx.databinding.ObservableList
+import androidx.databinding.ObservableArrayList
+
+import com.wireguard.android.BR
+import com.wireguard.config.InetEndpoint
+import com.wireguard.config.InetNetwork
+import com.wireguard.config.Peer
+import com.wireguard.crypto.Key;
+
+import java.util.Optional;
+
+import kotlin.collections.LinkedHashSet
+
+
+class PeerDetail : BaseObservable {
+ var peer: Peer?
+ private var owner: ConfigDetail? = null
+
+ @get:Bindable
+ var publicKey: Key
+
+ @get:Bindable
+ var allowedIps: ObservableList<InetNetwork> = ObservableArrayList<InetNetwork>()
+
+ @get:Bindable
+ var endpoint: Optional<InetEndpoint> = Optional.empty()
+ get() {
+ if (!field.isEmpty()) {
+ return field
+ } else {
+ return Optional.ofNullable(peer?.endpoint?.get())
+ }
+ }
+
+ set(value) {
+ Log.i(TAG, "notifyPropertyChanged endpoint " + this + ", " + value)
+ field = value
+ notifyPropertyChanged(BR.endpoint)
+ }
+
+ @get:Bindable
+ var persistentKeepalive: Optional<Int> = Optional.empty()
+
+ constructor(other: Peer) {
+ peer = other
+ publicKey = other.getPublicKey()
+ allowedIps.addAll(other.getAllowedIps())
+ endpoint = other.getEndpoint();
+ persistentKeepalive = other.getPersistentKeepalive()
+ }
+
+ constructor(publicKey: Key) {
+ peer = null
+ this.publicKey = publicKey
+ }
+
+ fun bind(owner: ConfigDetail) {
+ this.owner = owner
+ }
+
+ override fun addOnPropertyChangedCallback (callback: Observable.OnPropertyChangedCallback) {
+ Log.i(TAG, "addOnPropertyChangedCallback " + this + ", " + callback)
+ super.addOnPropertyChangedCallback(callback)
+ }
+
+ /**
+ * Converts the {@code Peer} into a string suitable for debugging purposes. The {@code Peer} is
+ * identified by its public key and (if known) its endpoint.
+ *
+ * @return a concise single-line identifier for the {@code Peer}
+ */
+ override fun toString(): String {
+ return "(Peer " + publicKey.toBase64() + ")"
+ }
+
+ companion object {
+ private const val TAG = "WireGuard/PeerDetail"
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt
index d1cb1046..4bf2ce9c 100644
--- a/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/PeerProxy.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.viewmodel
diff --git a/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt b/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt
index 6f941c86..bf42166d 100644
--- a/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
diff --git a/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt b/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt
index 38aee443..511cd287 100644
--- a/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
diff --git a/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt b/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt
index a047dab4..7af514d9 100644
--- a/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
diff --git a/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt b/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt
index 2ebf4fd8..8fcee9df 100644
--- a/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt
@@ -1,6 +1,6 @@
/*
* Copyright © 2018 The Android Open Source Project
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
diff --git a/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt b/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt
index 0b5fa09f..fe415fd5 100644
--- a/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt
@@ -1,6 +1,6 @@
/*
* Copyright © 2013 The Android Open Source Project
- * Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
diff --git a/ui/src/main/res/drawable/ic_action_add_white.xml b/ui/src/main/res/drawable/ic_action_add_white.xml
index 18fe19db..1ea440df 100644
--- a/ui/src/main/res/drawable/ic_action_add_white.xml
+++ b/ui/src/main/res/drawable/ic_action_add_white.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:tint="?attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_delete.xml b/ui/src/main/res/drawable/ic_action_delete.xml
index 51517c42..73eb7352 100644
--- a/ui/src/main/res/drawable/ic_action_delete.xml
+++ b/ui/src/main/res/drawable/ic_action_delete.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_edit.xml b/ui/src/main/res/drawable/ic_action_edit.xml
index 8d8acf85..d40c78d9 100644
--- a/ui/src/main/res/drawable/ic_action_edit.xml
+++ b/ui/src/main/res/drawable/ic_action_edit.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_edit_white.xml b/ui/src/main/res/drawable/ic_action_edit_white.xml
deleted file mode 100644
index 212fc72c..00000000
--- a/ui/src/main/res/drawable/ic_action_edit_white.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="?attr/colorOnSecondary"
- android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
-</vector>
diff --git a/ui/src/main/res/drawable/ic_action_generate.xml b/ui/src/main/res/drawable/ic_action_generate.xml
index 7324eae4..51d26aed 100644
--- a/ui/src/main/res/drawable/ic_action_generate.xml
+++ b/ui/src/main/res/drawable/ic_action_generate.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_open_white.xml b/ui/src/main/res/drawable/ic_action_open.xml
index 91075680..c9fd6fba 100644
--- a/ui/src/main/res/drawable/ic_action_open_white.xml
+++ b/ui/src/main/res/drawable/ic_action_open.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_save.xml b/ui/src/main/res/drawable/ic_action_save.xml
index ed98e85a..6e618edb 100644
--- a/ui/src/main/res/drawable/ic_action_save.xml
+++ b/ui/src/main/res/drawable/ic_action_save.xml
@@ -2,9 +2,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml b/ui/src/main/res/drawable/ic_action_scan_qr_code.xml
index a5ede695..4522ae46 100644
--- a/ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml
+++ b/ui/src/main/res/drawable/ic_action_scan_qr_code.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_select_all.xml b/ui/src/main/res/drawable/ic_action_select_all.xml
index 43f5f15e..490ca715 100644
--- a/ui/src/main/res/drawable/ic_action_select_all.xml
+++ b/ui/src/main/res/drawable/ic_action_select_all.xml
@@ -2,9 +2,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_share_white.xml b/ui/src/main/res/drawable/ic_action_share_white.xml
index 70843cf3..04ee5b74 100644
--- a/ui/src/main/res/drawable/ic_action_share_white.xml
+++ b/ui/src/main/res/drawable/ic_action_share_white.xml
@@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
diff --git a/ui/src/main/res/drawable/ic_arrow_back.xml b/ui/src/main/res/drawable/ic_arrow_back.xml
index 0d549893..0df5dc6c 100644
--- a/ui/src/main/res/drawable/ic_arrow_back.xml
+++ b/ui/src/main/res/drawable/ic_arrow_back.xml
@@ -1,14 +1,15 @@
<!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="#000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_settings.xml b/ui/src/main/res/drawable/ic_settings.xml
index 6d1cfa71..af9f2634 100644
--- a/ui/src/main/res/drawable/ic_settings.xml
+++ b/ui/src/main/res/drawable/ic_settings.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?android:attr/colorForeground"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
</vector>
diff --git a/ui/src/main/res/drawable/tv_logo_banner.xml b/ui/src/main/res/drawable/tv_logo_banner.xml
index 646967d6..734702f3 100644
--- a/ui/src/main/res/drawable/tv_logo_banner.xml
+++ b/ui/src/main/res/drawable/tv_logo_banner.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
diff --git a/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml b/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
index 68f186b6..9ed57ac6 100644
--- a/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
+++ b/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
@@ -20,7 +20,7 @@
android:text="@string/create_from_file"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_open_white"
+ app:icon="@drawable/ic_action_open"
app:iconPadding="@dimen/bottom_sheet_icon_padding"
app:iconTint="?attr/colorSecondary"
app:layout_constraintBottom_toTopOf="@+id/create_from_qrcode"
@@ -44,7 +44,7 @@
android:text="@string/create_from_qr_code"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_scan_qr_code_white"
+ app:icon="@drawable/ic_action_scan_qr_code"
app:iconPadding="@dimen/bottom_sheet_icon_padding"
app:iconTint="?attr/colorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/create_empty"
diff --git a/ui/src/main/res/layout/config_naming_dialog_fragment.xml b/ui/src/main/res/layout/config_naming_dialog_fragment.xml
index 0fd88c6c..88deb976 100644
--- a/ui/src/main/res/layout/config_naming_dialog_fragment.xml
+++ b/ui/src/main/res/layout/config_naming_dialog_fragment.xml
@@ -3,7 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
-
<import type="com.wireguard.android.widget.NameInputFilter" />
</data>
@@ -24,7 +23,9 @@
android:hint="@string/tunnel_name"
android:imeOptions="actionDone"
android:inputType="textNoSuggestions|textVisiblePassword"
- app:filter="@{NameInputFilter.newInstance()}" />
+ app:filter="@{NameInputFilter.newInstance()}">
+ <requestFocus/>
+ </com.google.android.material.textfield.TextInputEditText>
</com.google.android.material.textfield.TextInputLayout>
diff --git a/ui/src/main/res/layout/http_proxy_menu_item.xml b/ui/src/main/res/layout/http_proxy_menu_item.xml
new file mode 100644
index 00000000..8ad5c026
--- /dev/null
+++ b/ui/src/main/res/layout/http_proxy_menu_item.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+/>
diff --git a/ui/src/main/res/layout/log_viewer_activity.xml b/ui/src/main/res/layout/log_viewer_activity.xml
index c3780470..2a377a15 100644
--- a/ui/src/main/res/layout/log_viewer_activity.xml
+++ b/ui/src/main/res/layout/log_viewer_activity.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
diff --git a/ui/src/main/res/layout/log_viewer_entry.xml b/ui/src/main/res/layout/log_viewer_entry.xml
index 73680f0c..38e71d35 100644
--- a/ui/src/main/res/layout/log_viewer_entry.xml
+++ b/ui/src/main/res/layout/log_viewer_entry.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
diff --git a/ui/src/main/res/layout/tunnel_detail_fragment.xml b/ui/src/main/res/layout/tunnel_detail_fragment.xml
index 8e34f082..59112d5e 100644
--- a/ui/src/main/res/layout/tunnel_detail_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_detail_fragment.xml
@@ -5,6 +5,8 @@
<data>
+ <import type="android.os.Build" />
+
<import type="com.wireguard.android.backend.Tunnel.State" />
<import type="com.wireguard.android.util.ClipboardUtils" />
@@ -19,7 +21,7 @@
<variable
name="config"
- type="com.wireguard.config.Config" />
+ type="com.wireguard.android.viewmodel.ConfigDetail" />
</data>
<ScrollView
@@ -117,7 +119,7 @@
android:nextFocusForward="@id/addresses_text"
android:onClick="@{ClipboardUtils::copyTextView}"
android:singleLine="true"
- android:text="@{config.interface.keyPair.publicKey.toBase64}"
+ android:text="@{config.config.interface.keyPair.publicKey.toBase64}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/public_key_label"
tools:text="wOs2eguFEohqIZxlSJ1CAT9584tc6ejj9hfGFsoBVkA=" />
@@ -129,7 +131,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/addresses_text"
android:text="@string/addresses"
- android:visibility="@{config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/public_key_text" />
@@ -140,13 +142,40 @@
android:layout_height="wrap_content"
android:contentDescription="@string/addresses"
android:nextFocusUp="@id/public_key_text"
+ android:nextFocusDown="@id/dhcp_addresses_text"
+ android:nextFocusForward="@id/dhcp_addresses_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:text="@{config.config.interface.addresses}"
+ android:visibility="@{config.config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/addresses_label"
+ tools:text="fc00:bbbb:bbbb:bb11::3:368b/128" />
+
+ <TextView
+ android:id="@+id/dhcp_addresses_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/dhcp_addresses_text"
+ android:text="@string/dhcp_addresses"
+ android:visibility="@{tunnel.dhcp == null ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/addresses_text" />
+
+ <TextView
+ android:id="@+id/dhcp_addresses_text"
+ style="@style/DetailText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/dhcp_addresses"
+ android:nextFocusUp="@id/addresses_text"
android:nextFocusDown="@id/dns_servers_text"
android:nextFocusForward="@id/dns_servers_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.addresses}"
- android:visibility="@{config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{tunnel.dhcp.addresses}"
+ android:visibility="@{tunnel.dhcp == null ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/addresses_label"
+ app:layout_constraintTop_toBottomOf="@+id/dhcp_addresses_label"
tools:text="fc00:bbbb:bbbb:bb11::3:368b/128" />
<TextView
@@ -156,9 +185,9 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_servers_text"
android:text="@string/dns_servers"
- android:visibility="@{config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/addresses_text" />
+ app:layout_constraintTop_toBottomOf="@id/dhcp_addresses_text" />
<TextView
android:id="@+id/dns_servers_text"
@@ -166,12 +195,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_servers"
- android:nextFocusUp="@id/addresses_text"
+ android:nextFocusUp="@id/dhcp_addresses_text"
android:nextFocusDown="@id/dns_search_domains_text"
android:nextFocusForward="@id/dns_search_domains_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.dnsServers}"
- android:visibility="@{config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.dnsServers}"
+ android:visibility="@{config.config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dns_servers_label"
tools:text="8.8.8.8, 8.8.4.4" />
@@ -183,7 +212,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_search_domain_text"
android:text="@string/dns_search_domains"
- android:visibility="@{config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dns_servers_text" />
@@ -197,8 +226,8 @@
android:nextFocusDown="@id/listen_port_text"
android:nextFocusForward="@id/listen_port_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.dnsSearchDomains}"
- android:visibility="@{config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.dnsSearchDomains}"
+ android:visibility="@{config.config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dns_search_domains_label"
tools:text="zx2c4.com" />
@@ -210,7 +239,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/listen_port_text"
android:text="@string/listen_port"
- android:visibility="@{!config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!config.config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toStartOf="@id/mtu_label"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toStartOf="parent"
@@ -224,11 +253,11 @@
android:contentDescription="@string/listen_port"
android:nextFocusRight="@id/mtu_text"
android:nextFocusUp="@id/dns_search_domains_text"
- android:nextFocusDown="@id/applications_text"
+ android:nextFocusDown="@id/http_proxy_text"
android:nextFocusForward="@id/mtu_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.listenPort}"
- android:visibility="@{!config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.listenPort}"
+ android:visibility="@{!config.config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toStartOf="@id/mtu_label"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toStartOf="parent"
@@ -242,7 +271,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/mtu_text"
android:text="@string/mtu"
- android:visibility="@{!config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!config.config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintLeft_toRightOf="@id/listen_port_label"
@@ -257,10 +286,10 @@
android:contentDescription="@string/mtu"
android:nextFocusLeft="@id/listen_port_text"
android:nextFocusUp="@id/dns_servers_text"
- android:nextFocusForward="@id/applications_text"
+ android:nextFocusForward="@id/http_proxy_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.mtu}"
- android:visibility="@{!config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.mtu}"
+ android:visibility="@{!config.config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toEndOf="@id/listen_port_label"
@@ -268,6 +297,40 @@
app:layout_constraintTop_toBottomOf="@+id/mtu_label"
tools:text="1500" />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/listen_port_mtu_barrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="listen_port_text,mtu_text" />
+
+ <TextView
+ android:id="@+id/http_proxy_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/http_proxy_text"
+ android:text="@string/http_proxy"
+ android:visibility="@{(Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.Q || !config.config.interface.httpProxy.isPresent()) ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/listen_port_mtu_barrier" />
+
+ <TextView
+ android:id="@+id/http_proxy_text"
+ style="@style/DetailText"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/http_proxy"
+ android:nextFocusUp="@id/listen_port_text"
+ android:nextFocusDown="@id/applications_text"
+ android:nextFocusForward="@id/applications_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:text="@{config.config.interface.httpProxy}"
+ android:visibility="@{(Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.Q || !config.config.interface.httpProxy.isPresent()) ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_label"
+ app:layout_constraintStart_toStartOf="parent"
+ tools:text="http://example.com:8888" />
+
<TextView
android:id="@+id/applications_label"
android:layout_width="match_parent"
@@ -275,9 +338,9 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/applications_text"
android:text="@string/applications"
- android:visibility="@{config.interface.includedApplications.isEmpty() &amp;&amp; config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/listen_port_text" />
+ android:visibility="@{config.config.interface.includedApplications.isEmpty() &amp;&amp; config.config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintTop_toBottomOf="@+id/http_proxy_text"
+ app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/applications_text"
@@ -289,10 +352,10 @@
android:nextFocusDown="@id/peers_layout"
android:nextFocusForward="@id/peers_layout"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.includedApplications.isEmpty() ? @plurals/n_excluded_applications(config.interface.excludedApplications.size(), config.interface.excludedApplications.size()) : @plurals/n_included_applications(config.interface.includedApplications.size(), config.interface.includedApplications.size())}"
- android:visibility="@{config.interface.includedApplications.isEmpty() &amp;&amp; config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
- app:layout_constraintStart_toStartOf="parent"
+ android:text="@{config.config.interface.includedApplications.isEmpty() ? @plurals/n_excluded_applications(config.config.interface.excludedApplications.size(), config.config.interface.excludedApplications.size()) : @plurals/n_included_applications(config.config.interface.includedApplications.size(), config.config.interface.includedApplications.size())}"
+ android:visibility="@{config.config.interface.includedApplications.isEmpty() &amp;&amp; config.config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@+id/applications_label"
+ app:layout_constraintStart_toStartOf="parent"
tools:text="8 excluded" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
@@ -304,6 +367,7 @@
android:layout_marginTop="8dp"
android:divider="@null"
android:orientation="vertical"
+ app:fragment="@{fragment}"
app:items="@{config.peers}"
app:layout="@{@layout/tunnel_detail_peer}"
app:layout_constraintStart_toStartOf="parent"
diff --git a/ui/src/main/res/layout/tunnel_detail_peer.xml b/ui/src/main/res/layout/tunnel_detail_peer.xml
index 0fbee8f1..b4a07003 100644
--- a/ui/src/main/res/layout/tunnel_detail_peer.xml
+++ b/ui/src/main/res/layout/tunnel_detail_peer.xml
@@ -9,7 +9,7 @@
<variable
name="item"
- type="com.wireguard.config.Peer" />
+ type="com.wireguard.android.viewmodel.PeerDetail" />
</data>
<com.google.android.material.card.MaterialCardView
@@ -64,7 +64,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/pre_shared_key_text"
android:text="@string/pre_shared_key"
- android:visibility="@{!item.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!item.peer.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/public_key_text" />
@@ -81,7 +81,7 @@
android:nextFocusForward="@id/allowed_ips_text"
android:singleLine="true"
android:text="@string/pre_shared_key_enabled"
- android:visibility="@{!item.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!item.peer.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pre_shared_key_label"
tools:text="8VyS8W8XeMcBWfKp1GuG3/fZlnUQFkqMNbrdmZtVQIM=" />
@@ -194,6 +194,34 @@
app:layout_constraintTop_toBottomOf="@+id/transfer_label"
tools:text="1024 MB"
tools:visibility="visible" />
+
+ <TextView
+ android:id="@+id/last_handshake_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/persistent_keepalive_text"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/last_handshake_text"
+ android:text="@string/last_handshake"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/transfer_text"
+ tools:visibility="visible" />
+
+ <TextView
+ android:id="@+id/last_handshake_text"
+ style="@style/DetailText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/last_handshake_label"
+ android:contentDescription="@string/last_handshake"
+ android:nextFocusUp="@id/transfer_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/last_handshake_label"
+ tools:text="29 seconds ago"
+ tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
diff --git a/ui/src/main/res/layout/tunnel_editor_fragment.xml b/ui/src/main/res/layout/tunnel_editor_fragment.xml
index 59572b32..789c839b 100644
--- a/ui/src/main/res/layout/tunnel_editor_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_editor_fragment.xml
@@ -5,6 +5,8 @@
<data>
+ <import type="android.os.Build" />
+
<import type="com.wireguard.android.util.ClipboardUtils" />
<import type="com.wireguard.android.widget.KeyInputFilter" />
@@ -210,7 +212,7 @@
android:imeOptions="actionNext"
android:inputType="textNoSuggestions|textVisiblePassword"
android:nextFocusUp="@id/addresses_label_text"
- android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusDown="@id/http_proxy_hostname_text"
android:nextFocusForward="@id/mtu_text"
android:text="@={config.interface.dnsServers}" />
</com.google.android.material.textfield.TextInputLayout>
@@ -235,19 +237,112 @@
android:imeOptions="actionDone"
android:inputType="number"
android:nextFocusUp="@id/listen_port_text"
- android:nextFocusDown="@id/set_excluded_applications"
- android:nextFocusForward="@id/set_excluded_applications"
+ android:nextFocusDown="@id/http_proxy_hostname_text"
+ android:nextFocusForward="@id/http_proxy_hostname_text"
android:text="@={config.interface.mtu}"
android:textAlignment="center" />
</com.google.android.material.textfield.TextInputLayout>
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_menu"
+ style="@style/ExposedDropDownMenu"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy"
+ app:expandedHintEnabled="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dns_servers_label_layout">
+
+ <com.google.android.material.textfield.MaterialAutoCompleteTextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="none"
+ android:text="@={config.interface.httpProxyMenu}"
+ />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_hostname_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_hostname"
+ android:visibility="@{config.interface.httpProxyManualVisibility}"
+ app:layout_constraintEnd_toStartOf="@id/http_proxy_port_label_layout"
+ app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintHorizontal_weight="0.7"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_menu">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_hostname_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionNext"
+ android:inputType="textNoSuggestions|textVisiblePassword"
+ android:nextFocusUp="@id/mtu_text"
+ android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusForward="@id/http_proxy_port_text"
+ android:text="@={config.interface.httpProxyHostname}" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_port_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_port"
+ android:visibility="@{config.interface.httpProxyManualVisibility}"
+ app:expandedHintEnabled="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_weight="0.3"
+ app:layout_constraintStart_toEndOf="@id/http_proxy_hostname_label_layout"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_menu">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_port_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionDone"
+ android:nextFocusUp="@id/mtu_text"
+ android:nextFocusDown="@id/http_proxy_pac_label_layout"
+ android:nextFocusForward="@id/http_proxy_pac_label_layout"
+ android:text="@={config.interface.httpProxyPort}"
+ android:textAlignment="center" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_pac_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_pac"
+ android:visibility="@{config.interface.httpProxyPacVisibility}"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_hostname_label_layout">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_pac_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionNext"
+ android:inputType="textNoSuggestions|textVisiblePassword"
+ android:nextFocusUp="@id/http_proxy_hostname_text"
+ android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusForward="@id/set_excluded_applications"
+ android:text="@={config.interface.httpProxyPac}" />
+ </com.google.android.material.textfield.TextInputLayout>
+
<com.google.android.material.button.MaterialButton
android:id="@+id/set_excluded_applications"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- android:nextFocusUp="@id/dns_servers_text"
+ android:nextFocusUp="@id/http_proxy_hostname_text"
android:nextFocusDown="@id/peers_layout"
android:nextFocusForward="@id/peers_layout"
android:onClick="@{fragment::onRequestSetExcludedIncludedApplications}"
@@ -256,7 +351,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/mtu_label_layout"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_pac_label_layout"
app:rippleColor="?attr/colorSecondary"
tools:text="4 excluded applications" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/src/main/res/values-ca-rES/strings.xml b/ui/src/main/res/values-ca-rES/strings.xml
index 06ec2c50..7db86fd0 100644
--- a/ui/src/main/res/values-ca-rES/strings.xml
+++ b/ui/src/main/res/values-ca-rES/strings.xml
@@ -142,9 +142,9 @@
<string name="log_viewer_pref_title">Mostra el registre d\'aplicació</string>
<string name="log_viewer_title">Registre</string>
<string name="logcat_error">No es pot executar logcat: </string>
- <string name="module_enabler_disabled_summary">El mòdul experimental del kernel pot millorar el rendiment</string>
- <string name="module_enabler_disabled_title">Activa el backend del mòdul del kernel</string>
- <string name="module_enabler_enabled_title">Desactiva el backend del mòdul del kernel</string>
+ <string name="module_disabler_disabled_summary">El mòdul experimental del kernel pot millorar el rendiment</string>
+ <string name="module_disabler_disabled_title">Activa el backend del mòdul del kernel</string>
+ <string name="module_disabler_enabled_title">Desactiva el backend del mòdul del kernel</string>
<string name="module_installer_error">Alguna cosa ha anat malament. Si us plau, prova de nou</string>
<string name="module_installer_not_found">El vostre dispositiu no té mòduls disponibles</string>
<string name="module_installer_title">Descàrega i instala el mòdul kernel</string>
diff --git a/ui/src/main/res/values-cs-rCZ/strings.xml b/ui/src/main/res/values-cs-rCZ/strings.xml
index 3b50d711..a52e0a9f 100644
--- a/ui/src/main/res/values-cs-rCZ/strings.xml
+++ b/ui/src/main/res/values-cs-rCZ/strings.xml
@@ -119,9 +119,9 @@
<string name="log_viewer_pref_title">Zobrazit log aplikace</string>
<string name="log_viewer_title">Log</string>
<string name="logcat_error">Nelze spustit logcat: </string>
- <string name="module_enabler_disabled_summary">Experimentální kernel modul může zlepšit výkon</string>
- <string name="module_enabler_disabled_title">Povolit backend kernel modulu</string>
- <string name="module_enabler_enabled_title">Vypnout backend kernel modulu</string>
+ <string name="module_disabler_disabled_summary">Experimentální kernel modul může zlepšit výkon</string>
+ <string name="module_disabler_disabled_title">Povolit backend kernel modulu</string>
+ <string name="module_disabler_enabled_title">Vypnout backend kernel modulu</string>
<string name="module_installer_error">Něco se pokazilo. Zkuste to prosím znovu</string>
<string name="module_installer_initial">Experimentální kernel modul může zlepšit výkon</string>
<string name="module_installer_not_found">Pro toto zařízení nejsou dostupné žádné moduly</string>
diff --git a/ui/src/main/res/values-de/strings.xml b/ui/src/main/res/values-de/strings.xml
index db012dbf..57edd1da 100644
--- a/ui/src/main/res/values-de/strings.xml
+++ b/ui/src/main/res/values-de/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Anwendungs-Protokoll anzeigen</string>
<string name="log_viewer_title">Protokoll</string>
<string name="logcat_error">Konnte logcat nicht ausführen: </string>
- <string name="module_enabler_disabled_summary">Das experimentelle Kernelmodul kann die Leistung verbessern</string>
- <string name="module_enabler_disabled_title">Kernelmodul-Backend aktivieren</string>
- <string name="module_enabler_enabled_summary">Das langsamere Userspace-Backend kann die Stabilität verbessern</string>
- <string name="module_enabler_enabled_title">Kernelmodul-Backend deaktivieren</string>
+ <string name="module_disabler_disabled_summary">Das experimentelle Kernelmodul kann die Leistung verbessern</string>
+ <string name="module_disabler_disabled_title">Kernelmodul-Backend aktivieren</string>
+ <string name="module_disabler_enabled_summary">Das langsamere Userspace-Backend kann die Stabilität verbessern</string>
+ <string name="module_disabler_enabled_title">Kernelmodul-Backend deaktivieren</string>
<string name="module_installer_error">Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut</string>
<string name="module_installer_initial">Das experimentelle Kernelmodul kann die Leistung verbessern</string>
<string name="module_installer_not_found">Für Ihr Gerät sind keine Module verfügbar</string>
diff --git a/ui/src/main/res/values-es-rES/strings.xml b/ui/src/main/res/values-es-rES/strings.xml
index da5b89fb..478a5a7d 100644
--- a/ui/src/main/res/values-es-rES/strings.xml
+++ b/ui/src/main/res/values-es-rES/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Ver registro de aplicación</string>
<string name="log_viewer_title">Registro</string>
<string name="logcat_error">No se puede ejecutar logcat: </string>
- <string name="module_enabler_disabled_summary">El módulo experimental del kernel puede mejorar el rendimiento</string>
- <string name="module_enabler_disabled_title">Habilitar backend del módulo del kernel</string>
- <string name="module_enabler_enabled_summary">El backend más lento del espacio de usuario puede mejorar la estabilidad</string>
- <string name="module_enabler_enabled_title">Desactivar backend del módulo del kernel</string>
+ <string name="module_disabler_disabled_summary">El módulo experimental del kernel puede mejorar el rendimiento</string>
+ <string name="module_disabler_disabled_title">Habilitar backend del módulo del kernel</string>
+ <string name="module_disabler_enabled_summary">El backend más lento del espacio de usuario puede mejorar la estabilidad</string>
+ <string name="module_disabler_enabled_title">Desactivar backend del módulo del kernel</string>
<string name="module_installer_error">Ocurrió un error. Intente de nuevo</string>
<string name="module_installer_initial">El módulo experimental del kernel puede mejorar el rendimiento</string>
<string name="module_installer_not_found">No hay módulos disponibles para tu dispositivo</string>
diff --git a/ui/src/main/res/values-fa-rIR/strings.xml b/ui/src/main/res/values-fa-rIR/strings.xml
index cc3df4de..b1f3793b 100644
--- a/ui/src/main/res/values-fa-rIR/strings.xml
+++ b/ui/src/main/res/values-fa-rIR/strings.xml
@@ -97,6 +97,7 @@
<string name="create_output_dir_error">نمی‌توان دایرکتوری خروجی را ایجاد کرد</string>
<string name="create_temp_dir_error">نمی‌توان دایرکتوری موقت محلی را ساخت</string>
<string name="create_tunnel">ساختن تونل</string>
+ <string name="copied_to_clipboard">متن در کلیپ‌بورد کپی شد</string>
<string name="dark_theme_summary_off">اکنون از پوسته روشن(روز) استفاده می‌شود</string>
<string name="dark_theme_summary_on">اکنون از پوسته تاریک(شب) استفاده می‌شود</string>
<string name="dark_theme_title">استفاده از پوسته تاریک</string>
@@ -142,10 +143,10 @@
<string name="log_viewer_pref_title">نمایش گزارش رویداد برنامه</string>
<string name="log_viewer_title">گزارش رویداد</string>
<string name="logcat_error">نمی‌توان logcat را اجرا کرد: </string>
- <string name="module_enabler_disabled_summary">ماژول آزمایشی‌ِ کرنل می تواند کارایی را افزایش دهد</string>
- <string name="module_enabler_disabled_title">فعال‌سازی ماژول کرنل ِبک اند</string>
- <string name="module_enabler_enabled_summary">فضای کاربری کند ممکن است پایداری را بهبود ببخشد</string>
- <string name="module_enabler_enabled_title">غیرفعال‌سازی پس‌زمینه واحد هسته</string>
+ <string name="module_disabler_disabled_summary">ماژول آزمایشی‌ِ کرنل می تواند کارایی را افزایش دهد</string>
+ <string name="module_disabler_disabled_title">فعال‌سازی ماژول کرنل ِبک اند</string>
+ <string name="module_disabler_enabled_summary">فضای کاربری کند ممکن است پایداری را بهبود ببخشد</string>
+ <string name="module_disabler_enabled_title">غیرفعال‌سازی پس‌زمینه واحد هسته</string>
<string name="module_installer_error">مشکلی پیش آمد. لطفا دوباره تلاش کنید</string>
<string name="module_installer_initial">ماژول آزمایشی‌ِ کرنل می تواند کارایی را افزایش دهد</string>
<string name="module_installer_not_found">هیچ واحدی برای دستگاه شما در دسترس نیست</string>
diff --git a/ui/src/main/res/values-fr/strings.xml b/ui/src/main/res/values-fr/strings.xml
index 33f413b1..17abc8ab 100644
--- a/ui/src/main/res/values-fr/strings.xml
+++ b/ui/src/main/res/values-fr/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Afficher le journal de l\'application</string>
<string name="log_viewer_title">Journal</string>
<string name="logcat_error">Impossible d\'exécuter logcat : </string>
- <string name="module_enabler_disabled_summary">Le module expérimental du noyau peut améliorer les performances</string>
- <string name="module_enabler_disabled_title">Activer le backend du module du noyau</string>
- <string name="module_enabler_enabled_summary">Le backend plus lent de l\'espace utilisateur peut améliorer la stabilité</string>
- <string name="module_enabler_enabled_title">Désactiver le backend du module du noyau</string>
+ <string name="module_disabler_disabled_summary">Le module expérimental du noyau peut améliorer les performances</string>
+ <string name="module_disabler_disabled_title">Activer le backend du module du noyau</string>
+ <string name="module_disabler_enabled_summary">Le backend plus lent de l\'espace utilisateur peut améliorer la stabilité</string>
+ <string name="module_disabler_enabled_title">Désactiver le backend du module du noyau</string>
<string name="module_installer_error">Une erreur est survenue. Veuillez réessayer</string>
<string name="module_installer_initial">Le module expérimental du noyau peut améliorer les performances</string>
<string name="module_installer_not_found">Aucun module n\'est disponible pour votre appareil</string>
diff --git a/ui/src/main/res/values-hi-rIN/strings.xml b/ui/src/main/res/values-hi-rIN/strings.xml
index b614b475..e16d69ea 100644
--- a/ui/src/main/res/values-hi-rIN/strings.xml
+++ b/ui/src/main/res/values-hi-rIN/strings.xml
@@ -132,10 +132,10 @@
<string name="log_viewer_pref_title">एप्लिकेशन लॉग देखें</string>
<string name="log_viewer_title">लॉग</string>
<string name="logcat_error">लॉगकैट चलाने में असमर्थ: </string>
- <string name="module_enabler_disabled_summary">प्रयोगात्मक कर्नेल मॉड्यूल प्रदर्शन में सुधार कर सकता है</string>
- <string name="module_enabler_disabled_title">कर्नेल मॉड्यूल बैकएंड सक्षम करें</string>
- <string name="module_enabler_enabled_summary">धीमे यूजरस्पेस बैकएंड में स्थिरता में सुधार हो सकता है</string>
- <string name="module_enabler_enabled_title">कर्नेल मॉड्यूल बैकएंड को अक्षम करें</string>
+ <string name="module_disabler_disabled_summary">प्रयोगात्मक कर्नेल मॉड्यूल प्रदर्शन में सुधार कर सकता है</string>
+ <string name="module_disabler_disabled_title">कर्नेल मॉड्यूल बैकएंड सक्षम करें</string>
+ <string name="module_disabler_enabled_summary">धीमे यूजरस्पेस बैकएंड में स्थिरता में सुधार हो सकता है</string>
+ <string name="module_disabler_enabled_title">कर्नेल मॉड्यूल बैकएंड को अक्षम करें</string>
<string name="module_installer_error">कुछ गलत हो गया। कृपया पुन: प्रयास करें</string>
<string name="module_installer_initial">प्रयोगात्मक कर्नेल मॉड्यूल प्रदर्शन में सुधार कर सकता है</string>
<string name="module_installer_not_found">आपके डिवाइस के लिए कोई मॉड्यूल उपलब्ध नहीं हैं</string>
diff --git a/ui/src/main/res/values-in/strings.xml b/ui/src/main/res/values-in/strings.xml
index 7852ffd6..e8e664de 100644
--- a/ui/src/main/res/values-in/strings.xml
+++ b/ui/src/main/res/values-in/strings.xml
@@ -129,10 +129,10 @@
<string name="log_viewer_pref_title">Lihat log aplikasi</string>
<string name="log_viewer_title">Log</string>
<string name="logcat_error">Tidak bisa menjalankan logcat: </string>
- <string name="module_enabler_disabled_summary">Modul kernel eksperimental dapat meningkatkan kinerja</string>
- <string name="module_enabler_disabled_title">Aktifkan backend modul kernel</string>
- <string name="module_enabler_enabled_summary">Backend userspace yang lebih lambat dapat meningkatkan stabilitas</string>
- <string name="module_enabler_enabled_title">Nonaktifkan backend modul kernel</string>
+ <string name="module_disabler_disabled_summary">Modul kernel eksperimental dapat meningkatkan kinerja</string>
+ <string name="module_disabler_disabled_title">Aktifkan backend modul kernel</string>
+ <string name="module_disabler_enabled_summary">Backend userspace yang lebih lambat dapat meningkatkan stabilitas</string>
+ <string name="module_disabler_enabled_title">Nonaktifkan backend modul kernel</string>
<string name="module_installer_error">Ada yang salah. Silakan coba lagi</string>
<string name="module_installer_initial">Modul kernel eksperimental dapat meningkatkan kinerja</string>
<string name="module_installer_not_found">Tidak tersedia modul untuk perangkat anda</string>
diff --git a/ui/src/main/res/values-it/strings.xml b/ui/src/main/res/values-it/strings.xml
index 519d4b52..7b9a2d3f 100644
--- a/ui/src/main/res/values-it/strings.xml
+++ b/ui/src/main/res/values-it/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Visualizza log dell\'applicazione</string>
<string name="log_viewer_title">Log</string>
<string name="logcat_error">Impossibile eseguire logcat: </string>
- <string name="module_enabler_disabled_summary">Il modulo sperimentale del kernel può migliorare le prestazioni</string>
- <string name="module_enabler_disabled_title">Abilita il backend del modulo del kernel</string>
- <string name="module_enabler_enabled_summary">Il backend in userspace più lento potrebbe migliorare la stabilità</string>
- <string name="module_enabler_enabled_title">Disabilita il backend del modulo del kernel</string>
+ <string name="module_disabler_disabled_summary">Il modulo sperimentale del kernel può migliorare le prestazioni</string>
+ <string name="module_disabler_disabled_title">Abilita il backend del modulo del kernel</string>
+ <string name="module_disabler_enabled_summary">Il backend in userspace più lento potrebbe migliorare la stabilità</string>
+ <string name="module_disabler_enabled_title">Disabilita il backend del modulo del kernel</string>
<string name="module_installer_error">Qualcosa non ha funzionato. Riprova</string>
<string name="module_installer_initial">Il modulo sperimentale del kernel può migliorare le prestazioni</string>
<string name="module_installer_not_found">Nessun modulo disponibile per il tuo dispositivo</string>
diff --git a/ui/src/main/res/values-ja/strings.xml b/ui/src/main/res/values-ja/strings.xml
index e10a05a8..7aa5531a 100644
--- a/ui/src/main/res/values-ja/strings.xml
+++ b/ui/src/main/res/values-ja/strings.xml
@@ -129,10 +129,10 @@
<string name="log_viewer_pref_title">アプリケーションログを表示</string>
<string name="log_viewer_title">ログ</string>
<string name="logcat_error">logcat を実行できません: </string>
- <string name="module_enabler_disabled_summary">カーネルモジュールは実験的ですがパフォーマンスが向上する可能性があります。</string>
- <string name="module_enabler_disabled_title">カーネルモジュールバックエンドの有効化</string>
- <string name="module_enabler_enabled_summary">ユーザースペースバックエンドは低速ですが安定しています。</string>
- <string name="module_enabler_enabled_title">カーネルモジュールバックエンドの無効化</string>
+ <string name="module_disabler_disabled_summary">カーネルモジュールは実験的ですがパフォーマンスが向上する可能性があります。</string>
+ <string name="module_disabler_disabled_title">カーネルモジュールバックエンドの有効化</string>
+ <string name="module_disabler_enabled_summary">ユーザースペースバックエンドは低速ですが安定しています。</string>
+ <string name="module_disabler_enabled_title">カーネルモジュールバックエンドの無効化</string>
<string name="module_installer_error">失敗しました. 再度実行してみてください</string>
<string name="module_installer_initial">実験的カーネルモジュールはパフォーマンスが向上する場合があります</string>
<string name="module_installer_not_found">このデバイス用のモジュールは利用できません</string>
diff --git a/ui/src/main/res/values-ko-rKR/strings.xml b/ui/src/main/res/values-ko-rKR/strings.xml
index c975945a..a2890b26 100644
--- a/ui/src/main/res/values-ko-rKR/strings.xml
+++ b/ui/src/main/res/values-ko-rKR/strings.xml
@@ -129,10 +129,10 @@
<string name="log_viewer_pref_title">앱 로그 보기</string>
<string name="log_viewer_title">로그</string>
<string name="logcat_error">logcat을 실행할 수 없음: </string>
- <string name="module_enabler_disabled_summary">아직 실험중이 커널 모듈을 사용하면 성능이 향상될 수 있음</string>
- <string name="module_enabler_disabled_title">커널 모듈 백엔드 활성화하기</string>
- <string name="module_enabler_enabled_summary">사용자공간 백엔드를 사용하면 느리지만 안정성이 좋아짐</string>
- <string name="module_enabler_enabled_title">커널 모듈 백엔드를 비활성화하기</string>
+ <string name="module_disabler_disabled_summary">아직 실험중이 커널 모듈을 사용하면 성능이 향상될 수 있음</string>
+ <string name="module_disabler_disabled_title">커널 모듈 백엔드 활성화하기</string>
+ <string name="module_disabler_enabled_summary">사용자공간 백엔드를 사용하면 느리지만 안정성이 좋아짐</string>
+ <string name="module_disabler_enabled_title">커널 모듈 백엔드를 비활성화하기</string>
<string name="module_installer_error">문제가 발생했습니다. 다시 시도하십시오</string>
<string name="module_installer_initial">아직 실험중이 커널 모듈을 사용하면 성능이 향상될 수 있음</string>
<string name="module_installer_not_found">이 기기에서 사용가능한 모듈이 없음</string>
diff --git a/ui/src/main/res/values-no-rNO/strings.xml b/ui/src/main/res/values-no-rNO/strings.xml
index b0616f4e..f9c2c979 100644
--- a/ui/src/main/res/values-no-rNO/strings.xml
+++ b/ui/src/main/res/values-no-rNO/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Vis programlogg</string>
<string name="log_viewer_title">Logg</string>
<string name="logcat_error">Kan ikke kjøre logcat: </string>
- <string name="module_enabler_disabled_summary">Den eksperimentelle kjernemodulen kan gi bedre ytelse</string>
- <string name="module_enabler_disabled_title">Aktiver backend for kjerne-modul</string>
- <string name="module_enabler_enabled_summary">Backend i userspace er litt tregere men kan gi bedre stabilitet</string>
- <string name="module_enabler_enabled_title">Deaktiver backend for kjerne-modul</string>
+ <string name="module_disabler_disabled_summary">Den eksperimentelle kjernemodulen kan gi bedre ytelse</string>
+ <string name="module_disabler_disabled_title">Aktiver backend for kjerne-modul</string>
+ <string name="module_disabler_enabled_summary">Backend i userspace er litt tregere men kan gi bedre stabilitet</string>
+ <string name="module_disabler_enabled_title">Deaktiver backend for kjerne-modul</string>
<string name="module_installer_error">Noe gikk galt. Vennligst prøv igjen</string>
<string name="module_installer_initial">Den eksperimentelle kjernemodulen kan gi bedre ytelse</string>
<string name="module_installer_not_found">Ingen moduler er tilgjengelige for din enhet</string>
diff --git a/ui/src/main/res/values-pa-rIN/strings.xml b/ui/src/main/res/values-pa-rIN/strings.xml
index 1d7ac096..ed7e96ba 100644
--- a/ui/src/main/res/values-pa-rIN/strings.xml
+++ b/ui/src/main/res/values-pa-rIN/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">ਐਪਲੀਕੇਸ਼ਨ ਲਾਗ ਵੇਖੋ</string>
<string name="log_viewer_title">ਲਾਗ</string>
<string name="logcat_error">logcat ਚਲਾਉਣ ਲਈ ਅਸਮਰੱਥ: </string>
- <string name="module_enabler_disabled_summary">ਤਜਰਬੇ ਅਧੀਨ ਕਰਨਲ ਮੋਡੀਊਲ ਕਾਰਗੁਜ਼ਾਰੀ ਸੁਧਾਰ ਸਕਦਾ ਹੈ</string>
- <string name="module_enabler_disabled_title">ਕਰਨਲ ਮੋਡੀਊਲ ਬੈਕਐਂਡ ਸਮਰੱਥ ਕਰੋ</string>
- <string name="module_enabler_enabled_summary">ਹੌਲੀ ਵਰਤੋਂਕਾਰ-ਸਪੇਸ ਬੈਂਕਡ ਸਥਿਰਤਾ ਸੁਧਾਰ ਕਰ ਸਕਦਾ ਹੈ</string>
- <string name="module_enabler_enabled_title">ਕਰਨਲ ਮੋਡੀਊਲ ਬੈਕਐਂਡ ਅਸਮਰੱਥ ਕਰੋ</string>
+ <string name="module_disabler_disabled_summary">ਤਜਰਬੇ ਅਧੀਨ ਕਰਨਲ ਮੋਡੀਊਲ ਕਾਰਗੁਜ਼ਾਰੀ ਸੁਧਾਰ ਸਕਦਾ ਹੈ</string>
+ <string name="module_disabler_disabled_title">ਕਰਨਲ ਮੋਡੀਊਲ ਬੈਕਐਂਡ ਸਮਰੱਥ ਕਰੋ</string>
+ <string name="module_disabler_enabled_summary">ਹੌਲੀ ਵਰਤੋਂਕਾਰ-ਸਪੇਸ ਬੈਂਕਡ ਸਥਿਰਤਾ ਸੁਧਾਰ ਕਰ ਸਕਦਾ ਹੈ</string>
+ <string name="module_disabler_enabled_title">ਕਰਨਲ ਮੋਡੀਊਲ ਬੈਕਐਂਡ ਅਸਮਰੱਥ ਕਰੋ</string>
<string name="module_installer_error">ਕੁਝ ਗਲਤ ਵਾਪਰ ਗਿਆ। ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ</string>
<string name="module_installer_initial">ਤਜਰਬੇ ਅਧੀਨ ਕਰਨਲ ਮੋਡੀਊਲ ਕਾਰਗੁਜ਼ਾਰੀ ਸੁਧਾਰ ਸਕਦਾ ਹੈ</string>
<string name="module_installer_not_found">ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਲਈ ਕੋਈ ਮੋਡੀਊਲ ਮੌਜੂਦ ਨਹੀਂ ਹਨ</string>
diff --git a/ui/src/main/res/values-pl-rPL/strings.xml b/ui/src/main/res/values-pl-rPL/strings.xml
index 119bdc74..b739035e 100644
--- a/ui/src/main/res/values-pl-rPL/strings.xml
+++ b/ui/src/main/res/values-pl-rPL/strings.xml
@@ -168,10 +168,10 @@
<string name="log_viewer_pref_title">Wyświetl log aplikacji</string>
<string name="log_viewer_title">Log</string>
<string name="logcat_error">Nie można uruchomić narzędzia logcat: </string>
- <string name="module_enabler_disabled_summary">Eksperymentalny moduł jądra może poprawić wydajność</string>
- <string name="module_enabler_disabled_title">Włącz moduł jądra</string>
- <string name="module_enabler_enabled_summary">Wolniejsza implementacja w przestrzeni użytkownika może poprawić stabilność</string>
- <string name="module_enabler_enabled_title">Wyłącz moduł jądra</string>
+ <string name="module_disabler_disabled_summary">Eksperymentalny moduł jądra może poprawić wydajność</string>
+ <string name="module_disabler_disabled_title">Włącz moduł jądra</string>
+ <string name="module_disabler_enabled_summary">Wolniejsza implementacja w przestrzeni użytkownika może poprawić stabilność</string>
+ <string name="module_disabler_enabled_title">Wyłącz moduł jądra</string>
<string name="module_installer_error">Coś poszło nie tak. Proszę spróbować ponownie</string>
<string name="module_installer_initial">Eksperymentalny moduł jądra może poprawić wydajność</string>
<string name="module_installer_not_found">Brak dostępnych modułów dla tego urządzenia</string>
diff --git a/ui/src/main/res/values-pt-rPT/strings.xml b/ui/src/main/res/values-pt-rPT/strings.xml
index fc1f19cb..b609cf2b 100644
--- a/ui/src/main/res/values-pt-rPT/strings.xml
+++ b/ui/src/main/res/values-pt-rPT/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Ver log da aplicação</string>
<string name="log_viewer_title">Log</string>
<string name="logcat_error">Não foi possível executar o logcat: </string>
- <string name="module_enabler_disabled_summary">O módulo experimental de kernel pode melhorar o desempenho</string>
- <string name="module_enabler_disabled_title">Habilitar módulo backend do kernel</string>
- <string name="module_enabler_enabled_summary">O backend do userspace mais lento pode aumentar a estabilidade</string>
- <string name="module_enabler_enabled_title">Desabilitar módulo backend do kernel</string>
+ <string name="module_disabler_disabled_summary">O módulo experimental de kernel pode melhorar o desempenho</string>
+ <string name="module_disabler_disabled_title">Habilitar módulo backend do kernel</string>
+ <string name="module_disabler_enabled_summary">O backend do userspace mais lento pode aumentar a estabilidade</string>
+ <string name="module_disabler_enabled_title">Desabilitar módulo backend do kernel</string>
<string name="module_installer_error">Ocorreu um erro. Por favor, tente novamente</string>
<string name="module_installer_initial">O módulo experimental do kernel pode melhorar o desempenho</string>
<string name="module_installer_not_found">Não há módulos disponíveis para o seu dispositivo</string>
diff --git a/ui/src/main/res/values-ro-rRO/strings.xml b/ui/src/main/res/values-ro-rRO/strings.xml
index 5caa3cae..e0249364 100644
--- a/ui/src/main/res/values-ro-rRO/strings.xml
+++ b/ui/src/main/res/values-ro-rRO/strings.xml
@@ -155,10 +155,10 @@
<string name="log_viewer_pref_title">Vizualizare jurnal aplicație</string>
<string name="log_viewer_title">Jurnal</string>
<string name="logcat_error">Programul logcat nu poate fi executat: </string>
- <string name="module_enabler_disabled_summary">Modulul experimental de nucleu poate îmbunătăți performanța</string>
- <string name="module_enabler_disabled_title">Activează biblioteca modulului de nucleu</string>
- <string name="module_enabler_enabled_summary">Biblioteca mai lentă a spațiului utilizatorului poate îmbunătăți stabilitatea</string>
- <string name="module_enabler_enabled_title">Dezactivează biblioteca modulului de nucleu</string>
+ <string name="module_disabler_disabled_summary">Modulul experimental de nucleu poate îmbunătăți performanța</string>
+ <string name="module_disabler_disabled_title">Activează biblioteca modulului de nucleu</string>
+ <string name="module_disabler_enabled_summary">Biblioteca mai lentă a spațiului utilizatorului poate îmbunătăți stabilitatea</string>
+ <string name="module_disabler_enabled_title">Dezactivează biblioteca modulului de nucleu</string>
<string name="module_installer_error">A apărut o eroare. Încearcă din nou</string>
<string name="module_installer_initial">Modulul experimental de nucleu poate îmbunătăți performanța</string>
<string name="module_installer_not_found">Nu sunt disponibile module pentru dispozitivul tău</string>
diff --git a/ui/src/main/res/values-ru/strings.xml b/ui/src/main/res/values-ru/strings.xml
index b15436de..3fc6c911 100644
--- a/ui/src/main/res/values-ru/strings.xml
+++ b/ui/src/main/res/values-ru/strings.xml
@@ -168,10 +168,10 @@
<string name="log_viewer_pref_title">Просмотр журналов приложения</string>
<string name="log_viewer_title">Журнал</string>
<string name="logcat_error">Не удалось запустить logcat: </string>
- <string name="module_enabler_disabled_summary">Экспериментальный модуль ядра может улучшить производительность</string>
- <string name="module_enabler_disabled_title">Включить бэкэнд модуля ядра</string>
- <string name="module_enabler_enabled_summary">Пользовательское пространство немного медленнее и улучшает стабильность</string>
- <string name="module_enabler_enabled_title">Отключить бэкэнд модуля ядра</string>
+ <string name="module_disabler_disabled_summary">Экспериментальный модуль ядра может улучшить производительность</string>
+ <string name="module_disabler_disabled_title">Включить бэкэнд модуля ядра</string>
+ <string name="module_disabler_enabled_summary">Пользовательское пространство немного медленнее и улучшает стабильность</string>
+ <string name="module_disabler_enabled_title">Отключить бэкэнд модуля ядра</string>
<string name="module_installer_error">Что-то пошло не так. Пожалуйста, попробуйте еще раз</string>
<string name="module_installer_initial">Экспериментальный модуль ядра может улучшить производительность</string>
<string name="module_installer_not_found">Для вашего устройства нет доступных модулей</string>
diff --git a/ui/src/main/res/values-sk-rSK/strings.xml b/ui/src/main/res/values-sk-rSK/strings.xml
index 30ba9f98..3479091f 100644
--- a/ui/src/main/res/values-sk-rSK/strings.xml
+++ b/ui/src/main/res/values-sk-rSK/strings.xml
@@ -77,7 +77,7 @@
<string name="log_saver_activity_label">Uložiť denník udalostí</string>
<string name="log_viewer_pref_summary">Denník udalosti môžu byt nápomocné pri ladení aplikácie</string>
<string name="log_viewer_pref_title">Zobraziť denník udalostí aplikácie</string>
- <string name="module_enabler_enabled_summary">Pomalšie užívatelské prostredie môže zlepšiť stabilitu</string>
+ <string name="module_disabler_enabled_summary">Pomalšie užívatelské prostredie môže zlepšiť stabilitu</string>
<string name="module_installer_error">Niečo sa pokazilo. Prosím, skúste znova</string>
<string name="module_installer_not_found">Pre vaše zariadenie nie sú k dispozícii žiadne moduly</string>
<string name="module_installer_title">Stiahni a nainštaluj kernel modul</string>
diff --git a/ui/src/main/res/values-sl/strings.xml b/ui/src/main/res/values-sl/strings.xml
index 8c98bc12..87b548e1 100644
--- a/ui/src/main/res/values-sl/strings.xml
+++ b/ui/src/main/res/values-sl/strings.xml
@@ -166,10 +166,10 @@
<string name="log_viewer_pref_title">Prikaži dnevnik aplikacije</string>
<string name="log_viewer_title">Dnevnik</string>
<string name="logcat_error">Ukaza logcat ni bilo mogoče izvesti: </string>
- <string name="module_enabler_disabled_summary">Eksperimentalni modul jedra lahko izboljša zmogljivost</string>
- <string name="module_enabler_disabled_title">Omogoči zaledje za modul jedra</string>
- <string name="module_enabler_enabled_summary">Počasnejše uporabniško zaledje lahko izboljša stabilnost</string>
- <string name="module_enabler_enabled_title">Onemogoči zaledje za modul jedra</string>
+ <string name="module_disabler_disabled_summary">Eksperimentalni modul jedra lahko izboljša zmogljivost</string>
+ <string name="module_disabler_disabled_title">Omogoči zaledje za modul jedra</string>
+ <string name="module_disabler_enabled_summary">Počasnejše uporabniško zaledje lahko izboljša stabilnost</string>
+ <string name="module_disabler_enabled_title">Onemogoči zaledje za modul jedra</string>
<string name="module_installer_error">Nekaj je šlo narobe, prosimo poskusite znova</string>
<string name="module_installer_initial">Eksperimentalni modul jedra lahko izboljša zmogljivost</string>
<string name="module_installer_not_found">Za vašo napravo ni razpoložljivih modulov</string>
diff --git a/ui/src/main/res/values-sv-rSE/strings.xml b/ui/src/main/res/values-sv-rSE/strings.xml
index 16765166..686bd338 100644
--- a/ui/src/main/res/values-sv-rSE/strings.xml
+++ b/ui/src/main/res/values-sv-rSE/strings.xml
@@ -114,10 +114,10 @@
<string name="log_viewer_pref_title">Visa applikationslogg</string>
<string name="log_viewer_title">Logg</string>
<string name="logcat_error">Kunde inte köra logcat: </string>
- <string name="module_enabler_disabled_summary">Den experimentella kärnmodulen kan förbättra prestanda</string>
- <string name="module_enabler_disabled_title">Aktivera backend för kärnmodul</string>
- <string name="module_enabler_enabled_summary">Den långsammare backend för användarrymden kan förbättra stabiliteten</string>
- <string name="module_enabler_enabled_title">Inaktivera backend för kärnmodul</string>
+ <string name="module_disabler_disabled_summary">Den experimentella kärnmodulen kan förbättra prestanda</string>
+ <string name="module_disabler_disabled_title">Aktivera backend för kärnmodul</string>
+ <string name="module_disabler_enabled_summary">Den långsammare backend för användarrymden kan förbättra stabiliteten</string>
+ <string name="module_disabler_enabled_title">Inaktivera backend för kärnmodul</string>
<string name="module_installer_error">Något gick fel. Vänligen försök igen</string>
<string name="module_installer_initial">Den experimentella kärnmodulen kan förbättra prestanda</string>
<string name="module_installer_not_found">Inga moduler finns tillgängliga för din enhet</string>
diff --git a/ui/src/main/res/values-tr-rTR/strings.xml b/ui/src/main/res/values-tr-rTR/strings.xml
index b05d395d..89642b86 100644
--- a/ui/src/main/res/values-tr-rTR/strings.xml
+++ b/ui/src/main/res/values-tr-rTR/strings.xml
@@ -142,10 +142,10 @@
<string name="log_viewer_pref_title">Uygulama günlüğünü görüntüle</string>
<string name="log_viewer_title">Günlük</string>
<string name="logcat_error">Logcat çalıştırılamıyor: </string>
- <string name="module_enabler_disabled_summary">Deneysel çekirdek modülü performansı artırabilir</string>
- <string name="module_enabler_disabled_title">Çekirdek modülü arka ucunu etkinleştir</string>
- <string name="module_enabler_enabled_summary">Daha yavaş kullanıcı alanı arka ucu kararlılığı artırabilir</string>
- <string name="module_enabler_enabled_title">Çekirdek modülü arka ucunu devre dışı bırak</string>
+ <string name="module_disabler_disabled_summary">Deneysel çekirdek modülü performansı artırabilir</string>
+ <string name="module_disabler_disabled_title">Çekirdek modülü arka ucunu etkinleştir</string>
+ <string name="module_disabler_enabled_summary">Daha yavaş kullanıcı alanı arka ucu kararlılığı artırabilir</string>
+ <string name="module_disabler_enabled_title">Çekirdek modülü arka ucunu devre dışı bırak</string>
<string name="module_installer_error">Bir şeyler yanlış gitti. Lütfen tekrar deneyin</string>
<string name="module_installer_initial">Deneysel çekirdek modülü performansı artırabilir</string>
<string name="module_installer_not_found">Cihazınız için uygun modül yok</string>
diff --git a/ui/src/main/res/values-uk-rUA/strings.xml b/ui/src/main/res/values-uk-rUA/strings.xml
index 4ecdb781..252595f0 100644
--- a/ui/src/main/res/values-uk-rUA/strings.xml
+++ b/ui/src/main/res/values-uk-rUA/strings.xml
@@ -168,10 +168,10 @@
<string name="log_viewer_pref_title">Переглянути журнал програми</string>
<string name="log_viewer_title">Журнал</string>
<string name="logcat_error">Не вдалося запустити logcat: </string>
- <string name="module_enabler_disabled_summary">Експериментальний модуль ядра може підвищити продуктивність</string>
- <string name="module_enabler_disabled_title">Увімкнути модуль ядра</string>
- <string name="module_enabler_enabled_summary">Користувацький простір повільніший, проте може покращити стабільність</string>
- <string name="module_enabler_enabled_title">Вимкнути модуль ядра</string>
+ <string name="module_disabler_disabled_summary">Експериментальний модуль ядра може підвищити продуктивність</string>
+ <string name="module_disabler_disabled_title">Увімкнути модуль ядра</string>
+ <string name="module_disabler_enabled_summary">Користувацький простір повільніший, проте може покращити стабільність</string>
+ <string name="module_disabler_enabled_title">Вимкнути модуль ядра</string>
<string name="module_installer_error">Щось пішло не так. Спробуйте ще раз</string>
<string name="module_installer_initial">Експериментальний модуль ядра може підвищити продуктивність</string>
<string name="module_installer_not_found">Немає доступних модулів для вашого пристрою</string>
diff --git a/ui/src/main/res/values-vi-rVN/strings.xml b/ui/src/main/res/values-vi-rVN/strings.xml
new file mode 100644
index 00000000..582a81aa
--- /dev/null
+++ b/ui/src/main/res/values-vi-rVN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="all_applications">Tất cả các ứng dụng</string>
+ <string name="exclude_from_tunnel">Ngoại trừ</string>
+</resources>
diff --git a/ui/src/main/res/values-zh-rCN/strings.xml b/ui/src/main/res/values-zh-rCN/strings.xml
index 60e84c7e..24c5b6c3 100644
--- a/ui/src/main/res/values-zh-rCN/strings.xml
+++ b/ui/src/main/res/values-zh-rCN/strings.xml
@@ -129,10 +129,10 @@
<string name="log_viewer_pref_title">查看应用日志</string>
<string name="log_viewer_title">日志</string>
<string name="logcat_error">无法运行 logcat: </string>
- <string name="module_enabler_disabled_summary">内核空间的模块性能较强,但可能不稳定</string>
- <string name="module_enabler_disabled_title">启用内核模块</string>
- <string name="module_enabler_enabled_summary">用户空间的模块性能较弱,但稳定性更好</string>
- <string name="module_enabler_enabled_title">停用内核模块</string>
+ <string name="module_disabler_disabled_summary">内核空间的模块性能较强,但可能不稳定</string>
+ <string name="module_disabler_disabled_title">启用内核模块</string>
+ <string name="module_disabler_enabled_summary">用户空间的模块性能较弱,但稳定性更好</string>
+ <string name="module_disabler_enabled_title">停用内核模块</string>
<string name="module_installer_error">发生错误,请重试</string>
<string name="module_installer_initial">使用内核模块可以提升性能(实验性)</string>
<string name="module_installer_not_found">没有可用于此设备的模块</string>
diff --git a/ui/src/main/res/values-zh-rTW/strings.xml b/ui/src/main/res/values-zh-rTW/strings.xml
index f4150e2c..2f58e445 100644
--- a/ui/src/main/res/values-zh-rTW/strings.xml
+++ b/ui/src/main/res/values-zh-rTW/strings.xml
@@ -129,8 +129,8 @@
<string name="log_viewer_pref_title">檢視應用程式日誌</string>
<string name="log_viewer_title">日誌</string>
<string name="logcat_error">無法執行 logcat: </string>
- <string name="module_enabler_disabled_summary">使用還在實驗階段的 kernel module 以便改善效能</string>
- <string name="module_enabler_disabled_title">啟用 kernel module</string>
+ <string name="module_disabler_disabled_summary">使用還在實驗階段的 kernel module 以便改善效能</string>
+ <string name="module_disabler_disabled_title">啟用 kernel module</string>
<string name="save">儲存</string>
<string name="select_all">全選</string>
<string name="toggle_all">切換全部</string>
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
index fb6fad4b..116f68f9 100644
--- a/ui/src/main/res/values/strings.xml
+++ b/ui/src/main/res/values/strings.xml
@@ -70,6 +70,7 @@
<string name="bad_config_explanation_pka">: Must be positive and no more than 65535</string>
<string name="bad_config_explanation_positive_number">: Must be positive</string>
<string name="bad_config_explanation_udp_port">: Must be a valid UDP port number</string>
+ <string name="bad_config_explanation_http_proxy">: Must be valid proxy hostname and port</string>
<string name="bad_config_reason_invalid_key">Invalid key</string>
<string name="bad_config_reason_invalid_number">Invalid number</string>
<string name="bad_config_reason_invalid_value">Invalid value</string>
@@ -104,14 +105,22 @@
<string name="dark_theme_summary_on">Currently using dark (night) theme</string>
<string name="dark_theme_title">Use dark theme</string>
<string name="delete">Delete</string>
+ <string name="dhcp_addresses">DHCP Addresses</string>
<string name="tv_delete">Select tunnel to delete</string>
<string name="tv_select_a_storage_drive">Select a storage drive</string>
<string name="tv_no_file_picker">Please install a file management utility to browse files</string>
<string name="tv_add_tunnel_get_started">Add a tunnel to get started</string>
+ <string name="donate_title">♥ Donate to the WireGuard Project</string>
+ <string name="donate_summary">Every contribution helps</string>
+ <string name="donate_google_play_disappointment">Thank you for supporting the WireGuard Project!\n\nUnfortunately, due to Google\'s policies, we\'re not allowed to link to the part of the project webpage where you can make a donation. Hopefully you can figure this out!\n\nThanks again for your contribution.</string>
<string name="disable_config_export_title">Disable config exporting</string>
<string name="disable_config_export_description">Disabling config exporting makes private keys less accessible</string>
<string name="dns_servers">DNS servers</string>
<string name="dns_search_domains">Search domains</string>
+ <string name="duration_days">%d days</string>
+ <string name="duration_hours">%d hours</string>
+ <string name="duration_minutes">%d minutes</string>
+ <string name="duration_seconds">%d seconds</string>
<string name="edit">Edit</string>
<string name="endpoint">Endpoint</string>
<string name="error_down">Error bringing down tunnel: %s</string>
@@ -126,6 +135,10 @@
<string name="hint_optional">(optional)</string>
<string name="hint_optional_discouraged">(optional, not recommended)</string>
<string name="hint_random">(random)</string>
+ <string name="http_proxy">Proxy</string>
+ <string name="http_proxy_hostname">Proxy hostname</string>
+ <string name="http_proxy_pac">Proxy Auto-Config URL</string>
+ <string name="http_proxy_port">Proxy port</string>
<string name="illegal_filename_error">Illegal file name “%s”</string>
<string name="import_error">Unable to import tunnel: %s</string>
<string name="import_from_qr_code">Import Tunnel from QR Code</string>
@@ -136,6 +149,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 keys must be 44 characters (32 bytes)</string>
<string name="key_length_explanation_binary">: WireGuard keys must be 32 bytes</string>
<string name="key_length_explanation_hex">: WireGuard hex keys must be 64 characters (32 bytes)</string>
+ <string name="last_handshake">Latest handshake</string>
+ <string name="last_handshake_ago">%s ago</string>
<string name="listen_port">Listen port</string>
<string name="log_export_error">Unable to export log: %s</string>
<string name="log_export_subject">WireGuard Android Log File</string>
diff --git a/ui/src/main/res/values/styles.xml b/ui/src/main/res/values/styles.xml
index b90f111f..da3c2cb7 100644
--- a/ui/src/main/res/values/styles.xml
+++ b/ui/src/main/res/values/styles.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="WireGuardTheme" parent="Theme.MaterialComponents.DayNight">
+ <style name="WireGuardTheme" parent="Theme.Material3.DayNight">
<item name="colorPrimary">@color/primary_color</item>
<item name="colorOnPrimary">@color/color_control_normal</item>
<item name="colorPrimaryDark">@color/primary_color</item>
@@ -17,9 +17,6 @@
<item name="elevationOverlayEnabled">true</item>
<item name="android:statusBarColor">@color/status_bar_color</item>
<item name="android:windowBackground">@color/primary_color</item>
- <item name="alertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="textInputStyle">@style/TextInputLayoutBase</item>
<item name="materialCardViewStyle">@style/AppTheme.MaterialCardView</item>
</style>
@@ -32,12 +29,6 @@
<item name="cardBackgroundColor">?attr/elevationOverlayColor</item>
</style>
- <style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
- <item name="colorPrimary">@color/secondary_color</item>
- <item name="colorSecondary">@color/secondary_color</item>
- <item name="android:windowBackground">?attr/colorBackground</item>
- </style>
-
<style name="BottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:navigationBarColor">?attr/colorBackground</item>
@@ -71,11 +62,7 @@
<item name="colorControlActivated">@color/color_control_normal</item>
</style>
- <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
+ <style name="ExposedDropDownMenu" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<item name="boxStrokeColor">?attr/colorSecondary</item>
- <item name="hintTextColor">?attr/colorOnPrimary</item>
- <item name="materialThemeOverlay">
- @style/ThemeOverlay.AppTheme.TextInputEditText.OutlinedBox
- </item>
</style>
</resources>
diff --git a/ui/src/main/res/values/tv_styles.xml b/ui/src/main/res/values/tv_styles.xml
index c5477f6a..536ca752 100644
--- a/ui/src/main/res/values/tv_styles.xml
+++ b/ui/src/main/res/values/tv_styles.xml
@@ -16,12 +16,26 @@
<item name="elevationOverlayEnabled">false</item>
<item name="android:statusBarColor">@color/tv_primary_color</item>
<item name="android:windowBackground">@color/tv_primary_color</item>
- <item name="alertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item>
+ <item name="alertDialogTheme">@style/TvTheme.Dialog</item>
+ <item name="materialAlertDialogTheme">@style/TvTheme.Dialog</item>
<item name="textInputStyle">@style/TextInputLayoutBase</item>
<item name="materialCardViewStyle">@style/TvTheme.MaterialCardView</item>
</style>
+ <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
+ <item name="boxStrokeColor">?attr/colorSecondary</item>
+ <item name="hintTextColor">?attr/colorOnPrimary</item>
+ <item name="materialThemeOverlay">
+ @style/ThemeOverlay.AppTheme.TextInputEditText.OutlinedBox
+ </item>
+ </style>
+
+ <style name="TvTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
+ <item name="colorPrimary">@color/secondary_color</item>
+ <item name="colorSecondary">@color/secondary_color</item>
+ <item name="android:windowBackground">?attr/colorBackground</item>
+ </style>
+
<style name="TvTheme.MaterialCardView" parent="Widget.MaterialComponents.CardView">
<item name="cornerRadius">4dp</item>
<item name="cardElevation">8dp</item>
diff --git a/ui/src/main/res/xml/app_restrictions.xml b/ui/src/main/res/xml/app_restrictions.xml
index fefa8a80..2eaa7bc5 100644
--- a/ui/src/main/res/xml/app_restrictions.xml
+++ b/ui/src/main/res/xml/app_restrictions.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
~ SPDX-License-Identifier: Apache-2.0
-->
diff --git a/ui/src/main/res/xml/preferences.xml b/ui/src/main/res/xml/preferences.xml
index 5c9505d4..aa89f27c 100644
--- a/ui/src/main/res/xml/preferences.xml
+++ b/ui/src/main/res/xml/preferences.xml
@@ -40,4 +40,6 @@
android:summaryOff="@string/allow_remote_control_intents_summary_off"
android:summaryOn="@string/allow_remote_control_intents_summary_on"
android:title="@string/allow_remote_control_intents_title" />
+ <com.wireguard.android.preference.DonatePreference
+ android:singleLineTitle="false" />
</androidx.preference.PreferenceScreen>
diff --git a/version.gradle b/version.gradle
index 6137523a..68ca559e 100644
--- a/version.gradle
+++ b/version.gradle
@@ -1,6 +1,7 @@
+
buildscript {
ext {
- wireguardVersionCode = 492
- wireguardVersionName = '1.0.20220516'
+ wireguardVersionCode = 493
+ wireguardVersionName = 'git describe --tags HEAD'.execute().text.trim()
}
}