summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2021-03-13 21:58:21 +0000
committerMikael Magnusson <mikma@users.sourceforge.net>2023-02-10 21:30:04 +0100
commit6141eafb86c9dc72d39784b86d2136b25ea269f6 (patch)
treeb8ad8a13fdaa784ef74eec08ef9b35148c30be80
parent8cd49f9a8d3e14568d7c0fda696fe62efb14d0e9 (diff)
dhcp
WIP: implement wgOnEvent WIP: debug WIP: netstack: implement dhcp and ipvlan WIP: implement dhcp in tunnel WIP: add dhcp information to detail
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/Backend.java2
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/DhcpInfo.java57
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java126
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java6
-rw-r--r--tunnel/tools/libwg-go/api-android.go225
-rw-r--r--tunnel/tools/libwg-go/go.mod2
-rw-r--r--tunnel/tools/libwg-go/jni.c101
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt4
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt30
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelManager.kt5
-rw-r--r--ui/src/main/res/layout/tunnel_detail_fragment.xml32
-rw-r--r--ui/src/main/res/values/strings.xml2
12 files changed, 562 insertions, 30 deletions
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..1d9be593 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/Backend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/Backend.java
@@ -18,6 +18,8 @@ import androidx.annotation.Nullable;
@NonNullForAll
public interface Backend {
+ DhcpInfo getDhcpInfo(Tunnel tunnel) throws Exception;
+
/**
* Enumerate names of currently-running tunnels.
*
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/DhcpInfo.java b/tunnel/src/main/java/com/wireguard/android/backend/DhcpInfo.java
new file mode 100644
index 00000000..35a7dd43
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/DhcpInfo.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2020 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;
+
+/**
+ * Class representing DHCP info for a {@link Tunnel} instance.
+ */
+@NonNullForAll
+public class DhcpInfo {
+ private String info;
+ private long lastTouched = SystemClock.elapsedRealtime();
+
+ DhcpInfo() {
+ }
+
+ /**
+ * Add a peer and its current data usage to the internal map.
+ *
+ * @param key A WireGuard public key bound to a particular peer
+ * @param rx The received traffic for the {@link com.wireguard.config.Peer} referenced by
+ * the provided {@link Key}. This value is in bytes
+ * @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
+ * the provided {@link Key}. This value is in bytes.
+ */
+ void set(final String info) {
+ this.info = info;
+ 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 String get() {
+ return info;
+ }
+
+}
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 e8148c04..4e0a9f94 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -46,6 +46,7 @@ import com.wireguard.config.Config;
import com.wireguard.config.HttpProxy;
import com.wireguard.config.InetEndpoint;
import com.wireguard.config.InetNetwork;
+import com.wireguard.config.ParseException;
import com.wireguard.config.Peer;
import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
@@ -71,6 +72,7 @@ import java.net.UnknownHostException;
import java.net.URL;
import java.nio.ByteOrder;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -91,7 +93,7 @@ import androidx.collection.ArraySet;
* WireGuard tunnels.
*/
@NonNullForAll
-public final class GoBackend implements Backend {
+public final class GoBackend implements Backend, EventHandler {
private static final int DNS_RESOLUTION_RETRIES = 10;
private static final String TAG = "WireGuard/GoBackend";
@Nullable private static AlwaysOnCallback alwaysOnCallback;
@@ -140,14 +142,29 @@ 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 int wgTurnOn(String ifName, int tunFd, String settings, EventHandler handler);
+
+ private static native int wgTurnOnDhcp(String ifName, String settings, EventHandler handler);
private static native String wgVersion();
private static native int wgStartGrpc(String sockName);
+ @Override
+ public DhcpInfo getDhcpInfo(final Tunnel tunnel) {
+ final DhcpInfo info = new DhcpInfo();
+ if (tunnel != currentTunnel) {
+ return info;
+ }
+ // TODO update info
+ info.set("FIXME");
+ return info;
+ }
+
/**
* Method to get the names of running tunnels.
*
@@ -426,14 +443,9 @@ public final class GoBackend implements Backend {
Log.i(TAG, "Exit streamReverse");
}
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
+ private void setStateInternalFinalize(final Tunnel tunnel, @Nullable final Config config, Set<InetNetwork> addresses)
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 (true) { // Add scope to avoid changing indentation level
if (VpnService.prepare(context) != null)
throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
@@ -452,18 +464,13 @@ public final class GoBackend implements Backend {
}
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()) {
@@ -495,9 +502,13 @@ public final class GoBackend implements Backend {
for (final String includedApplication : config.getInterface().getIncludedApplications())
builder.addAllowedApplication(includedApplication);
- for (final InetNetwork addr : config.getInterface().getAddresses())
- builder.addAddress(addr.getAddress(), addr.getMask());
-
+ if (addresses.size() > 0) {
+ for (final InetNetwork addr : addresses)
+ builder.addAddress(addr.getAddress(), addr.getMask());
+ } else {
+ for (final InetNetwork addr : config.getInterface().getAddresses())
+ builder.addAddress(addr.getAddress(), addr.getMask());
+ }
for (final InetAddress addr : config.getInterface().getDnsServers())
builder.addDnsServer(addr.getHostAddress());
@@ -547,7 +558,11 @@ public final class GoBackend implements Backend {
if (tun == null)
throw new BackendException(Reason.TUN_CREATION_ERROR);
Log.d(TAG, "Go backend " + wgVersion());
- currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
+ if (currentTunnelHandle < 0) {
+ currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig, this);
+ } else {
+ wgSetFd(currentTunnelHandle, tun.detachFd());
+ }
}
if (currentTunnelHandle < 0)
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
@@ -560,6 +575,35 @@ public final class GoBackend implements Backend {
NetworkRequest req = new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).build();
connectivityManager.requestNetwork(req, myNetworkCallback);
+ }
+ }
+
+ 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 (currentTunnelHandle != -1) {
+ Log.w(TAG, "Tunnel already up");
+ return;
+ }
+
+ if (false) {
+ setStateInternalFinalize(tunnel, config, null);
+ } else {
+ // Build config
+ final String goConfig = config.toWgUserspaceString();
+
+ currentTunnelHandle = wgTurnOnDhcp(tunnel.getName(), goConfig, this);
+ if (currentTunnelHandle < 0)
+ throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
+
+ currentTunnel = tunnel;
+ currentConfig = config;
+ }
} else {
if (currentTunnelHandle == -1) {
Log.w(TAG, "Tunnel already down");
@@ -578,6 +622,46 @@ public final class GoBackend implements Backend {
tunnel.onStateChange(state);
}
+ public void onEvent(String event) {
+ boolean isEvent = false;
+ Set<InetNetwork> addresses = new LinkedHashSet<>();
+
+ Log.d(TAG, "onEvent: " + event);
+ for (final String line : event.split("\\n")) {
+ final String[] params = line.split("=", 2);
+ if (params.length != 2)
+ break;
+
+ final String key = params[0];
+ final String value = params[1];
+
+ if ("event".equals(key)) {
+ isEvent = true;
+ } else if (!isEvent) {
+ break;
+ }
+
+ if ("address".equals(key)) {
+ for (final String strAddr : value.split(",")) {
+ try {
+ InetNetwork addr = InetNetwork.parse(strAddr);
+ if (addr.getMask() < 33 || addr.getMask() == 128)
+ addresses.add(addr);
+ } catch(final ParseException ignored) {
+ }
+ }
+ }
+ }
+
+ if (addresses.size() > 0) {
+ try {
+ setStateInternalFinalize(currentTunnel, currentConfig, addresses);
+ } catch (final Exception e) {
+ Log.d(TAG, "setStateInternalFinalize", e);
+ }
+ }
+ }
+
/**
* Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the
* system's Always-On VPN mode.
@@ -691,3 +775,7 @@ public final class GoBackend implements Backend {
}
}
}
+
+interface EventHandler {
+ public void onEvent(String event);
+}
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 d0dc6a46..06a4db86 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
@@ -58,6 +58,12 @@ public final class WgQuickBackend implements Backend {
}
@Override
+ public DhcpInfo getDhcpInfo(final Tunnel tunnel) {
+ final DhcpInfo info = new DhcpInfo();
+ return info;
+ }
+
+ @Override
public Set<String> getRunningTunnelNames() {
final List<String> output = new ArrayList<>();
// Don't throw an exception here or nothing will show up in the UI.
diff --git a/tunnel/tools/libwg-go/api-android.go b/tunnel/tools/libwg-go/api-android.go
index 0ab80be9..619dfccd 100644
--- a/tunnel/tools/libwg-go/api-android.go
+++ b/tunnel/tools/libwg-go/api-android.go
@@ -7,10 +7,13 @@ package main
// #cgo LDFLAGS: -llog
// #include <android/log.h>
+// extern void wgOnEvent(void *eventHandler, const char *event);
import "C"
import (
+ "context"
"fmt"
+ "log"
"math"
"net"
"os"
@@ -25,6 +28,16 @@ import (
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/ipc"
"golang.zx2c4.com/wireguard/tun"
+ "golang.zx2c4.com/wireguard/tun/netstack"
+ "golang.zx2c4.com/wireguard/tun/netstack/ipvlan"
+
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
+ "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
+ "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
+ "gvisor.dev/gvisor/pkg/tcpip/stack"
+ "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
+ "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
type AndroidLogger struct {
@@ -49,8 +62,14 @@ type TunnelHandle struct {
device *device.Device
uapi net.Listener
logger *device.Logger
+ eventHandler unsafe.Pointer
+ cancel context.CancelFunc
+ master *ipvlan.IPVLANMaster
+ // tunEP *stack.LinkEndpoint
+ tunFd int
}
+var globalStack *stack.Stack
var tunnelHandles map[int32]TunnelHandle
func GetTunnel(handle int32) (tunnelHandle TunnelHandle, ok bool) {
@@ -60,6 +79,12 @@ func GetTunnel(handle int32) (tunnelHandle TunnelHandle, ok bool) {
func init() {
tunnelHandles = make(map[int32]TunnelHandle)
+ opts := stack.Options{
+ NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
+ TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
+ HandleLocal: true,
+ }
+ globalStack = stack.New(opts)
signals := make(chan os.Signal)
signal.Notify(signals, unix.SIGUSR2)
go func() {
@@ -79,7 +104,7 @@ func init() {
}
//export wgTurnOn
-func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
+func wgTurnOn(interfaceName string, tunFd int32, settings string, eventHandler unsafe.Pointer) int32 {
tag := cstring("WireGuard/GoBackend/" + interfaceName)
logger := &device.Logger{
Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
@@ -148,21 +173,211 @@ 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, eventHandler: eventHandler}
+ logger.Verbosef("Before wgOnEvent %v", eventHandler)
+ C.wgOnEvent(tunnelHandles[i].eventHandler, C.CString("FOOBAR"))
+ C.wgOnEvent(tunnelHandles[i].eventHandler, C.CString("Called wgTurnOn"))
+ logger.Verbosef("After wgOnEvent")
return i
}
-//export wgTurnOff
-func wgTurnOff(tunnelHandle int32) {
+func createNetTUNWithStack(stack *stack.Stack, nicID tcpip.NICID, localAddresses []net.IP, dnsServers []net.IP, mtu int) (tun.Device, *netstack.Net, *ipvlan.IPVLANMaster, error) {
+ dev, ep, err := netstack.NewNetTUN(stack, mtu)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ master := ipvlan.NewIPVLANMaster(ep)
+
+ addrs := make([]tcpip.Address, 0, len(localAddresses))
+ for _, addr := range localAddresses {
+ addrs = append(addrs, tcpip.Address(addr))
+ }
+ ipvlanEP := master.NewIPVLAN(addrs)
+
+ tcpipErr := stack.CreateNIC(nicID, ipvlanEP)
+ if tcpipErr != nil {
+ return nil, nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
+ }
+
+ tnet, err := netstack.NewNetAdapter(stack, nicID, localAddresses, dnsServers)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ return dev, tnet, master, nil
+}
+
+//export wgTurnOnDhcp
+func wgTurnOnDhcp(interfaceName string, settings string, eventHandler unsafe.Pointer) int32 {
+ tag := cstring("WireGuard/GoBackend/" + interfaceName)
+ logger := &device.Logger{
+ Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
+ Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
+ }
+
+ stack := globalStack
+
+ // FIXME IP address
+ ipStr := "fe80::101"
+ // FIXME name
+ name := "wg0"
+ var tundev tun.Device
+ var tnet *netstack.Net
+ tundev, tnet, master, err := createNetTUNWithStack(
+ stack,
+ 1,
+ []net.IP{net.ParseIP(ipStr)},
+ // []net.IP{net.ParseIP("fe80::1")},
+ []net.IP{net.ParseIP("fe80::1")},
+ 1420)
+ if err != nil {
+ log.Panic(err)
+ }
+
+ logger.Verbosef("Attaching to netstack interface")
+ device := device.NewDevice(tundev, conn.NewStdNetBind(), logger)
+
+ err = device.IpcSet(settings)
+ if err != nil {
+ logger.Errorf("IpcSet: %v", err)
+ return -1
+ }
+ device.DisableSomeRoamingForBrokenMobileSemantics()
+
+ var uapi net.Listener
+
+ uapiFile, err := ipc.UAPIOpen(name)
+ if err != nil {
+ logger.Errorf("UAPIOpen: %v", err)
+ } else {
+ uapi, err = ipc.UAPIListen(name, uapiFile)
+ if err != nil {
+ uapiFile.Close()
+ logger.Errorf("UAPIListen: %v", err)
+ } else {
+ go func() {
+ for {
+ conn, err := uapi.Accept()
+ if err != nil {
+ return
+ }
+ go device.IpcHandle(conn)
+ }
+ }()
+ }
+ }
+
+ err = device.Up()
+ if err != nil {
+ logger.Errorf("Unable to bring up device: %v", err)
+ uapiFile.Close()
+ device.Close()
+ return -1
+ }
+ logger.Verbosef("Device started")
+
+ var i int32
+ for i = 0; i < math.MaxInt32; i++ {
+ if _, exists := tunnelHandles[i]; !exists {
+ break
+ }
+ }
+ if i == math.MaxInt32 {
+ logger.Errorf("Unable to find empty handle")
+ uapiFile.Close()
+ device.Close()
+ return -1
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+
+ tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi, eventHandler: eventHandler, cancel: cancel, master: master}
+
+ go func(ctx context.Context) {
+ logger.Verbosef("Start dhcp client")
+ src, err := net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s%%1]:546", ipStr))
+ if err != nil {
+ logger.Errorf("ResolveUDPAddr: %v", err)
+ return
+ }
+
+ var dst *net.UDPAddr
+ dst = nil
+
+ conn, err := tnet.DialUDP(src, dst)
+ if err != nil {
+ logger.Errorf("DialUDP: %v", err)
+ return
+ }
+
+ logger.Verbosef("Conn: %v %v %v", src, dst, conn)
+ hwAddr := []byte(" 101")
+ addrs, err := netstack.RunDhcp(ctx, conn, hwAddr)
+
+ if err != nil {
+ logger.Errorf("DHCP: %v", err)
+ return
+ }
+
+ msg := ""
+
+ logger.Verbosef("DHCP finished")
+ for _, addr := range addrs {
+ logger.Verbosef("Address: %v", addr)
+ if msg == "" {
+ msg = "event=1\naddress=" + addr.String()
+ } else {
+ msg += "," + addr.String()
+ }
+ }
+
+ C.wgOnEvent(tunnelHandles[i].eventHandler, C.CString(msg))
+ }(ctx)
+
+ logger.Verbosef("Before wgOnEvent %v", eventHandler)
+ C.wgOnEvent(tunnelHandles[i].eventHandler, C.CString("FOOBAR"))
+ C.wgOnEvent(tunnelHandles[i].eventHandler, C.CString("Called wgTurnOn"))
+ logger.Verbosef("After wgOnEvent")
+ return i
+}
+
+//export wgSetFd
+func wgSetFd(tunnelHandle int32, tunFd int32) {
handle, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
+
+ // FIXME MTU
+ var mtu uint32 = 1280
+ tunEP, err := fdbased.New(&fdbased.Options{FDs: []int{int(tunFd)}, MTU: mtu})
+ if err != nil {
+ log.Fatal(err)
+ }
+ handle.tunFd = int(tunFd)
+ handle.master.SetIntEP(tunEP)
+}
+
+//export wgTurnOff
+func wgTurnOff(tunnelHandle int32) unsafe.Pointer {
+ handle, ok := tunnelHandles[tunnelHandle]
+ if !ok {
+ return nil
+ }
+ if handle.master != nil {
+ globalStack.RemoveNIC(1)
+ handle.master.SetIntEP(nil)
+ unix.Close(handle.tunFd)
+ }
+
+ eventHandler := handle.eventHandler
+ C.wgOnEvent(handle.eventHandler, C.CString("Called wgTurnOff"))
delete(tunnelHandles, tunnelHandle)
if handle.uapi != nil {
handle.uapi.Close()
}
handle.device.Close()
+ return eventHandler
}
//export wgGetSocketV4
@@ -171,6 +386,7 @@ func wgGetSocketV4(tunnelHandle int32) int32 {
if !ok {
return -1
}
+ C.wgOnEvent(handle.eventHandler, C.CString("Called wgGetSocketV4"))
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
if bind == nil {
return -1
@@ -188,6 +404,7 @@ func wgGetSocketV6(tunnelHandle int32) int32 {
if !ok {
return -1
}
+ C.wgOnEvent(handle.eventHandler, C.CString("Called wgGetSocketV6"))
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
if bind == nil {
return -1
diff --git a/tunnel/tools/libwg-go/go.mod b/tunnel/tools/libwg-go/go.mod
index 49fe3780..d9ee9e25 100644
--- a/tunnel/tools/libwg-go/go.mod
+++ b/tunnel/tools/libwg-go/go.mod
@@ -19,3 +19,5 @@ require (
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
)
+
+replace golang.zx2c4.com/wireguard/tun/netstack => golang.m7n.se/wireguard/tun/netstack v0.0.0-20210315192121-e7c9dcf39d3f
diff --git a/tunnel/tools/libwg-go/jni.c b/tunnel/tools/libwg-go/jni.c
index 121729a7..5dd20ed2 100644
--- a/tunnel/tools/libwg-go/jni.c
+++ b/tunnel/tools/libwg-go/jni.c
@@ -3,40 +3,105 @@
* Copyright © 2017-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
+#include <assert.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
+#include <android/log.h>
+
+#define TAG "WireGuard/GoBackend"
struct go_string { const char *str; long n; };
-extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings);
-extern void wgTurnOff(int handle);
+extern int wgTurnOn(struct go_string ifname, int tun_fd, struct go_string settings, void *eventHandler);
+extern int wgTurnOnDhcp(struct go_string ifname, struct go_string settings, void *eventHandler);
+extern void wgSetFd(int handle, int tun_fd);
+extern void *wgTurnOff(int handle);
extern int wgGetSocketV4(int handle);
extern int wgGetSocketV6(int handle);
extern char *wgGetConfig(int handle);
extern char *wgVersion();
extern int wgStartGrpc();
+extern void wgOnEvent(void *eventHandler, const char *event);
+
+static JavaVM *gVm;
+static jclass gEventHandlerClass;
+static jmethodID gOnEventMethod;
-JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings)
+JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+ JNIEnv* env;
+ char buf[128];
+
+ __android_log_write(ANDROID_LOG_ERROR, TAG, "JNI_OnLoad");
+
+ gVm = vm;
+ if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_write(ANDROID_LOG_ERROR, TAG, "GetEnv error");
+ return JNI_ERR;
+ }
+
+ sprintf(buf, "GetEnv FindClass %p", (*env)->FindClass);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+ gEventHandlerClass = (*env)->FindClass(env, "com/wireguard/android/backend/EventHandler");
+ sprintf(buf, "gEventHandlerClass %p", gEventHandlerClass);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+
+ gOnEventMethod = (*env)->GetMethodID(env, gEventHandlerClass, "onEvent", "(Ljava/lang/String;)V");
+ sprintf(buf, "gOnEventMethod %p", gOnEventMethod);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+ //assert(gOnEventMethod);
+
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOn(JNIEnv *env, jclass c, jstring ifname, jint tun_fd, jstring settings, jobject eventHandler)
{
const char *ifname_str = (*env)->GetStringUTFChars(env, ifname, 0);
size_t ifname_len = (*env)->GetStringUTFLength(env, ifname);
const char *settings_str = (*env)->GetStringUTFChars(env, settings, 0);
size_t settings_len = (*env)->GetStringUTFLength(env, settings);
+ jobject event_handler = (*env)->NewGlobalRef(env, eventHandler);
int ret = wgTurnOn((struct go_string){
.str = ifname_str,
.n = ifname_len
}, tun_fd, (struct go_string){
.str = settings_str,
.n = settings_len
- });
+ }, event_handler);
(*env)->ReleaseStringUTFChars(env, ifname, ifname_str);
(*env)->ReleaseStringUTFChars(env, settings, settings_str);
return ret;
}
+JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOnDhcp(JNIEnv *env, jclass c, jstring ifname, jstring settings, jobject eventHandler)
+{
+ const char *ifname_str = (*env)->GetStringUTFChars(env, ifname, 0);
+ size_t ifname_len = (*env)->GetStringUTFLength(env, ifname);
+ const char *settings_str = (*env)->GetStringUTFChars(env, settings, 0);
+ size_t settings_len = (*env)->GetStringUTFLength(env, settings);
+ jobject event_handler = (*env)->NewGlobalRef(env, eventHandler);
+ int ret = wgTurnOnDhcp((struct go_string){
+ .str = ifname_str,
+ .n = ifname_len
+ }, (struct go_string){
+ .str = settings_str,
+ .n = settings_len
+ }, event_handler);
+ (*env)->ReleaseStringUTFChars(env, ifname, ifname_str);
+ (*env)->ReleaseStringUTFChars(env, settings, settings_str);
+ return ret;
+}
+
+JNIEXPORT void JNICALL Java_com_wireguard_android_backend_GoBackend_wgSetFd(JNIEnv *env, jclass c, jint handle, jint tun_fd)
+{
+ wgSetFd(handle, tun_fd);
+}
+
JNIEXPORT void JNICALL Java_com_wireguard_android_backend_GoBackend_wgTurnOff(JNIEnv *env, jclass c, jint handle)
{
- wgTurnOff(handle);
+ jobject event_handler = wgTurnOff(handle);
+ if (event_handler)
+ (*env)->DeleteGlobalRef(env, event_handler);
}
JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgGetSocketV4(JNIEnv *env, jclass c, jint handle)
@@ -82,3 +147,29 @@ JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgStartGrpc(
(*env)->ReleaseStringUTFChars(env, sockname, sockname_str);
return res;
}
+
+void wgOnEvent(void *eventHandler, const char *event)
+{
+ JavaVMAttachArgs attachArgs = { JNI_VERSION_1_6, NULL, NULL };
+ jobject jEventHandler = eventHandler;
+ JNIEnv *env;
+ char buf[128];
+
+ __android_log_write(ANDROID_LOG_ERROR, TAG, "wgOnEvent enter 2");
+ jint rs = (*gVm)->AttachCurrentThread(gVm, &env, &attachArgs);
+
+ sprintf(buf, "AttachCurrentThread %i %p", rs, env);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+ assert (rs == JNI_OK);
+ jstring jevent = (*env)->NewStringUTF(env, event);
+ sprintf(buf, "NewStringUTF %p", jevent);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+ //free(event);
+ sprintf(buf, "CallVoidMethod %p %p %p %p", env, jEventHandler, gOnEventMethod, jevent);
+ __android_log_write(ANDROID_LOG_ERROR, TAG, buf);
+ (*env)->CallVoidMethod(env, jEventHandler, gOnEventMethod, jevent);
+ /* sprintf(buf, "CallVoidMethod"); */
+ /* __android_log_write(ANDROID_LOG_ERROR, TAG, buf); */
+ // TODO free jevent?
+ __android_log_write(ANDROID_LOG_ERROR, TAG, "wgOnEvent end");
+}
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 fad1b5d4..f2cf9040 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
@@ -104,6 +104,10 @@ class TunnelDetailFragment : BaseFragment() {
lastState = state
var now = LocalDateTime.now(ZoneId.of("UTC"))
try {
+ binding.dhcpInfoText.text = tunnel.getDhcpInfo().get()
+ binding.dhcpInfoLabel.visibility = View.VISIBLE
+ binding.dhcpInfoText.visibility = View.VISIBLE
+
val statistics = tunnel.getStatisticsAsync()
for (i in 0 until binding.peersLayout.childCount) {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
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..edf8feaf 100644
--- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
@@ -8,6 +8,7 @@ import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.wireguard.android.BR
+import com.wireguard.android.backend.DhcpInfo
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.Keyed
@@ -136,6 +137,35 @@ class ObservableTunnel internal constructor(
return statistics
}
+ @get:Bindable
+ var dhcpInfo: DhcpInfo? = null
+ get() {
+ if (field == null || field?.isStale != false)
+ applicationScope.launch {
+ try {
+ manager.getTunnelDhcpInfo(this@ObservableTunnel)
+ } catch (e: Throwable) {
+ Log.e(TAG, Log.getStackTraceString(e))
+ }
+ }
+ return field
+ }
+ private set
+
+ suspend fun getDhcpInfo(): DhcpInfo = withContext(Dispatchers.Main.immediate) {
+ dhcpInfo.let {
+ if (it == null || it.isStale)
+ manager.getTunnelDhcpInfo(this@ObservableTunnel)
+ else
+ it
+ }
+ }
+
+ fun onDhcpInfoChanged(dhcpInfo: DhcpInfo?): DhcpInfo? {
+ this.dhcpInfo = dhcpInfo
+ notifyPropertyChanged(BR.dhcpInfo)
+ return dhcpInfo
+ }
suspend fun deleteAsync() = manager.delete(this)
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..ac39ac4a 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
@@ -17,6 +17,7 @@ import com.wireguard.android.Application.Companion.getBackend
import com.wireguard.android.Application.Companion.getTunnelManager
import com.wireguard.android.BR
import com.wireguard.android.R
+import com.wireguard.android.backend.DhcpInfo
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.configStore.ConfigStore
@@ -240,6 +241,10 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
}
}
+ suspend fun getTunnelDhcpInfo(tunnel: ObservableTunnel): DhcpInfo = withContext(Dispatchers.Main.immediate) {
+ tunnel.onDhcpInfoChanged(withContext(Dispatchers.IO) { getBackend().getDhcpInfo(tunnel) })!!
+ }
+
suspend fun getTunnelState(tunnel: ObservableTunnel): Tunnel.State = withContext(Dispatchers.Main.immediate) {
tunnel.onStateChanged(withContext(Dispatchers.IO) { getBackend().getState(tunnel) })
}
diff --git a/ui/src/main/res/layout/tunnel_detail_fragment.xml b/ui/src/main/res/layout/tunnel_detail_fragment.xml
index aef71e18..c004fc5a 100644
--- a/ui/src/main/res/layout/tunnel_detail_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_detail_fragment.xml
@@ -322,14 +322,42 @@
android:layout_height="wrap_content"
android:contentDescription="@string/applications"
android:nextFocusUp="@id/mtu_text"
- android:nextFocusDown="@id/peers_layout"
- android:nextFocusForward="@id/peers_layout"
+ android:nextFocusDown="@id/dhcp_info_text"
+ android:nextFocusForward="@id/dhcp_info_text"
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_constraintTop_toBottomOf="@+id/applications_label"
app:layout_constraintStart_toStartOf="parent"
tools:text="8 excluded" />
+
+ <TextView
+ android:id="@+id/dhcp_info_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/dhcp_info_text"
+ android:text="@string/dhcp_info"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/applications_text" />
+
+ <TextView
+ android:id="@+id/dhcp_info_text"
+ style="@style/DetailText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/dhcp_info"
+ android:nextFocusUp="@id/applications_text"
+ android:nextFocusDown="@id/peers_layout"
+ android:nextFocusForward="@id/peers_layout"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/dhcp_info_label"
+ tools:text="Addresses: 192.0.2.123/24, 2001:db8::123/128"
+ tools:visibility="visible" />
+
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
index 3b32ab79..4da4e8fe 100644
--- a/ui/src/main/res/values/strings.xml
+++ b/ui/src/main/res/values/strings.xml
@@ -248,4 +248,6 @@
<string name="biometric_prompt_private_key_title">Authenticate to view private key</string>
<string name="biometric_auth_error">Authentication failure</string>
<string name="biometric_auth_error_reason">Authentication failure: %s</string>
+ <string name="dhcp_info">DHCP information</string>
+ <string name="dhcp_info_text">Addresses: %s\nIPv6 prefixes: %s\nDNS servers: %s</string>
</resources>