diff options
Diffstat (limited to 'packages/VpnServices/src/com/android/server/vpn/VpnService.java')
-rw-r--r-- | packages/VpnServices/src/com/android/server/vpn/VpnService.java | 469 |
1 files changed, 254 insertions, 215 deletions
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java index 87bd780..b107c7d 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java @@ -28,7 +28,11 @@ import android.text.TextUtils; import android.util.Log; import java.io.IOException; +import java.io.Serializable; +import java.net.DatagramSocket; +import java.net.Socket; import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -36,7 +40,9 @@ import java.util.List; /** * The service base class for managing a type of VPN connection. */ -abstract class VpnService<E extends VpnProfile> { +abstract class VpnService<E extends VpnProfile> implements Serializable { + protected static final long serialVersionUID = 1L; + private static final boolean DBG = true; private static final int NOTIFICATION_ID = 1; private static final String DNS1 = "net.dns1"; @@ -50,29 +56,34 @@ abstract class VpnService<E extends VpnProfile> { private static final String REMOTE_IP = "net.ipremote"; private static final String DNS_DOMAIN_SUFFICES = "net.dns.search"; + private static final int CHALLENGE_ERROR_CODE = 5; + private static final int REMOTE_HUNG_UP_ERROR_CODE = 7; + private static final int AUTH_ERROR_CODE = 51; + private final String TAG = VpnService.class.getSimpleName(); + // FIXME: profile is only needed in connecting phase, so we can just save + // the profile name and service class name for recovery E mProfile; - VpnServiceBinder mContext; + transient VpnServiceBinder mContext; private VpnState mState = VpnState.IDLE; - private boolean mInError; - private VpnConnectingError mError; + private Throwable mError; // connection settings private String mOriginalDns1; private String mOriginalDns2; - private String mVpnDns1 = ""; - private String mVpnDns2 = ""; private String mOriginalDomainSuffices; + private String mLocalIp; + private String mLocalIf; private long mStartTime; // VPN connection start time - // for helping managing multiple Android services - private ServiceHelper mServiceHelper = new ServiceHelper(); + // for helping managing multiple daemons + private DaemonHelper mDaemonHelper = new DaemonHelper(); // for helping showing, updating notification - private NotificationHelper mNotification = new NotificationHelper(); + private transient NotificationHelper mNotification; /** * Establishes a VPN connection with the specified username and password. @@ -80,19 +91,21 @@ abstract class VpnService<E extends VpnProfile> { protected abstract void connect(String serverIp, String username, String password) throws IOException; + protected abstract void stopPreviouslyRunDaemons(); + /** - * Tears down the VPN connection. The base class simply terminates all the - * Android services. A subclass may need to do some clean-up before that. + * Starts a VPN daemon. */ - protected void disconnect() { + protected DaemonProxy startDaemon(String daemonName) + throws IOException { + return mDaemonHelper.startDaemon(daemonName); } /** - * Starts an Android service defined in init.rc. + * Stops a VPN daemon. */ - protected AndroidServiceProxy startService(String serviceName) - throws IOException { - return mServiceHelper.startService(serviceName); + protected void stopDaemon(String daemonName) { + new DaemonProxy(daemonName).stop(); } /** @@ -109,31 +122,23 @@ abstract class VpnService<E extends VpnProfile> { return InetAddress.getByName(hostName).getHostAddress(); } - /** - * Sets the system property. The method is blocked until the value is - * settled in. - * @param name the name of the property - * @param value the value of the property - * @throws IOException if it fails to set the property within 2 seconds - */ - protected void setSystemProperty(String name, String value) - throws IOException { - SystemProperties.set(name, value); - for (int i = 0; i < 5; i++) { - String v = SystemProperties.get(name); - if (v.equals(value)) { - return; - } else { - Log.d(TAG, "sys_prop: wait for " + name + " to settle in"); - sleep(400); - } - } - throw new IOException("Failed to set system property: " + name); + void setContext(VpnServiceBinder context, E profile) { + mProfile = profile; + recover(context); } - void setContext(VpnServiceBinder context, E profile) { + void recover(VpnServiceBinder context) { mContext = context; - mProfile = profile; + mNotification = new NotificationHelper(); + + if (VpnState.CONNECTED.equals(mState)) { + Log.i("VpnService", " recovered: " + mProfile.getName()); + new Thread(new Runnable() { + public void run() { + enterConnectivityLoop(); + } + }).start(); + } } VpnState getState() { @@ -145,189 +150,161 @@ abstract class VpnService<E extends VpnProfile> { mState = VpnState.CONNECTING; broadcastConnectivity(VpnState.CONNECTING); + stopPreviouslyRunDaemons(); String serverIp = getIp(getProfile().getServerName()); - + saveLocalIpAndInterface(serverIp); onBeforeConnect(); connect(serverIp, username, password); waitUntilConnectedOrTimedout(); return true; } catch (Throwable e) { - Log.e(TAG, "onConnect()", e); - mError = newConnectingError(e); - onError(); + onError(e); return false; } } - synchronized void onDisconnect(boolean cleanUpServices) { + synchronized void onDisconnect() { try { - Log.d(TAG, " disconnecting VPN..."); + Log.i(TAG, "disconnecting VPN..."); mState = VpnState.DISCONNECTING; broadcastConnectivity(VpnState.DISCONNECTING); mNotification.showDisconnect(); - // subclass implementation - if (cleanUpServices) disconnect(); - - mServiceHelper.stop(); + mDaemonHelper.stopAll(); } catch (Throwable e) { Log.e(TAG, "onDisconnect()", e); + } finally { onFinalCleanUp(); } } - synchronized void onError() { + private void onError(Throwable error) { // error may occur during or after connection setup // and it may be due to one or all services gone - mInError = true; - switch (mState) { - case CONNECTED: - onDisconnect(true); - break; - - case CONNECTING: - onDisconnect(false); - break; + if (mError != null) { + Log.w(TAG, " multiple errors occur, record the last one: " + + error); } + Log.e(TAG, "onError()", error); + mError = error; + onDisconnect(); + } + + private void onError(int errorCode) { + onError(new VpnConnectingError(errorCode)); } - private void onBeforeConnect() { + + private void onBeforeConnect() throws IOException { mNotification.disableNotification(); - SystemProperties.set(VPN_DNS1, "-"); - SystemProperties.set(VPN_DNS2, "-"); + SystemProperties.set(VPN_DNS1, ""); + SystemProperties.set(VPN_DNS2, ""); SystemProperties.set(VPN_STATUS, VPN_IS_DOWN); - Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS)); + if (DBG) { + Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS)); + } } - private void waitUntilConnectedOrTimedout() { - // Run this in the background thread to not block UI - new Thread(new Runnable() { - public void run() { - sleep(2000); // 2 seconds - for (int i = 0; i < 60; i++) { - if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) { - onConnected(); - return; - } else if (mState != VpnState.CONNECTING) { - break; - } - sleep(500); // 0.5 second - } + private void waitUntilConnectedOrTimedout() throws IOException { + sleep(2000); // 2 seconds + for (int i = 0; i < 60; i++) { + if (mState != VpnState.CONNECTING) { + break; + } else if (VPN_IS_UP.equals( + SystemProperties.get(VPN_STATUS))) { + onConnected(); + return; + } else if (mDaemonHelper.anySocketError()) { + return; + } + sleep(500); // 0.5 second + } - synchronized (VpnService.this) { - if (mState == VpnState.CONNECTING) { - Log.d(TAG, " connecting timed out !!"); - mError = newConnectingError( - new IOException("Connecting timed out")); - onError(); - } - } + synchronized (VpnService.this) { + if (mState == VpnState.CONNECTING) { + onError(new IOException("Connecting timed out")); } - }).start(); + } } - private synchronized void onConnected() { - Log.d(TAG, "onConnected()"); + private synchronized void onConnected() throws IOException { + if (DBG) Log.d(TAG, "onConnected()"); - saveVpnDnsProperties(); + mDaemonHelper.closeSockets(); + saveOriginalDns(); saveAndSetDomainSuffices(); - startConnectivityMonitor(); mState = VpnState.CONNECTED; + mStartTime = System.currentTimeMillis(); + + // set DNS after saving the states in case the process gets killed + // before states are saved + saveSelf(); + setVpnDns(); broadcastConnectivity(VpnState.CONNECTED); + + enterConnectivityLoop(); + } + + private void saveSelf() throws IOException { + mContext.saveStates(); } private synchronized void onFinalCleanUp() { - Log.d(TAG, "onFinalCleanUp()"); + if (DBG) Log.d(TAG, "onFinalCleanUp()"); if (mState == VpnState.IDLE) return; // keep the notification when error occurs - if (!mInError) mNotification.disableNotification(); + if (!anyError()) mNotification.disableNotification(); - restoreOriginalDnsProperties(); + restoreOriginalDns(); restoreOriginalDomainSuffices(); mState = VpnState.IDLE; broadcastConnectivity(VpnState.IDLE); // stop the service itself + mContext.removeStates(); mContext.stopSelf(); } - private VpnConnectingError newConnectingError(Throwable e) { - return new VpnConnectingError( - (e instanceof UnknownHostException) - ? VpnManager.VPN_ERROR_UNKNOWN_SERVER - : VpnManager.VPN_ERROR_CONNECTION_FAILED); - } - - private synchronized void onOneServiceGone() { - switch (mState) { - case IDLE: - case DISCONNECTING: - break; - - default: - onError(); - } + private boolean anyError() { + return (mError != null); } - private synchronized void onAllServicesGone() { - switch (mState) { - case IDLE: - break; - - case DISCONNECTING: - // daemons are gone; now clean up everything - onFinalCleanUp(); - break; - - default: - onError(); - } - } - - private void restoreOriginalDnsProperties() { + private void restoreOriginalDns() { // restore only if they are not overridden - if (mVpnDns1.equals(SystemProperties.get(DNS1))) { - Log.d(TAG, String.format("restore original dns prop: %s --> %s", + String vpnDns1 = SystemProperties.get(VPN_DNS1); + if (vpnDns1.equals(SystemProperties.get(DNS1))) { + Log.i(TAG, String.format("restore original dns prop: %s --> %s", SystemProperties.get(DNS1), mOriginalDns1)); - Log.d(TAG, String.format("restore original dns prop: %s --> %s", + Log.i(TAG, String.format("restore original dns prop: %s --> %s", SystemProperties.get(DNS2), mOriginalDns2)); SystemProperties.set(DNS1, mOriginalDns1); SystemProperties.set(DNS2, mOriginalDns2); } } - private void saveVpnDnsProperties() { - mOriginalDns1 = mOriginalDns2 = ""; - for (int i = 0; i < 5; i++) { - mVpnDns1 = SystemProperties.get(VPN_DNS1); - mVpnDns2 = SystemProperties.get(VPN_DNS2); - if (mOriginalDns1.equals(mVpnDns1)) { - Log.d(TAG, "wait for vpn dns to settle in..." + i); - sleep(200); - } else { - mOriginalDns1 = SystemProperties.get(DNS1); - mOriginalDns2 = SystemProperties.get(DNS2); - SystemProperties.set(DNS1, mVpnDns1); - SystemProperties.set(DNS2, mVpnDns2); - Log.d(TAG, String.format("save original dns prop: %s, %s", - mOriginalDns1, mOriginalDns2)); - Log.d(TAG, String.format("set vpn dns prop: %s, %s", - mVpnDns1, mVpnDns2)); - return; - } - } - Log.d(TAG, "saveVpnDnsProperties(): DNS not updated??"); - mOriginalDns1 = mVpnDns1 = SystemProperties.get(DNS1); - mOriginalDns2 = mVpnDns2 = SystemProperties.get(DNS2); + private void saveOriginalDns() { + mOriginalDns1 = SystemProperties.get(DNS1); + mOriginalDns2 = SystemProperties.get(DNS2); + Log.i(TAG, String.format("save original dns prop: %s, %s", + mOriginalDns1, mOriginalDns2)); + } + + private void setVpnDns() { + String vpnDns1 = SystemProperties.get(VPN_DNS1); + String vpnDns2 = SystemProperties.get(VPN_DNS2); + SystemProperties.set(DNS1, vpnDns1); + SystemProperties.set(DNS2, vpnDns2); + Log.i(TAG, String.format("set vpn dns prop: %s, %s", + vpnDns1, vpnDns2)); } private void saveAndSetDomainSuffices() { mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES); - Log.d(TAG, "save original dns search: " + mOriginalDomainSuffices); + Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices); String list = mProfile.getDomainSuffices(); if (!TextUtils.isEmpty(list)) { SystemProperties.set(DNS_DOMAIN_SUFFICES, list); @@ -335,112 +312,174 @@ abstract class VpnService<E extends VpnProfile> { } private void restoreOriginalDomainSuffices() { - Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices); + Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices); SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices); } private void broadcastConnectivity(VpnState s) { VpnManager m = new VpnManager(mContext); - if ((s == VpnState.IDLE) && (mError != null)) { - m.broadcastConnectivity(mProfile.getName(), s, - mError.getErrorCode()); + Throwable err = mError; + if ((s == VpnState.IDLE) && (err != null)) { + if (err instanceof UnknownHostException) { + m.broadcastConnectivity(mProfile.getName(), s, + VpnManager.VPN_ERROR_UNKNOWN_SERVER); + } else if (err instanceof VpnConnectingError) { + m.broadcastConnectivity(mProfile.getName(), s, + ((VpnConnectingError) err).getErrorCode()); + } else { + m.broadcastConnectivity(mProfile.getName(), s, + VpnManager.VPN_ERROR_CONNECTION_FAILED); + } } else { m.broadcastConnectivity(mProfile.getName(), s); } } - private void startConnectivityMonitor() { - mStartTime = System.currentTimeMillis(); - - new Thread(new Runnable() { - public void run() { - Log.d(TAG, " +++++ connectivity monitor running"); - try { - for (;;) { - synchronized (VpnService.this) { - if (mState != VpnState.CONNECTED) break; - mNotification.update(); - checkConnectivity(); - VpnService.this.wait(1000); // 1 second - } + private void enterConnectivityLoop() { + Log.i(TAG, "VPN connectivity monitor running"); + try { + for (;;) { + synchronized (VpnService.this) { + if (mState != VpnState.CONNECTED || !checkConnectivity()) { + break; } - } catch (InterruptedException e) { - Log.e(TAG, "connectivity monitor", e); + mNotification.update(); + checkDns(); + VpnService.this.wait(1000); // 1 second } - Log.d(TAG, " ----- connectivity monitor stopped"); } - }).start(); + } catch (InterruptedException e) { + onError(e); + } + Log.i(TAG, "VPN connectivity monitor stopped"); } - private void checkConnectivity() { - checkDnsProperties(); + private void saveLocalIpAndInterface(String serverIp) throws IOException { + DatagramSocket s = new DatagramSocket(); + int port = 80; // arbitrary + s.connect(InetAddress.getByName(serverIp), port); + InetAddress localIp = s.getLocalAddress(); + mLocalIp = localIp.getHostAddress(); + NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp); + mLocalIf = (localIf == null) ? null : localIf.getName(); + if (TextUtils.isEmpty(mLocalIf)) { + throw new IOException("Local interface is empty!"); + } + if (DBG) { + Log.d(TAG, " Local IP: " + mLocalIp + ", if: " + mLocalIf); + } } - private void checkDnsProperties() { + // returns false if vpn connectivity is broken + private boolean checkConnectivity() { + if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) { + onDisconnect(); + return false; + } else { + return true; + } + } + + private void checkDns() { String dns1 = SystemProperties.get(DNS1); - if (!mVpnDns1.equals(dns1)) { - Log.w(TAG, " dns being overridden by: " + dns1); - onError(); + String vpnDns1 = SystemProperties.get(VPN_DNS1); + if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) { + // dhcp expires? + setVpnDns(); } } - protected void sleep(int ms) { + private boolean isLocalIpChanged() { try { - Thread.currentThread().sleep(ms); - } catch (InterruptedException e) { + InetAddress localIp = InetAddress.getByName(mLocalIp); + NetworkInterface localIf = + NetworkInterface.getByInetAddress(localIp); + if (localIf == null || !mLocalIf.equals(localIf.getName())) { + Log.w(TAG, " local If changed from " + mLocalIf + + " to " + localIf); + return true; + } else { + return false; + } + } catch (IOException e) { + Log.w(TAG, "isLocalIpChanged()", e); + return true; } } - private InetAddress toInetAddress(int addr) throws IOException { - byte[] aa = new byte[4]; - for (int i= 0; i < aa.length; i++) { - aa[i] = (byte) (addr & 0x0FF); - addr >>= 8; + protected void sleep(int ms) { + try { + Thread.currentThread().sleep(ms); + } catch (InterruptedException e) { } - return InetAddress.getByAddress(aa); } - private class ServiceHelper implements ProcessProxy.Callback { - private List<AndroidServiceProxy> mServiceList = - new ArrayList<AndroidServiceProxy>(); + private class DaemonHelper implements Serializable { + private List<DaemonProxy> mDaemonList = + new ArrayList<DaemonProxy>(); - // starts an Android service - AndroidServiceProxy startService(String serviceName) + synchronized DaemonProxy startDaemon(String daemonName) throws IOException { - AndroidServiceProxy service = new AndroidServiceProxy(serviceName); - mServiceList.add(service); - service.start(this); - return service; + DaemonProxy daemon = new DaemonProxy(daemonName); + mDaemonList.add(daemon); + daemon.start(); + return daemon; } - // stops all the Android services - void stop() { - if (mServiceList.isEmpty()) { + synchronized void stopAll() { + if (mDaemonList.isEmpty()) { onFinalCleanUp(); } else { - for (AndroidServiceProxy s : mServiceList) s.stop(); + for (DaemonProxy s : mDaemonList) s.stop(); } } - //@Override - public void done(ProcessProxy p) { - Log.d(TAG, "service done: " + p.getName()); - commonCallback((AndroidServiceProxy) p); + synchronized void closeSockets() { + for (DaemonProxy s : mDaemonList) s.closeControlSocket(); } - //@Override - public void error(ProcessProxy p, Throwable e) { - Log.e(TAG, "service error: " + p.getName(), e); - if (e instanceof VpnConnectingError) { - mError = (VpnConnectingError) e; + synchronized boolean anyDaemonStopped() { + for (DaemonProxy s : mDaemonList) { + if (s.isStopped()) { + Log.w(TAG, " VPN daemon gone: " + s.getName()); + return true; + } + } + return false; + } + + private int getResultFromSocket(DaemonProxy s) { + try { + return s.getResultFromSocket(); + } catch (IOException e) { + return -1; } - commonCallback((AndroidServiceProxy) p); } - private void commonCallback(AndroidServiceProxy service) { - mServiceList.remove(service); - onOneServiceGone(); - if (mServiceList.isEmpty()) onAllServicesGone(); + synchronized boolean anySocketError() { + for (DaemonProxy s : mDaemonList) { + switch (getResultFromSocket(s)) { + case 0: + continue; + + case AUTH_ERROR_CODE: + onError(VpnManager.VPN_ERROR_AUTH); + return true; + + case CHALLENGE_ERROR_CODE: + onError(VpnManager.VPN_ERROR_CHALLENGE); + return true; + + case REMOTE_HUNG_UP_ERROR_CODE: + onError(VpnManager.VPN_ERROR_REMOTE_HUNG_UP); + return true; + + default: + onError(VpnManager.VPN_ERROR_CONNECTION_FAILED); + return true; + } + } + return false; } } |