Skip to content
This repository was archived by the owner on Aug 2, 2023. It is now read-only.

Commit 2e7dd79

Browse files
committed
Better (but more complicated) method for (dis)connecting OpenVPN
1 parent 6b0e5fa commit 2e7dd79

12 files changed

+386
-21
lines changed

app/src/main/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<service
4949
android:name=".service.StunnelIntentService"
5050
android:exported="false" />
51+
<service android:name=".service.ServiceStopReceiver$OpenVPNIntentService" />
5152

5253
<receiver
5354
android:name=".service.BootReceiver"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package de.blinkt.openvpn.api;
2+
3+
parcelable APIVpnProfile;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// IOpenVPNAPIService.aidl
2+
package de.blinkt.openvpn.api;
3+
4+
import de.blinkt.openvpn.api.APIVpnProfile;
5+
import de.blinkt.openvpn.api.IOpenVPNStatusCallback;
6+
7+
import android.content.Intent;
8+
import android.os.ParcelFileDescriptor;
9+
10+
interface IOpenVPNAPIService {
11+
List<APIVpnProfile> getProfiles();
12+
13+
void startProfile (String profileUUID);
14+
15+
/** Use a profile with all certificates etc. embedded,
16+
* old version which does not return the UUID of the addded profile, see
17+
* below for a version that return the UUID on add */
18+
boolean addVPNProfile (String name, String config);
19+
20+
/** start a profile using a config as inline string. Make sure that all needed data is inlined,
21+
* e.g., using <ca>...</ca> or <auth-user-pass>...</auth-user-pass>
22+
* See the OpenVPN manual page for more on inlining files */
23+
void startVPN (in String inlineconfig);
24+
25+
/** This permission framework is used to avoid confused deputy style attack to the VPN
26+
* calling this will give null if the app is allowed to use the external API and an Intent
27+
* that can be launched to request permissions otherwise */
28+
Intent prepare (in String packagename);
29+
30+
/** Used to trigger to the Android VPN permission dialog (VPNService.prepare()) in advance,
31+
* if this return null OpenVPN for ANdroid already has the permissions otherwise you can start the returned Intent
32+
* to let OpenVPN for Android request the permission */
33+
Intent prepareVPNService ();
34+
35+
/* Disconnect the VPN */
36+
void disconnect();
37+
38+
/* Pause the VPN (same as using the pause feature in the notifcation bar) */
39+
void pause();
40+
41+
/* Resume the VPN (same as using the pause feature in the notifcation bar) */
42+
void resume();
43+
44+
/**
45+
* Registers to receive OpenVPN Status Updates
46+
*/
47+
void registerStatusCallback(in IOpenVPNStatusCallback cb);
48+
49+
/**
50+
* Remove a previously registered callback interface.
51+
*/
52+
void unregisterStatusCallback(in IOpenVPNStatusCallback cb);
53+
54+
/** Remove a profile by UUID */
55+
void removeProfile (in String profileUUID);
56+
57+
/** Request a socket to be protected as a VPN socket would be. Useful for creating
58+
* a helper socket for an app controlling OpenVPN
59+
* Before calling this function you should make sure OpenVPN for Android may actually
60+
* this function by checking if prepareVPNService returns null; */
61+
boolean protectSocket(in ParcelFileDescriptor fd);
62+
63+
64+
/** Use a profile with all certificates etc. embedded */
65+
APIVpnProfile addNewVPNProfile (String name, boolean userEditable, String config);
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package de.blinkt.openvpn.api;
2+
3+
/**
4+
* Example of a callback interface used by IRemoteService to send
5+
* synchronous notifications back to its clients. Note that this is a
6+
* one-way interface so the server does not block waiting for the client.
7+
*/
8+
interface IOpenVPNStatusCallback {
9+
/**
10+
* Called when the service has a new status for you.
11+
*/
12+
oneway void newStatus(in String uuid, in String state, in String message, in String level);
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2012-2016 Arne Schwabe
3+
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4+
*/
5+
6+
package de.blinkt.openvpn.api;
7+
8+
import android.os.Parcel;
9+
import android.os.Parcelable;
10+
11+
public class APIVpnProfile implements Parcelable {
12+
13+
public final String mUUID;
14+
public final String mName;
15+
public final boolean mUserEditable;
16+
//public final String mProfileCreator;
17+
18+
public APIVpnProfile(Parcel in) {
19+
mUUID = in.readString();
20+
mName = in.readString();
21+
mUserEditable = in.readInt() != 0;
22+
//mProfileCreator = in.readString();
23+
}
24+
25+
public APIVpnProfile(String uuidString, String name, boolean userEditable, String profileCreator) {
26+
mUUID = uuidString;
27+
mName = name;
28+
mUserEditable = userEditable;
29+
//mProfileCreator = profileCreator;
30+
}
31+
32+
@Override
33+
public int describeContents() {
34+
return 0;
35+
}
36+
37+
@Override
38+
public void writeToParcel(Parcel dest, int flags) {
39+
dest.writeString(mUUID);
40+
dest.writeString(mName);
41+
if (mUserEditable)
42+
dest.writeInt(0);
43+
else
44+
dest.writeInt(1);
45+
//dest.writeString(mProfileCreator);
46+
}
47+
48+
public static final Parcelable.Creator<APIVpnProfile> CREATOR
49+
= new Parcelable.Creator<APIVpnProfile>() {
50+
public APIVpnProfile createFromParcel(Parcel in) {
51+
return new APIVpnProfile(in);
52+
}
53+
54+
public APIVpnProfile[] newArray(int size) {
55+
return new APIVpnProfile[size];
56+
}
57+
};
58+
59+
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package link.infra.sslsocks.gui;
2+
3+
import android.app.Activity;
4+
import android.content.ComponentName;
5+
import android.content.Context;
6+
import android.content.Intent;
7+
import android.content.ServiceConnection;
8+
import android.os.IBinder;
9+
import android.os.RemoteException;
10+
import android.util.Log;
11+
12+
import java.lang.ref.WeakReference;
13+
import java.util.List;
14+
import java.util.Objects;
15+
16+
import de.blinkt.openvpn.api.APIVpnProfile;
17+
import de.blinkt.openvpn.api.IOpenVPNAPIService;
18+
19+
public class OpenVPNIntegrationHandler {
20+
private IOpenVPNAPIService srv = null;
21+
public static final int PERMISSION_REQUEST = 100;
22+
public static final int VPN_PERMISSION_REQUEST = 101;
23+
24+
private static final String TAG = OpenVPNIntegrationHandler.class.getSimpleName();
25+
private WeakReference<Context> ctxRef;
26+
private boolean isActivity = true;
27+
private final Runnable doneCallback;
28+
private final String profileName;
29+
private final boolean shouldDisconnect;
30+
31+
public OpenVPNIntegrationHandler(Activity ctx, Runnable doneCallback, String profile, boolean shouldDisconnect) {
32+
this.ctxRef = new WeakReference<Context>(ctx);
33+
this.doneCallback = doneCallback;
34+
this.profileName = profile;
35+
this.shouldDisconnect = shouldDisconnect;
36+
Log.d(TAG, "created");
37+
}
38+
39+
public OpenVPNIntegrationHandler(Context ctx, Runnable doneCallback, String profile, boolean shouldDisconnect) {
40+
isActivity = false;
41+
this.ctxRef = new WeakReference<>(ctx);
42+
this.doneCallback = doneCallback;
43+
this.profileName = profile;
44+
this.shouldDisconnect = shouldDisconnect;
45+
Log.d(TAG, "created");
46+
}
47+
48+
private final ServiceConnection mConnection = new ServiceConnection() {
49+
public void onServiceConnected(ComponentName className, IBinder service) {
50+
srv = IOpenVPNAPIService.Stub.asInterface(service);
51+
52+
try {
53+
Intent intent = srv.prepare(ctxRef.get().getPackageName());
54+
if (intent != null && isActivity) {
55+
Log.d(TAG, "requesting permission");
56+
((Activity)ctxRef.get()).startActivityForResult(intent, PERMISSION_REQUEST);
57+
} else {
58+
doVpnPermissionRequest();
59+
}
60+
} catch (RemoteException e) {
61+
Log.e(TAG, "Failed to connect to OpenVPN", e);
62+
}
63+
}
64+
65+
public void onServiceDisconnected(ComponentName className) {
66+
srv = null;
67+
}
68+
};
69+
70+
public void bind() {
71+
if (srv != null || ctxRef.get() == null) return;
72+
Intent intent = new Intent(IOpenVPNAPIService.class.getName());
73+
intent.setPackage("de.blinkt.openvpn");
74+
ctxRef.get().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
75+
Log.d(TAG, "bound");
76+
}
77+
78+
public void unbind() {
79+
if (ctxRef.get() == null) return;
80+
ctxRef.get().unbindService(mConnection);
81+
}
82+
83+
public void doVpnPermissionRequest() {
84+
try {
85+
Intent intent = srv.prepareVPNService();
86+
if (intent != null && isActivity) {
87+
Log.d(TAG, "requesting vpn perms");
88+
((Activity)ctxRef.get()).startActivityForResult(intent, VPN_PERMISSION_REQUEST);
89+
} else {
90+
if (shouldDisconnect) {
91+
disconnect();
92+
} else {
93+
connectProfile();
94+
}
95+
}
96+
} catch (RemoteException e) {
97+
Log.e(TAG, "Failed to connect to OpenVPN", e);
98+
}
99+
}
100+
101+
public void connectProfile() {
102+
try {
103+
List<APIVpnProfile> profiles = srv.getProfiles();
104+
APIVpnProfile foundProfile = null;
105+
for (APIVpnProfile profile : profiles) {
106+
if (Objects.equals(profile.mName, profileName)) {
107+
foundProfile = profile;
108+
break;
109+
}
110+
}
111+
if (foundProfile == null) {
112+
Log.e(TAG, "Failed to find profile");
113+
return;
114+
}
115+
Log.d(TAG, "starting profile");
116+
srv.startProfile(foundProfile.mUUID);
117+
} catch (RemoteException e) {
118+
Log.e(TAG, "Failed to connect to OpenVPN", e);
119+
}
120+
doneCallback.run();
121+
}
122+
123+
public void disconnect() {
124+
try {
125+
srv.disconnect();
126+
} catch (RemoteException e) {
127+
Log.e(TAG, "Failed to connect to OpenVPN", e);
128+
}
129+
if (shouldDisconnect) {
130+
doneCallback.run();
131+
}
132+
}
133+
134+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
11
package link.infra.sslsocks.gui;
22

33
import android.app.Activity;
4+
import android.content.Intent;
45
import android.os.Bundle;
56

7+
import androidx.preference.PreferenceManager;
8+
69
import link.infra.sslsocks.service.StunnelIntentService;
710

811
public class ServiceShortcutActivity extends Activity {
12+
OpenVPNIntegrationHandler openVPNIntegrationHandler;
913

1014
@Override
1115
protected void onCreate(Bundle savedInstanceState) {
1216
super.onCreate(savedInstanceState);
1317

1418
StunnelIntentService.start(this);
15-
finish();
19+
String openVpnProfile = PreferenceManager.getDefaultSharedPreferences(this).getString("open_vpn_profile", "");
20+
if (openVpnProfile != null && openVpnProfile.trim().length() > 0) {
21+
openVPNIntegrationHandler = new OpenVPNIntegrationHandler(this, new Runnable() {
22+
@Override
23+
public void run() {
24+
ServiceShortcutActivity.this.finish();
25+
}
26+
}, openVpnProfile, false);
27+
openVPNIntegrationHandler.bind();
28+
}
29+
if (openVPNIntegrationHandler == null) {
30+
finish();
31+
}
32+
}
33+
34+
@Override
35+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
36+
super.onActivityResult(requestCode, resultCode, data);
37+
if (requestCode == OpenVPNIntegrationHandler.PERMISSION_REQUEST) {
38+
if (resultCode == RESULT_OK && openVPNIntegrationHandler != null) {
39+
openVPNIntegrationHandler.doVpnPermissionRequest();
40+
}
41+
} else if (requestCode == OpenVPNIntegrationHandler.VPN_PERMISSION_REQUEST) {
42+
if (resultCode == RESULT_OK && openVPNIntegrationHandler != null) {
43+
openVPNIntegrationHandler.connectProfile();
44+
}
45+
}
46+
}
47+
48+
@Override
49+
protected void onDestroy() {
50+
super.onDestroy();
51+
if (openVPNIntegrationHandler != null) {
52+
openVPNIntegrationHandler.unbind();
53+
}
1654
}
1755
}

0 commit comments

Comments
 (0)