diff --git a/README.md b/README.md index abd0d747..8d4b7c7f 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,51 @@ To stop a client: -n com.genymobile.gnirehtet/.GnirehtetActivity +## Usage with Hotspot + +The most common way to share a VPN connection over a mobile hotspot on _[nonroot]_ Android is via a Proxy Service - for example, [Every Proxy](https://play.google.com/store/apps/details?id=com.gorillasoftware.everyproxy) by Gorilla Software LLP. + +`gnirehtet` does not, by default, support inbound proxy connections as they are automatically forwarded to your computer. There are workarounds though. + +### Android 13 + +Devices running Android 13 or newer (API Level >= 33) support individual route blocking. `gnirehtet` allows you to specify a range of routes to be excluded from being forwarded along with other traffic. + +To get started, retrieve your hotspot IP Address. This can be done through your proxy app of choice, but if it is not displayed, run the following command: + +``` +adb shell ip route +``` + +If your mobile hotspot is enabled, the entry "`wlan1`" should appear with its IP Address. + +To start the client with this IP Address excluded: +*Note: The client may need to be stopped in order to apply these changes - see above* + +``` +adb shell am start -a com.genymobile.gnirehtet.START \ + -n com.genymobile.gnirehtet/.GnirehtetActivity \ + --esa "excludedRoutes" "[ip address]" +``` + +### Android 5 + +Android 5 (API Level >= 21) added the ability to exclude specific applications in a VPN's configuration. This legacy feature makes it possible to allow proxy apps to share the `gnirehtet`'s connection. + +First, find the package name of your installed proxy app. For example, [Every Proxy](https://play.google.com/store/apps/details?id=com.gorillasoftware.everyproxy) has the package name `com.gorillasoftware.everyproxy`. + +This method will generally require an additional "bridging" app so that the proxy can still access VPN traffic. For example, the [Every Proxy Network Bridge](https://play.google.com/store/apps/details?id=com.gorillasoftware.everyproxybridge) app (must be enabled through the proxy app's settings). + +To start the client with this app excluded: +*Note: The client may need to be stopped in order to apply these changes - see above* + +``` +adb shell am start -a com.genymobile.gnirehtet.START \ + -n com.genymobile.gnirehtet/.GnirehtetActivity \ + --esa "excludedApps" "[package name]" +``` + + ## Environment variables `ADB` defines a custom path to the `adb` executable: @@ -230,6 +275,17 @@ GNIREHTET_APK=/usr/share/gnirehtet/gnirehtet.apk ./gnirehtet run ``` +## Intent Arguments + +`gnirehtet` supports several optional intent arguments for VPN configuration. + +- `dnsServers` (default: `8.8.8.8` - Google DNS) +- `routes` (default: `0.0.0.0` - all network traffic) +- `excludedRoutes` (Android 13+, API Level >= 33) +- `apps` (if argument is present, all apps not included will bypass `gnirehtet`) +- `excludedApps` (ignored if `apps` argument is present) + + ## Why _gnirehtet_? rev <<< tethering diff --git a/app/build.gradle b/app/build.gradle index e0a87b5a..92809617 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion - defaultConfig { archivesBaseName = "gnirehtet" // change apk name applicationId "com.genymobile.gnirehtet" - minSdkVersion 21 - targetSdkVersion 29 + minSdk 21 + targetSdk 33 + compileSdk 33 versionCode 9 versionName "2.5.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ffc0e121..ba6a05a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + = Build.VERSION_CODES.TIRAMISU) { + for (CIDR route : excludedRoutes) { + builder.excludeRoute(route.getIpPrefix()); + } + } + InetAddress[] dnsServers = config.getDnsServers(); if (dnsServers.length == 0) { // no DNS server defined, use Google DNS @@ -135,6 +143,26 @@ private boolean setupVpn(VpnConfiguration config) { } } + String[] apps = config.getApps(); + String[] excludedApps = config.getExcludedApps(); + if (apps.length != 0) { + for (String app : apps) { + try { + builder.addAllowedApplication(app); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot add allowed app " + app, e); + } + } + } else { + for (String app : excludedApps) { + try { + builder.addDisallowedApplication(app); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot add disallowed app " + app, e); + } + } + } + // non-blocking by default, but FileChannel is not selectable, that's stupid! // so switch to synchronous I/O to avoid polling builder.setBlocking(true); diff --git a/app/src/main/java/com/genymobile/gnirehtet/Notifier.java b/app/src/main/java/com/genymobile/gnirehtet/Notifier.java index eb1bdf40..73122be5 100644 --- a/app/src/main/java/com/genymobile/gnirehtet/Notifier.java +++ b/app/src/main/java/com/genymobile/gnirehtet/Notifier.java @@ -84,7 +84,7 @@ public void setFailure(boolean failure) { private Notification.Action createStopAction() { Intent stopIntent = GnirehtetService.createStopIntent(context); - PendingIntent stopPendingIntent = PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_ONE_SHOT); + PendingIntent stopPendingIntent = PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); // the non-deprecated constructor is not available in API 21 @SuppressWarnings("deprecation") Notification.Action.Builder actionBuilder = new Notification.Action.Builder(R.drawable.ic_close_24dp, context.getString(R.string.stop_vpn), diff --git a/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java b/app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java index 9027a184..7e91f7a0 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 CIDR[] excludedRoutes; + private final String[] apps; + private final String[] excludedApps; public VpnConfiguration() { this.dnsServers = new InetAddress[0]; this.routes = new CIDR[0]; + this.excludedRoutes = new CIDR[0]; + this.apps = new String[0]; + this.excludedApps = new String[0]; } - public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) { + public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes, CIDR[] excludedRoutes, String[] apps, String[] excludedApps) { this.dnsServers = dnsServers; this.routes = routes; + this.excludedRoutes = excludedRoutes; + this.apps = apps; + this.excludedApps = excludedApps; } private VpnConfiguration(Parcel source) { @@ -48,6 +57,9 @@ private VpnConfiguration(Parcel source) { throw new AssertionError("Invalid address", e); } routes = source.createTypedArray(CIDR.CREATOR); + excludedRoutes = source.createTypedArray(CIDR.CREATOR); + apps = source.createStringArray(); + excludedApps = source.createStringArray(); } public InetAddress[] getDnsServers() { @@ -58,6 +70,12 @@ public CIDR[] getRoutes() { return routes; } + public CIDR[] getExcludedRoutes() { return excludedRoutes; } + + public String[] getApps() { return apps; } + + public String[] getExcludedApps() { return excludedApps; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(dnsServers.length); @@ -65,6 +83,9 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeByteArray(addr.getAddress()); } dest.writeTypedArray(routes, 0); + dest.writeTypedArray(excludedRoutes, 0); + dest.writeStringArray(apps); + dest.writeStringArray(excludedApps); } @Override diff --git a/build.gradle b/build.gradle index e87852c0..4a8edb02 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -ext { - compileSdkVersion = 28 - buildToolsVersion = "28.0.3" -} - buildscript { repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:7.4.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b81e6d75..b45f5891 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Sep 07 21:43:49 CEST 2019 +#Sat Aug 10 22:43:21 EDT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/relay-java/build.gradle b/relay-java/build.gradle index a9d241b8..c5af0eb3 100644 --- a/relay-java/build.gradle +++ b/relay-java/build.gradle @@ -3,8 +3,8 @@ apply plugin: 'application' mainClassName = 'com.genymobile.gnirehtet.Main' dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' } jar {