Merge "Begin moving VPN to NetworkStateTracker pattern." into jb-mr1-dev

This commit is contained in:
Jeff Sharkey
2012-08-23 16:44:49 -07:00
committed by Android (Google) Code Review
7 changed files with 396 additions and 101 deletions

View File

@@ -0,0 +1,153 @@
/*
* 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 android.net;
import android.content.Context;
import android.os.Handler;
import com.android.internal.util.Preconditions;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Interface to control and observe state of a specific network, hiding
* network-specific details from {@link ConnectivityManager}. Surfaces events
* through the registered {@link Handler} to enable {@link ConnectivityManager}
* to respond to state changes over time.
*
* @hide
*/
public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
// TODO: better document threading expectations
// TODO: migrate to make NetworkStateTracker abstract class
public static final String PROP_TCP_BUFFER_UNKNOWN = "net.tcp.buffersize.unknown";
public static final String PROP_TCP_BUFFER_WIFI = "net.tcp.buffersize.wifi";
protected Context mContext;
private Handler mTarget;
protected NetworkInfo mNetworkInfo;
protected LinkProperties mLinkProperties;
protected LinkCapabilities mLinkCapabilities;
private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
public BaseNetworkStateTracker(int networkType) {
mNetworkInfo = new NetworkInfo(
networkType, -1, ConnectivityManager.getNetworkTypeName(networkType), null);
mLinkProperties = new LinkProperties();
mLinkCapabilities = new LinkCapabilities();
}
@Deprecated
protected Handler getTargetHandler() {
return mTarget;
}
protected final void dispatchStateChanged() {
// TODO: include snapshot of other fields when sending
mTarget.obtainMessage(EVENT_STATE_CHANGED, getNetworkInfo()).sendToTarget();
}
protected final void dispatchConfigurationChanged() {
// TODO: include snapshot of other fields when sending
mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, getNetworkInfo()).sendToTarget();
}
@Override
public final void startMonitoring(Context context, Handler target) {
mContext = Preconditions.checkNotNull(context);
mTarget = Preconditions.checkNotNull(target);
startMonitoringInternal();
}
protected abstract void startMonitoringInternal();
@Override
public final NetworkInfo getNetworkInfo() {
return new NetworkInfo(mNetworkInfo);
}
@Override
public final LinkProperties getLinkProperties() {
return new LinkProperties(mLinkProperties);
}
@Override
public final LinkCapabilities getLinkCapabilities() {
return new LinkCapabilities(mLinkCapabilities);
}
@Override
public boolean setRadio(boolean turnOn) {
// Base tracker doesn't handle radios
return true;
}
@Override
public boolean isAvailable() {
return mNetworkInfo.isAvailable();
}
@Override
public void setUserDataEnable(boolean enabled) {
// Base tracker doesn't handle enabled flags
}
@Override
public void setPolicyDataEnable(boolean enabled) {
// Base tracker doesn't handle enabled flags
}
@Override
public boolean isPrivateDnsRouteSet() {
return mPrivateDnsRouteSet.get();
}
@Override
public void privateDnsRouteSet(boolean enabled) {
mPrivateDnsRouteSet.set(enabled);
}
@Override
public boolean isDefaultRouteSet() {
return mDefaultRouteSet.get();
}
@Override
public void defaultRouteSet(boolean enabled) {
mDefaultRouteSet.set(enabled);
}
@Override
public boolean isTeardownRequested() {
return mTeardownRequested.get();
}
@Override
public void setTeardownRequested(boolean isRequested) {
mTeardownRequested.set(isRequested);
}
@Override
public void setDependencyMet(boolean met) {
// Base tracker doesn't handle dependencies
}
}

View File

@@ -16,6 +16,8 @@
package android.os; package android.os;
import android.util.Slog;
import com.google.android.collect.Maps; import com.google.android.collect.Maps;
import java.util.HashMap; import java.util.HashMap;
@@ -81,7 +83,7 @@ public class SystemService {
if (state != null) { if (state != null) {
return state; return state;
} else { } else {
throw new IllegalStateException("Service " + service + " in unknown state " + rawState); return State.STOPPED;
} }
} }

View File

@@ -17,8 +17,10 @@
package com.android.internal.net; package com.android.internal.net;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.net.NetworkInfo;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
/** /**
* A simple container used to carry information of the ongoing legacy VPN. * A simple container used to carry information of the ongoing legacy VPN.
@@ -27,6 +29,8 @@ import android.os.Parcelable;
* @hide * @hide
*/ */
public class LegacyVpnInfo implements Parcelable { public class LegacyVpnInfo implements Parcelable {
private static final String TAG = "LegacyVpnInfo";
public static final int STATE_DISCONNECTED = 0; public static final int STATE_DISCONNECTED = 0;
public static final int STATE_INITIALIZING = 1; public static final int STATE_INITIALIZING = 1;
public static final int STATE_CONNECTING = 2; public static final int STATE_CONNECTING = 2;
@@ -66,4 +70,25 @@ public class LegacyVpnInfo implements Parcelable {
return new LegacyVpnInfo[size]; return new LegacyVpnInfo[size];
} }
}; };
/**
* Return best matching {@link LegacyVpnInfo} state based on given
* {@link NetworkInfo}.
*/
public static int stateFromNetworkInfo(NetworkInfo info) {
switch (info.getDetailedState()) {
case CONNECTING:
return STATE_CONNECTING;
case CONNECTED:
return STATE_CONNECTED;
case DISCONNECTED:
return STATE_DISCONNECTED;
case FAILED:
return STATE_FAILED;
default:
Log.w(TAG, "Unhandled state " + info.getDetailedState()
+ " ; treating as disconnected");
return STATE_DISCONNECTED;
}
}
} }

View File

@@ -22,6 +22,8 @@ import android.content.Intent;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.List; import java.util.List;
/** /**
@@ -45,13 +47,14 @@ public class VpnConfig implements Parcelable {
} }
public static PendingIntent getIntentForStatusPanel(Context context, VpnConfig config) { public static PendingIntent getIntentForStatusPanel(Context context, VpnConfig config) {
Preconditions.checkNotNull(config);
Intent intent = new Intent(); Intent intent = new Intent();
intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ManageDialog"); intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ManageDialog");
intent.putExtra("config", config); intent.putExtra("config", config);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
return PendingIntent.getActivity(context, 0, intent, (config == null) ? return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent.FLAG_NO_CREATE : PendingIntent.FLAG_CANCEL_CURRENT);
} }
public String user; public String user;
@@ -64,6 +67,7 @@ public class VpnConfig implements Parcelable {
public List<String> searchDomains; public List<String> searchDomains;
public PendingIntent configureIntent; public PendingIntent configureIntent;
public long startTime = -1; public long startTime = -1;
public boolean legacy;
@Override @Override
public int describeContents() { public int describeContents() {
@@ -82,6 +86,7 @@ public class VpnConfig implements Parcelable {
out.writeStringList(searchDomains); out.writeStringList(searchDomains);
out.writeParcelable(configureIntent, flags); out.writeParcelable(configureIntent, flags);
out.writeLong(startTime); out.writeLong(startTime);
out.writeInt(legacy ? 1 : 0);
} }
public static final Parcelable.Creator<VpnConfig> CREATOR = public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -99,6 +104,7 @@ public class VpnConfig implements Parcelable {
config.searchDomains = in.createStringArrayList(); config.searchDomains = in.createStringArrayList();
config.configureIntent = in.readParcelable(null); config.configureIntent = in.readParcelable(null);
config.startTime = in.readLong(); config.startTime = in.readLong();
config.legacy = in.readInt() != 0;
return config; return config;
} }

View File

@@ -79,7 +79,7 @@ public class ManageDialog extends AlertActivity implements
mDataReceived = (TextView) view.findViewById(R.id.data_received); mDataReceived = (TextView) view.findViewById(R.id.data_received);
mDataRowsHidden = true; mDataRowsHidden = true;
if (mConfig.user.equals(VpnConfig.LEGACY_VPN)) { if (mConfig.legacy) {
mAlertParams.mIconId = android.R.drawable.ic_dialog_info; mAlertParams.mIconId = android.R.drawable.ic_dialog_info;
mAlertParams.mTitle = getText(R.string.legacy_title); mAlertParams.mTitle = getText(R.string.legacy_title);
} else { } else {

View File

@@ -115,13 +115,15 @@ import java.util.List;
* @hide * @hide
*/ */
public class ConnectivityService extends IConnectivityManager.Stub { public class ConnectivityService extends IConnectivityManager.Stub {
private static final String TAG = "ConnectivityService";
private static final boolean DBG = true; private static final boolean DBG = true;
private static final boolean VDBG = false; private static final boolean VDBG = false;
private static final String TAG = "ConnectivityService";
private static final boolean LOGD_RULES = false; private static final boolean LOGD_RULES = false;
// TODO: create better separation between radio types and network types
// how long to wait before switching back to a radio's default network // how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000; private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value // system property that can override the above value
@@ -136,6 +138,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private boolean mTetheringConfigValid = false; private boolean mTetheringConfigValid = false;
private Vpn mVpn; private Vpn mVpn;
private VpnCallback mVpnCallback = new VpnCallback();
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */ /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object(); private Object mRulesLock = new Object();
@@ -328,7 +331,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
this(context, netd, statsService, policyManager, null); this(context, netd, statsService, policyManager, null);
} }
public ConnectivityService(Context context, INetworkManagementService netd, public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager, INetworkStatsService statsService, INetworkPolicyManager policyManager,
NetworkFactory netFactory) { NetworkFactory netFactory) {
if (DBG) log("ConnectivityService starting up"); if (DBG) log("ConnectivityService starting up");
@@ -366,7 +369,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
mContext = checkNotNull(context, "missing Context"); mContext = checkNotNull(context, "missing Context");
mNetd = checkNotNull(netd, "missing INetworkManagementService"); mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager"); mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
try { try {
@@ -506,11 +509,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mTethering.getTetherableBluetoothRegexs().length != 0) && mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0); mTethering.getUpstreamIfaceTypes().length != 0);
mVpn = new Vpn(mContext, new VpnCallback()); mVpn = new Vpn(mContext, mVpnCallback, mNetd);
mVpn.startMonitoring(mContext, mTrackerHandler);
try { try {
mNetd.registerObserver(mTethering); mNetd.registerObserver(mTethering);
mNetd.registerObserver(mVpn);
mNetd.registerObserver(mDataActivityObserver); mNetd.registerObserver(mDataActivityObserver);
} catch (RemoteException e) { } catch (RemoteException e) {
loge("Error registering observer :" + e); loge("Error registering observer :" + e);
@@ -2238,9 +2241,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
public void updateNetworkSettings(NetworkStateTracker nt) { public void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName(); String key = nt.getTcpBufferSizesPropName();
String bufferSizes = SystemProperties.get(key); String bufferSizes = key == null ? null : SystemProperties.get(key);
if (bufferSizes.length() == 0) { if (TextUtils.isEmpty(bufferSizes)) {
if (VDBG) log(key + " not found in system properties. Using defaults"); if (VDBG) log(key + " not found in system properties. Using defaults");
// Setting to default values so we won't be stuck to previous values // Setting to default values so we won't be stuck to previous values
@@ -3153,10 +3156,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* be done whenever a better abstraction is developed. * be done whenever a better abstraction is developed.
*/ */
public class VpnCallback { public class VpnCallback {
private VpnCallback() { private VpnCallback() {
} }
public void onStateChanged(NetworkInfo info) {
// TODO: if connected, release delayed broadcast
// TODO: if disconnected, consider kicking off reconnect
}
public void override(List<String> dnsServers, List<String> searchDomains) { public void override(List<String> dnsServers, List<String> searchDomains) {
if (dnsServers == null) { if (dnsServers == null) {
restore(); restore();

View File

@@ -16,8 +16,11 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -28,15 +31,21 @@ import android.content.pm.ResolveInfo;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.BaseNetworkStateTracker;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver; import android.net.INetworkManagementEventObserver;
import android.net.LocalSocket; import android.net.LocalSocket;
import android.net.LocalSocketAddress; import android.net.LocalSocketAddress;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Binder; import android.os.Binder;
import android.os.FileUtils; import android.os.FileUtils;
import android.os.IBinder; import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Parcel; import android.os.Parcel;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.Process; import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.SystemService; import android.os.SystemService;
import android.util.Log; import android.util.Log;
@@ -44,7 +53,9 @@ import android.util.Log;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnConfig;
import com.android.internal.util.Preconditions;
import com.android.server.ConnectivityService.VpnCallback; import com.android.server.ConnectivityService.VpnCallback;
import com.android.server.net.BaseNetworkObserver;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@@ -57,24 +68,63 @@ import libcore.io.IoUtils;
/** /**
* @hide * @hide
*/ */
public class Vpn extends INetworkManagementEventObserver.Stub { public class Vpn extends BaseNetworkStateTracker {
private static final String TAG = "Vpn";
private static final boolean LOGD = true;
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
private final static String TAG = "Vpn";
private final static String BIND_VPN_SERVICE =
android.Manifest.permission.BIND_VPN_SERVICE;
private final Context mContext;
private final VpnCallback mCallback; private final VpnCallback mCallback;
private String mPackage = VpnConfig.LEGACY_VPN; private String mPackage = VpnConfig.LEGACY_VPN;
private String mInterface; private String mInterface;
private Connection mConnection; private Connection mConnection;
private LegacyVpnRunner mLegacyVpnRunner; private LegacyVpnRunner mLegacyVpnRunner;
private PendingIntent mStatusIntent;
public Vpn(Context context, VpnCallback callback) { public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
// TODO: create dedicated TYPE_VPN network type
super(ConnectivityManager.TYPE_DUMMY);
mContext = context; mContext = context;
mCallback = callback; mCallback = callback;
try {
netService.registerObserver(mObserver);
} catch (RemoteException e) {
Log.wtf(TAG, "Problem registering observer", e);
}
}
@Override
protected void startMonitoringInternal() {
// Ignored; events are sent through callbacks for now
}
@Override
public boolean teardown() {
// TODO: finish migration to unique tracker for each VPN
throw new UnsupportedOperationException();
}
@Override
public boolean reconnect() {
// TODO: finish migration to unique tracker for each VPN
throw new UnsupportedOperationException();
}
@Override
public String getTcpBufferSizesPropName() {
return PROP_TCP_BUFFER_UNKNOWN;
}
/**
* Update current state, dispaching event to listeners.
*/
private void updateState(DetailedState detailedState, String reason) {
if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
mNetworkInfo.setDetailedState(detailedState, reason, null);
mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
} }
/** /**
@@ -113,10 +163,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
// Reset the interface and hide the notification. // Reset the interface and hide the notification.
if (mInterface != null) { if (mInterface != null) {
jniReset(mInterface); jniReset(mInterface);
long identity = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
mCallback.restore(); try {
hideNotification(); mCallback.restore();
Binder.restoreCallingIdentity(identity); hideNotification();
} finally {
Binder.restoreCallingIdentity(token);
}
mInterface = null; mInterface = null;
} }
@@ -137,6 +190,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
mPackage = newPackage; mPackage = newPackage;
updateState(DetailedState.IDLE, "prepare");
return true; return true;
} }
@@ -145,7 +199,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
* interface. The socket is NOT closed by this method. * interface. The socket is NOT closed by this method.
* *
* @param socket The socket to be bound. * @param socket The socket to be bound.
* @param name The name of the interface. * @param interfaze The name of the interface.
*/ */
public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception { public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
PackageManager pm = mContext.getPackageManager(); PackageManager pm = mContext.getPackageManager();
@@ -209,6 +263,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
// Configure the interface. Abort if any of these steps fails. // Configure the interface. Abort if any of these steps fails.
ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
try { try {
updateState(DetailedState.CONNECTING, "establish");
String interfaze = jniGetName(tun.getFd()); String interfaze = jniGetName(tun.getFd());
if (jniSetAddresses(interfaze, config.addresses) < 1) { if (jniSetAddresses(interfaze, config.addresses) < 1) {
throw new IllegalArgumentException("At least one address must be specified"); throw new IllegalArgumentException("At least one address must be specified");
@@ -229,6 +284,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
mConnection = connection; mConnection = connection;
mInterface = interfaze; mInterface = interfaze;
} catch (RuntimeException e) { } catch (RuntimeException e) {
updateState(DetailedState.FAILED, "establish");
IoUtils.closeQuietly(tun); IoUtils.closeQuietly(tun);
throw e; throw e;
} }
@@ -239,57 +295,61 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
config.interfaze = mInterface; config.interfaze = mInterface;
// Override DNS servers and show the notification. // Override DNS servers and show the notification.
long identity = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
mCallback.override(config.dnsServers, config.searchDomains); try {
showNotification(config, label, bitmap); mCallback.override(config.dnsServers, config.searchDomains);
Binder.restoreCallingIdentity(identity); showNotification(config, label, bitmap);
} finally {
Binder.restoreCallingIdentity(token);
}
// TODO: ensure that contract class eventually marks as connected
updateState(DetailedState.AUTHENTICATING, "establish");
return tun; return tun;
} }
// INetworkManagementEventObserver.Stub @Deprecated
@Override public synchronized void interfaceStatusChanged(String iface, boolean up) {
public void interfaceAdded(String interfaze) { try {
} mObserver.interfaceStatusChanged(iface, up);
} catch (RemoteException e) {
// INetworkManagementEventObserver.Stub // ignored; target is local
@Override
public synchronized void interfaceStatusChanged(String interfaze, boolean up) {
if (!up && mLegacyVpnRunner != null) {
mLegacyVpnRunner.check(interfaze);
} }
} }
// INetworkManagementEventObserver.Stub private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
@Override @Override
public void interfaceLinkStateChanged(String interfaze, boolean up) { public void interfaceStatusChanged(String interfaze, boolean up) {
} synchronized (Vpn.this) {
if (!up && mLegacyVpnRunner != null) {
// INetworkManagementEventObserver.Stub mLegacyVpnRunner.check(interfaze);
@Override }
public synchronized void interfaceRemoved(String interfaze) {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
long identity = Binder.clearCallingIdentity();
mCallback.restore();
hideNotification();
Binder.restoreCallingIdentity(identity);
mInterface = null;
if (mConnection != null) {
mContext.unbindService(mConnection);
mConnection = null;
} else if (mLegacyVpnRunner != null) {
mLegacyVpnRunner.exit();
mLegacyVpnRunner = null;
} }
} }
}
// INetworkManagementEventObserver.Stub @Override
@Override public void interfaceRemoved(String interfaze) {
public void limitReached(String limit, String interfaze) { synchronized (Vpn.this) {
} if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
final long token = Binder.clearCallingIdentity();
public void interfaceClassDataActivityChanged(String label, boolean active) { try {
} mCallback.restore();
hideNotification();
} finally {
Binder.restoreCallingIdentity(token);
}
mInterface = null;
if (mConnection != null) {
mContext.unbindService(mConnection);
mConnection = null;
updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
} else if (mLegacyVpnRunner != null) {
mLegacyVpnRunner.exit();
mLegacyVpnRunner = null;
}
}
}
}
};
private void enforceControlPermission() { private void enforceControlPermission() {
// System user is allowed to control VPN. // System user is allowed to control VPN.
@@ -326,6 +386,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
} }
private void showNotification(VpnConfig config, String label, Bitmap icon) { private void showNotification(VpnConfig config, String label, Bitmap icon) {
mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
NotificationManager nm = (NotificationManager) NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE); mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -341,15 +403,17 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
.setLargeIcon(icon) .setLargeIcon(icon)
.setContentTitle(title) .setContentTitle(title)
.setContentText(text) .setContentText(text)
.setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config)) .setContentIntent(mStatusIntent)
.setDefaults(0) .setDefaults(0)
.setOngoing(true) .setOngoing(true)
.getNotification(); .build();
nm.notify(R.drawable.vpn_connected, notification); nm.notify(R.drawable.vpn_connected, notification);
} }
} }
private void hideNotification() { private void hideNotification() {
mStatusIntent = null;
NotificationManager nm = (NotificationManager) NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE); mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -372,25 +436,51 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
* thread, so callers will not be blocked for a long time. * thread, so callers will not be blocked for a long time.
* *
* @param config The parameters to configure the network. * @param config The parameters to configure the network.
* @param raoocn The arguments to be passed to racoon. * @param racoon The arguments to be passed to racoon.
* @param mtpd The arguments to be passed to mtpd. * @param mtpd The arguments to be passed to mtpd.
*/ */
public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
stopLegacyVpn();
// TODO: move legacy definition to settings
config.legacy = true;
// Prepare for the new request. This also checks the caller. // Prepare for the new request. This also checks the caller.
prepare(null, VpnConfig.LEGACY_VPN); prepare(null, VpnConfig.LEGACY_VPN);
updateState(DetailedState.CONNECTING, "startLegacyVpn");
// Start a new LegacyVpnRunner and we are done! // Start a new LegacyVpnRunner and we are done!
mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
mLegacyVpnRunner.start(); mLegacyVpnRunner.start();
} }
public synchronized void stopLegacyVpn() {
if (mLegacyVpnRunner != null) {
mLegacyVpnRunner.exit();
mLegacyVpnRunner = null;
synchronized (LegacyVpnRunner.TAG) {
// wait for old thread to completely finish before spinning up
// new instance, otherwise state updates can be out of order.
}
}
}
/** /**
* Return the information of the current ongoing legacy VPN. * Return the information of the current ongoing legacy VPN.
*/ */
public synchronized LegacyVpnInfo getLegacyVpnInfo() { public synchronized LegacyVpnInfo getLegacyVpnInfo() {
// Check if the caller is authorized. // Check if the caller is authorized.
enforceControlPermission(); enforceControlPermission();
return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo(); if (mLegacyVpnRunner == null) return null;
final LegacyVpnInfo info = new LegacyVpnInfo();
info.key = mLegacyVpnRunner.mConfig.user;
info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
if (mNetworkInfo.isConnected()) {
info.intent = mStatusIntent;
}
return info;
} }
/** /**
@@ -407,8 +497,6 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
private final String[] mDaemons; private final String[] mDaemons;
private final String[][] mArguments; private final String[][] mArguments;
private final LocalSocket[] mSockets; private final LocalSocket[] mSockets;
private final String mOuterInterface;
private final LegacyVpnInfo mInfo;
private long mTimer = -1; private long mTimer = -1;
@@ -416,20 +504,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
super(TAG); super(TAG);
mConfig = config; mConfig = config;
mDaemons = new String[] {"racoon", "mtpd"}; mDaemons = new String[] {"racoon", "mtpd"};
// TODO: clear arguments from memory once launched
mArguments = new String[][] {racoon, mtpd}; mArguments = new String[][] {racoon, mtpd};
mSockets = new LocalSocket[mDaemons.length]; mSockets = new LocalSocket[mDaemons.length];
mInfo = new LegacyVpnInfo();
// This is the interface which VPN is running on.
mOuterInterface = mConfig.interfaze;
// Legacy VPN is not a real package, so we use it to carry the key.
mInfo.key = mConfig.user;
mConfig.user = VpnConfig.LEGACY_VPN;
} }
public void check(String interfaze) { public void check(String interfaze) {
if (interfaze.equals(mOuterInterface)) { if (interfaze.equals(mConfig.interfaze)) {
Log.i(TAG, "Legacy VPN is going down with " + interfaze); Log.i(TAG, "Legacy VPN is going down with " + interfaze);
exit(); exit();
} }
@@ -441,15 +522,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
for (LocalSocket socket : mSockets) { for (LocalSocket socket : mSockets) {
IoUtils.closeQuietly(socket); IoUtils.closeQuietly(socket);
} }
} updateState(DetailedState.DISCONNECTED, "exit");
public LegacyVpnInfo getInfo() {
// Update the info when VPN is disconnected.
if (mInfo.state == LegacyVpnInfo.STATE_CONNECTED && mInterface == null) {
mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
mInfo.intent = null;
}
return mInfo;
} }
@Override @Override
@@ -459,6 +532,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
synchronized (TAG) { synchronized (TAG) {
Log.v(TAG, "Executing"); Log.v(TAG, "Executing");
execute(); execute();
monitorDaemons();
} }
} }
@@ -470,17 +544,17 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
} else if (now - mTimer <= 60000) { } else if (now - mTimer <= 60000) {
Thread.sleep(yield ? 200 : 1); Thread.sleep(yield ? 200 : 1);
} else { } else {
mInfo.state = LegacyVpnInfo.STATE_TIMEOUT; updateState(DetailedState.FAILED, "checkpoint");
throw new IllegalStateException("Time is up"); throw new IllegalStateException("Time is up");
} }
} }
private void execute() { private void execute() {
// Catch all exceptions so we can clean up few things. // Catch all exceptions so we can clean up few things.
boolean initFinished = false;
try { try {
// Initialize the timer. // Initialize the timer.
checkpoint(false); checkpoint(false);
mInfo.state = LegacyVpnInfo.STATE_INITIALIZING;
// Wait for the daemons to stop. // Wait for the daemons to stop.
for (String daemon : mDaemons) { for (String daemon : mDaemons) {
@@ -496,6 +570,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
throw new IllegalStateException("Cannot delete the state"); throw new IllegalStateException("Cannot delete the state");
} }
new File("/data/misc/vpn/abort").delete(); new File("/data/misc/vpn/abort").delete();
initFinished = true;
// Check if we need to restart any of the daemons. // Check if we need to restart any of the daemons.
boolean restart = false; boolean restart = false;
@@ -503,10 +578,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
restart = restart || (arguments != null); restart = restart || (arguments != null);
} }
if (!restart) { if (!restart) {
mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED; updateState(DetailedState.DISCONNECTED, "execute");
return; return;
} }
mInfo.state = LegacyVpnInfo.STATE_CONNECTING; updateState(DetailedState.CONNECTING, "execute");
// Start the daemon with arguments. // Start the daemon with arguments.
for (int i = 0; i < mDaemons.length; ++i) { for (int i = 0; i < mDaemons.length; ++i) {
@@ -633,26 +708,53 @@ public class Vpn extends INetworkManagementEventObserver.Stub {
showNotification(mConfig, null, null); showNotification(mConfig, null, null);
Log.i(TAG, "Connected!"); Log.i(TAG, "Connected!");
mInfo.state = LegacyVpnInfo.STATE_CONNECTED; updateState(DetailedState.CONNECTED, "execute");
mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null);
} }
} catch (Exception e) { } catch (Exception e) {
Log.i(TAG, "Aborting", e); Log.i(TAG, "Aborting", e);
exit(); exit();
} finally { } finally {
// Kill the daemons if they fail to stop. // Kill the daemons if they fail to stop.
if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING) { if (!initFinished) {
for (String daemon : mDaemons) { for (String daemon : mDaemons) {
SystemService.stop(daemon); SystemService.stop(daemon);
} }
} }
// Do not leave an unstable state. // Do not leave an unstable state.
if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING || if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
mInfo.state == LegacyVpnInfo.STATE_CONNECTING) { updateState(DetailedState.FAILED, "execute");
mInfo.state = LegacyVpnInfo.STATE_FAILED;
} }
} }
} }
/**
* Monitor the daemons we started, moving to disconnected state if the
* underlying services fail.
*/
private void monitorDaemons() {
if (!mNetworkInfo.isConnected()) {
return;
}
try {
while (true) {
Thread.sleep(2000);
for (int i = 0; i < mDaemons.length; i++) {
if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
return;
}
}
}
} catch (InterruptedException e) {
Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
} finally {
for (String daemon : mDaemons) {
SystemService.stop(daemon);
}
updateState(DetailedState.DISCONNECTED, "babysit");
}
}
} }
} }