Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2009, The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.vpn; |
| 18 | |
| 19 | import android.net.LocalSocket; |
| 20 | import android.net.LocalSocketAddress; |
| 21 | import android.net.vpn.VpnManager; |
| 22 | import android.os.SystemProperties; |
| 23 | import android.util.Log; |
| 24 | |
| 25 | import java.io.IOException; |
| 26 | import java.io.InputStream; |
| 27 | import java.io.OutputStream; |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 28 | import java.io.Serializable; |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 29 | |
| 30 | /** |
| 31 | * Proxy to start, stop and interact with a VPN daemon. |
| 32 | * The daemon is expected to accept connection through Unix domain socket. |
| 33 | * When the proxy successfully starts the daemon, it will establish a socket |
| 34 | * connection with the daemon, to both send commands to the daemon and receive |
| 35 | * response and connecting error code from the daemon. |
| 36 | */ |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 37 | class DaemonProxy implements Serializable { |
| 38 | private static final long serialVersionUID = 1L; |
| 39 | private static final boolean DBG = true; |
| 40 | |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 41 | private static final int WAITING_TIME = 15; // sec |
| 42 | |
| 43 | private static final String SVC_STATE_CMD_PREFIX = "init.svc."; |
| 44 | private static final String SVC_START_CMD = "ctl.start"; |
| 45 | private static final String SVC_STOP_CMD = "ctl.stop"; |
| 46 | private static final String SVC_STATE_RUNNING = "running"; |
| 47 | private static final String SVC_STATE_STOPPED = "stopped"; |
| 48 | |
| 49 | private static final int END_OF_ARGUMENTS = 255; |
| 50 | |
| 51 | private String mName; |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 52 | private String mTag; |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 53 | private transient LocalSocket mControlSocket; |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 54 | |
| 55 | /** |
| 56 | * Creates a proxy of the specified daemon. |
| 57 | * @param daemonName name of the daemon |
| 58 | */ |
| 59 | DaemonProxy(String daemonName) { |
| 60 | mName = daemonName; |
| 61 | mTag = "SProxy_" + daemonName; |
| 62 | } |
| 63 | |
| 64 | String getName() { |
| 65 | return mName; |
| 66 | } |
| 67 | |
| 68 | void start() throws IOException { |
| 69 | String svc = mName; |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 70 | |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 71 | Log.i(mTag, "Start VPN daemon: " + svc); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 72 | SystemProperties.set(SVC_START_CMD, svc); |
| 73 | |
| 74 | if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) { |
| 75 | throw new IOException("cannot start service: " + svc); |
| 76 | } else { |
| 77 | mControlSocket = createServiceSocket(); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | void sendCommand(String ...args) throws IOException { |
| 82 | OutputStream out = getControlSocketOutput(); |
| 83 | for (String arg : args) outputString(out, arg); |
| 84 | out.write(END_OF_ARGUMENTS); |
| 85 | out.flush(); |
| 86 | |
| 87 | int result = getResultFromSocket(true); |
| 88 | if (result != args.length) { |
| 89 | throw new IOException("socket error, result from service: " |
| 90 | + result); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // returns 0 if nothing is in the receive buffer |
| 95 | int getResultFromSocket() throws IOException { |
| 96 | return getResultFromSocket(false); |
| 97 | } |
| 98 | |
| 99 | void closeControlSocket() { |
| 100 | if (mControlSocket == null) return; |
| 101 | try { |
| 102 | mControlSocket.close(); |
| 103 | } catch (IOException e) { |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 104 | Log.w(mTag, "close control socket", e); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 105 | } finally { |
| 106 | mControlSocket = null; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | void stop() { |
| 111 | String svc = mName; |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 112 | Log.i(mTag, "Stop VPN daemon: " + svc); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 113 | SystemProperties.set(SVC_STOP_CMD, svc); |
| 114 | boolean success = blockUntil(SVC_STATE_STOPPED, 5); |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 115 | if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 116 | } |
| 117 | |
| 118 | boolean isStopped() { |
| 119 | String cmd = SVC_STATE_CMD_PREFIX + mName; |
| 120 | return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd)); |
| 121 | } |
| 122 | |
| 123 | private int getResultFromSocket(boolean blocking) throws IOException { |
| 124 | LocalSocket s = mControlSocket; |
| 125 | if (s == null) return 0; |
| 126 | InputStream in = s.getInputStream(); |
| 127 | if (!blocking && in.available() == 0) return 0; |
| 128 | |
| 129 | int data = in.read(); |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 130 | Log.i(mTag, "got data from control socket: " + data); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 131 | |
| 132 | return data; |
| 133 | } |
| 134 | |
| 135 | private LocalSocket createServiceSocket() throws IOException { |
| 136 | LocalSocket s = new LocalSocket(); |
| 137 | LocalSocketAddress a = new LocalSocketAddress(mName, |
| 138 | LocalSocketAddress.Namespace.RESERVED); |
| 139 | |
| 140 | // try a few times in case the service has not listen()ed |
| 141 | IOException excp = null; |
| 142 | for (int i = 0; i < 10; i++) { |
| 143 | try { |
| 144 | s.connect(a); |
| 145 | return s; |
| 146 | } catch (IOException e) { |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 147 | if (DBG) Log.d(mTag, "service not yet listen()ing; try again"); |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 148 | excp = e; |
| 149 | sleep(500); |
| 150 | } |
| 151 | } |
| 152 | throw excp; |
| 153 | } |
| 154 | |
| 155 | private OutputStream getControlSocketOutput() throws IOException { |
| 156 | if (mControlSocket != null) { |
| 157 | return mControlSocket.getOutputStream(); |
| 158 | } else { |
| 159 | throw new IOException("no control socket available"); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Waits for the process to be in the expected state. The method returns |
| 165 | * false if after the specified duration (in seconds), the process is still |
| 166 | * not in the expected state. |
| 167 | */ |
| 168 | private boolean blockUntil(String expectedState, int waitTime) { |
| 169 | String cmd = SVC_STATE_CMD_PREFIX + mName; |
| 170 | int sleepTime = 200; // ms |
| 171 | int n = waitTime * 1000 / sleepTime; |
| 172 | for (int i = 0; i < n; i++) { |
| 173 | if (expectedState.equals(SystemProperties.get(cmd))) { |
Hung-ying Tyan | fe8e48cd | 2009-07-30 14:02:48 +0800 | [diff] [blame^] | 174 | if (DBG) { |
| 175 | Log.d(mTag, mName + " is " + expectedState + " after " |
| 176 | + (i * sleepTime) + " msec"); |
| 177 | } |
Hung-ying Tyan | 21bd4af | 2009-07-23 07:37:27 +0800 | [diff] [blame] | 178 | break; |
| 179 | } |
| 180 | sleep(sleepTime); |
| 181 | } |
| 182 | return expectedState.equals(SystemProperties.get(cmd)); |
| 183 | } |
| 184 | |
| 185 | private void outputString(OutputStream out, String s) throws IOException { |
| 186 | byte[] bytes = s.getBytes(); |
| 187 | out.write(bytes.length); |
| 188 | out.write(bytes); |
| 189 | out.flush(); |
| 190 | } |
| 191 | |
| 192 | private void sleep(int msec) { |
| 193 | try { |
| 194 | Thread.currentThread().sleep(msec); |
| 195 | } catch (InterruptedException e) { |
| 196 | throw new RuntimeException(e); |
| 197 | } |
| 198 | } |
| 199 | } |