summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@google.com>2009-07-26 21:47:40 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2009-07-26 21:47:40 -0700
commitc0ecdf152d796419b8e3eb4f8ea7fc3aa3619ec5 (patch)
tree3fbf67cab2bb1e9054840941cb29b1d16b59a280 /packages
parentd5ec6ebee26e530d96299d9821950f7cce4b3636 (diff)
parent21bd4af88a24d0df020f68683f7c60698ebcc76a (diff)
downloadframeworks_base-c0ecdf152d796419b8e3eb4f8ea7fc3aa3619ec5.zip
frameworks_base-c0ecdf152d796419b8e3eb4f8ea7fc3aa3619ec5.tar.gz
frameworks_base-c0ecdf152d796419b8e3eb4f8ea7fc3aa3619ec5.tar.bz2
am 21bd4af8: Simplify the VPN service implementation.
Merge commit '21bd4af88a24d0df020f68683f7c60698ebcc76a' * commit '21bd4af88a24d0df020f68683f7c60698ebcc76a': Simplify the VPN service implementation.
Diffstat (limited to 'packages')
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java261
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java199
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java8
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java7
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java4
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java85
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java210
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/VpnService.java300
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java36
9 files changed, 370 insertions, 740 deletions
diff --git a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java
deleted file mode 100644
index e4c070f..0000000
--- a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * 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;
-
-/**
- * Proxy to start, stop and interact with an Android service defined in init.rc.
- * The android service is expected to accept connection through Unix domain
- * socket. When the proxy successfully starts the service, it will establish a
- * socket connection with the service. The socket serves two purposes: (1) send
- * commands to the service; (2) for the proxy to know whether the service is
- * alive.
- *
- * After the service receives commands from the proxy, it should return either
- * 0 if the service will close the socket (and the proxy will re-establish
- * another connection immediately after), or 1 if the socket is remained alive.
- */
-public class AndroidServiceProxy extends ProcessProxy {
- 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 static final int STOP_SERVICE = -1;
- private static final int AUTH_ERROR_CODE = 51;
-
- private String mServiceName;
- private String mSocketName;
- private LocalSocket mKeepaliveSocket;
- private boolean mControlSocketInUse;
- private Integer mSocketResult = null;
- private String mTag;
-
- /**
- * Creates a proxy with the service name.
- * @param serviceName the service name
- */
- public AndroidServiceProxy(String serviceName) {
- mServiceName = serviceName;
- mSocketName = serviceName;
- mTag = "SProxy_" + serviceName;
- }
-
- @Override
- public String getName() {
- return "Service " + mServiceName;
- }
-
- @Override
- public synchronized void stop() {
- if (isRunning()) {
- try {
- setResultAndCloseControlSocket(STOP_SERVICE);
- } catch (IOException e) {
- // should not occur
- throw new RuntimeException(e);
- }
- }
- Log.d(mTag, "----- Stop: " + mServiceName);
- SystemProperties.set(SVC_STOP_CMD, mServiceName);
- }
-
- /**
- * Sends a command with arguments to the service through the control socket.
- */
- public synchronized void sendCommand(String ...args) throws IOException {
- OutputStream out = getControlSocketOutput();
- for (String arg : args) outputString(out, arg);
- out.write(END_OF_ARGUMENTS);
- out.flush();
- checkSocketResult();
- }
-
- /**
- * {@inheritDoc}
- * The method returns when the service exits.
- */
- @Override
- protected void performTask() throws IOException {
- String svc = mServiceName;
- Log.d(mTag, "----- Stop the daemon just in case: " + mServiceName);
- SystemProperties.set(SVC_STOP_CMD, mServiceName);
- if (!blockUntil(SVC_STATE_STOPPED, 5)) {
- throw new IOException("cannot start service anew: " + svc
- + ", it is still running");
- }
-
- Log.d(mTag, "+++++ Start: " + svc);
- SystemProperties.set(SVC_START_CMD, svc);
-
- boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME);
-
- if (success) {
- Log.d(mTag, "----- Running: " + svc + ", create keepalive socket");
- LocalSocket s = mKeepaliveSocket = createServiceSocket();
- setState(ProcessState.RUNNING);
-
- if (s == null) {
- // no socket connection, stop hosting the service
- stop();
- return;
- }
- try {
- for (;;) {
- InputStream in = s.getInputStream();
- int data = in.read();
- if (data >= 0) {
- Log.d(mTag, "got data from control socket: " + data);
-
- setSocketResult(data);
- } else {
- // service is gone
- if (mControlSocketInUse) setSocketResult(-1);
- break;
- }
- }
- Log.d(mTag, "control connection closed");
- } catch (IOException e) {
- if (e instanceof VpnConnectingError) {
- throw e;
- } else {
- Log.d(mTag, "control socket broken: " + e.getMessage());
- }
- }
-
- // Wait 5 seconds for the service to exit
- success = blockUntil(SVC_STATE_STOPPED, 5);
- Log.d(mTag, "stopping " + svc + ", success? " + success);
- } else {
- setState(ProcessState.STOPPED);
- throw new IOException("cannot start service: " + svc);
- }
- }
-
- private LocalSocket createServiceSocket() throws IOException {
- LocalSocket s = new LocalSocket();
- LocalSocketAddress a = new LocalSocketAddress(mSocketName,
- 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) {
- Log.d(mTag, "service not yet listen()ing; try again");
- excp = e;
- sleep(500);
- }
- }
- throw excp;
- }
-
- private OutputStream getControlSocketOutput() throws IOException {
- if (mKeepaliveSocket != null) {
- mControlSocketInUse = true;
- mSocketResult = null;
- return mKeepaliveSocket.getOutputStream();
- } else {
- throw new IOException("no control socket available");
- }
- }
-
- private void checkSocketResult() throws IOException {
- try {
- // will be notified when the result comes back from service
- if (mSocketResult == null) wait();
- } catch (InterruptedException e) {
- Log.d(mTag, "checkSocketResult(): " + e);
- } finally {
- mControlSocketInUse = false;
- if ((mSocketResult == null) || (mSocketResult < 0)) {
- throw new IOException("socket error, result from service: "
- + mSocketResult);
- }
- }
- }
-
- private synchronized void setSocketResult(int result)
- throws VpnConnectingError {
- if (mControlSocketInUse) {
- mSocketResult = result;
- notifyAll();
- } else if (result > 0) {
- // error from daemon
- throw new VpnConnectingError((result == AUTH_ERROR_CODE)
- ? VpnManager.VPN_ERROR_AUTH
- : VpnManager.VPN_ERROR_CONNECTION_FAILED);
- }
- }
-
- private void setResultAndCloseControlSocket(int result)
- throws VpnConnectingError {
- setSocketResult(result);
- try {
- mKeepaliveSocket.shutdownInput();
- mKeepaliveSocket.shutdownOutput();
- mKeepaliveSocket.close();
- } catch (IOException e) {
- Log.e(mTag, "close keepalive socket", e);
- } finally {
- mKeepaliveSocket = null;
- }
- }
-
- /**
- * 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 + mServiceName;
- int sleepTime = 200; // ms
- int n = waitTime * 1000 / sleepTime;
- for (int i = 0; i < n; i++) {
- if (expectedState.equals(SystemProperties.get(cmd))) {
- Log.d(mTag, mServiceName + " 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();
- }
-}
diff --git a/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java b/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
new file mode 100644
index 0000000..b749821
--- /dev/null
+++ b/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
@@ -0,0 +1,199 @@
+/*
+ * 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;
+
+/**
+ * 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 {
+ 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 LocalSocket mControlSocket;
+ private String mTag;
+
+ /**
+ * 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.d(mTag, "----- Stop the daemon just in case: " + mName);
+ SystemProperties.set(SVC_STOP_CMD, mName);
+ if (!blockUntil(SVC_STATE_STOPPED, 5)) {
+ throw new IOException("cannot start service anew: " + svc
+ + ", it is still running");
+ }
+
+ Log.d(mTag, "+++++ Start: " + 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.e(mTag, "close control socket", e);
+ } finally {
+ mControlSocket = null;
+ }
+ }
+
+ void stop() {
+ String svc = mName;
+ Log.d(mTag, "----- Stop: " + svc);
+ SystemProperties.set(SVC_STOP_CMD, svc);
+ boolean success = blockUntil(SVC_STATE_STOPPED, 5);
+ 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.d(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) {
+ 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))) {
+ 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);
+ }
+ }
+}
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
index 7b3ddf8..8efd7c4 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
@@ -25,7 +25,7 @@ import java.io.IOException;
* connection.
*/
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
- private static final String IPSEC_DAEMON = "racoon";
+ private static final String IPSEC = "racoon";
@Override
protected void connect(String serverIp, String username, String password)
@@ -33,9 +33,9 @@ class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
L2tpIpsecPskProfile p = getProfile();
// IPSEC
- AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
- ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
- p.getPresharedKey());
+ DaemonProxy ipsec = startDaemon(IPSEC);
+ ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT, p.getPresharedKey());
+ ipsec.closeControlSocket();
sleep(2000); // 2 seconds
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
index e2d4ff4..56694b6 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
@@ -25,15 +25,16 @@ 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_DAEMON = "racoon";
+ private static final String IPSEC = "racoon";
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
// IPSEC
- AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
- ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
+ DaemonProxy ipsec = startDaemon(IPSEC);
+ ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT,
getUserkeyPath(), getUserCertPath(), getCaCertPath());
+ ipsec.closeControlSocket();
sleep(2000); // 2 seconds
diff --git a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
index 5fac799..805a5b5 100644
--- a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
+++ b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
@@ -24,7 +24,7 @@ import java.util.Arrays;
* A helper class for sending commands to the MTP daemon (mtpd).
*/
class MtpdHelper {
- private static final String MTPD_SERVICE = "mtpd";
+ private static final String MTPD = "mtpd";
private static final String VPN_LINKNAME = "vpn";
private static final String PPP_ARGS_SEPARATOR = "";
@@ -37,7 +37,7 @@ class MtpdHelper {
args.add(PPP_ARGS_SEPARATOR);
addPppArguments(vpnService, args, serverIp, username, password);
- AndroidServiceProxy mtpd = vpnService.startService(MTPD_SERVICE);
+ DaemonProxy mtpd = vpnService.startDaemon(MTPD);
mtpd.sendCommand(args.toArray(new String[args.size()]));
}
diff --git a/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java b/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java
deleted file mode 100644
index f0bbc34..0000000
--- a/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.util.Log;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
-
-/**
- * Proxy to perform a command with arguments.
- */
-public class NormalProcessProxy extends ProcessProxy {
- private Process mProcess;
- private String[] mArgs;
- private String mTag;
-
- /**
- * Creates a proxy with the arguments.
- * @param args the argument list with the first one being the command
- */
- public NormalProcessProxy(String ...args) {
- if ((args == null) || (args.length == 0)) {
- throw new IllegalArgumentException();
- }
- mArgs = args;
- mTag = "PProxy_" + getName();
- }
-
- @Override
- public String getName() {
- return mArgs[0];
- }
-
- @Override
- public synchronized void stop() {
- if (isStopped()) return;
- getHostThread().interrupt();
- // TODO: not sure how to reliably kill a process
- mProcess.destroy();
- setState(ProcessState.STOPPING);
- }
-
- @Override
- protected void performTask() throws IOException, InterruptedException {
- String[] args = mArgs;
- Log.d(mTag, "+++++ Execute: " + getEntireCommand());
- ProcessBuilder pb = new ProcessBuilder(args);
- setState(ProcessState.RUNNING);
- Process p = mProcess = pb.start();
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- p.getInputStream()));
- while (true) {
- String line = reader.readLine();
- if ((line == null) || isStopping()) break;
- Log.d(mTag, line);
- }
-
- Log.d(mTag, "----- p.waitFor(): " + getName());
- p.waitFor();
- Log.d(mTag, "----- Done: " + getName());
- }
-
- private CharSequence getEntireCommand() {
- String[] args = mArgs;
- StringBuilder sb = new StringBuilder(args[0]);
- for (int i = 1; i < args.length; i++) sb.append(' ').append(args[i]);
- return sb;
- }
-}
diff --git a/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java b/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java
deleted file mode 100644
index 50fbf4b..0000000
--- a/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * 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.os.ConditionVariable;
-
-import java.io.IOException;
-
-/**
- * A proxy class that spawns a process to accomplish a certain task.
- */
-public abstract class ProcessProxy {
- /**
- * Defines the interface to call back when the process is finished or an
- * error occurs during execution.
- */
- public static interface Callback {
- /**
- * Called when the process is finished.
- * @param proxy the proxy that hosts the process
- */
- void done(ProcessProxy proxy);
-
- /**
- * Called when some error occurs.
- * @param proxy the proxy that hosts the process
- */
- void error(ProcessProxy proxy, Throwable error);
- }
-
- protected enum ProcessState {
- STOPPED, STARTING, RUNNING, STOPPING, ERROR
- }
-
- private ProcessState mState = ProcessState.STOPPED;
- private ConditionVariable mLock = new ConditionVariable();
- private Thread mThread;
-
- /**
- * Returns the name of the process.
- */
- public abstract String getName();
-
- /**
- * Starts the process with a callback.
- * @param callback the callback to get notified when the process is finished
- * or an error occurs during execution
- * @throws IOException when the process is already running or failed to
- * start
- */
- public synchronized void start(final Callback callback) throws IOException {
- if (!isStopped()) {
- throw new IOException("Process is already running: " + this);
- }
- mLock.close();
- setState(ProcessState.STARTING);
- Thread thread = new Thread(new Runnable() {
- public void run() {
- try {
- performTask();
- setState(ProcessState.STOPPED);
- mLock.open();
- if (callback != null) callback.done(ProcessProxy.this);
- } catch (Throwable e) {
- setState(ProcessState.ERROR);
- if (callback != null) callback.error(ProcessProxy.this, e);
- } finally {
- mThread = null;
- }
- }
- });
- thread.setPriority(Thread.MIN_PRIORITY);
- thread.start();
- mThread = thread;
- if (!waitUntilRunning()) {
- throw new IOException("Failed to start the process: " + this);
- }
- }
-
- /**
- * Starts the process.
- * @throws IOException when the process is already running or failed to
- * start
- */
- public synchronized void start() throws IOException {
- start(null);
- if (!waitUntilDone()) {
- throw new IOException("Failed to complete the process: " + this);
- }
- }
-
- /**
- * Returns the thread that hosts the process.
- */
- public Thread getHostThread() {
- return mThread;
- }
-
- /**
- * Blocks the current thread until the hosted process is finished.
- *
- * @return true if the process is finished normally; false if an error
- * occurs
- */
- public boolean waitUntilDone() {
- while (!mLock.block(1000)) {
- if (isStopped() || isInError()) break;
- }
- return isStopped();
- }
-
- /**
- * Blocks the current thread until the hosted process is running.
- *
- * @return true if the process is running normally; false if the process
- * is in another state
- */
- private boolean waitUntilRunning() {
- for (;;) {
- if (!isStarting()) break;
- }
- return isRunning();
- }
-
- /**
- * Stops and destroys the process.
- */
- public abstract void stop();
-
- /**
- * Checks whether the process is finished.
- * @return true if the process is stopped
- */
- public boolean isStopped() {
- return (mState == ProcessState.STOPPED);
- }
-
- /**
- * Checks whether the process is being stopped.
- * @return true if the process is being stopped
- */
- public boolean isStopping() {
- return (mState == ProcessState.STOPPING);
- }
-
- /**
- * Checks whether the process is being started.
- * @return true if the process is being started
- */
- public boolean isStarting() {
- return (mState == ProcessState.STARTING);
- }
-
- /**
- * Checks whether the process is running.
- * @return true if the process is running
- */
- public boolean isRunning() {
- return (mState == ProcessState.RUNNING);
- }
-
- /**
- * Checks whether some error has occurred and the process is stopped.
- * @return true if some error has occurred and the process is stopped
- */
- public boolean isInError() {
- return (mState == ProcessState.ERROR);
- }
-
- /**
- * Performs the actual task. Subclasses must make sure that the method
- * is blocked until the task is done or an error occurs.
- */
- protected abstract void performTask()
- throws IOException, InterruptedException;
-
- /**
- * Sets the process state.
- * @param state the new state to be in
- */
- protected void setState(ProcessState state) {
- mState = state;
- }
-
- /**
- * Makes the current thread sleep for the specified time.
- * @param msec time to sleep in miliseconds
- */
- protected void sleep(int msec) {
- try {
- Thread.currentThread().sleep(msec);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 87bd780..60a07d5 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -50,14 +50,15 @@ 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 AUTH_ERROR_CODE = 51;
+
private final String TAG = VpnService.class.getSimpleName();
E mProfile;
VpnServiceBinder mContext;
private VpnState mState = VpnState.IDLE;
- private boolean mInError;
- private VpnConnectingError mError;
+ private Throwable mError;
// connection settings
private String mOriginalDns1;
@@ -68,8 +69,8 @@ abstract class VpnService<E extends VpnProfile> {
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();
@@ -81,18 +82,11 @@ abstract class VpnService<E extends VpnProfile> {
String password) throws IOException;
/**
- * 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.
- */
- protected void disconnect() {
- }
-
- /**
- * Starts an Android service defined in init.rc.
+ * Starts a VPN daemon.
*/
- protected AndroidServiceProxy startService(String serviceName)
+ protected DaemonProxy startDaemon(String daemonName)
throws IOException {
- return mServiceHelper.startService(serviceName);
+ return mDaemonHelper.startDaemon(daemonName);
}
/**
@@ -109,28 +103,6 @@ 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) {
mContext = context;
mProfile = profile;
@@ -153,44 +125,42 @@ abstract class VpnService<E extends VpnProfile> {
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...");
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);
}
+ mError = error;
+ onDisconnect();
+ }
+
+ private void onError(int errorCode) {
+ onError(new VpnConnectingError(errorCode));
}
+
private void onBeforeConnect() {
mNotification.disableNotification();
@@ -201,41 +171,39 @@ abstract class VpnService<E extends VpnProfile> {
}
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
- }
+ 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) {
+ Log.d(TAG, " connecting timed out !!");
+ onError(new IOException("Connecting timed out"));
}
- }).start();
+ }
}
private synchronized void onConnected() {
Log.d(TAG, "onConnected()");
+ mDaemonHelper.closeSockets();
saveVpnDnsProperties();
saveAndSetDomainSuffices();
- startConnectivityMonitor();
mState = VpnState.CONNECTED;
broadcastConnectivity(VpnState.CONNECTED);
+
+ enterConnectivityLoop();
}
private synchronized void onFinalCleanUp() {
@@ -244,7 +212,7 @@ abstract class VpnService<E extends VpnProfile> {
if (mState == VpnState.IDLE) return;
// keep the notification when error occurs
- if (!mInError) mNotification.disableNotification();
+ if (!anyError()) mNotification.disableNotification();
restoreOriginalDnsProperties();
restoreOriginalDomainSuffices();
@@ -255,37 +223,8 @@ abstract class VpnService<E extends VpnProfile> {
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 synchronized void onAllServicesGone() {
- switch (mState) {
- case IDLE:
- break;
-
- case DISCONNECTING:
- // daemons are gone; now clean up everything
- onFinalCleanUp();
- break;
-
- default:
- onError();
- }
+ private boolean anyError() {
+ return (mError != null);
}
private void restoreOriginalDnsProperties() {
@@ -341,46 +280,65 @@ abstract class VpnService<E extends VpnProfile> {
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() {
+ private void enterConnectivityLoop() {
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
- }
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "connectivity monitor", e);
+ 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
}
- Log.d(TAG, " ----- connectivity monitor stopped");
}
- }).start();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "connectivity monitor", e);
+ }
+ Log.d(TAG, " ----- connectivity monitor stopped");
}
private void checkConnectivity() {
- checkDnsProperties();
+ if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
+ onDisconnect();
+ }
+ }
+
+ private boolean isLocalIpChanged() {
+ // TODO
+ if (!isDnsIntact()) {
+ Log.w(TAG, " local IP changed");
+ return true;
+ } else {
+ return false;
+ }
}
- private void checkDnsProperties() {
+ private boolean isDnsIntact() {
String dns1 = SystemProperties.get(DNS1);
if (!mVpnDns1.equals(dns1)) {
Log.w(TAG, " dns being overridden by: " + dns1);
- onError();
+ return false;
+ } else {
+ return true;
}
}
@@ -391,56 +349,64 @@ abstract class VpnService<E extends VpnProfile> {
}
}
- 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;
- }
- return InetAddress.getByAddress(aa);
- }
-
- private class ServiceHelper implements ProcessProxy.Callback {
- private List<AndroidServiceProxy> mServiceList =
- new ArrayList<AndroidServiceProxy>();
+ private class DaemonHelper {
+ 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();
+ }
+
+ synchronized boolean anyDaemonStopped() {
+ for (DaemonProxy s : mDaemonList) {
+ if (s.isStopped()) {
+ Log.w(TAG, " daemon gone: " + s.getName());
+ return true;
+ }
+ }
+ return false;
}
- //@Override
- public void error(ProcessProxy p, Throwable e) {
- Log.e(TAG, "service error: " + p.getName(), e);
- if (e instanceof VpnConnectingError) {
- mError = (VpnConnectingError) e;
+ 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;
+
+ default:
+ onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
index 32b8e51..513a2c9 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
@@ -42,11 +42,13 @@ public class VpnServiceBinder extends Service {
private final IBinder mBinder = new IVpnService.Stub() {
public boolean connect(VpnProfile p, String username, String password) {
+ android.util.Log.d("VpnServiceBinder", "becoming foreground");
+ setForeground(true);
return VpnServiceBinder.this.connect(p, username, password);
}
public void disconnect() {
- if (mService != null) mService.onDisconnect(true);
+ VpnServiceBinder.this.disconnect();
}
public void checkStatus(VpnProfile p) {
@@ -54,21 +56,39 @@ public class VpnServiceBinder extends Service {
}
};
- public void onStart (Intent intent, int startId) {
+ @Override
+ public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
- setForeground(true);
- android.util.Log.d("VpnServiceBinder", "becomes a foreground service");
}
+ @Override
public IBinder onBind(Intent intent) {
return mBinder;
}
- private synchronized boolean connect(
- VpnProfile p, String username, String password) {
+ private synchronized boolean connect(final VpnProfile p,
+ final String username, final String password) {
if (mService != null) return false;
- mService = createService(p);
- return mService.onConnect(username, password);
+
+ new Thread(new Runnable() {
+ public void run() {
+ mService = createService(p);
+ mService.onConnect(username, password);
+ }
+ }).start();
+ return true;
+ }
+
+ private synchronized void disconnect() {
+ if (mService == null) return;
+
+ new Thread(new Runnable() {
+ public void run() {
+ mService.onDisconnect();
+ android.util.Log.d("VpnServiceBinder", "becoming background");
+ setForeground(false);
+ }
+ }).start();
}
private synchronized void checkStatus(VpnProfile p) {