blob: 7e8185ef43702a63ac69e2c06851f772419c1862 [file] [log] [blame]
Hung-ying Tyanf94b6442009-06-08 13:27:11 +08001/*
Hung-ying Tyand3aba7f2009-06-19 19:45:38 +08002 * Copyright (C) 2009, The Android Open Source Project
Hung-ying Tyanf94b6442009-06-08 13:27:11 +08003 *
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
17package com.android.server.vpn;
18
19import android.net.LocalSocket;
20import android.net.LocalSocketAddress;
Hung-ying Tyandf1aa332009-07-11 22:23:30 +080021import android.net.vpn.VpnManager;
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080022import android.os.SystemProperties;
23import android.util.Log;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28
29/**
30 * Proxy to start, stop and interact with an Android service defined in init.rc.
31 * The android service is expected to accept connection through Unix domain
32 * socket. When the proxy successfully starts the service, it will establish a
33 * socket connection with the service. The socket serves two purposes: (1) send
34 * commands to the service; (2) for the proxy to know whether the service is
35 * alive.
36 *
37 * After the service receives commands from the proxy, it should return either
38 * 0 if the service will close the socket (and the proxy will re-establish
39 * another connection immediately after), or 1 if the socket is remained alive.
40 */
41public class AndroidServiceProxy extends ProcessProxy {
42 private static final int WAITING_TIME = 15; // sec
43
44 private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
45 private static final String SVC_START_CMD = "ctl.start";
46 private static final String SVC_STOP_CMD = "ctl.stop";
47 private static final String SVC_STATE_RUNNING = "running";
48 private static final String SVC_STATE_STOPPED = "stopped";
49
50 private static final int END_OF_ARGUMENTS = 255;
51
Hung-ying Tyandf1aa332009-07-11 22:23:30 +080052 private static final int STOP_SERVICE = -1;
53 private static final int AUTH_ERROR_CODE = 51;
54
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080055 private String mServiceName;
56 private String mSocketName;
57 private LocalSocket mKeepaliveSocket;
58 private boolean mControlSocketInUse;
59 private Integer mSocketResult = null;
60 private String mTag;
61
62 /**
63 * Creates a proxy with the service name.
64 * @param serviceName the service name
65 */
66 public AndroidServiceProxy(String serviceName) {
67 mServiceName = serviceName;
68 mSocketName = serviceName;
69 mTag = "SProxy_" + serviceName;
70 }
71
72 @Override
73 public String getName() {
74 return "Service " + mServiceName;
75 }
76
77 @Override
78 public synchronized void stop() {
Hung-ying Tyandf1aa332009-07-11 22:23:30 +080079 if (isRunning()) {
80 try {
81 setResultAndCloseControlSocket(STOP_SERVICE);
82 } catch (IOException e) {
83 // should not occur
84 throw new RuntimeException(e);
85 }
86 }
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080087 SystemProperties.set(SVC_STOP_CMD, mServiceName);
88 }
89
90 /**
91 * Sends a command with arguments to the service through the control socket.
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080092 */
Hung-ying Tyandf1aa332009-07-11 22:23:30 +080093 public synchronized void sendCommand(String ...args) throws IOException {
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080094 OutputStream out = getControlSocketOutput();
95 for (String arg : args) outputString(out, arg);
Hung-ying Tyanf94b6442009-06-08 13:27:11 +080096 out.write(END_OF_ARGUMENTS);
97 out.flush();
98 checkSocketResult();
99 }
100
101 /**
102 * {@inheritDoc}
103 * The method returns when the service exits.
104 */
105 @Override
106 protected void performTask() throws IOException {
107 String svc = mServiceName;
108 Log.d(mTag, "+++++ Execute: " + svc);
109 SystemProperties.set(SVC_START_CMD, svc);
110
111 boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME);
112
113 if (success) {
114 Log.d(mTag, "----- Running: " + svc + ", create keepalive socket");
115 LocalSocket s = mKeepaliveSocket = createServiceSocket();
116 setState(ProcessState.RUNNING);
117
118 if (s == null) {
119 // no socket connection, stop hosting the service
120 stop();
121 return;
122 }
123 try {
124 for (;;) {
125 InputStream in = s.getInputStream();
126 int data = in.read();
127 if (data >= 0) {
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800128 Log.d(mTag, "got data from control socket: " + data);
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800129
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800130 setSocketResult(data);
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800131 } else {
132 // service is gone
133 if (mControlSocketInUse) setSocketResult(-1);
134 break;
135 }
136 }
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800137 Log.d(mTag, "control connection closed");
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800138 } catch (IOException e) {
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800139 if (e instanceof VpnConnectingError) {
140 throw e;
141 } else {
142 Log.d(mTag, "control socket broken: " + e.getMessage());
143 }
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800144 }
145
146 // Wait 5 seconds for the service to exit
147 success = blockUntil(SVC_STATE_STOPPED, 5);
148 Log.d(mTag, "stopping " + svc + ", success? " + success);
149 } else {
150 setState(ProcessState.STOPPED);
151 throw new IOException("cannot start service: " + svc);
152 }
153 }
154
155 private LocalSocket createServiceSocket() throws IOException {
156 LocalSocket s = new LocalSocket();
157 LocalSocketAddress a = new LocalSocketAddress(mSocketName,
158 LocalSocketAddress.Namespace.RESERVED);
159
160 // try a few times in case the service has not listen()ed
161 IOException excp = null;
162 for (int i = 0; i < 10; i++) {
163 try {
164 s.connect(a);
165 return s;
166 } catch (IOException e) {
167 Log.d(mTag, "service not yet listen()ing; try again");
168 excp = e;
169 sleep(500);
170 }
171 }
172 throw excp;
173 }
174
175 private OutputStream getControlSocketOutput() throws IOException {
176 if (mKeepaliveSocket != null) {
177 mControlSocketInUse = true;
178 mSocketResult = null;
179 return mKeepaliveSocket.getOutputStream();
180 } else {
181 throw new IOException("no control socket available");
182 }
183 }
184
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800185 private void checkSocketResult() throws IOException {
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800186 try {
187 // will be notified when the result comes back from service
188 if (mSocketResult == null) wait();
189 } catch (InterruptedException e) {
190 Log.d(mTag, "checkSocketResult(): " + e);
191 } finally {
192 mControlSocketInUse = false;
193 if ((mSocketResult == null) || (mSocketResult < 0)) {
194 throw new IOException("socket error, result from service: "
195 + mSocketResult);
196 }
197 }
198 }
199
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800200 private synchronized void setSocketResult(int result)
201 throws VpnConnectingError {
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800202 if (mControlSocketInUse) {
203 mSocketResult = result;
204 notifyAll();
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800205 } else if (result > 0) {
206 // error from daemon
207 throw new VpnConnectingError((result == AUTH_ERROR_CODE)
208 ? VpnManager.VPN_ERROR_AUTH
209 : VpnManager.VPN_ERROR_CONNECTION_FAILED);
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800210 }
211 }
212
Hung-ying Tyandf1aa332009-07-11 22:23:30 +0800213 private void setResultAndCloseControlSocket(int result)
214 throws VpnConnectingError {
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800215 setSocketResult(result);
216 try {
217 mKeepaliveSocket.shutdownInput();
218 mKeepaliveSocket.shutdownOutput();
219 mKeepaliveSocket.close();
220 } catch (IOException e) {
221 Log.e(mTag, "close keepalive socket", e);
222 } finally {
223 mKeepaliveSocket = null;
224 }
225 }
226
227 /**
228 * Waits for the process to be in the expected state. The method returns
229 * false if after the specified duration (in seconds), the process is still
230 * not in the expected state.
231 */
232 private boolean blockUntil(String expectedState, int waitTime) {
233 String cmd = SVC_STATE_CMD_PREFIX + mServiceName;
234 int sleepTime = 200; // ms
235 int n = waitTime * 1000 / sleepTime;
236 for (int i = 0; i < n; i++) {
237 if (expectedState.equals(SystemProperties.get(cmd))) {
238 Log.d(mTag, mServiceName + " is " + expectedState + " after "
239 + (i * sleepTime) + " msec");
240 break;
241 }
242 sleep(sleepTime);
243 }
244 return expectedState.equals(SystemProperties.get(cmd));
245 }
246
247 private void outputString(OutputStream out, String s) throws IOException {
Hung-ying Tyanf94b6442009-06-08 13:27:11 +0800248 byte[] bytes = s.getBytes();
249 out.write(bytes.length);
250 out.write(bytes);
251 out.flush();
252 }
253}