diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-03-29 23:51:21 +0200 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2023-03-27 00:22:32 +0200 |
commit | dc2291ab3615f72102f1e3c039bb021b00035c9c (patch) | |
tree | ff6d63a387db92033fa37fa363c11ccd19e94813 /tunnel/src/main | |
parent | 91eafea51205e1f72d01eb4fcfd33a822255b1e0 (diff) |
tunnel: implement http proxy for Android 10+
Allow getting package names of tv apps.
Diffstat (limited to 'tunnel/src/main')
-rw-r--r-- | tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java | 169 | ||||
-rw-r--r-- | tunnel/src/main/proto/libwg.proto | 66 |
2 files changed, 232 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 1338a1b6..14d6c53f 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,17 +48,29 @@ 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.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; @@ -68,6 +92,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. @@ -77,7 +102,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)); @@ -234,6 +259,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); @@ -328,8 +477,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); @@ -356,6 +517,7 @@ public final class GoBackend implements Backend { currentTunnel = null; currentTunnelHandle = -1; currentConfig = null; + stopHttpProxy(); wgTurnOff(handleToClose); } @@ -418,6 +580,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) diff --git a/tunnel/src/main/proto/libwg.proto b/tunnel/src/main/proto/libwg.proto index 2d964897..977dacdd 100644 --- a/tunnel/src/main/proto/libwg.proto +++ b/tunnel/src/main/proto/libwg.proto @@ -11,6 +11,31 @@ package api; service Libwg { rpc StopGrpc(StopGrpcRequest) returns (StopGrpcResponse); rpc Version(VersionRequest) returns (VersionResponse); + rpc StartHttpProxy(StartHttpProxyRequest) returns (StartHttpProxyResponse); + rpc StopHttpProxy(StopHttpProxyRequest) returns (StopHttpProxyResponse); + rpc Reverse(stream ReverseRequest) returns (stream ReverseResponse); +} + +message TunnelHandle { int32 handle = 1; } + +message Error { + enum Code { + NO_ERROR = 0; + UNSPECIFIED = 1; + INVALID_PROTOCOL_BUFFER = 2; + INVALID_RESPONSE = 3; + } + Code code = 1; + string message = 2; +} + +message InetAddress { + bytes address = 1; +} + +message InetSocketAddress { + InetAddress address = 1; + uint32 port = 2; } message StopGrpcRequest { @@ -25,3 +50,44 @@ message VersionRequest { message VersionResponse { string version = 1; } + +message StartHttpProxyRequest { + string pacFileUrl = 1; +} + +message StartHttpProxyResponse { + uint32 listen_port = 1; + Error error = 2; +} + +message StopHttpProxyRequest { +} + +message StopHttpProxyResponse { + Error error = 1; +} + +message ReverseRequest { + oneof response { + GetConnectionOwnerUidResponse uid = 1; + } +} + +message ReverseResponse { + oneof request { + GetConnectionOwnerUidRequest uid = 1; + } +} + +message GetConnectionOwnerUidRequest { + // ConnectivityManager.getConnectionOwnerUid(int protocol, + // InetSocketAddress local, InetSocketAddress remote) + int32 protocol = 1; + InetSocketAddress local = 2; + InetSocketAddress remote = 3; +} + +message GetConnectionOwnerUidResponse { + int32 uid = 1; + string package = 2; // context.getPackageManager().getNameForUid() +} |