blob: 289ee458a0beda941efb43089e7e86a688306c28 [file] [log] [blame]
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +08001/*
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
17package com.android.server.vpn;
18
19import android.net.LocalSocket;
20import android.net.LocalSocketAddress;
21import android.net.vpn.VpnManager;
22import android.os.SystemProperties;
23import android.util.Log;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
Hung-ying Tyanfe8e48cd2009-07-30 14:02:48 +080028import java.io.Serializable;
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +080029
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 Tyanfe8e48cd2009-07-30 14:02:48 +080037class DaemonProxy implements Serializable {
38 private static final long serialVersionUID = 1L;
39 private static final boolean DBG = true;
40
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +080041 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 Tyan21bd4af2009-07-23 07:37:27 +080052 private String mTag;
Hung-ying Tyanfe8e48cd2009-07-30 14:02:48 +080053 private transient LocalSocket mControlSocket;
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +080054
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 Tyan21bd4af2009-07-23 07:37:27 +080070
Hung-ying Tyanfe8e48cd2009-07-30 14:02:48 +080071 Log.i(mTag, "Start VPN daemon: " + svc);
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +080072 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 Tyanfe8e48cd2009-07-30 14:02:48 +0800104 Log.w(mTag, "close control socket", e);
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800105 } finally {
106 mControlSocket = null;
107 }
108 }
109
110 void stop() {
111 String svc = mName;
Hung-ying Tyanfe8e48cd2009-07-30 14:02:48 +0800112 Log.i(mTag, "Stop VPN daemon: " + svc);
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800113 SystemProperties.set(SVC_STOP_CMD, svc);
114 boolean success = blockUntil(SVC_STATE_STOPPED, 5);
Hung-ying Tyanfe8e48cd2009-07-30 14:02:48 +0800115 if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800116 }
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 Tyanfe8e48cd2009-07-30 14:02:48 +0800130 Log.i(mTag, "got data from control socket: " + data);
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800131
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 Tyanfe8e48cd2009-07-30 14:02:48 +0800147 if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800148 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 Tyanfe8e48cd2009-07-30 14:02:48 +0800174 if (DBG) {
175 Log.d(mTag, mName + " is " + expectedState + " after "
176 + (i * sleepTime) + " msec");
177 }
Hung-ying Tyan21bd4af2009-07-23 07:37:27 +0800178 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}