diff options
author | Chia-chi Yeh <chiachi@android.com> | 2011-06-29 16:05:58 -0700 |
---|---|---|
committer | Chia-chi Yeh <chiachi@android.com> | 2011-06-29 16:05:58 -0700 |
commit | 85a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758 (patch) | |
tree | 9a1fb0d1e21b3beb165104c625c76f2b59d792c7 /services | |
parent | d2ab6d07065479b12730b01cfbe77816e0f8b030 (diff) | |
download | frameworks_base-85a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758.zip frameworks_base-85a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758.tar.gz frameworks_base-85a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758.tar.bz2 |
VPN: add a method to handle requests of legacy VPNs.
The code is working but not complete yet.
Change-Id: Id8c8f137665373ad52c626b9d34e5a2cad028597
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/connectivity/Vpn.java | 204 |
1 files changed, 202 insertions, 2 deletions
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index db3b61e..f5efda9 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -27,15 +27,23 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.net.INetworkManagementEventObserver; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; import android.os.Binder; import android.os.ParcelFileDescriptor; -import android.os.RemoteException; +import android.os.Process; +import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.R; import com.android.internal.net.VpnConfig; import com.android.server.ConnectivityService.VpnCallback; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charsets; + /** * @hide */ @@ -49,7 +57,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private String mPackageName; private String mInterfaceName; - private String mDnsPropertyPrefix; + + private LegacyVpnRunner mLegacyVpnRunner; public Vpn(Context context, VpnCallback callback) { mContext = context; @@ -249,4 +258,195 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private native void nativeReset(String name); private native int nativeCheck(String name); private native void nativeProtect(int fd, String name); + + /** + * Handle legacy VPN requests. This method stops the services and restart + * them if their arguments are not null. Heavy things are offloaded to + * another thread, so callers will not be blocked too long. + * + * @param raoocn The arguments to be passed to racoon. + * @param mtpd The arguments to be passed to mtpd. + */ + public synchronized void doLegacyVpn(String[] racoon, String[] mtpd) { + // Currently only system user is allowed. + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Unauthorized Caller"); + } + + // If the previous runner is still alive, interrupt it. + if (mLegacyVpnRunner != null && mLegacyVpnRunner.isAlive()) { + mLegacyVpnRunner.interrupt(); + } + + // Start a new runner and we are done! + mLegacyVpnRunner = new LegacyVpnRunner( + new String[] {"racoon", "mtpd"}, racoon, mtpd); + mLegacyVpnRunner.start(); + } + + /** + * Bringing up a VPN connection takes time, and that is all this thread + * does. Here we have plenty of time. The only thing we need to take + * care of is responding to interruptions as soon as possible. Otherwise + * requests will be piled up. This can be done in a Handler as a state + * machine, but it is much easier to read in the current form. + */ + private class LegacyVpnRunner extends Thread { + private static final String TAG = "LegacyVpnRunner"; + + private static final String NONE = "--"; + + private final String[] mServices; + private final String[][] mArguments; + private long mTimer = -1; + + public LegacyVpnRunner(String[] services, String[]... arguments) { + super(TAG); + mServices = services; + mArguments = arguments; + } + + @Override + public void run() { + // Wait for the previous thread since it has been interrupted. + Log.v(TAG, "wait"); + synchronized (TAG) { + Log.v(TAG, "run"); + execute(); + Log.v(TAG, "exit"); + } + } + + private void checkpoint(boolean yield) throws InterruptedException { + long now = SystemClock.elapsedRealtime(); + if (mTimer == -1) { + mTimer = now; + Thread.sleep(1); + } else if (now - mTimer <= 30000) { + Thread.sleep(yield ? 200 : 1); + } else { + throw new InterruptedException("timeout"); + } + } + + private void execute() { + // Catch all exceptions so we can clean up few things. + try { + // Initialize the timer. + checkpoint(false); + + // First stop the services. + for (String service : mServices) { + SystemProperties.set("ctl.stop", service); + } + + // Wait for the services to stop. + for (String service : mServices) { + String key = "init.svc." + service; + while (!"stopped".equals(SystemProperties.get(key))) { + checkpoint(true); + } + } + + // Reset the properties. + SystemProperties.set("vpn.dns", NONE); + SystemProperties.set("vpn.via", NONE); + while (!NONE.equals(SystemProperties.get("vpn.dns")) || + !NONE.equals(SystemProperties.get("vpn.via"))) { + checkpoint(true); + } + + // Check if we need to restart some services. + boolean restart = false; + for (String[] arguments : mArguments) { + restart = restart || (arguments != null); + } + if (!restart) { + return; + } + + // Start the service with arguments. + for (int i = 0; i < mServices.length; ++i) { + String[] arguments = mArguments[i]; + if (arguments == null) { + continue; + } + + // Start the service. + String service = mServices[i]; + SystemProperties.set("ctl.start", service); + + // Wait for the service to start. + String key = "init.svc." + service; + while (!"running".equals(SystemProperties.get(key))) { + checkpoint(true); + } + + // Create the control socket. + LocalSocket socket = new LocalSocket(); + LocalSocketAddress address = new LocalSocketAddress( + service, LocalSocketAddress.Namespace.RESERVED); + + // Wait for the socket to connect. + while (true) { + try { + socket.connect(address); + break; + } catch (Exception e) { + // ignore + } + checkpoint(true); + } + socket.setSoTimeout(500); + + // Send over the arguments. + OutputStream output = socket.getOutputStream(); + for (String argument : arguments) { + byte[] bytes = argument.getBytes(Charsets.UTF_8); + if (bytes.length >= 0xFFFF) { + throw new IllegalArgumentException("argument too large"); + } + output.write(bytes.length >> 8); + output.write(bytes.length); + output.write(bytes); + checkpoint(false); + } + + // Send End-Of-Arguments. + output.write(0xFF); + output.write(0xFF); + output.flush(); + socket.close(); + } + + // Now here is the beast from the old days. We check few + // properties to figure out the current status. Ideally we + // can read things back from the sockets and get rid of the + // properties, but we have no time... + while (NONE.equals(SystemProperties.get("vpn.dns")) || + NONE.equals(SystemProperties.get("vpn.via"))) { + + // Check if a running service is dead. + for (int i = 0; i < mServices.length; ++i) { + String service = mServices[i]; + if (mArguments[i] != null && !"running".equals( + SystemProperties.get("init.svc." + service))) { + throw new IllegalArgumentException(service + " is dead"); + } + } + checkpoint(true); + } + + // Great! Now we are connected! + Log.i(TAG, "connected!"); + // TODO: + + } catch (Exception e) { + Log.i(TAG, e.getMessage()); + for (String service : mServices) { + SystemProperties.set("ctl.stop", service); + } + } + } + } } |