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 {