diff --git a/app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java b/app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java index 602d757..7099cec 100644 --- a/app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java +++ b/app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java @@ -23,6 +23,7 @@ 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_EXCLUSION_LIST = "proxyExclusionList"; private static final int VPN_REQUEST_CODE = 0; @@ -59,7 +60,11 @@ private static VpnConfiguration createConfig(Intent intent) { if (routes == null) { routes = new String[0]; } - return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes)); + String[] proxyExclusionList = intent.getStringArrayExtra(EXTRA_PROXY_EXCLUSION_LIST); + if (proxyExclusionList == null) { + proxyExclusionList = new String[0]; + } + return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes), proxyExclusionList); } private boolean startGnirehtet(VpnConfiguration config) { diff --git a/app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java b/app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java index 7e8b716..351e96f 100644 --- a/app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java +++ b/app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java @@ -22,6 +22,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; +import android.net.ProxyInfo; import android.net.VpnService; import android.os.Build; import android.os.Handler; @@ -135,6 +136,9 @@ private boolean setupVpn(VpnConfiguration config) { } } + // Set up HTTP proxy with exclusion list + setupHttpProxy(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); @@ -151,6 +155,30 @@ private boolean setupVpn(VpnConfiguration config) { return true; } + @SuppressWarnings("checkstyle:MagicNumber") + private void setupHttpProxy(Builder builder, VpnConfiguration config) { + String[] exclusionList = config.getProxyExclusionList(); + if (exclusionList == null || exclusionList.length == 0) { + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Convert exclusion list to comma-separated string + StringBuilder exclusionListStr = new StringBuilder(); + for (int i = 0; i < exclusionList.length; i++) { + if (i > 0) { + exclusionListStr.append(","); + } + exclusionListStr.append(exclusionList[i]); + } + ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(VPN_ADDRESS.getHostAddress(), 31416, exclusionList); + builder.setHttpProxy(proxyInfo); + Log.d(TAG, "HTTP proxy configured with exclusion list: " + exclusionListStr.toString()); + } else { + Log.w(TAG, "HTTP proxy exclusion list requires Android 10 (API 29) or higher"); + } + } + @SuppressWarnings("checkstyle:MagicNumber") private void setAsUndernlyingNetwork() { if (Build.VERSION.SDK_INT >= 22) { diff --git a/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java b/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java index 9027a18..f311644 100644 --- a/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java +++ b/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java @@ -26,15 +26,24 @@ public class VpnConfiguration implements Parcelable { private final InetAddress[] dnsServers; private final CIDR[] routes; + private final String[] proxyExclusionList; public VpnConfiguration() { this.dnsServers = new InetAddress[0]; this.routes = new CIDR[0]; + this.proxyExclusionList = new String[0]; } public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) { this.dnsServers = dnsServers; this.routes = routes; + this.proxyExclusionList = new String[0]; + } + + public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes, String[] proxyExclusionList) { + this.dnsServers = dnsServers; + this.routes = routes; + this.proxyExclusionList = proxyExclusionList != null ? proxyExclusionList : new String[0]; } private VpnConfiguration(Parcel source) { @@ -48,6 +57,10 @@ private VpnConfiguration(Parcel source) { throw new AssertionError("Invalid address", e); } routes = source.createTypedArray(CIDR.CREATOR); + proxyExclusionList = source.createStringArray(); + if (proxyExclusionList == null) { + proxyExclusionList = new String[0]; + } } public InetAddress[] getDnsServers() { @@ -58,6 +71,10 @@ public CIDR[] getRoutes() { return routes; } + public String[] getProxyExclusionList() { + return proxyExclusionList; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(dnsServers.length); @@ -65,6 +82,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeByteArray(addr.getAddress()); } dest.writeTypedArray(routes, 0); + dest.writeStringArray(proxyExclusionList); } @Override diff --git a/relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java b/relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java index 6413d0c..bb5a714 100644 --- a/relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java +++ b/relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java @@ -27,6 +27,7 @@ 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_EXCLUSION_LIST = 1 << 4; public static final int DEFAULT_PORT = 31416; @@ -34,6 +35,7 @@ public class CommandLineArguments { private String serial; private String dnsServers; private String routes; + private String proxyExclusionList; public static CommandLineArguments parse(int acceptedParameters, String... args) { CommandLineArguments arguments = new CommandLineArguments(); @@ -69,6 +71,15 @@ public static CommandLineArguments parse(int acceptedParameters, String... args) throw new IllegalArgumentException("Invalid port: " + arguments.port); } ++i; + } else if ((acceptedParameters & PARAM_PROXY_EXCLUSION_LIST) != 0 && "-x".equals(arg)) { + if (arguments.proxyExclusionList != null) { + throw new IllegalArgumentException("Proxy exclusion list already set"); + } + if (i == args.length - 1) { + throw new IllegalArgumentException("Missing -x parameter"); + } + arguments.proxyExclusionList = args[i + 1]; + ++i; // consume the -x parameter } else if ((acceptedParameters & PARAM_SERIAL) != 0 && arguments.serial == null) { arguments.serial = arg; } else { @@ -96,4 +107,8 @@ public String getRoutes() { public int getPort() { return port; } + + public String getProxyExclusionList() { + return proxyExclusionList; + } } diff --git a/relay-java/src/main/java/com/genymobile/gnirehtet/Main.java b/relay-java/src/main/java/com/genymobile/gnirehtet/Main.java index 9451e74..6253ab3 100644 --- a/relay-java/src/main/java/com/genymobile/gnirehtet/Main.java +++ b/relay-java/src/main/java/com/genymobile/gnirehtet/Main.java @@ -87,7 +87,7 @@ void execute(CommandLineArguments args) throws Exception { } }, RUN("run", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES - | CommandLineArguments.PARAM_PORT) { + | CommandLineArguments.PARAM_PORT | CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) { @Override String getDescription() { return "Enable reverse tethering for exactly one device:\n" @@ -99,10 +99,11 @@ String getDescription() { @Override void execute(CommandLineArguments args) throws Exception { - cmdRun(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort()); + cmdRun(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort(), args.getProxyExclusionList()); } }, - AUTORUN("autorun", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) { + AUTORUN("autorun", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT + | CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) { @Override String getDescription() { return "Enable reverse tethering for all devices:\n" @@ -112,11 +113,11 @@ String getDescription() { @Override void execute(CommandLineArguments args) throws Exception { - cmdAutorun(args.getDnsServers(), args.getRoutes(), args.getPort()); + cmdAutorun(args.getDnsServers(), args.getRoutes(), args.getPort(), args.getProxyExclusionList()); } }, START("start", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES - | CommandLineArguments.PARAM_PORT) { + | CommandLineArguments.PARAM_PORT | CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) { @Override String getDescription() { return "Start a client on the Android device and exit.\n" @@ -127,6 +128,7 @@ String getDescription() { + "If -r is given, then only reverse tether the specified routes.\n" + "If -p is given, then make the relay server listen on the specified\n" + "port. Otherwise, use port 31416.\n" + + "If -x is given, then exclude the specified domains from being proxied.\n" + "Otherwise, use 0.0.0.0/0 (redirect the whole traffic).\n" + "If the client is already started, then do nothing, and ignore\n" + "the other parameters.\n" @@ -135,10 +137,11 @@ String getDescription() { @Override void execute(CommandLineArguments args) throws Exception { - cmdStart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort()); + cmdStart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort(), args.getProxyExclusionList()); } }, - AUTOSTART("autostart", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) { + AUTOSTART("autostart", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT + | CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) { @Override String getDescription() { return "Listen for device connexions and start a client on every detected\n" @@ -149,7 +152,7 @@ String getDescription() { @Override void execute(CommandLineArguments args) throws Exception { - cmdAutostart(args.getDnsServers(), args.getRoutes(), args.getPort()); + cmdAutostart(args.getDnsServers(), args.getRoutes(), args.getPort(), args.getProxyExclusionList()); } }, STOP("stop", CommandLineArguments.PARAM_SERIAL) { @@ -166,7 +169,7 @@ void execute(CommandLineArguments args) throws Exception { } }, RESTART("restart", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES - | CommandLineArguments.PARAM_PORT) { + | CommandLineArguments.PARAM_PORT | CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) { @Override String getDescription() { return "Stop then start."; @@ -174,7 +177,7 @@ String getDescription() { @Override void execute(CommandLineArguments args) throws Exception { - cmdRestart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort()); + cmdRestart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort(), args.getProxyExclusionList()); } }, TUNNEL("tunnel", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_PORT) { @@ -231,9 +234,9 @@ private static void cmdReinstall(String serial) throws InterruptedException, IOE cmdInstall(serial); } - private static void cmdRun(String serial, String dnsServers, String routes, int port) throws IOException { + private static void cmdRun(String serial, String dnsServers, String routes, int port, String proxyExclusionList) throws IOException { // start in parallel so that the relay server is ready when the client connects - asyncStart(serial, dnsServers, routes, port); + asyncStart(serial, dnsServers, routes, port, proxyExclusionList); Runtime.getRuntime().addShutdownHook(new Thread(() -> { // executed on Ctrl+C @@ -247,10 +250,10 @@ private static void cmdRun(String serial, String dnsServers, String routes, int cmdRelay(port); } - private static void cmdAutorun(final String dnsServers, final String routes, int port) throws IOException { + private static void cmdAutorun(final String dnsServers, final String routes, int port, final String proxyExclusionList) throws IOException { new Thread(() -> { try { - cmdAutostart(dnsServers, routes, port); + cmdAutostart(dnsServers, routes, port, proxyExclusionList); } catch (Exception e) { Log.e(TAG, "Cannot auto start clients", e); } @@ -260,7 +263,7 @@ private static void cmdAutorun(final String dnsServers, final String routes, int } @SuppressWarnings("checkstyle:MagicNumber") - private static void cmdStart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException, + private static void cmdStart(String serial, String dnsServers, String routes, int port, String proxyExclusionList) throws InterruptedException, IOException, CommandExecutionException { if (mustInstallClient(serial)) { cmdInstall(serial); @@ -280,12 +283,15 @@ private static void cmdStart(String serial, String dnsServers, String routes, in if (routes != null) { Collections.addAll(cmd, "--esa", "routes", routes); } + if (proxyExclusionList != null) { + Collections.addAll(cmd, "--esa", "proxyExclusionList", proxyExclusionList); + } execAdb(serial, cmd); } - private static void cmdAutostart(final String dnsServers, final String routes, int port) { + private static void cmdAutostart(final String dnsServers, final String routes, int port, final String proxyExclusionList) { AdbMonitor adbMonitor = new AdbMonitor((serial) -> { - asyncStart(serial, dnsServers, routes, port); + asyncStart(serial, dnsServers, routes, port, proxyExclusionList); }); adbMonitor.monitor(); } @@ -296,10 +302,10 @@ private static void cmdStop(String serial) throws InterruptedException, IOExcept "com.genymobile.gnirehtet/.GnirehtetActivity"); } - private static void cmdRestart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException, + private static void cmdRestart(String serial, String dnsServers, String routes, int port, String proxyExclusionList) throws InterruptedException, IOException, CommandExecutionException { cmdStop(serial); - cmdStart(serial, dnsServers, routes, port); + cmdStart(serial, dnsServers, routes, port, proxyExclusionList); } private static void cmdTunnel(String serial, int port) throws InterruptedException, IOException, CommandExecutionException { @@ -311,10 +317,10 @@ private static void cmdRelay(int port) throws IOException { new Relay(port).run(); } - private static void asyncStart(String serial, String dnsServers, String routes, int port) { + private static void asyncStart(String serial, String dnsServers, String routes, int port, String proxyExclusionList) { new Thread(() -> { try { - cmdStart(serial, dnsServers, routes, port); + cmdStart(serial, dnsServers, routes, port, proxyExclusionList); } catch (Exception e) { Log.e(TAG, "Cannot start client", e); } @@ -412,6 +418,9 @@ private static void appendCommandUsage(StringBuilder builder, Command command) { if ((command.acceptedParameters & CommandLineArguments.PARAM_ROUTES) != 0) { builder.append(" [-r ROUTE[,ROUTE2,...]]"); } + if ((command.acceptedParameters & CommandLineArguments.PARAM_PROXY_EXCLUSION_LIST) != 0) { + builder.append(" [-x EXCLUDE[,EXCLUDE2,...]]"); + } builder.append(NL); String[] descLines = command.getDescription().split("\n"); for (String descLine : descLines) {