summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/net/IConnectivityManager.aidl3
-rw-r--r--core/java/com/android/internal/net/VpnProfile.aidl19
-rw-r--r--core/java/com/android/internal/net/VpnProfile.java29
-rw-r--r--packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java6
-rw-r--r--services/java/com/android/server/ConnectivityService.java22
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java135
6 files changed, 195 insertions, 19 deletions
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 92aeff2..dea25dd 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -26,6 +26,7 @@ import android.os.ParcelFileDescriptor;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
/**
* Interface that answers queries about, and allows changing, the
@@ -118,7 +119,7 @@ interface IConnectivityManager
ParcelFileDescriptor establishVpn(in VpnConfig config);
- void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd);
+ void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo();
}
diff --git a/core/java/com/android/internal/net/VpnProfile.aidl b/core/java/com/android/internal/net/VpnProfile.aidl
new file mode 100644
index 0000000..a072160
--- /dev/null
+++ b/core/java/com/android/internal/net/VpnProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+parcelable VpnProfile;
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 154b16b..d6c5702 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -16,6 +16,9 @@
package com.android.internal.net;
+import android.os.Parcel;
+import android.os.Parcelable;
+
import java.nio.charset.Charsets;
/**
@@ -27,7 +30,7 @@ import java.nio.charset.Charsets;
*
* @hide
*/
-public class VpnProfile implements Cloneable {
+public class VpnProfile implements Cloneable, Parcelable {
// Match these constants with R.array.vpn_types.
public static final int TYPE_PPTP = 0;
public static final int TYPE_L2TP_IPSEC_PSK = 1;
@@ -120,4 +123,28 @@ public class VpnProfile implements Cloneable {
builder.append('\0').append(ipsecServerCert);
return builder.toString().getBytes(Charsets.UTF_8);
}
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(key);
+ out.writeByteArray(encode());
+ }
+
+ public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() {
+ @Override
+ public VpnProfile createFromParcel(Parcel in) {
+ final String key = in.readString();
+ return decode(key, in.createByteArray());
+ }
+
+ @Override
+ public VpnProfile[] newArray(int size) {
+ return new VpnProfile[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
}
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index ff9bbc5..96de1b9 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -123,7 +123,11 @@ public class ManageDialog extends AlertActivity implements
if (which == DialogInterface.BUTTON_POSITIVE) {
mConfig.configureIntent.send();
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
- mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
+ if (mConfig.legacy) {
+ mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } else {
+ mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
+ }
}
} catch (Exception e) {
Log.e(TAG, "onClick", e);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index d0db0d2..3c2ab16 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -31,6 +31,8 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
@@ -78,6 +80,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.security.KeyStore;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
@@ -85,8 +88,10 @@ import android.util.SparseIntArray;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
@@ -137,6 +142,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
+ private final KeyStore mKeyStore;
+
private Vpn mVpn;
private VpnCallback mVpnCallback = new VpnCallback();
@@ -371,6 +378,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mContext = checkNotNull(context, "missing Context");
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+ mKeyStore = KeyStore.getInstance();
try {
mPolicyManager.registerListener(mPolicyListener);
@@ -3124,14 +3132,16 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
/**
- * Start legacy VPN and return an intent to VpnDialogs. This method is
- * used by VpnSettings and not available in ConnectivityManager.
- * Permissions are checked in Vpn class.
- * @hide
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
*/
@Override
- public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
- mVpn.startLegacyVpn(config, racoon, mtpd);
+ public void startLegacyVpn(VpnProfile profile) {
+ final LinkProperties egress = getActiveLinkProperties();
+ if (egress == null) {
+ throw new IllegalStateException("Missing active network connection");
+ }
+ mVpn.startLegacyVpn(profile, mKeyStore, egress);
}
/**
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index d490f24..d96bd0d 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -34,9 +34,11 @@ import android.graphics.drawable.Drawable;
import android.net.BaseNetworkStateTracker;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.NetworkInfo;
+import android.net.RouteInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Binder;
import android.os.FileUtils;
@@ -48,11 +50,15 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemService;
+import android.security.Credentials;
+import android.security.KeyStore;
import android.util.Log;
+import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
import com.android.internal.util.Preconditions;
import com.android.server.ConnectivityService.VpnCallback;
import com.android.server.net.BaseNetworkObserver;
@@ -60,6 +66,8 @@ import com.android.server.net.BaseNetworkObserver;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
import java.nio.charset.Charsets;
import java.util.Arrays;
@@ -430,20 +438,127 @@ public class Vpn extends BaseNetworkStateTracker {
private native int jniCheck(String interfaze);
private native void jniProtect(int socket, String interfaze);
+ private static String findLegacyVpnGateway(LinkProperties prop) {
+ for (RouteInfo route : prop.getRoutes()) {
+ // Currently legacy VPN only works on IPv4.
+ if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
+ return route.getGateway().getHostAddress();
+ }
+ }
+
+ throw new IllegalStateException("Unable to find suitable gateway");
+ }
+
/**
- * Start legacy VPN. This method stops the daemons and restart them
- * if arguments are not null. Heavy things are offloaded to another
- * thread, so callers will not be blocked for a long time.
- *
- * @param config The parameters to configure the network.
- * @param racoon The arguments to be passed to racoon.
- * @param mtpd The arguments to be passed to mtpd.
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
*/
- public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
- stopLegacyVpn();
+ public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
+ if (keyStore.state() != KeyStore.State.UNLOCKED) {
+ throw new IllegalStateException("KeyStore isn't unlocked");
+ }
+
+ final String iface = egress.getInterfaceName();
+ final String gateway = findLegacyVpnGateway(egress);
+
+ // Load certificates.
+ String privateKey = "";
+ String userCert = "";
+ String caCert = "";
+ String serverCert = "";
+ if (!profile.ipsecUserCert.isEmpty()) {
+ privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
+ byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
+ userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (!profile.ipsecCaCert.isEmpty()) {
+ byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
+ caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (!profile.ipsecServerCert.isEmpty()) {
+ byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
+ serverCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
+ throw new IllegalStateException("Cannot load credentials");
+ }
+
+ // Prepare arguments for racoon.
+ String[] racoon = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ racoon = new String[] {
+ iface, profile.server, "udppsk", profile.ipsecIdentifier,
+ profile.ipsecSecret, "1701",
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ racoon = new String[] {
+ iface, profile.server, "udprsa", privateKey, userCert,
+ caCert, serverCert, "1701",
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ racoon = new String[] {
+ iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
+ profile.ipsecSecret, profile.username, profile.password, "", gateway,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ racoon = new String[] {
+ iface, profile.server, "xauthrsa", privateKey, userCert,
+ caCert, serverCert, profile.username, profile.password, "", gateway,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ racoon = new String[] {
+ iface, profile.server, "hybridrsa",
+ caCert, serverCert, profile.username, profile.password, "", gateway,
+ };
+ break;
+ }
+
+ // Prepare arguments for mtpd.
+ String[] mtpd = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ mtpd = new String[] {
+ iface, "pptp", profile.server, "1723",
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ (profile.mppe ? "+mppe" : "nomppe"),
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mtpd = new String[] {
+ iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ };
+ break;
+ }
- // TODO: move legacy definition to settings
+ VpnConfig config = new VpnConfig();
config.legacy = true;
+ config.user = profile.key;
+ config.interfaze = iface;
+ config.session = profile.name;
+ config.routes = profile.routes;
+ if (!profile.dnsServers.isEmpty()) {
+ config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
+ }
+ if (!profile.searchDomains.isEmpty()) {
+ config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
+ }
+
+ startLegacyVpn(config, racoon, mtpd);
+ }
+
+ private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+ stopLegacyVpn();
// Prepare for the new request. This also checks the caller.
prepare(null, VpnConfig.LEGACY_VPN);