aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tunnel/src
diff options
context:
space:
mode:
Diffstat (limited to 'tunnel/src')
-rw-r--r--tunnel/src/main/AndroidManifest.xml8
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Backend.java7
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/BackendException.java28
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java130
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Statistics.java77
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java11
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java20
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java189
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/RootShell.java9
-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.java6
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Attribute.java5
-rw-r--r--tunnel/src/main/java/com/wireguard/config/BadConfigException.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Config.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetAddresses.java13
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetEndpoint.java9
-rw-r--r--tunnel/src/main/java/com/wireguard/config/InetNetwork.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Interface.java53
-rw-r--r--tunnel/src/main/java/com/wireguard/config/ParseException.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Peer.java4
-rw-r--r--tunnel/src/main/java/com/wireguard/crypto/Curve25519.java6
-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/test/java/com/wireguard/config/BadConfigExceptionTest.java2
-rw-r--r--tunnel/src/test/java/com/wireguard/config/ConfigTest.java2
-rw-r--r--tunnel/src/test/resources/invalid-value.conf2
28 files changed, 313 insertions, 286 deletions
diff --git a/tunnel/src/main/AndroidManifest.xml b/tunnel/src/main/AndroidManifest.xml
index ba9cc77c..7ac3b123 100644
--- a/tunnel/src/main/AndroidManifest.xml
+++ b/tunnel/src/main/AndroidManifest.xml
@@ -1,15 +1,15 @@
<!--
- ~ Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ ~ Copyright © 2017-2025 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:permission="android.permission.BIND_VPN_SERVICE"
+ 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 4c18d98b..224d5849 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -30,6 +30,7 @@ public interface Backend {
*
* @param tunnel The tunnel to examine the state of.
* @return The state of the tunnel.
+ * @throws Exception Exception raised when retrieving tunnel's state.
*/
Tunnel.State getState(Tunnel tunnel) throws Exception;
@@ -39,6 +40,7 @@ public interface Backend {
*
* @param tunnel The tunnel to retrieve statistics for.
* @return The statistics for the tunnel.
+ * @throws Exception Exception raised when retrieving statistics.
*/
Statistics getStatistics(Tunnel tunnel) throws Exception;
@@ -46,7 +48,7 @@ public interface Backend {
* Determine version of underlying backend.
*
* @return The version of the backend.
- * @throws Exception
+ * @throws Exception Exception raised while retrieving version.
*/
String getVersion() throws Exception;
@@ -59,6 +61,7 @@ public interface Backend {
* {@code TOGGLE}.
* @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
* @return The updated state of the tunnel.
+ * @throws Exception Exception raised while changing state.
*/
Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception;
}
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 5552b3ee..94f7b098 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 © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -7,24 +7,47 @@ package com.wireguard.android.backend;
import com.wireguard.util.NonNullForAll;
+/**
+ * A subclass of {@link Exception} that encapsulates the reasons for a failure originating in
+ * implementations of {@link Backend}.
+ */
@NonNullForAll
public final class BackendException extends Exception {
private final Object[] format;
private final Reason reason;
+ /**
+ * Public constructor for BackendException.
+ *
+ * @param reason The {@link Reason} which caused this exception to be thrown
+ * @param format Format string values used when converting exceptions to user-facing strings.
+ */
public BackendException(final Reason reason, final Object... format) {
this.reason = reason;
this.format = format;
}
+ /**
+ * Get the format string values associated with the instance.
+ *
+ * @return Array of {@link Object} for string formatting purposes
+ */
public Object[] getFormat() {
return format;
}
+ /**
+ * Get the reason for this exception.
+ *
+ * @return Associated {@link Reason} for this exception.
+ */
public Reason getReason() {
return reason;
}
+ /**
+ * Enum class containing all known reasons for why a {@link BackendException} might be thrown.
+ */
public enum Reason {
UNKNOWN_KERNEL_MODULE_NAME,
WG_QUICK_CONFIG_ERROR_CODE,
@@ -32,6 +55,7 @@ public final class BackendException extends Exception {
VPN_NOT_AUTHORIZED,
UNABLE_TO_START_VPN,
TUN_CREATION_ERROR,
- GO_ACTIVATION_ERROR_CODE
+ GO_ACTIVATION_ERROR_CODE,
+ DNS_RESOLUTION_FAILURE,
}
}
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 9fafc918..6b66f2c5 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -16,6 +16,7 @@ import com.wireguard.android.backend.BackendException.Reason;
import com.wireguard.android.backend.Tunnel.State;
import com.wireguard.android.util.SharedLibraryLoader;
import com.wireguard.config.Config;
+import com.wireguard.config.InetEndpoint;
import com.wireguard.config.InetNetwork;
import com.wireguard.config.Peer;
import com.wireguard.crypto.Key;
@@ -23,6 +24,7 @@ import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
import java.net.InetAddress;
+import java.time.Instant;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -34,8 +36,13 @@ import java.util.concurrent.TimeoutException;
import androidx.annotation.Nullable;
import androidx.collection.ArraySet;
+/**
+ * Implementation of {@link Backend} that uses the wireguard-go userspace implementation to provide
+ * WireGuard tunnels.
+ */
@NonNullForAll
public final class GoBackend implements Backend {
+ private static final int DNS_RESOLUTION_RETRIES = 10;
private static final String TAG = "WireGuard/GoBackend";
@Nullable private static AlwaysOnCallback alwaysOnCallback;
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
@@ -44,16 +51,27 @@ public final class GoBackend implements Backend {
@Nullable private Tunnel currentTunnel;
private int currentTunnelHandle = -1;
+ /**
+ * Public constructor for GoBackend.
+ *
+ * @param context An Android {@link Context}
+ */
public GoBackend(final Context context) {
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
this.context = context;
}
+ /**
+ * Set a {@link AlwaysOnCallback} to be invoked when {@link VpnService} is started by the
+ * system's Always-On VPN mode.
+ *
+ * @param cb Callback to be invoked
+ */
public static void setAlwaysOnCallback(final AlwaysOnCallback cb) {
alwaysOnCallback = cb;
}
- private static native String wgGetConfig(int handle);
+ @Nullable private static native String wgGetConfig(int handle);
private static native int wgGetSocketV4(int handle);
@@ -65,6 +83,11 @@ public final class GoBackend implements Backend {
private static native String wgVersion();
+ /**
+ * Method to get the names of running tunnels.
+ *
+ * @return A set of string values denoting names of running tunnels.
+ */
@Override
public Set<String> getRunningTunnelNames() {
if (currentTunnel != null) {
@@ -75,27 +98,42 @@ public final class GoBackend implements Backend {
return Collections.emptySet();
}
+ /**
+ * Get the associated {@link State} for a given {@link Tunnel}.
+ *
+ * @param tunnel The tunnel to examine the state of.
+ * @return {@link State} associated with the given tunnel.
+ */
@Override
public State getState(final Tunnel tunnel) {
return currentTunnel == tunnel ? State.UP : State.DOWN;
}
+ /**
+ * Get the associated {@link Statistics} for a given {@link Tunnel}.
+ *
+ * @param tunnel The tunnel to retrieve statistics for.
+ * @return {@link Statistics} associated with the given tunnel.
+ */
@Override
public Statistics getStatistics(final Tunnel tunnel) {
final Statistics stats = new Statistics();
- if (tunnel != currentTunnel) {
+ if (tunnel != currentTunnel || currentTunnelHandle == -1)
return stats;
- }
final String config = wgGetConfig(currentTunnelHandle);
+ if (config == null)
+ return stats;
Key key = null;
long rx = 0;
long tx = 0;
+ long latestHandshakeMSec = 0;
for (final String line : config.split("\\n")) {
if (line.startsWith("public_key=")) {
if (key != null)
- stats.add(key, rx, tx);
+ stats.add(key, rx, tx, latestHandshakeMSec);
rx = 0;
tx = 0;
+ latestHandshakeMSec = 0;
try {
key = Key.fromHex(line.substring(11));
} catch (final KeyFormatException ignored) {
@@ -117,18 +155,49 @@ 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 {
+ latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
+ } catch (final NumberFormatException ignored) {
+ latestHandshakeMSec = 0;
+ }
+ } else if (line.startsWith("last_handshake_time_nsec=")) {
+ if (key == null)
+ continue;
+ try {
+ latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
+ } catch (final NumberFormatException ignored) {
+ latestHandshakeMSec = 0;
+ }
}
}
if (key != null)
- stats.add(key, rx, tx);
+ stats.add(key, rx, tx, latestHandshakeMSec);
return stats;
}
+ /**
+ * Get the version of the underlying wireguard-go library.
+ *
+ * @return {@link String} value of the version of the wireguard-go library.
+ */
@Override
public String getVersion() {
return wgVersion();
}
+ /**
+ * Change the state of a given {@link Tunnel}, optionally applying a given {@link Config}.
+ *
+ * @param tunnel The tunnel to control the state of.
+ * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
+ * {@code TOGGLE}.
+ * @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
+ * @return {@link State} of the tunnel after state changes are applied.
+ * @throws Exception Exception raised while changing tunnel state.
+ */
@Override
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
final State originalState = getState(tunnel);
@@ -167,8 +236,10 @@ public final class GoBackend implements Backend {
throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
final VpnService service;
- if (!vpnService.isDone())
- startVpnService();
+ if (!vpnService.isDone()) {
+ Log.d(TAG, "Requesting to start VpnService");
+ context.startService(new Intent(context, VpnService.class));
+ }
try {
service = vpnService.get(2, TimeUnit.SECONDS);
@@ -184,6 +255,25 @@ public final class GoBackend implements Backend {
return;
}
+
+ 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());
+ }
+ }
+ break;
+ }
+
// Build config
final String goConfig = config.toWgUserspaceString();
@@ -203,6 +293,9 @@ public final class GoBackend implements Backend {
for (final InetAddress addr : config.getInterface().getDnsServers())
builder.addDnsServer(addr.getHostAddress());
+ for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains())
+ builder.addSearchDomain(dnsSearchDomain);
+
boolean sawDefaultRoute = false;
for (final Peer peer : config.getPeers()) {
for (final InetNetwork addr : peer.getAllowedIps()) {
@@ -229,7 +322,7 @@ public final class GoBackend implements Backend {
try (final ParcelFileDescriptor tun = builder.establish()) {
if (tun == null)
throw new BackendException(Reason.TUN_CREATION_ERROR);
- Log.d(TAG, "Go backend v" + wgVersion());
+ Log.d(TAG, "Go backend " + wgVersion());
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
}
if (currentTunnelHandle < 0)
@@ -245,21 +338,23 @@ public final class GoBackend implements Backend {
Log.w(TAG, "Tunnel already down");
return;
}
-
- wgTurnOff(currentTunnelHandle);
+ int handleToClose = currentTunnelHandle;
currentTunnel = null;
currentTunnelHandle = -1;
currentConfig = null;
+ wgTurnOff(handleToClose);
+ try {
+ vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
+ } catch (final TimeoutException ignored) { }
}
tunnel.onStateChange(state);
}
- private void startVpnService() {
- Log.d(TAG, "Requesting to start VpnService");
- context.startService(new Intent(context, VpnService.class));
- }
-
+ /**
+ * Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the
+ * system's Always-On VPN mode.
+ */
public interface AlwaysOnCallback {
void alwaysOnTriggered();
}
@@ -293,6 +388,9 @@ public final class GoBackend implements Backend {
}
}
+ /**
+ * {@link android.net.VpnService} implementation for {@link GoBackend}
+ */
public static class VpnService extends android.net.VpnService {
@Nullable private GoBackend owner;
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 b4e01e76..d5d41c5f 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java
@@ -1,64 +1,101 @@
/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.backend;
import android.os.SystemClock;
-import android.util.Pair;
import com.wireguard.crypto.Key;
import com.wireguard.util.NonNullForAll;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
+import androidx.annotation.Nullable;
+
+/**
+ * Class representing transfer statistics for a {@link Tunnel} instance.
+ */
@NonNullForAll
public class Statistics {
- private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
+ public record PeerStats(long rxBytes, long txBytes, long latestHandshakeEpochMillis) { }
+ private final Map<Key, PeerStats> stats = new HashMap<>();
private long lastTouched = SystemClock.elapsedRealtime();
Statistics() {
}
- void add(final Key key, final long rx, final long tx) {
- peerBytes.put(key, Pair.create(rx, tx));
+ /**
+ * Add a peer and its current stats to the internal map.
+ *
+ * @param key A WireGuard public key bound to a particular peer
+ * @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
+ * the provided {@link Key}. This value is in bytes
+ * @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
+ * the provided {@link Key}. This value is in bytes.
+ * @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
+ * referenced by the provided {@link Key}. The value is in epoch milliseconds.
+ */
+ void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
+ stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
lastTouched = SystemClock.elapsedRealtime();
}
+ /**
+ * Check if the statistics are stale, indicating the need for the {@link Backend} to update them.
+ *
+ * @return boolean indicating if the current statistics instance has stale values.
+ */
public boolean isStale() {
return SystemClock.elapsedRealtime() - lastTouched > 900;
}
- public long peerRx(final Key peer) {
- if (!peerBytes.containsKey(peer))
- return 0;
- return peerBytes.get(peer).first;
- }
-
- public long peerTx(final Key peer) {
- if (!peerBytes.containsKey(peer))
- return 0;
- return peerBytes.get(peer).second;
+ /**
+ * Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
+ *
+ * @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
+ * @return a {@link PeerStats} representing various statistics about this peer.
+ */
+ @Nullable
+ public PeerStats peer(final Key peer) {
+ return stats.get(peer);
}
+ /**
+ * Get the list of peers being tracked by this instance.
+ *
+ * @return An array of {@link Key} instances representing WireGuard
+ * {@link com.wireguard.config.Peer}s
+ */
public Key[] peers() {
- return peerBytes.keySet().toArray(new Key[0]);
+ return stats.keySet().toArray(new Key[0]);
}
+ /**
+ * Get the total received traffic by all the peers being tracked by this instance
+ *
+ * @return a long representing the number of bytes received by the peers being tracked.
+ */
public long totalRx() {
long rx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- rx += val.first;
+ for (final PeerStats val : stats.values()) {
+ rx += val.rxBytes;
}
return rx;
}
+ /**
+ * Get the total transmitted traffic by all the peers being tracked by this instance
+ *
+ * @return a long representing the number of bytes transmitted by the peers being tracked.
+ */
public long totalTx() {
long tx = 0;
- for (final Pair<Long, Long> val : peerBytes.values()) {
- tx += val.second;
+ for (final PeerStats val : stats.values()) {
+ tx += val.txBytes;
}
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 a9761929..dbc91c27 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -36,11 +36,20 @@ public interface Tunnel {
*/
void onStateChange(State newState);
+ /**
+ * Enum class to represent all possible states of a {@link Tunnel}.
+ */
enum State {
DOWN,
TOGGLE,
UP;
+ /**
+ * Get the state of a {@link Tunnel}
+ *
+ * @param running boolean indicating if the tunnel is running.
+ * @return State of the tunnel based on whether or not it is running.
+ */
public static State of(final boolean running) {
return running ? UP : DOWN;
}
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 d4d1f152..87fdf6e5 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -20,6 +20,7 @@ import com.wireguard.util.NonNullForAll;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -32,11 +33,10 @@ import java.util.Objects;
import java.util.Set;
import androidx.annotation.Nullable;
-import java9.util.stream.Collectors;
-import java9.util.stream.Stream;
/**
- * WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
+ * Implementation of {@link Backend} that uses the kernel module and {@code wg-quick} to provide
+ * WireGuard tunnels.
*/
@NonNullForAll
@@ -54,6 +54,10 @@ public final class WgQuickBackend implements Backend {
this.toolsInstaller = toolsInstaller;
}
+ public static boolean hasKernelSupport() {
+ return new File("/sys/module/wireguard").exists();
+ }
+
@Override
public Set<String> getRunningTunnelNames() {
final List<String> output = new ArrayList<>();
@@ -67,7 +71,7 @@ public final class WgQuickBackend implements Backend {
return Collections.emptySet();
}
// wg puts all interface names on the same line. Split them into separate elements.
- return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet());
+ return Set.of(output.get(0).split(" "));
}
@Override
@@ -80,17 +84,17 @@ public final class WgQuickBackend implements Backend {
final Statistics stats = new Statistics();
final Collection<String> output = new ArrayList<>();
try {
- if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
+ if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
return stats;
} catch (final Exception ignored) {
return stats;
}
for (final String line : output) {
final String[] parts = line.split("\\t");
- if (parts.length != 3)
+ if (parts.length != 8)
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[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
} catch (final Exception ignored) {
}
}
diff --git a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java b/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java
deleted file mode 100644
index 8f7749e1..00000000
--- a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright © 2019-2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import android.system.OsConstants;
-import android.util.Base64;
-
-import com.wireguard.android.util.RootShell.RootShellException;
-import com.wireguard.util.NonNullForAll;
-
-import net.i2p.crypto.eddsa.EdDSAEngine;
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
-import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
-import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import androidx.annotation.Nullable;
-
-@NonNullForAll
-@SuppressWarnings("MagicNumber")
-public class ModuleLoader {
- private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
- private static final String MODULE_NAME = "wireguard-%s.ko";
- private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
- private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
- private final File moduleDir;
- private final RootShell rootShell;
- private final File tmpDir;
- private final String userAgent;
-
- public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
- moduleDir = new File(context.getCacheDir(), "kmod");
- tmpDir = new File(context.getCacheDir(), "tmp");
- this.rootShell = rootShell;
- this.userAgent = userAgent;
- }
-
- public static boolean isModuleLoaded() {
- return new File("/sys/module/wireguard").exists();
- }
-
- public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
- final List<String> output = new ArrayList<>();
- rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
- if (output.size() != 1 || output.get(0).length() != 64)
- throw new InvalidParameterException("Invalid sha256 of /proc/version");
- final String moduleName = String.format(MODULE_NAME, output.get(0));
- HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection();
- connection.setRequestProperty("User-Agent", userAgent);
- connection.connect();
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
- throw new IOException("Hash list could not be found");
- final byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
- int len;
- try (final InputStream inputStream = connection.getInputStream()) {
- len = inputStream.read(input);
- }
- if (len <= 0)
- throw new IOException("Hash list was empty");
- final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
- if (modules == null)
- throw new InvalidParameterException("The signature did not verify or invalid hash list format");
- if (!modules.containsKey(moduleName))
- return OsConstants.ENOENT;
- connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection();
- connection.setRequestProperty("User-Agent", userAgent);
- connection.connect();
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
- throw new IOException("Module file could not be found, despite being on hash list");
-
- tmpDir.mkdirs();
- moduleDir.mkdir();
- File tempFile = null;
- try {
- tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
- final MessageDigest digest = MessageDigest.getInstance("SHA-256");
- try (final InputStream inputStream = connection.getInputStream();
- final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
- int total = 0;
- while ((len = inputStream.read(input)) > 0) {
- total += len;
- if (total > 1024 * 1024 * 15 /* 15 MiB */)
- throw new IOException("File too big");
- outputStream.write(input, 0, len);
- digest.update(input, 0, len);
- }
- outputStream.getFD().sync();
- }
- if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
- throw new IOException("Incorrect file hash");
-
- if (!tempFile.renameTo(new File(moduleDir, moduleName)))
- throw new IOException("Unable to rename to final destination");
- } finally {
- if (tempFile != null)
- tempFile.delete();
- }
- return OsConstants.EXIT_SUCCESS;
- }
-
- public void loadModule() throws IOException, RootShellException {
- rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
- }
-
- public boolean moduleMightExist() {
- return moduleDir.exists() && moduleDir.isDirectory();
- }
-
- @Nullable
- private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
- final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT);
-
- if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd')
- return null;
-
- final String[] lines = signifyDigest.split("\n", 3);
- if (lines.length != 3)
- return null;
- if (!lines[0].startsWith("untrusted comment: "))
- return null;
-
- final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT);
- if (signatureBytes == null || signatureBytes.length != 64 + 10)
- return null;
- for (int i = 0; i < 10; ++i) {
- if (signatureBytes[i] != publicKeyBytes[i])
- return null;
- }
-
- try {
- final EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
- final Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm()));
- final byte[] rawPublicKeyBytes = new byte[32];
- System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32);
- signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec)));
- signature.update(lines[2].getBytes(StandardCharsets.UTF_8));
- if (!signature.verify(signatureBytes, 10, 64))
- return null;
- } catch (final Exception ignored) {
- return null;
- }
-
- final Map<String, Sha256Digest> hashes = new HashMap<>();
- for (final String line : lines[2].split("\n")) {
- final String[] components = line.split(" {2}", 2);
- if (components.length != 2)
- return null;
- try {
- hashes.put(components[1], new Sha256Digest(components[0]));
- } catch (final Exception ignored) {
- return null;
- }
- }
- return hashes;
- }
-
- private static final class Sha256Digest {
- private final byte[] bytes;
-
- private Sha256Digest(final String hex) {
- if (hex.length() != 64)
- throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
- bytes = new byte[32];
- for (int i = 0; i < 32; ++i)
- bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
- }
- }
-}
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 6fe89a83..5839ec67 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -43,8 +43,11 @@ public class RootShell {
public RootShell(final Context context) {
localBinaryDir = new File(context.getCodeCacheDir(), "bin");
localTemporaryDir = new File(context.getCacheDir(), "tmp");
- preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
- context.getPackageName(), localBinaryDir, localTemporaryDir);
+ final String packageName = context.getPackageName();
+ if (packageName.contains("'"))
+ throw new RuntimeException("Impossibly invalid package name contains a single quote");
+ preamble = String.format("export CALLING_PACKAGE='%s' PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE uid=%d\" >/dev/null 2>&1; id -u\n",
+ packageName, localBinaryDir, localTemporaryDir, android.os.Process.myUid());
}
private static boolean isExecutableInPath(final String name) {
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 6b2c6142..98e3e63d 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 37f00a37..d3882498 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -143,9 +143,9 @@ public final class ToolsInstaller {
extract();
final StringBuilder script = new StringBuilder("set -ex; ");
- script.append("trap 'rm -rf /data/adb/moduleswireguard' INT TERM EXIT; ");
+ script.append("trap 'rm -rf /data/adb/modules/wireguard' INT TERM EXIT; ");
script.append(String.format("rm -rf /data/adb/modules/wireguard/; mkdir -p /data/adb/modules/wireguard%s; ", INSTALL_DIR));
- script.append("printf 'name=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; ");
+ script.append("printf 'id=wireguard\nname=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; ");
script.append("touch /data/adb/modules/wireguard/auto_mount; ");
for (final String name : EXECUTABLES) {
final File destination = new File("/data/adb/modules/wireguard" + INSTALL_DIR, name);
diff --git a/tunnel/src/main/java/com/wireguard/config/Attribute.java b/tunnel/src/main/java/com/wireguard/config/Attribute.java
index 659f7cf4..c43750d7 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 © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,11 +8,10 @@ package com.wireguard.config;
import com.wireguard.util.NonNullForAll;
import java.util.Iterator;
+import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java9.util.Optional;
-
@NonNullForAll
public final class Attribute {
private static final Pattern LINE_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*([^\\s#][^#]*)");
diff --git a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
index d1e3c627..e70418c9 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 © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/Config.java b/tunnel/src/main/java/com/wireguard/config/Config.java
index a183a75a..21e45f5d 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java
index 573c522d..165a702b 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
public final class InetAddresses {
@Nullable private static final Method PARSER_METHOD;
private static final Pattern WONT_TOUCH_RESOLVER = Pattern.compile("^(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?)|((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$");
+ private static final Pattern VALID_HOSTNAME = Pattern.compile("^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\\.?$");
static {
Method m = null;
@@ -39,6 +40,16 @@ public final class InetAddresses {
}
/**
+ * Determines whether input is a valid DNS hostname.
+ *
+ * @param maybeHostname a string that is possibly a DNS hostname
+ * @return whether or not maybeHostname is a valid DNS hostname
+ */
+ public static boolean isHostname(final CharSequence maybeHostname) {
+ return VALID_HOSTNAME.matcher(maybeHostname).matches();
+ }
+
+ /**
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
*
* @param address a string representing the IP address
diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
index 6eff4a1b..c0ef433e 100644
--- a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
+++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -7,18 +7,17 @@ package com.wireguard.config;
import com.wireguard.util.NonNullForAll;
-import org.threeten.bp.Duration;
-import org.threeten.bp.Instant;
-
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
-import java9.util.Optional;
/**
diff --git a/tunnel/src/main/java/com/wireguard/config/InetNetwork.java b/tunnel/src/main/java/com/wireguard/config/InetNetwork.java
index 7fd12577..84aea82b 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/main/java/com/wireguard/config/Interface.java b/tunnel/src/main/java/com/wireguard/config/Interface.java
index c49357f7..53ca9111 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -20,13 +20,11 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import androidx.annotation.Nullable;
-import java9.util.Lists;
-import java9.util.Optional;
-import java9.util.stream.Collectors;
-import java9.util.stream.StreamSupport;
/**
* Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must
@@ -42,6 +40,7 @@ public final class Interface {
private final Set<InetNetwork> addresses;
private final Set<InetAddress> dnsServers;
+ private final Set<String> dnsSearchDomains;
private final Set<String> excludedApplications;
private final Set<String> includedApplications;
private final KeyPair keyPair;
@@ -52,6 +51,7 @@ public final class Interface {
// Defensively copy to ensure immutability even if the Builder is reused.
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
+ dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains));
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
@@ -110,6 +110,7 @@ public final class Interface {
final Interface other = (Interface) obj;
return addresses.equals(other.addresses)
&& dnsServers.equals(other.dnsServers)
+ && dnsSearchDomains.equals(other.dnsSearchDomains)
&& excludedApplications.equals(other.excludedApplications)
&& includedApplications.equals(other.includedApplications)
&& keyPair.equals(other.keyPair)
@@ -138,6 +139,16 @@ public final class Interface {
}
/**
+ * Returns the set of DNS search domains associated with the interface.
+ *
+ * @return a set of strings
+ */
+ public Set<String> getDnsSearchDomains() {
+ // The collection is already immutable.
+ return dnsSearchDomains;
+ }
+
+ /**
* Returns the set of applications excluded from using the interface.
*
* @return a set of package names
@@ -223,9 +234,8 @@ public final class Interface {
if (!addresses.isEmpty())
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
if (!dnsServers.isEmpty()) {
- final List<String> dnsServerStrings = StreamSupport.stream(dnsServers)
- .map(InetAddress::getHostAddress)
- .collect(Collectors.toUnmodifiableList());
+ final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
+ dnsServerStrings.addAll(dnsSearchDomains);
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
}
if (!excludedApplications.isEmpty())
@@ -258,6 +268,8 @@ public final class Interface {
// Defaults to an empty set.
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
// Defaults to an empty set.
+ private final Set<String> dnsSearchDomains = new LinkedHashSet<>();
+ // Defaults to an empty set.
private final Set<String> excludedApplications = new LinkedHashSet<>();
// Defaults to an empty set.
private final Set<String> includedApplications = new LinkedHashSet<>();
@@ -288,6 +300,16 @@ public final class Interface {
return this;
}
+ public Builder addDnsSearchDomain(final String dnsSearchDomain) {
+ dnsSearchDomains.add(dnsSearchDomain);
+ return this;
+ }
+
+ public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) {
+ this.dnsSearchDomains.addAll(dnsSearchDomains);
+ return this;
+ }
+
public Interface build() throws BadConfigException {
if (keyPair == null)
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
@@ -330,8 +352,15 @@ public final class Interface {
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
try {
- for (final String dnsServer : Attribute.split(dnsServers))
- addDnsServer(InetAddresses.parse(dnsServer));
+ for (final String dnsServer : Attribute.split(dnsServers)) {
+ try {
+ addDnsServer(InetAddresses.parse(dnsServer));
+ } catch (final ParseException e) {
+ if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer))
+ throw e;
+ addDnsSearchDomain(dnsServer);
+ }
+ }
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
@@ -339,11 +368,11 @@ public final class Interface {
}
public Builder parseExcludedApplications(final CharSequence apps) {
- return excludeApplications(Lists.of(Attribute.split(apps)));
+ return excludeApplications(List.of(Attribute.split(apps)));
}
public Builder parseIncludedApplications(final CharSequence apps) {
- return includeApplications(Lists.of(Attribute.split(apps)));
+ return includeApplications(List.of(Attribute.split(apps)));
}
public Builder parseListenPort(final String listenPort) throws BadConfigException {
diff --git a/tunnel/src/main/java/com/wireguard/config/ParseException.java b/tunnel/src/main/java/com/wireguard/config/ParseException.java
index 289f1120..ff430e6b 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 © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 984dbe9c..b308a937 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -17,10 +17,10 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import androidx.annotation.Nullable;
-import java9.util.Optional;
/**
* Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key,
diff --git a/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java b/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java
index 10f51351..c9a592f2 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -13,7 +13,7 @@ import java.util.Arrays;
import androidx.annotation.Nullable;
/**
- * Implementation of the Curve25519 elliptic curve algorithm.
+ * Implementation of Curve25519 ECDH.
* <p>
* This implementation was imported to WireGuard from noise-java:
* https://github.com/rweather/noise-java
@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
*/
@SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"})
@NonNullForAll
-final class Curve25519 {
+public final class Curve25519 {
// Numbers modulo 2^255 - 19 are broken up into ten 26-bit words.
private static final int NUM_LIMBS_255BIT = 10;
private static final int NUM_LIMBS_510BIT = 20;
diff --git a/tunnel/src/main/java/com/wireguard/crypto/Key.java b/tunnel/src/main/java/com/wireguard/crypto/Key.java
index c11688d5..1aff6701 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 8608fc36..b1503f29 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 © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 22c21734..85f94ca5 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 eb5b683f..a6598fd8 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-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java b/tunnel/src/test/java/com/wireguard/config/BadConfigExceptionTest.java
index 3743852d..59badad7 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 © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 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 693a37ea..582c7ac0 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 © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
diff --git a/tunnel/src/test/resources/invalid-value.conf b/tunnel/src/test/resources/invalid-value.conf
index 2889111e..6a1e3b62 100644
--- a/tunnel/src/test/resources/invalid-value.conf
+++ b/tunnel/src/test/resources/invalid-value.conf
@@ -1,6 +1,6 @@
[Interface]
Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
-DNS = 192.0.2.0,yes
+DNS = 192.0.2.0,invalid_value
PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
[Peer]
AllowedIPs = 0.0.0.0/0, ::0/0