Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ In another terminal, for each client, execute:

./gnirehtet start [serial]

To configure an HTTP proxy for the VPN network on Android 10+:

./gnirehtet start [serial] --proxy-host HOST --proxy-port PORT \
--proxy-exclusion-list HOST[,HOST2,...]

To stop a client:

./gnirehtet stop [serial]
Expand Down Expand Up @@ -209,6 +214,15 @@ To start a client:
adb shell am start -a com.genymobile.gnirehtet.START \
-n com.genymobile.gnirehtet/.GnirehtetActivity

To start a client with an HTTP proxy and exclusions:

adb reverse localabstract:gnirehtet tcp:31416
adb shell am start -a com.genymobile.gnirehtet.START \
-n com.genymobile.gnirehtet/.GnirehtetActivity \
--es proxyHost HOST \
--ei proxyPort PORT \
--esa proxyExclusionList HOST,HOST2

To stop a client:

adb shell am start -a com.genymobile.gnirehtet.STOP \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import android.os.Bundle;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
* This (invisible) activity receives the {@link #ACTION_GNIREHTET_START START} and
* {@link #ACTION_GNIREHTET_STOP} actions from the command line.
Expand All @@ -23,6 +26,9 @@ public class GnirehtetActivity extends Activity {

public static final String EXTRA_DNS_SERVERS = "dnsServers";
public static final String EXTRA_ROUTES = "routes";
public static final String EXTRA_PROXY_HOST = "proxyHost";
public static final String EXTRA_PROXY_PORT = "proxyPort";
public static final String EXTRA_PROXY_EXCLUSION_LIST = "proxyExclusionList";

private static final int VPN_REQUEST_CODE = 0;

Expand Down Expand Up @@ -59,7 +65,27 @@ private static VpnConfiguration createConfig(Intent intent) {
if (routes == null) {
routes = new String[0];
}
return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes));
String proxyHost = intent.getStringExtra(EXTRA_PROXY_HOST);
int proxyPort = intent.getIntExtra(EXTRA_PROXY_PORT, 0);
String[] proxyExclusionList = normalizeProxyExclusionList(intent.getStringArrayExtra(EXTRA_PROXY_EXCLUSION_LIST));
return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes), proxyHost, proxyPort, proxyExclusionList);
}

private static String[] normalizeProxyExclusionList(String[] rawList) {
if (rawList == null) {
return new String[0];
}
List<String> values = new ArrayList<>(rawList.length);
for (String rawValue : rawList) {
if (rawValue == null) {
continue;
}
String value = rawValue.trim();
if (!value.isEmpty()) {
values.add(value);
}
}
return values.toArray(new String[0]);
}

private boolean startGnirehtet(VpnConfiguration config) {
Expand Down
55 changes: 55 additions & 0 deletions app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
import android.util.Log;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

public class GnirehtetService extends VpnService {
Expand Down Expand Up @@ -135,6 +137,8 @@ private boolean setupVpn(VpnConfiguration config) {
}
}

configureHttpProxy(builder, config);

// non-blocking by default, but FileChannel is not selectable, that's stupid!
// so switch to synchronous I/O to avoid polling
builder.setBlocking(true);
Expand All @@ -151,6 +155,57 @@ private boolean setupVpn(VpnConfiguration config) {
return true;
}

private void configureHttpProxy(Builder builder, VpnConfiguration config) {
String proxyHost = config.getProxyHost();
int proxyPort = config.getProxyPort();
if (proxyHost == null || proxyHost.isEmpty() || proxyPort == 0) {
return;
}

if (Build.VERSION.SDK_INT < 29) {
Log.w(TAG, "HTTP proxy requested but only supported on Android 10+ (API 29+)");
return;
}

try {
Class<?> proxyInfoClass = Class.forName("android.net.ProxyInfo");
Object proxyInfo = createProxyInfo(proxyInfoClass, proxyHost, proxyPort, config.getProxyExclusionList());
Method setHttpProxyMethod = Builder.class.getMethod("setHttpProxy", proxyInfoClass);
setHttpProxyMethod.invoke(builder, proxyInfo);
} catch (ReflectiveOperationException e) {
Log.w(TAG, "Cannot configure HTTP proxy", e);
}
}

private static Object createProxyInfo(Class<?> proxyInfoClass, String host, int port, String[] exclusionList)
throws ReflectiveOperationException {
List<String> exclusions = toProxyExclusions(exclusionList);
try {
Method buildDirectProxyMethod = proxyInfoClass.getMethod("buildDirectProxy", String.class, int.class, List.class);
return buildDirectProxyMethod.invoke(null, host, port, exclusions);
} catch (NoSuchMethodException e) {
Method buildDirectProxyMethod = proxyInfoClass.getMethod("buildDirectProxy", String.class, int.class);
return buildDirectProxyMethod.invoke(null, host, port);
}
}

private static List<String> toProxyExclusions(String[] exclusionList) {
List<String> result = new ArrayList<>();
if (exclusionList == null) {
return result;
}
for (String rawValue : exclusionList) {
if (rawValue == null) {
continue;
}
String value = rawValue.trim();
if (!value.isEmpty()) {
result.add(value);
}
}
return result;
}

@SuppressWarnings("checkstyle:MagicNumber")
private void setAsUndernlyingNetwork() {
if (Build.VERSION.SDK_INT >= 22) {
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,28 @@ public class VpnConfiguration implements Parcelable {

private final InetAddress[] dnsServers;
private final CIDR[] routes;
private final String proxyHost;
private final int proxyPort;
private final String[] proxyExclusionList;

public VpnConfiguration() {
this.dnsServers = new InetAddress[0];
this.routes = new CIDR[0];
this.proxyHost = null;
this.proxyPort = 0;
this.proxyExclusionList = new String[0];
}

public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) {
this(dnsServers, routes, null, 0, new String[0]);
}

public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes, String proxyHost, int proxyPort, String[] proxyExclusionList) {
this.dnsServers = dnsServers;
this.routes = routes;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.proxyExclusionList = proxyExclusionList;
}

private VpnConfiguration(Parcel source) {
Expand All @@ -48,6 +61,10 @@ private VpnConfiguration(Parcel source) {
throw new AssertionError("Invalid address", e);
}
routes = source.createTypedArray(CIDR.CREATOR);
proxyHost = source.readString();
proxyPort = source.readInt();
String[] exclusions = source.createStringArray();
proxyExclusionList = exclusions != null ? exclusions : new String[0];
}

public InetAddress[] getDnsServers() {
Expand All @@ -58,13 +75,28 @@ public CIDR[] getRoutes() {
return routes;
}

public String getProxyHost() {
return proxyHost;
}

public int getProxyPort() {
return proxyPort;
}

public String[] getProxyExclusionList() {
return proxyExclusionList;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(dnsServers.length);
for (InetAddress addr : dnsServers) {
dest.writeByteArray(addr.getAddress());
}
dest.writeTypedArray(routes, 0);
dest.writeString(proxyHost);
dest.writeInt(proxyPort);
dest.writeStringArray(proxyExclusionList);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@ public class CommandLineArguments {
public static final int PARAM_DNS_SERVER = 1 << 1;
public static final int PARAM_ROUTES = 1 << 2;
public static final int PARAM_PORT = 1 << 3;
public static final int PARAM_PROXY_HOST = 1 << 4;
public static final int PARAM_PROXY_PORT = 1 << 5;
public static final int PARAM_PROXY_EXCLUSION_LIST = 1 << 6;

public static final int DEFAULT_PORT = 31416;

private int port;
private String serial;
private String dnsServers;
private String routes;
private String proxyHost;
private int proxyPort;
private String proxyExclusionList;

public static CommandLineArguments parse(int acceptedParameters, String... args) {
CommandLineArguments arguments = new CommandLineArguments();
Expand Down Expand Up @@ -64,23 +70,70 @@ public static CommandLineArguments parse(int acceptedParameters, String... args)
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing -p parameter");
}
arguments.port = Integer.parseInt(args[i + 1]);
if (arguments.port <= 0 || arguments.port >= 65536) {
throw new IllegalArgumentException("Invalid port: " + arguments.port);
arguments.port = parsePort(args[i + 1], "-p");
++i;
} else if ((acceptedParameters & PARAM_PROXY_HOST) != 0 && "--proxy-host".equals(arg)) {
if (arguments.proxyHost != null) {
throw new IllegalArgumentException("Proxy host already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing --proxy-host parameter");
}
arguments.proxyHost = args[i + 1];
if (arguments.proxyHost.trim().isEmpty()) {
throw new IllegalArgumentException("Proxy host must not be empty");
}
++i;
} else if ((acceptedParameters & PARAM_PROXY_PORT) != 0 && "--proxy-port".equals(arg)) {
if (arguments.proxyPort != 0) {
throw new IllegalArgumentException("Proxy port already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing --proxy-port parameter");
}
arguments.proxyPort = parsePort(args[i + 1], "--proxy-port");
++i;
} else if ((acceptedParameters & PARAM_PROXY_EXCLUSION_LIST) != 0
&& "--proxy-exclusion-list".equals(arg)) {
if (arguments.proxyExclusionList != null) {
throw new IllegalArgumentException("Proxy exclusion list already set");
}
if (i == args.length - 1) {
throw new IllegalArgumentException("Missing --proxy-exclusion-list parameter");
}
arguments.proxyExclusionList = args[i + 1];
++i;
} else if ((acceptedParameters & PARAM_SERIAL) != 0 && arguments.serial == null) {
arguments.serial = arg;
} else {
throw new IllegalArgumentException("Unexpected argument: \"" + arg + "\"");
}
}
if ((arguments.proxyHost == null) != (arguments.proxyPort == 0)) {
throw new IllegalArgumentException("Both --proxy-host and --proxy-port must be provided together");
}
if (arguments.proxyExclusionList != null && arguments.proxyHost == null) {
throw new IllegalArgumentException("Proxy exclusion list requires --proxy-host and --proxy-port");
}
if (arguments.port == 0) {
arguments.port = DEFAULT_PORT;
}
return arguments;
}

private static int parsePort(String value, String parameterName) {
int port;
try {
port = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid " + parameterName + " value: " + value, e);
}
if (port <= 0 || port >= 65536) {
throw new IllegalArgumentException("Invalid port: " + port);
}
return port;
}

public String getSerial() {
return serial;
}
Expand All @@ -96,4 +149,16 @@ public String getRoutes() {
public int getPort() {
return port;
}

public String getProxyHost() {
return proxyHost;
}

public int getProxyPort() {
return proxyPort;
}

public String getProxyExclusionList() {
return proxyExclusionList;
}
}
Loading