From 2e5c0ca5c079a783992fc3f2bb6bd5f64bceadc1 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Sat, 20 Nov 2021 20:03:32 +0100 Subject: tunnel: WIP implement http proxy for Android 10+ TODO: fix hard-coded proxy URLs. --- .../com/wireguard/android/backend/GoBackend.java | 128 ++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) (limited to 'tunnel/src/main/java') 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 b78fa9ce..a68d205b 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -7,7 +7,9 @@ package com.wireguard.android.backend; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.LocalSocketAddress; +import android.net.ProxyInfo; import android.os.Build; import android.os.ParcelFileDescriptor; import android.system.OsConstants; @@ -30,16 +32,21 @@ import com.wireguard.util.NonNullForAll; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.okhttp.OkHttpChannelBuilder; +import io.grpc.stub.StreamObserver; import java.io.File; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.util.Collections; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; import javax.net.SocketFactory; @@ -52,6 +59,13 @@ import com.wireguard.android.backend.gen.TunnelHandle; import com.wireguard.android.backend.gen.TurnOnRequest; import com.wireguard.android.backend.gen.TurnOnResponse; import com.wireguard.android.backend.gen.VersionResponse; +import com.wireguard.android.backend.gen.ReverseRequest; +import com.wireguard.android.backend.gen.ReverseResponse; +import com.wireguard.android.backend.gen.StartHttpProxyRequest; +import com.wireguard.android.backend.gen.StartHttpProxyResponse; +import com.wireguard.android.backend.gen.StopHttpProxyRequest; +import com.wireguard.android.backend.gen.StopHttpProxyResponse; +import com.wireguard.android.backend.gen.GetConnectionOwnerUidResponse; /** @@ -69,6 +83,7 @@ public final class GoBackend implements Backend { @Nullable private Tunnel currentTunnel; private int currentTunnelHandle = -1; private ManagedChannel channel; + private ConnectivityManager connectivityManager; /** * Public constructor for GoBackend. @@ -79,9 +94,11 @@ public final class GoBackend implements Backend { SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); this.context = context; + connectivityManager = context.getSystemService(ConnectivityManager.class); + File socketFile = new File(context.getCacheDir(), "libwg.sock"); String socketName = socketFile.getAbsolutePath(); - Log.i(TAG, "wgStartGrpc: " + wgStartGrpc(socketName)); + Log.i(TAG, "java wgStartGrpc: " + wgStartGrpc(socketName)); ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forAddress("localhost", 10000).usePlaintext(); LocalSocketAddress address = new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM); SocketFactory socketFactory = new UnixDomainSocketFactory(address); @@ -264,6 +281,107 @@ public final class GoBackend implements Backend { } + private int startHttpProxy() { + LibwgGrpc.LibwgStub asyncStub = LibwgGrpc.newStub(channel); + LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel); + + Thread streamer = new Thread(new Runnable() { + public void run() { + try { + while (true) { + Log.i(TAG, "Before streamReverse"); + streamReverse(asyncStub); + Log.i(TAG, "After streamReverse"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + streamer.start(); + + StartHttpProxyRequest req = StartHttpProxyRequest.newBuilder().build(); + StartHttpProxyResponse resp = stub.startHttpProxy(req); + Log.i(TAG, "Start http proxy listen_port:" + resp.getListenPort() + ", error:" + resp.getError().getMessage()); + return resp.getListenPort(); + } + + private void stopHttpProxy() { + LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel); + StopHttpProxyRequest req = StopHttpProxyRequest.newBuilder().build(); + StopHttpProxyResponse resp = stub.stopHttpProxy(req); + Log.i(TAG, "Stop http proxy: " + resp.getError().getMessage()); + } + + private static InetSocketAddress toInetSocketAddress(com.wireguard.android.backend.gen.InetSocketAddress sockAddr) { + try { + return new InetSocketAddress(InetAddress.getByAddress(sockAddr.getAddress().getAddress().toByteArray()), sockAddr.getPort()); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + private void streamReverse(LibwgGrpc.LibwgStub asyncStub) throws InterruptedException, + RuntimeException { + Log.i(TAG, "In streamReverse"); + final CountDownLatch finishLatch = new CountDownLatch(1); + final AtomicReference> atomicRequestObserver = new AtomicReference>(); + // Throwable failed = null; + + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onNext(ReverseResponse resp) { + int uid = connectivityManager.getConnectionOwnerUid(resp.getUid().getProtocol(), toInetSocketAddress(resp.getUid().getLocal()), toInetSocketAddress(resp.getUid().getRemote())); + String pkg = context.getPackageManager().getNameForUid(uid); + Log.i(TAG, "reverse onNext uid:" + uid + " package:" + pkg); + + ReverseRequest req = ReverseRequest.newBuilder() + .setUid(GetConnectionOwnerUidResponse.newBuilder() + .setUid(uid) + .setPackage(pkg) + .build()) + .build(); + + io.grpc.Context.current().fork().run(new Runnable() { + public void run() { + atomicRequestObserver.get().onNext(req); + } + }); + } + + @Override + public void onError(Throwable t) { + // failed = t; + Log.i(TAG, "streamReverse error: " + t); + finishLatch.countDown(); + } + + @Override + public void onCompleted() { + Log.i(TAG, "streamReverse completed"); + finishLatch.countDown(); + } + }; + StreamObserver requestObserver = asyncStub.reverse(responseObserver); + atomicRequestObserver.set(requestObserver); + + // Mark the end of requests + //requestObserver.onCompleted(); + + //requestObserver.onNext(ReverseRequest.getDefaultInstance()); + + Log.i(TAG, "Waiting streamReverse"); + // Receiving happens asynchronously + if (!finishLatch.await(1, TimeUnit.HOURS)) { + throw new RuntimeException("Could not finish rpc within 1 minute, the server is likely down"); + } + + // if (failed != null) { + // throw new RuntimeException(failed); + // } + Log.i(TAG, "Exit streamReverse"); + } + private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception { Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state); @@ -359,6 +477,12 @@ public final class GoBackend implements Backend { service.setUnderlyingNetworks(null); builder.setBlocking(true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + int listenPort = startHttpProxy(); + ProxyInfo proxy = ProxyInfo.buildDirectProxy("localhost", listenPort); + builder.setHttpProxy(proxy); + } try (final ParcelFileDescriptor tun = builder.establish()) { if (tun == null) throw new BackendException(Reason.TUN_CREATION_ERROR); @@ -383,6 +507,7 @@ public final class GoBackend implements Backend { currentTunnel = null; currentTunnelHandle = -1; currentConfig = null; + stopHttpProxy(); wgTurnOff(handleToClose); } @@ -445,6 +570,7 @@ public final class GoBackend implements Backend { @Override public void onDestroy() { if (owner != null) { + owner.stopHttpProxy(); final Tunnel tunnel = owner.currentTunnel; if (tunnel != null) { if (owner.currentTunnelHandle != -1) -- cgit v1.2.3