summaryrefslogtreecommitdiffhomepage
path: root/tunnel/src
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2022-03-29 23:51:21 +0200
committerMikael Magnusson <mikma@users.sourceforge.net>2023-04-28 23:11:06 +0200
commitbabc3137d7f9339e45c58d0c32c921548962e4dd (patch)
tree3538e48bc0a25557c49930119983a994b245d16f /tunnel/src
parent73913f449437d7c5aab8b47650835af10fcee882 (diff)
ui,tunnel: implement http proxy for Android 10+
Allow getting package names of tv apps.
Diffstat (limited to 'tunnel/src')
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java169
-rw-r--r--tunnel/src/main/proto/libwg.proto66
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 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)
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()
+}