diff options
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); |