diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-03-29 23:51:21 +0200 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2023-05-09 01:38:14 +0200 |
commit | d31cb6a4683a5aeca1b975918392f82716f2823c (patch) | |
tree | 05716556f7a8521e3193d3485087adc7a9f23d11 /tunnel/src/main/java/com | |
parent | 9f5c6588b6bc8117420e9346143c8264e7732483 (diff) |
ui,tunnel: implement http proxy for Android 10+
Allow getting package names of tv apps.
Diffstat (limited to 'tunnel/src/main/java/com')
-rw-r--r-- | tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java | 169 |
1 files changed, 166 insertions, 3 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 98ce8d7d..68acd2e8 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -7,10 +7,14 @@ package com.wireguard.android.backend; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.LocalSocketAddress; import android.net.ProxyInfo; +import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.system.OsConstants; import android.util.Log; @@ -20,11 +24,19 @@ 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.GetConnectionOwnerUidResponse; import com.wireguard.android.backend.gen.LibwgGrpc; +import com.wireguard.android.backend.gen.ReverseRequest; +import com.wireguard.android.backend.gen.ReverseResponse; +import com.wireguard.android.backend.gen.StartHttpProxyRequest; +import com.wireguard.android.backend.gen.StartHttpProxyResponse; +import com.wireguard.android.backend.gen.StopHttpProxyRequest; +import com.wireguard.android.backend.gen.StopHttpProxyResponse; import com.wireguard.android.backend.gen.VersionRequest; import com.wireguard.android.backend.gen.VersionResponse; import com.wireguard.android.util.SharedLibraryLoader; import com.wireguard.config.Config; +import com.wireguard.config.HttpProxy; import com.wireguard.config.InetEndpoint; import com.wireguard.config.InetNetwork; import com.wireguard.config.Peer; @@ -36,18 +48,30 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.android.UdsChannelBuilder; import io.grpc.okhttp.OkHttpChannelBuilder; +import io.grpc.stub.StreamObserver; +import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; +import java.io.IOException; import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.net.URL; import java.time.Instant; +import java.nio.ByteOrder; import java.util.Collections; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; import javax.net.SocketFactory; @@ -69,6 +93,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. @@ -78,7 +103,7 @@ public final class GoBackend implements Backend { public GoBackend(final Context context) { SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); this.context = context; - + connectivityManager = context.getSystemService(ConnectivityManager.class); File socketFile = new File(context.getCacheDir(), "libwg.sock"); String socketName = socketFile.getAbsolutePath(); Log.i(TAG, "wgStartGrpc: " + wgStartGrpc(socketName)); @@ -253,6 +278,130 @@ public final class GoBackend implements Backend { return getState(tunnel); } + private int startHttpProxy(Uri pacFileUrl) { + LibwgGrpc.LibwgStub asyncStub = LibwgGrpc.newStub(channel); + LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel); + StartHttpProxyRequest.Builder reqBuilder = StartHttpProxyRequest.newBuilder(); + if (pacFileUrl != null && pacFileUrl != Uri.EMPTY) { + reqBuilder.setPacFileUrl(pacFileUrl.toString()); + } + + Thread streamer = new Thread(new Runnable() { + public void run() { + try { + Log.i(TAG, "Before streamReverse"); + streamReverse(asyncStub); + Log.i(TAG, "After streamReverse"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + + StartHttpProxyRequest req = reqBuilder.build(); + StartHttpProxyResponse resp = stub.startHttpProxy(req); + Log.i(TAG, "Start http proxy listen_port:" + resp.getListenPort() + ", error:" + resp.getError().getMessage()); + streamer.start(); + return resp.getListenPort(); + } + + private void stopHttpProxy() { + LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel); + StopHttpProxyRequest req = StopHttpProxyRequest.newBuilder().build(); + StopHttpProxyResponse resp = stub.stopHttpProxy(req); + Log.i(TAG, "Stop http proxy: " + resp.getError().getMessage()); + } + + private static InetSocketAddress toInetSocketAddress(com.wireguard.android.backend.gen.InetSocketAddress sockAddr) { + try { + return new InetSocketAddress(InetAddress.getByAddress(sockAddr.getAddress().getAddress().toByteArray()), sockAddr.getPort()); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + private int getConnectionOwnerUid(int protocol, InetSocketAddress local, InetSocketAddress remote) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + return connectivityManager.getConnectionOwnerUid(protocol, local, remote); + else + return Process.INVALID_UID; + } + + private void streamReverse(LibwgGrpc.LibwgStub asyncStub) throws InterruptedException { + Log.i(TAG, "In streamReverse"); + final CountDownLatch finishLatch = new CountDownLatch(1); + final AtomicReference<StreamObserver<ReverseRequest>> atomicRequestObserver = new AtomicReference<StreamObserver<ReverseRequest>>(); + // Throwable failed = null; + + StreamObserver<ReverseResponse> responseObserver = new StreamObserver<ReverseResponse>() { + @Override + public void onNext(ReverseResponse resp) { + try { + String pkg = ""; + int uid = getConnectionOwnerUid(resp.getUid().getProtocol(), toInetSocketAddress(resp.getUid().getLocal()), toInetSocketAddress(resp.getUid().getRemote())); + if (uid != Process.INVALID_UID) { + PackageManager pm = context.getPackageManager(); + pkg = pm.getNameForUid(uid); + String[] pkgs = pm.getPackagesForUid(uid); + Log.i(TAG, "reverse onNext uid:" + uid + " package:" + pkg); + if (pkgs != null) { + for (int i=0; i < pkgs.length; i++) { + Log.i(TAG, "getPackagesForUid() = " + pkgs[i]); + } + } + } else { + Log.i(TAG, "Connection not found"); + } + + ReverseRequest req = ReverseRequest.newBuilder() + .setUid(GetConnectionOwnerUidResponse.newBuilder() + .setUid(uid) + .setPackage(pkg != null ? pkg: "") + .build()) + .build(); + + io.grpc.Context.current().fork().run(new Runnable() { + public void run() { + atomicRequestObserver.get().onNext(req); + } + }); + } catch (RuntimeException ex) { + Log.i(TAG, "onNext " + ex); + throw ex; + } + } + + @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<ReverseRequest> requestObserver = asyncStub.reverse(responseObserver); + atomicRequestObserver.set(requestObserver); + + // Mark the end of requests + //requestObserver.onCompleted(); + + //requestObserver.onNext(ReverseRequest.getDefaultInstance()); + + Log.i(TAG, "Waiting streamReverse"); + // Receiving happens asynchronously + finishLatch.await(); + + // if (failed != null) { + // throw new RuntimeException(failed); + // } + Log.i(TAG, "Exit streamReverse"); + } + private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception { Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state); @@ -347,8 +496,20 @@ public final class GoBackend implements Backend { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) service.setUnderlyingNetworks(null); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - config.getInterface().getHttpProxy().ifPresent(pi -> builder.setHttpProxy(pi.getProxyInfo())); + 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); + } + } } builder.setBlocking(true); @@ -375,6 +536,7 @@ public final class GoBackend implements Backend { currentTunnel = null; currentTunnelHandle = -1; currentConfig = null; + stopHttpProxy(); wgTurnOff(handleToClose); try { vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf(); @@ -440,6 +602,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) |