diff options
author | Hung-ying Tyan <tyanh@google.com> | 2009-07-26 21:47:40 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2009-07-26 21:47:40 -0700 |
commit | c0ecdf152d796419b8e3eb4f8ea7fc3aa3619ec5 (patch) | |
tree | 3fbf67cab2bb1e9054840941cb29b1d16b59a280 /packages | |
parent | d5ec6ebee26e530d96299d9821950f7cce4b3636 (diff) | |
parent | 21bd4af88a24d0df020f68683f7c60698ebcc76a (diff) | |
download | frameworks_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')
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) { |