summaryrefslogtreecommitdiffhomepage
path: root/tunnel/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'tunnel/src/main/java')
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java201
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java325
-rw-r--r--tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java59
-rw-r--r--tunnel/src/main/java/com/wireguard/config/BadConfigException.java9
-rw-r--r--tunnel/src/main/java/com/wireguard/config/HttpProxy.java78
-rw-r--r--tunnel/src/main/java/com/wireguard/config/Interface.java33
6 files changed, 703 insertions, 2 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 3d0886cf..c8158a72 100644
--- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java
@@ -7,15 +7,36 @@ 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;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+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;
@@ -23,14 +44,34 @@ import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
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.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;
import androidx.annotation.Nullable;
import androidx.collection.ArraySet;
@@ -49,6 +90,8 @@ public final class GoBackend implements Backend {
@Nullable private Config currentConfig;
@Nullable private Tunnel currentTunnel;
private int currentTunnelHandle = -1;
+ private ManagedChannel channel;
+ private ConnectivityManager connectivityManager;
/**
* Public constructor for GoBackend.
@@ -58,6 +101,15 @@ 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));
+ ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress("localhost", 10000).usePlaintext();
+ LocalSocketAddress address = new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM);
+ SocketFactory socketFactory = new UnixDomainSocketFactory(address);
+ ((OkHttpChannelBuilder) channelBuilder).socketFactory(socketFactory);
+ channel = channelBuilder.build();
}
/**
@@ -82,6 +134,8 @@ public final class GoBackend implements Backend {
private static native String wgVersion();
+ private static native int wgStartGrpc(String sockName);
+
/**
* Method to get the names of running tunnels.
*
@@ -166,7 +220,10 @@ public final class GoBackend implements Backend {
*/
@Override
public String getVersion() {
- return wgVersion();
+ LibwgGrpc.LibwgBlockingStub stub = LibwgGrpc.newBlockingStub(channel);
+ VersionRequest request = VersionRequest.newBuilder().build();
+ VersionResponse resp = stub.version(request);
+ return resp.getVersion();
}
/**
@@ -205,6 +262,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);
@@ -299,6 +480,22 @@ public final class GoBackend implements Backend {
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);
+ }
+ }
+ }
+
builder.setBlocking(true);
try (final ParcelFileDescriptor tun = builder.establish()) {
if (tun == null)
@@ -323,6 +520,7 @@ public final class GoBackend implements Backend {
currentTunnel = null;
currentTunnelHandle = -1;
currentConfig = null;
+ stopHttpProxy();
wgTurnOff(handleToClose);
}
@@ -385,6 +583,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/java/com/wireguard/android/backend/LocalSocketAdapter.java b/tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java
new file mode 100644
index 00000000..bf027ae1
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/LocalSocketAdapter.java
@@ -0,0 +1,325 @@
+/*
+ */
+package com.wireguard.android.backend;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImplFactory;
+import java.net.SocketOptions;
+import java.nio.channels.SocketChannel;
+import java.util.Vector;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+
+/**
+ * Adaptor allows using a LocalSocket as a Socket.
+ */
+final class LocalSocketAdapter extends Socket {
+ private final LocalSocketAddress address;
+ private final LocalSocket unix;
+ private final SocketAddress localAddress;
+ private InetSocketAddress inetSocketAddress;
+ private InputStream is;
+ private OutputStream os;
+
+ LocalSocketAdapter(LocalSocketAddress address) {
+ this.address = address;
+ this.localAddress = new InetSocketAddress(0);
+ unix = new LocalSocket();
+ }
+
+ LocalSocketAdapter(LocalSocketAddress address, InetSocketAddress inetAddress) {
+ this(address);
+ this.inetSocketAddress = inetAddress;
+ }
+
+ private void throwUnsupportedOperationException() {
+ Log.i("helloworld", "Unsupported: " + Log.getStackTraceString(new Exception()));
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void bind (SocketAddress bindpoint) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void close() throws IOException {
+ unix.close();
+ }
+
+ @Override public void connect(SocketAddress endpoint) throws IOException {
+ this.inetSocketAddress = (InetSocketAddress) endpoint;
+ try {
+ unix.connect(address);
+ } catch (IOException e) {
+ Log.i("helloworld", "Error: " + e.toString());
+ throw e;
+ }
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint, int timeout) throws IOException {
+ this.inetSocketAddress = (InetSocketAddress) endpoint;
+ unix.connect(address, timeout);
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ throwUnsupportedOperationException();
+ return null;
+ }
+
+ @Override public InetAddress getInetAddress() {
+ return inetSocketAddress.getAddress();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ is = unix.getInputStream();
+ return is;
+ }
+
+ @Override
+ public boolean getKeepAlive() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ throwUnsupportedOperationException();
+ return null;
+ }
+
+ @Override
+ public int getLocalPort() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ //throwUnsupportedOperationException();
+ return localAddress;
+ }
+
+ @Override
+ public boolean getOOBInline() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ if (os != null)
+ return os;
+
+ OutputStream unixOs = unix.getOutputStream();
+ os = new OutputStream() {
+ @Override
+ public void close() throws IOException {
+ // LocalSocket's default implementation closes the socket,
+ // which leaves readers of thes InputStream hanging.
+ // Instead shutdown input (and output) to release readers.
+ LocalSocketAdapter.this.shutdownInput();
+ LocalSocketAdapter.this.shutdownOutput();
+ }
+
+ @Override
+ public void write (byte[] b) throws IOException {
+ unixOs.write(b);
+ }
+
+ @Override
+ public void write (byte[] b, int off, int len) throws IOException {
+ unixOs.write(b, off, len);
+ }
+
+ @Override
+ public void write (int b) throws IOException {
+ unixOs.write(b);
+ }
+ };
+ return os;
+ }
+
+ @Override
+ public int getPort() {
+ return inetSocketAddress.getPort();
+ }
+
+ @Override
+ public int getReceiveBufferSize() throws SocketException {
+ try {
+ return unix.getReceiveBufferSize();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return inetSocketAddress;
+ }
+
+ @Override
+ public boolean getReuseAddress() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public int getSendBufferSize() throws SocketException {
+ try {
+ return unix.getSendBufferSize();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int getSoLinger() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public int getSoTimeout() throws SocketException {
+ try {
+ return unix.getSoTimeout();
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean getTcpNoDelay() {
+ throwUnsupportedOperationException();
+ return false;
+ }
+
+ @Override
+ public int getTrafficClass() {
+ throwUnsupportedOperationException();
+ return 0;
+ }
+
+ @Override
+ public boolean isBound() {
+ return unix.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return unix.isClosed();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return unix.isConnected();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return unix.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return unix.isOutputShutdown();
+ }
+
+ @Override
+ public void sendUrgentData (int data) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setKeepAlive (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setOOBInline (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setPerformancePreferences (int connectionTime,
+ int latency,
+ int bandwidth) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setReceiveBufferSize(int size) throws SocketException {
+ try {
+ unix.setReceiveBufferSize(size);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setReuseAddress (boolean on) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setSendBufferSize(int size) throws SocketException {
+ try {
+ unix.setSendBufferSize(size);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setSoLinger (boolean on,
+ int linger) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void setSoTimeout(int timeout) throws SocketException {
+ try {
+ unix.setSoTimeout(timeout);
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void setTcpNoDelay (boolean on) {
+ // Not relevant for local sockets.
+ }
+
+ @Override
+ public void setTrafficClass (int tc) {
+ throwUnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ unix.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ unix.shutdownOutput();
+ }
+
+ @Override
+ public String toString() {
+ return unix.toString();
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java b/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java
new file mode 100644
index 00000000..427a19f1
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/android/backend/UnixDomainSocketFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.wireguard.android.backend;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import javax.net.SocketFactory;
+import android.net.LocalSocketAddress;
+
+/** Impersonate TCP-style SocketFactory over UNIX domain sockets. */
+public final class UnixDomainSocketFactory extends SocketFactory {
+ private final LocalSocketAddress address;
+
+ public UnixDomainSocketFactory(LocalSocketAddress address) {
+ this.address = address;
+ }
+
+ @Override public Socket createSocket() throws IOException {
+ return new LocalSocketAdapter(address);
+ }
+
+ @Override public Socket createSocket(String host, int port) throws IOException {
+ Socket result = createSocket();
+ result.connect(new InetSocketAddress(host, port));
+ return result;
+ }
+
+ @Override public Socket createSocket(
+ String host, int port, InetAddress localHost, int localPort) throws IOException {
+ return createSocket(host, port);
+ }
+
+ @Override public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket result = createSocket();
+ result.connect(new InetSocketAddress(host, port));
+ return result;
+ }
+
+ @Override public Socket createSocket(
+ InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocket(host, port);
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
index 8766ce51..e5a94e89 100644
--- a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
+++ b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java
@@ -8,6 +8,8 @@ package com.wireguard.config;
import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
+import java.net.MalformedURLException;
+
import androidx.annotation.Nullable;
@NonNullForAll
@@ -44,6 +46,12 @@ public class BadConfigException extends Exception {
}
public BadConfigException(final Section section, final Location location,
+ @Nullable final CharSequence text,
+ final MalformedURLException cause) {
+ this(section, location, Reason.INVALID_VALUE, text, cause);
+ }
+
+ public BadConfigException(final Section section, final Location location,
final ParseException cause) {
this(section, location, Reason.INVALID_VALUE, cause.getText(), cause);
}
@@ -73,6 +81,7 @@ public class BadConfigException extends Exception {
ENDPOINT("Endpoint"),
EXCLUDED_APPLICATIONS("ExcludedApplications"),
INCLUDED_APPLICATIONS("IncludedApplications"),
+ HTTP_PROXY("HttpProxy"),
LISTEN_PORT("ListenPort"),
MTU("MTU"),
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
diff --git a/tunnel/src/main/java/com/wireguard/config/HttpProxy.java b/tunnel/src/main/java/com/wireguard/config/HttpProxy.java
new file mode 100644
index 00000000..d45914f8
--- /dev/null
+++ b/tunnel/src/main/java/com/wireguard/config/HttpProxy.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2022 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.config;
+
+import com.wireguard.config.BadConfigException.Location;
+import com.wireguard.config.BadConfigException.Section;
+import com.wireguard.util.NonNullForAll;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import android.net.ProxyInfo;
+import android.net.Uri;
+
+@NonNullForAll
+public final class HttpProxy {
+ public static final int DEFAULT_PROXY_PORT = 8080;
+
+ private ProxyInfo pi;
+
+ protected HttpProxy(ProxyInfo pi) {
+ this.pi = pi;
+ }
+
+ public ProxyInfo getProxyInfo() {
+ return pi;
+ }
+
+ public String getHost() {
+ return pi.getHost();
+ }
+
+ public Uri getPacFileUrl() {
+ return pi.getPacFileUrl();
+ }
+
+ public int getPort() {
+ return pi.getPort();
+ }
+
+ public static HttpProxy parse(final String httpProxy) throws BadConfigException {
+ try {
+ if (httpProxy.startsWith("pac:")) {
+ return new HttpProxy(ProxyInfo.buildPacProxy(Uri.parse(httpProxy.substring(4))));
+ } else {
+ final String urlStr;
+ if (!httpProxy.contains("://")) {
+ urlStr = "http://" + httpProxy;
+ } else {
+ urlStr = httpProxy;
+ }
+ URL url = new URL(urlStr);
+ return new HttpProxy(ProxyInfo.buildDirectProxy(url.getHost(), url.getPort() <= 0 ? DEFAULT_PROXY_PORT : url.getPort()));
+ }
+ } catch (final MalformedURLException e) {
+ throw new BadConfigException(Section.INTERFACE, Location.HTTP_PROXY, httpProxy, e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ if (pi.getPacFileUrl() != null && pi.getPacFileUrl() != Uri.EMPTY)
+ sb.append("pac:").append(pi.getPacFileUrl());
+ else {
+ sb.append("http://").append(pi.getHost()).append(':');
+ if (pi.getPort() <= 0)
+ sb.append(DEFAULT_PROXY_PORT);
+ else
+ sb.append(pi.getPort());
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/tunnel/src/main/java/com/wireguard/config/Interface.java b/tunnel/src/main/java/com/wireguard/config/Interface.java
index 694f313a..a4fa2a19 100644
--- a/tunnel/src/main/java/com/wireguard/config/Interface.java
+++ b/tunnel/src/main/java/com/wireguard/config/Interface.java
@@ -46,6 +46,7 @@ public final class Interface {
private final KeyPair keyPair;
private final Optional<Integer> listenPort;
private final Optional<Integer> mtu;
+ private final Optional<HttpProxy> httpProxy;
private Interface(final Builder builder) {
// Defensively copy to ensure immutability even if the Builder is reused.
@@ -57,6 +58,7 @@ public final class Interface {
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
listenPort = builder.listenPort;
mtu = builder.mtu;
+ httpProxy = builder.httpProxy;
}
/**
@@ -92,6 +94,9 @@ public final class Interface {
case "mtu":
builder.parseMtu(attribute.getValue());
break;
+ case "httpproxy":
+ builder.parseHttpProxy(attribute.getValue());
+ break;
case "privatekey":
builder.parsePrivateKey(attribute.getValue());
break;
@@ -115,7 +120,8 @@ public final class Interface {
&& includedApplications.equals(other.includedApplications)
&& keyPair.equals(other.keyPair)
&& listenPort.equals(other.listenPort)
- && mtu.equals(other.mtu);
+ && mtu.equals(other.mtu)
+ && httpProxy.equals(other.httpProxy);
}
/**
@@ -195,6 +201,15 @@ public final class Interface {
return mtu;
}
+ /**
+ * Returns the HTTP proxy used for the WireGuard interface.
+ *
+ * @return the HTTP proxy, or {@code Optional.empty()} if none is configured
+ */
+ public Optional<HttpProxy> getHttpProxy() {
+ return httpProxy;
+ }
+
@Override
public int hashCode() {
int hash = 1;
@@ -205,6 +220,7 @@ public final class Interface {
hash = 31 * hash + keyPair.hashCode();
hash = 31 * hash + listenPort.hashCode();
hash = 31 * hash + mtu.hashCode();
+ hash = 31 * hash + httpProxy.hashCode();
return hash;
}
@@ -244,6 +260,7 @@ public final class Interface {
sb.append("IncludedApplications = ").append(Attribute.join(includedApplications)).append('\n');
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
+ httpProxy.ifPresent(p -> sb.append("HttpProxy = ").append(p).append('\n'));
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
return sb.toString();
}
@@ -279,6 +296,8 @@ public final class Interface {
private Optional<Integer> listenPort = Optional.empty();
// Defaults to not present.
private Optional<Integer> mtu = Optional.empty();
+ // Defaults to not present.
+ private Optional<HttpProxy> httpProxy = Optional.empty();
public Builder addAddress(final InetNetwork address) {
addresses.add(address);
@@ -391,6 +410,10 @@ public final class Interface {
}
}
+ public Builder parseHttpProxy(final String httpProxy) throws BadConfigException {
+ return setHttpProxy(HttpProxy.parse(httpProxy));
+ }
+
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
try {
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
@@ -419,5 +442,13 @@ public final class Interface {
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
return this;
}
+
+ public Builder setHttpProxy(final HttpProxy httpProxy) throws BadConfigException {
+ if (httpProxy == null)
+ throw new BadConfigException(Section.INTERFACE, Location.HTTP_PROXY,
+ Reason.INVALID_VALUE, String.valueOf(httpProxy));
+ this.httpProxy = httpProxy == null ? Optional.empty() : Optional.of(httpProxy);
+ return this;
+ }
}
}