• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
28 import java.io.Serializable;
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  */
37 class DaemonProxy implements Serializable {
38     private static final long serialVersionUID = 1L;
39     private static final boolean DBG = true;
40 
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;
52     private String mTag;
53     private transient LocalSocket mControlSocket;
54 
55     /**
56      * Creates a proxy of the specified daemon.
57      * @param daemonName name of the daemon
58      */
DaemonProxy(String daemonName)59     DaemonProxy(String daemonName) {
60         mName = daemonName;
61         mTag = "SProxy_" + daemonName;
62     }
63 
getName()64     String getName() {
65         return mName;
66     }
67 
start()68     void start() throws IOException {
69         String svc = mName;
70 
71         Log.i(mTag, "Start VPN daemon: " + svc);
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 
sendCommand(String ....args)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
getResultFromSocket()95     int getResultFromSocket() throws IOException {
96         return getResultFromSocket(false);
97     }
98 
closeControlSocket()99     void closeControlSocket() {
100         if (mControlSocket == null) return;
101         try {
102             mControlSocket.close();
103         } catch (IOException e) {
104             Log.w(mTag, "close control socket", e);
105         } finally {
106             mControlSocket = null;
107         }
108     }
109 
stop()110     void stop() {
111         String svc = mName;
112         Log.i(mTag, "Stop VPN daemon: " + svc);
113         SystemProperties.set(SVC_STOP_CMD, svc);
114         boolean success = blockUntil(SVC_STATE_STOPPED, 5);
115         if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
116     }
117 
isStopped()118     boolean isStopped() {
119         String cmd = SVC_STATE_CMD_PREFIX + mName;
120         return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd));
121     }
122 
getResultFromSocket(boolean blocking)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();
130         Log.i(mTag, "got data from control socket: " + data);
131 
132         return data;
133     }
134 
createServiceSocket()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) {
147                 if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
148                 excp = e;
149                 sleep(500);
150             }
151         }
152         throw excp;
153     }
154 
getControlSocketOutput()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      */
blockUntil(String expectedState, int waitTime)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))) {
174                 if (DBG) {
175                     Log.d(mTag, mName + " is " + expectedState + " after "
176                             + (i * sleepTime) + " msec");
177                 }
178                 break;
179             }
180             sleep(sleepTime);
181         }
182         return expectedState.equals(SystemProperties.get(cmd));
183     }
184 
outputString(OutputStream out, String s)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 
sleep(int msec)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 }
200