Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for additional VPN Configuration and target Android 13 #548

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 3 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:allowBackup="false"
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/genymobile/gnirehtet/CIDR.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.genymobile.gnirehtet;

import android.annotation.TargetApi;
import android.net.IpPrefix;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
Expand Down Expand Up @@ -74,6 +77,11 @@ public int getPrefixLength() {
return prefixLength;
}

@TargetApi(Build.VERSION_CODES.TIRAMISU)
public IpPrefix getIpPrefix() {
return new IpPrefix(address, prefixLength);
}

@Override
public String toString() {
return address.getHostAddress() + "/" + prefixLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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_EXCLUDED_ROUTES = "excludedRoutes";
public static final String EXTRA_APPS = "apps";
public static final String EXTRA_EXCLUDED_APPS = "excludedApps";

private static final int VPN_REQUEST_CODE = 0;

Expand Down Expand Up @@ -59,7 +62,20 @@ private static VpnConfiguration createConfig(Intent intent) {
if (routes == null) {
routes = new String[0];
}
return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes));
String[] excludedRoutes = intent.getStringArrayExtra(EXTRA_EXCLUDED_ROUTES);
if (excludedRoutes == null) {
excludedRoutes = new String[0];
}
String[] apps = intent.getStringArrayExtra(EXTRA_APPS);
if (apps == null) {
apps = new String[0];
}
String[] excludedApps = intent.getStringArrayExtra(EXTRA_EXCLUDED_APPS);
if (excludedApps == null) {
excludedApps = new String[0];
}

return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes), Net.toCIDRs(excludedRoutes), apps, excludedApps);
}

private boolean startGnirehtet(VpnConfiguration config) {
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
Expand Down Expand Up @@ -125,6 +126,13 @@ private boolean setupVpn(VpnConfiguration config) {
}
}

CIDR[] excludedRoutes = config.getExcludedRoutes();
if (Build.VERSION.SDK_INT >= 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
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/genymobile/gnirehtet/Notifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
23 changes: 22 additions & 1 deletion app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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() {
Expand All @@ -58,13 +70,22 @@ 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);
for (InetAddress addr : dnsServers) {
dest.writeByteArray(addr.getAddress());
}
dest.writeTypedArray(routes, 0);
dest.writeTypedArray(excludedRoutes, 0);
dest.writeStringArray(apps);
dest.writeStringArray(excludedApps);
}

@Override
Expand Down
7 changes: 1 addition & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions relay-java/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down