diff options
Diffstat (limited to 'services/java/com/android/server/connectivity')
| -rw-r--r-- | services/java/com/android/server/connectivity/Tethering.java | 38 | ||||
| -rw-r--r-- | services/java/com/android/server/connectivity/Vpn.java | 451 |
2 files changed, 366 insertions, 123 deletions
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 682ecf8..79fb458 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -43,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -319,6 +320,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void limitReached(String limitName, String iface) {} + public void interfaceClassDataActivityChanged(String label, boolean active) {} + public int tether(String iface) { if (DBG) Log.d(TAG, "Tethering " + iface); TetherInterfaceSM sm = null; @@ -415,7 +418,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList); broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, erroredList); - mContext.sendStickyBroadcast(broadcast); + mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL); if (DBG) { Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + activeList.size() + ", " + erroredList.size()); @@ -501,8 +504,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mUsbTetherRequested = false; } } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); + NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + if (networkInfo != null && + networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) { + if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); + mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); + } } } } @@ -1135,7 +1143,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private State mStopTetheringErrorState; private State mSetDnsForwardersErrorState; - private ArrayList mNotifyList; + private ArrayList<TetherInterfaceSM> mNotifyList; private int mCurrentConnectionSequence; private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; @@ -1165,7 +1173,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mSetDnsForwardersErrorState = new SetDnsForwardersErrorState(); addState(mSetDnsForwardersErrorState); - mNotifyList = new ArrayList(); + mNotifyList = new ArrayList<TetherInterfaceSM>(); setInitialState(mInitialState); } @@ -1362,8 +1370,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { if (DBG) Log.d(TAG, "notifying tethered with iface =" + ifaceName); mUpstreamIfaceName = ifaceName; - for (Object o : mNotifyList) { - TetherInterfaceSM sm = (TetherInterfaceSM)o; + for (TetherInterfaceSM sm : mNotifyList) { sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, ifaceName); } @@ -1381,13 +1388,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_MODE_REQUESTED: TetherInterfaceSM who = (TetherInterfaceSM)message.obj; - if (VDBG) Log.d(TAG, "Tether Mode requested by " + who.toString()); + if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; - if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who.toString()); + if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { mNotifyList.remove(who); @@ -1424,18 +1431,29 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_MODE_REQUESTED: TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, mUpstreamIfaceName); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; + if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { + if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who); mNotifyList.remove(index); if (mNotifyList.isEmpty()) { turnOffMasterTetherSettings(); // transitions appropriately + } else { + if (DBG) { + Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() + + " live requests:"); + for (Object o : mNotifyList) Log.d(TAG, " " + o); + } } + } else { + Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who); } break; case CMD_UPSTREAM_CHANGED: @@ -1562,7 +1580,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { pw.println(); pw.println("Tether state:"); for (Object o : mIfaces.values()) { - pw.println(" "+o.toString()); + pw.println(" " + o); } } pw.println(); diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 4b82037..b3cbb84 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -16,8 +16,11 @@ package com.android.server.connectivity; +import static android.Manifest.permission.BIND_VPN_SERVICE; + import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -25,55 +28,116 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; 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; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; +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; 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; +import libcore.io.IoUtils; + /** * @hide */ -public class Vpn extends INetworkManagementEventObserver.Stub { - - private final static String TAG = "Vpn"; - - private final static String BIND_VPN_SERVICE = - android.Manifest.permission.BIND_VPN_SERVICE; +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 Context mContext; private final VpnCallback mCallback; private String mPackage = VpnConfig.LEGACY_VPN; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; + private PendingIntent mStatusIntent; + private boolean mEnableNotif = true; - 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; mCallback = callback; + + try { + netService.registerObserver(mObserver); + } catch (RemoteException e) { + Log.wtf(TAG, "Problem registering observer", e); + } + } + + public void setEnableNotifications(boolean enableNotif) { + mEnableNotif = enableNotif; + } + + @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)); } /** @@ -112,10 +176,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Reset the interface and hide the notification. if (mInterface != null) { jniReset(mInterface); - long identity = Binder.clearCallingIdentity(); - mCallback.restore(); - hideNotification(); - Binder.restoreCallingIdentity(identity); + final long token = Binder.clearCallingIdentity(); + try { + mCallback.restore(); + hideNotification(); + } finally { + Binder.restoreCallingIdentity(token); + } mInterface = null; } @@ -136,6 +203,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + updateState(DetailedState.IDLE, "prepare"); return true; } @@ -144,7 +212,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { * interface. The socket is NOT closed by this method. * * @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 { PackageManager pm = mContext.getPackageManager(); @@ -208,6 +276,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); try { + updateState(DetailedState.CONNECTING, "establish"); String interfaze = jniGetName(tun.getFd()); if (jniSetAddresses(interfaze, config.addresses) < 1) { throw new IllegalArgumentException("At least one address must be specified"); @@ -228,11 +297,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub { mConnection = connection; mInterface = interfaze; } catch (RuntimeException e) { - try { - tun.close(); - } catch (Exception ex) { - // ignore - } + updateState(DetailedState.FAILED, "establish"); + IoUtils.closeQuietly(tun); throw e; } Log.i(TAG, "Established by " + config.user + " on " + mInterface); @@ -242,54 +308,61 @@ public class Vpn extends INetworkManagementEventObserver.Stub { config.interfaze = mInterface; // Override DNS servers and show the notification. - long identity = Binder.clearCallingIdentity(); - mCallback.override(config.dnsServers, config.searchDomains); - showNotification(config, label, bitmap); - Binder.restoreCallingIdentity(identity); + final long token = Binder.clearCallingIdentity(); + try { + mCallback.override(config.dnsServers, config.searchDomains); + showNotification(config, label, bitmap); + } finally { + Binder.restoreCallingIdentity(token); + } + // TODO: ensure that contract class eventually marks as connected + updateState(DetailedState.AUTHENTICATING, "establish"); return tun; } - // INetworkManagementEventObserver.Stub - @Override - public void interfaceAdded(String interfaze) { - } - - // INetworkManagementEventObserver.Stub - @Override - public synchronized void interfaceStatusChanged(String interfaze, boolean up) { - if (!up && mLegacyVpnRunner != null) { - mLegacyVpnRunner.check(interfaze); + @Deprecated + public synchronized void interfaceStatusChanged(String iface, boolean up) { + try { + mObserver.interfaceStatusChanged(iface, up); + } catch (RemoteException e) { + // ignored; target is local } } - // INetworkManagementEventObserver.Stub - @Override - public void interfaceLinkStateChanged(String interfaze, boolean up) { - } - - // INetworkManagementEventObserver.Stub - @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; + private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() { + @Override + public void interfaceStatusChanged(String interfaze, boolean up) { + synchronized (Vpn.this) { + if (!up && mLegacyVpnRunner != null) { + mLegacyVpnRunner.check(interfaze); + } } } - } - // INetworkManagementEventObserver.Stub - @Override - public void limitReached(String limit, String interfaze) { - } + @Override + public void interfaceRemoved(String interfaze) { + synchronized (Vpn.this) { + if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { + final long token = Binder.clearCallingIdentity(); + 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() { // System user is allowed to control VPN. @@ -326,6 +399,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } private void showNotification(VpnConfig config, String label, Bitmap icon) { + if (!mEnableNotif) return; + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config); + NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -341,15 +417,18 @@ public class Vpn extends INetworkManagementEventObserver.Stub { .setLargeIcon(icon) .setContentTitle(title) .setContentText(text) - .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config)) + .setContentIntent(mStatusIntent) .setDefaults(0) .setOngoing(true) - .getNotification(); + .build(); nm.notify(R.drawable.vpn_connected, notification); } } private void hideNotification() { + if (!mEnableNotif) return; + mStatusIntent = null; + NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -366,31 +445,172 @@ public class Vpn extends INetworkManagementEventObserver.Stub { 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 raoocn 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) { + 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; + } + + 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); + updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); 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. */ public synchronized LegacyVpnInfo getLegacyVpnInfo() { // Check if the caller is authorized. 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; + } + + public VpnConfig getLegacyVpnConfig() { + if (mLegacyVpnRunner != null) { + return mLegacyVpnRunner.mConfig; + } else { + return null; + } } /** @@ -407,8 +627,6 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private final String[] mDaemons; private final String[][] mArguments; private final LocalSocket[] mSockets; - private final String mOuterInterface; - private final LegacyVpnInfo mInfo; private long mTimer = -1; @@ -416,20 +634,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub { super(TAG); mConfig = config; mDaemons = new String[] {"racoon", "mtpd"}; + // TODO: clear arguments from memory once launched mArguments = new String[][] {racoon, mtpd}; 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) { - if (interfaze.equals(mOuterInterface)) { + if (interfaze.equals(mConfig.interfaze)) { Log.i(TAG, "Legacy VPN is going down with " + interfaze); exit(); } @@ -439,21 +650,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // We assume that everything is reset after stopping the daemons. interrupt(); for (LocalSocket socket : mSockets) { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } - } - - 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; + IoUtils.closeQuietly(socket); } - return mInfo; + updateState(DetailedState.DISCONNECTED, "exit"); } @Override @@ -463,6 +662,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { synchronized (TAG) { Log.v(TAG, "Executing"); execute(); + monitorDaemons(); } } @@ -474,22 +674,21 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } else if (now - mTimer <= 60000) { Thread.sleep(yield ? 200 : 1); } else { - mInfo.state = LegacyVpnInfo.STATE_TIMEOUT; + updateState(DetailedState.FAILED, "checkpoint"); throw new IllegalStateException("Time is up"); } } private void execute() { // Catch all exceptions so we can clean up few things. + boolean initFinished = false; try { // Initialize the timer. checkpoint(false); - mInfo.state = LegacyVpnInfo.STATE_INITIALIZING; // Wait for the daemons to stop. for (String daemon : mDaemons) { - String key = "init.svc." + daemon; - while (!"stopped".equals(SystemProperties.get(key, "stopped"))) { + while (!SystemService.isStopped(daemon)) { checkpoint(true); } } @@ -501,6 +700,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { throw new IllegalStateException("Cannot delete the state"); } new File("/data/misc/vpn/abort").delete(); + initFinished = true; // Check if we need to restart any of the daemons. boolean restart = false; @@ -508,10 +708,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub { restart = restart || (arguments != null); } if (!restart) { - mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED; + updateState(DetailedState.DISCONNECTED, "execute"); return; } - mInfo.state = LegacyVpnInfo.STATE_CONNECTING; + updateState(DetailedState.CONNECTING, "execute"); // Start the daemon with arguments. for (int i = 0; i < mDaemons.length; ++i) { @@ -522,11 +722,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Start the daemon. String daemon = mDaemons[i]; - SystemProperties.set("ctl.start", daemon); + SystemService.start(daemon); // Wait for the daemon to start. - String key = "init.svc." + daemon; - while (!"running".equals(SystemProperties.get(key))) { + while (!SystemService.isRunning(daemon)) { checkpoint(true); } @@ -582,8 +781,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Check if a running daemon is dead. for (int i = 0; i < mDaemons.length; ++i) { String daemon = mDaemons[i]; - if (mArguments[i] != null && !"running".equals( - SystemProperties.get("init.svc." + daemon))) { + if (mArguments[i] != null && !SystemService.isRunning(daemon)) { throw new IllegalStateException(daemon + " is dead"); } } @@ -640,26 +838,53 @@ public class Vpn extends INetworkManagementEventObserver.Stub { showNotification(mConfig, null, null); Log.i(TAG, "Connected!"); - mInfo.state = LegacyVpnInfo.STATE_CONNECTED; - mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null); + updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); exit(); } finally { // Kill the daemons if they fail to stop. - if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING) { + if (!initFinished) { for (String daemon : mDaemons) { - SystemProperties.set("ctl.stop", daemon); + SystemService.stop(daemon); } } // Do not leave an unstable state. - if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING || - mInfo.state == LegacyVpnInfo.STATE_CONNECTING) { - mInfo.state = LegacyVpnInfo.STATE_FAILED; + if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { + updateState(DetailedState.FAILED, "execute"); } } } + + /** + * 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"); + } + } } } |
