summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2023-02-11 22:24:31 +0100
committerMikael Magnusson <mikma@users.sourceforge.net>2023-05-09 01:38:14 +0200
commitaebb7fc1dbd9edac8f5b8520b842eccdee016c85 (patch)
tree9e8a813c2efb9005c5d614f2a107384c75cb1e9e
parent0c523bbeb6ab3adee5dc997bd47e27dd4fbfd61e (diff)
tunnel: request DHCPv6 leases
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java230
-rw-r--r--tunnel/src/main/java/com/wireguard/util/Resolver.java2
-rw-r--r--tunnel/src/main/proto/libwg.proto19
-rw-r--r--tunnel/tools/libwg-go/Makefile2
-rw-r--r--tunnel/tools/libwg-go/api-android.go22
-rw-r--r--tunnel/tools/libwg-go/dhcp.go192
-rw-r--r--tunnel/tools/libwg-go/jni.c6
-rw-r--r--tunnel/tools/libwg-go/service.go63
8 files changed, 477 insertions, 59 deletions
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 ac1d03c4..c4c8f70a 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -28,9 +28,12 @@ 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;
@@ -71,6 +74,7 @@ import java.net.URL;
import java.time.Instant;
import java.nio.ByteOrder;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -103,7 +107,9 @@ public final class GoBackend implements Backend {
private ManagedChannel channel;
private ConnectivityManager connectivityManager;
private ConnectivityManager.NetworkCallback myNetworkCallback = new MyNetworkCallback();
+ private ConnectivityManager.NetworkCallback vpnNetworkCallback;
@Nullable private Network activeNetwork;
+ private boolean obtainDhcpLease = false;
/**
* Public constructor for GoBackend.
@@ -136,6 +142,8 @@ 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);
@@ -330,6 +338,50 @@ public final class GoBackend implements Backend {
}
}
+ 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();
+ }
+ }
+ }
+
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ DhcpRequest.Builder requestBuilder = DhcpRequest.newBuilder();
+ if (source != null) {
+ requestBuilder.setSource(source);
+ }
+ DhcpRequest request = requestBuilder.build();
+ DhcpResponse resp = stub.dhcp(request);
+ Log.i(TAG, "Dhcp: " + resp.getError().getMessage());
+
+ // Replace the vpn tunnel
+ final VpnService.Builder builder = getBuilder(currentTunnel.getName(), currentConfig, service, resp.getLeasesList());
+
+ 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");
+ }
+
private int getConnectionOwnerUid(int protocol, InetSocketAddress local, InetSocketAddress remote) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return connectivityManager.getConnectionOwnerUid(protocol, local, remote);
@@ -412,6 +464,94 @@ public final class GoBackend implements Backend {
Log.i(TAG, "Exit streamReverse");
}
+ private VpnService.Builder getBuilder(final String name, @Nullable final Config config, final VpnService service, @Nullable final List<Lease> leases) throws PackageManager.NameNotFoundException {
+ Log.i(TAG, "Builder 1");
+ final VpnService.Builder builder = service.getBuilder();
+ 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 Lease lease: leases) {
+ try {
+ InetAddress addr = InetAddress.getByAddress(lease.getAddress().getAddress().toByteArray());
+ Log.i(TAG, "Lease: " + addr);
+ builder.addAddress(addr, 128);
+ } catch (UnknownHostException ex) {
+ // Ignore
+ }
+ }
+ }
+
+ 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()) {
+ if (addr.getMask() == 0)
+ sawDefaultRoute = true;
+ builder.addRoute(addr.getAddress(), addr.getMask());
+ }
+ }
+
+ 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();
+ if (proxy.isPresent()) {
+ ProxyInfo pi = proxy.get().getProxyInfo();
+ Uri pacFileUrl = pi.getPacFileUrl();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (pacFileUrl != null && pacFileUrl != Uri.EMPTY) {
+ int listenPort = startHttpProxy(pacFileUrl);
+ ProxyInfo localPi = ProxyInfo.buildDirectProxy("localhost", listenPort);
+ builder.setHttpProxy(localPi);
+ } else {
+ builder.setHttpProxy(pi);
+ }
+ }
+ }
+
+ 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);
@@ -472,63 +612,8 @@ public final class GoBackend implements Backend {
final String goConfig = config.toWgUserspaceString(resolver);
// Create the vpn tunnel with android API
- final VpnService.Builder builder = service.getBuilder();
- builder.setSession(tunnel.getName());
-
- for (final String excludedApplication : config.getInterface().getExcludedApplications())
- builder.addDisallowedApplication(excludedApplication);
-
- for (final String includedApplication : config.getInterface().getIncludedApplications())
- builder.addAllowedApplication(includedApplication);
-
- for (final InetNetwork addr : config.getInterface().getAddresses())
- builder.addAddress(addr.getAddress(), addr.getMask());
-
- 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()) {
- if (addr.getMask() == 0)
- sawDefaultRoute = true;
- builder.addRoute(addr.getAddress(), addr.getMask());
- }
- }
-
- // "Kill-switch" semantics
- if (!(sawDefaultRoute && config.getPeers().size() == 1)) {
- builder.allowFamily(OsConstants.AF_INET);
- builder.allowFamily(OsConstants.AF_INET6);
- }
-
- builder.setMtu(config.getInterface().getMtu().orElse(1280));
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- builder.setMetered(false);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
- service.setUnderlyingNetworks(null);
-
- Optional<HttpProxy> proxy = config.getInterface().getHttpProxy();
- if (proxy.isPresent()) {
- ProxyInfo pi = proxy.get().getProxyInfo();
- Uri pacFileUrl = pi.getPacFileUrl();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (pacFileUrl != null && pacFileUrl != Uri.EMPTY) {
- int listenPort = startHttpProxy(pacFileUrl);
- ProxyInfo localPi = ProxyInfo.buildDirectProxy("localhost", listenPort);
- builder.setHttpProxy(localPi);
- } else {
- builder.setHttpProxy(pi);
- }
- }
- }
+ final VpnService.Builder builder = getBuilder(tunnel.getName(), config, service, null);
- builder.setBlocking(true);
try (final ParcelFileDescriptor tun = builder.establish()) {
if (tun == null)
throw new BackendException(Reason.TUN_CREATION_ERROR);
@@ -544,8 +629,14 @@ 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");
@@ -556,6 +647,9 @@ public final class GoBackend implements Backend {
currentTunnelHandle = -1;
currentConfig = null;
stopHttpProxy();
+ if (vpnNetworkCallback != null)
+ connectivityManager.unregisterNetworkCallback(vpnNetworkCallback);
+ vpnNetworkCallback = null;
connectivityManager.unregisterNetworkCallback(myNetworkCallback);
activeNetwork = null;
wgTurnOff(handleToClose);
@@ -627,6 +721,9 @@ public final class GoBackend implements Backend {
final Tunnel tunnel = owner.currentTunnel;
if (tunnel != null) {
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);
@@ -657,6 +754,27 @@ public final class GoBackend implements Backend {
}
}
+ 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) {
diff --git a/tunnel/src/main/java/com/wireguard/util/Resolver.java b/tunnel/src/main/java/com/wireguard/util/Resolver.java
index f401b584..301e03e0 100644
--- a/tunnel/src/main/java/com/wireguard/util/Resolver.java
+++ b/tunnel/src/main/java/com/wireguard/util/Resolver.java
@@ -36,7 +36,7 @@ public class Resolver {
}
}
- static boolean isULA(Inet6Address addr) {
+ public static boolean isULA(Inet6Address addr) {
byte[] raw = addr.getAddress();
return ((raw[0] & 0xfe) == 0xfc);
}
diff --git a/tunnel/src/main/proto/libwg.proto b/tunnel/src/main/proto/libwg.proto
index e633ea46..90c4f46a 100644
--- a/tunnel/src/main/proto/libwg.proto
+++ b/tunnel/src/main/proto/libwg.proto
@@ -1,5 +1,7 @@
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";
@@ -15,6 +17,7 @@ service Libwg {
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; }
@@ -101,3 +104,19 @@ message IpcSetRequest {
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/tools/libwg-go/Makefile b/tunnel/tools/libwg-go/Makefile
index 51290b08..8085ded1 100644
--- a/tunnel/tools/libwg-go/Makefile
+++ b/tunnel/tools/libwg-go/Makefile
@@ -71,7 +71,7 @@ gen/%_grpc.pb.go: $(PROTODIR)/%.proto $(BUILDDIR)/go-$(GO_VERSION)/.prepared $(P
$(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 api-android.go http-proxy.go service.go gen/libwg.pb.go gen/libwg_grpc.pb.go jni.c
+$(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 -buildid=" -v -trimpath -buildvcs=false -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 0ab80be9..78ee7f54 100644
--- a/tunnel/tools/libwg-go/api-android.go
+++ b/tunnel/tools/libwg-go/api-android.go
@@ -49,6 +49,7 @@ type TunnelHandle struct {
device *device.Device
uapi net.Listener
logger *device.Logger
+ tun *tun.NativeTun
}
var tunnelHandles map[int32]TunnelHandle
@@ -148,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]
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/jni.c b/tunnel/tools/libwg-go/jni.c
index 121729a7..20bfb332 100644
--- a/tunnel/tools/libwg-go/jni.c
+++ b/tunnel/tools/libwg-go/jni.c
@@ -15,6 +15,7 @@ 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)
{
@@ -82,3 +83,8 @@ JNIEXPORT jint JNICALL Java_com_wireguard_android_backend_GoBackend_wgStartGrpc(
(*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
index 1f2e629c..e49081e7 100644
--- a/tunnel/tools/libwg-go/service.go
+++ b/tunnel/tools/libwg-go/service.go
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net"
+ "net/netip"
"net/url"
"os"
@@ -244,3 +245,65 @@ func (e *LibwgServiceImpl) IpcSet(ctx context.Context, req *gen.IpcSetRequest) (
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
+}