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