VPN: add a method to handle requests of legacy VPNs.

The code is working but not complete yet.

Change-Id: Id8c8f137665373ad52c626b9d34e5a2cad028597
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.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 @@
 
     private String mPackageName;
     private String mInterfaceName;
-    private String mDnsPropertyPrefix;
+
+    private LegacyVpnRunner mLegacyVpnRunner;
 
     public Vpn(Context context, VpnCallback callback) {
         mContext = context;
@@ -249,4 +258,195 @@
     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);
+                }
+            }
+        }
+    }
 }