Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 560a32e1 authored by Hung-ying Tyan's avatar Hung-ying Tyan Committed by Android Git Automerger
Browse files

am 71196f0c: Merge "Make VpnService synchronous API." into honeycomb

* commit '71196f0cb3406cc4b0b82643ca08b59a97bc4cc8':
  Make VpnService synchronous API.
parents 5b669c23 a85f3a83
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -24,10 +24,11 @@ import android.net.vpn.VpnProfile;
 */
interface IVpnService {
    /**
     * Sets up the VPN connection.
     * Sets up a VPN connection.
     * @param profile the profile object
     * @param username the username for authentication
     * @param password the corresponding password for authentication
     * @return true if VPN is successfully connected
     */
    boolean connect(in VpnProfile profile, String username, String password);

@@ -37,7 +38,13 @@ interface IVpnService {
    void disconnect();

    /**
     * Makes the service broadcast the connectivity state.
     * Gets the the current connection state.
     */
    void checkStatus(in VpnProfile profile);
    String getState(in VpnProfile profile);

    /**
     * Returns the idle state.
     * @return true if the system is not connecting/connected to a VPN
     */
    boolean isIdle();
}
+72 −34
Original line number Diff line number Diff line
@@ -16,17 +16,19 @@

package android.net.vpn;

import java.io.File;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.Log;

import com.android.server.vpn.VpnServiceBinder;

/**
 * The class provides interface to manage all VPN-related tasks, including:
 * <ul>
@@ -40,8 +42,6 @@ import android.util.Log;
 * {@hide}
 */
public class VpnManager {
    // Action for broadcasting a connectivity state.
    private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
    /** Key to the profile name of a connectivity broadcast event. */
    public static final String BROADCAST_PROFILE_NAME = "profile_name";
    /** Key to the connectivity state of a connectivity broadcast event. */
@@ -74,8 +74,10 @@ public class VpnManager {
    private static final String PACKAGE_PREFIX =
            VpnManager.class.getPackage().getName() + ".";

    // Action to start VPN service
    private static final String ACTION_VPN_SERVICE = PACKAGE_PREFIX + "SERVICE";
    // Action for broadcasting a connectivity state.
    private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";

    private static final String VPN_SERVICE_NAME = "vpn";

    // Action to start VPN settings
    private static final String ACTION_VPN_SETTINGS =
@@ -96,13 +98,76 @@ public class VpnManager {
        return VpnType.values();
    }

    public static void startVpnService(Context c) {
        ServiceManager.addService(VPN_SERVICE_NAME, new VpnServiceBinder(c));
    }

    private Context mContext;
    private IVpnService mVpnService;

    /**
     * Creates a manager object with the specified context.
     */
    public VpnManager(Context c) {
        mContext = c;
        createVpnServiceClient();
    }

    private void createVpnServiceClient() {
        IBinder b = ServiceManager.getService(VPN_SERVICE_NAME);
        mVpnService = IVpnService.Stub.asInterface(b);
    }

    /**
     * Sets up a VPN connection.
     * @param profile the profile object
     * @param username the username for authentication
     * @param password the corresponding password for authentication
     * @return true if VPN is successfully connected
     */
    public boolean connect(VpnProfile p, String username, String password) {
        try {
            return mVpnService.connect(p, username, password);
        } catch (RemoteException e) {
            Log.e(TAG, "connect()", e);
            return false;
        }
    }

    /**
     * Tears down the VPN connection.
     */
    public void disconnect() {
        try {
            mVpnService.disconnect();
        } catch (RemoteException e) {
            Log.e(TAG, "disconnect()", e);
        }
    }

    /**
     * Gets the the current connection state.
     */
    public VpnState getState(VpnProfile p) {
        try {
            return Enum.valueOf(VpnState.class, mVpnService.getState(p));
        } catch (RemoteException e) {
            Log.e(TAG, "getState()", e);
            return VpnState.IDLE;
        }
    }

    /**
     * Returns the idle state.
     * @return true if the system is not connecting/connected to a VPN
     */
    public boolean isIdle() {
        try {
            return mVpnService.isIdle();
        } catch (RemoteException e) {
            Log.e(TAG, "isIdle()", e);
            return true;
        }
    }

    /**
@@ -134,33 +199,6 @@ public class VpnManager {
        }
    }

    /**
     * Starts the VPN service to establish VPN connection.
     */
    public void startVpnService() {
        mContext.startService(new Intent(ACTION_VPN_SERVICE));
    }

    /**
     * Stops the VPN service.
     */
    public void stopVpnService() {
        mContext.stopService(new Intent(ACTION_VPN_SERVICE));
    }

    /**
     * Binds the specified ServiceConnection with the VPN service.
     */
    public boolean bindVpnService(ServiceConnection c) {
        if (!mContext.bindService(new Intent(ACTION_VPN_SERVICE), c, 0)) {
            Log.w(TAG, "failed to connect to VPN service");
            return false;
        } else {
            Log.d(TAG, "succeeded to connect to VPN service");
            return true;
        }
    }

    /** Broadcasts the connectivity state of the specified profile. */
    public void broadcastConnectivity(String profileName, VpnState s) {
        broadcastConnectivity(profileName, s, VPN_ERROR_NO_ERROR);
+199 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009, 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 com.android.server.vpn;

import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.vpn.VpnManager;
import android.os.SystemProperties;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;

/**
 * Proxy to start, stop and interact with a VPN daemon.
 * The daemon is expected to accept connection through Unix domain socket.
 * When the proxy successfully starts the daemon, it will establish a socket
 * connection with the daemon, to both send commands to the daemon and receive
 * response and connecting error code from the daemon.
 */
class DaemonProxy implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final boolean DBG = true;

    private static final int WAITING_TIME = 15; // sec

    private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
    private static final String SVC_START_CMD = "ctl.start";
    private static final String SVC_STOP_CMD = "ctl.stop";
    private static final String SVC_STATE_RUNNING = "running";
    private static final String SVC_STATE_STOPPED = "stopped";

    private static final int END_OF_ARGUMENTS = 255;

    private String mName;
    private String mTag;
    private transient LocalSocket mControlSocket;

    /**
     * Creates a proxy of the specified daemon.
     * @param daemonName name of the daemon
     */
    DaemonProxy(String daemonName) {
        mName = daemonName;
        mTag = "SProxy_" + daemonName;
    }

    String getName() {
        return mName;
    }

    void start() throws IOException {
        String svc = mName;

        Log.i(mTag, "Start VPN daemon: " + svc);
        SystemProperties.set(SVC_START_CMD, svc);

        if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
            throw new IOException("cannot start service: " + svc);
        } else {
            mControlSocket = createServiceSocket();
        }
    }

    void sendCommand(String ...args) throws IOException {
        OutputStream out = getControlSocketOutput();
        for (String arg : args) outputString(out, arg);
        out.write(END_OF_ARGUMENTS);
        out.flush();

        int result = getResultFromSocket(true);
        if (result != args.length) {
            throw new IOException("socket error, result from service: "
                    + result);
        }
    }

    // returns 0 if nothing is in the receive buffer
    int getResultFromSocket() throws IOException {
        return getResultFromSocket(false);
    }

    void closeControlSocket() {
        if (mControlSocket == null) return;
        try {
            mControlSocket.close();
        } catch (IOException e) {
            Log.w(mTag, "close control socket", e);
        } finally {
            mControlSocket = null;
        }
    }

    void stop() {
        String svc = mName;
        Log.i(mTag, "Stop VPN daemon: " + svc);
        SystemProperties.set(SVC_STOP_CMD, svc);
        boolean success = blockUntil(SVC_STATE_STOPPED, 5);
        if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
    }

    boolean isStopped() {
        String cmd = SVC_STATE_CMD_PREFIX + mName;
        return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd));
    }

    private int getResultFromSocket(boolean blocking) throws IOException {
        LocalSocket s = mControlSocket;
        if (s == null) return 0;
        InputStream in = s.getInputStream();
        if (!blocking && in.available() == 0) return 0;

        int data = in.read();
        Log.i(mTag, "got data from control socket: " + data);

        return data;
    }

    private LocalSocket createServiceSocket() throws IOException {
        LocalSocket s = new LocalSocket();
        LocalSocketAddress a = new LocalSocketAddress(mName,
                LocalSocketAddress.Namespace.RESERVED);

        // try a few times in case the service has not listen()ed
        IOException excp = null;
        for (int i = 0; i < 10; i++) {
            try {
                s.connect(a);
                return s;
            } catch (IOException e) {
                if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
                excp = e;
                sleep(500);
            }
        }
        throw excp;
    }

    private OutputStream getControlSocketOutput() throws IOException {
        if (mControlSocket != null) {
            return mControlSocket.getOutputStream();
        } else {
            throw new IOException("no control socket available");
        }
    }

    /**
     * Waits for the process to be in the expected state. The method returns
     * false if after the specified duration (in seconds), the process is still
     * not in the expected state.
     */
    private boolean blockUntil(String expectedState, int waitTime) {
        String cmd = SVC_STATE_CMD_PREFIX + mName;
        int sleepTime = 200; // ms
        int n = waitTime * 1000 / sleepTime;
        for (int i = 0; i < n; i++) {
            if (expectedState.equals(SystemProperties.get(cmd))) {
                if (DBG) {
                    Log.d(mTag, mName + " is " + expectedState + " after "
                            + (i * sleepTime) + " msec");
                }
                break;
            }
            sleep(sleepTime);
        }
        return expectedState.equals(SystemProperties.get(cmd));
    }

    private void outputString(OutputStream out, String s) throws IOException {
        byte[] bytes = s.getBytes();
        out.write(bytes.length);
        out.write(bytes);
        out.flush();
    }

    private void sleep(int msec) {
        try {
            Thread.currentThread().sleep(msec);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
+47 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009, 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 com.android.server.vpn;

import android.net.vpn.L2tpIpsecPskProfile;

import java.io.IOException;

/**
 * The service that manages the preshared key based L2TP-over-IPSec VPN
 * connection.
 */
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
    private static final String IPSEC = "racoon";

    @Override
    protected void connect(String serverIp, String username, String password)
            throws IOException {
        L2tpIpsecPskProfile p = getProfile();
        VpnDaemons daemons = getDaemons();

        // IPSEC
        daemons.startIpsecForL2tp(serverIp, p.getPresharedKey())
                .closeControlSocket();

        sleep(2000); // 2 seconds

        // L2TP
        daemons.startL2tp(serverIp,
                (p.isSecretEnabled() ? p.getSecretString() : null),
                username, password);
    }
}
+50 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009, 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 com.android.server.vpn;

import android.net.vpn.L2tpIpsecProfile;
import android.security.Credentials;

import java.io.IOException;

/**
 * The service that manages the certificate based L2TP-over-IPSec VPN connection.
 */
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
    private static final String IPSEC = "racoon";

    @Override
    protected void connect(String serverIp, String username, String password)
            throws IOException {
        L2tpIpsecProfile p = getProfile();
        VpnDaemons daemons = getDaemons();

        // IPSEC
        DaemonProxy ipsec = daemons.startIpsecForL2tp(serverIp,
                Credentials.USER_PRIVATE_KEY + p.getUserCertificate(),
                Credentials.USER_CERTIFICATE + p.getUserCertificate(),
                Credentials.CA_CERTIFICATE + p.getCaCertificate());
        ipsec.closeControlSocket();

        sleep(2000); // 2 seconds

        // L2TP
        daemons.startL2tp(serverIp,
                (p.isSecretEnabled() ? p.getSecretString() : null),
                username, password);
    }
}
Loading