Simplify the VPN service implementation.
+ Remove NormalProcessProxy and ProcessProxy as they are not used anymore. + Rename AndroidServiceProxy to DaemonProxy and simplify its implementation as it does not extend to ProcessProxy anymore. + Execute connect() in VpnService in one thread, which simplifies socket and error handling. + Modify service subclasses accordingly. + Execute connect() and disconnect() in VpnServiceBinder so that the operations do not block the UI thread. Mark service as foreground only upon connecting.
This commit is contained in:
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vpn;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.net.vpn.VpnManager;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Proxy to start, stop and interact with an Android service defined in init.rc.
|
||||
* The android service is expected to accept connection through Unix domain
|
||||
* socket. When the proxy successfully starts the service, it will establish a
|
||||
* socket connection with the service. The socket serves two purposes: (1) send
|
||||
* commands to the service; (2) for the proxy to know whether the service is
|
||||
* alive.
|
||||
*
|
||||
* After the service receives commands from the proxy, it should return either
|
||||
* 0 if the service will close the socket (and the proxy will re-establish
|
||||
* another connection immediately after), or 1 if the socket is remained alive.
|
||||
*/
|
||||
public class AndroidServiceProxy extends ProcessProxy {
|
||||
private static final int WAITING_TIME = 15; // sec
|
||||
|
||||
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
|
||||
private static final String SVC_START_CMD = "ctl.start";
|
||||
private static final String SVC_STOP_CMD = "ctl.stop";
|
||||
private static final String SVC_STATE_RUNNING = "running";
|
||||
private static final String SVC_STATE_STOPPED = "stopped";
|
||||
|
||||
private static final int END_OF_ARGUMENTS = 255;
|
||||
|
||||
private static final int STOP_SERVICE = -1;
|
||||
private static final int AUTH_ERROR_CODE = 51;
|
||||
|
||||
private String mServiceName;
|
||||
private String mSocketName;
|
||||
private LocalSocket mKeepaliveSocket;
|
||||
private boolean mControlSocketInUse;
|
||||
private Integer mSocketResult = null;
|
||||
private String mTag;
|
||||
|
||||
/**
|
||||
* Creates a proxy with the service name.
|
||||
* @param serviceName the service name
|
||||
*/
|
||||
public AndroidServiceProxy(String serviceName) {
|
||||
mServiceName = serviceName;
|
||||
mSocketName = serviceName;
|
||||
mTag = "SProxy_" + serviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Service " + mServiceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
if (isRunning()) {
|
||||
try {
|
||||
setResultAndCloseControlSocket(STOP_SERVICE);
|
||||
} catch (IOException e) {
|
||||
// should not occur
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
Log.d(mTag, "----- Stop: " + mServiceName);
|
||||
SystemProperties.set(SVC_STOP_CMD, mServiceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command with arguments to the service through the control socket.
|
||||
*/
|
||||
public synchronized void sendCommand(String ...args) throws IOException {
|
||||
OutputStream out = getControlSocketOutput();
|
||||
for (String arg : args) outputString(out, arg);
|
||||
out.write(END_OF_ARGUMENTS);
|
||||
out.flush();
|
||||
checkSocketResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* The method returns when the service exits.
|
||||
*/
|
||||
@Override
|
||||
protected void performTask() throws IOException {
|
||||
String svc = mServiceName;
|
||||
Log.d(mTag, "----- Stop the daemon just in case: " + mServiceName);
|
||||
SystemProperties.set(SVC_STOP_CMD, mServiceName);
|
||||
if (!blockUntil(SVC_STATE_STOPPED, 5)) {
|
||||
throw new IOException("cannot start service anew: " + svc
|
||||
+ ", it is still running");
|
||||
}
|
||||
|
||||
Log.d(mTag, "+++++ Start: " + svc);
|
||||
SystemProperties.set(SVC_START_CMD, svc);
|
||||
|
||||
boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME);
|
||||
|
||||
if (success) {
|
||||
Log.d(mTag, "----- Running: " + svc + ", create keepalive socket");
|
||||
LocalSocket s = mKeepaliveSocket = createServiceSocket();
|
||||
setState(ProcessState.RUNNING);
|
||||
|
||||
if (s == null) {
|
||||
// no socket connection, stop hosting the service
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (;;) {
|
||||
InputStream in = s.getInputStream();
|
||||
int data = in.read();
|
||||
if (data >= 0) {
|
||||
Log.d(mTag, "got data from control socket: " + data);
|
||||
|
||||
setSocketResult(data);
|
||||
} else {
|
||||
// service is gone
|
||||
if (mControlSocketInUse) setSocketResult(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.d(mTag, "control connection closed");
|
||||
} catch (IOException e) {
|
||||
if (e instanceof VpnConnectingError) {
|
||||
throw e;
|
||||
} else {
|
||||
Log.d(mTag, "control socket broken: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Wait 5 seconds for the service to exit
|
||||
success = blockUntil(SVC_STATE_STOPPED, 5);
|
||||
Log.d(mTag, "stopping " + svc + ", success? " + success);
|
||||
} else {
|
||||
setState(ProcessState.STOPPED);
|
||||
throw new IOException("cannot start service: " + svc);
|
||||
}
|
||||
}
|
||||
|
||||
private LocalSocket createServiceSocket() throws IOException {
|
||||
LocalSocket s = new LocalSocket();
|
||||
LocalSocketAddress a = new LocalSocketAddress(mSocketName,
|
||||
LocalSocketAddress.Namespace.RESERVED);
|
||||
|
||||
// try a few times in case the service has not listen()ed
|
||||
IOException excp = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
s.connect(a);
|
||||
return s;
|
||||
} catch (IOException e) {
|
||||
Log.d(mTag, "service not yet listen()ing; try again");
|
||||
excp = e;
|
||||
sleep(500);
|
||||
}
|
||||
}
|
||||
throw excp;
|
||||
}
|
||||
|
||||
private OutputStream getControlSocketOutput() throws IOException {
|
||||
if (mKeepaliveSocket != null) {
|
||||
mControlSocketInUse = true;
|
||||
mSocketResult = null;
|
||||
return mKeepaliveSocket.getOutputStream();
|
||||
} else {
|
||||
throw new IOException("no control socket available");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSocketResult() throws IOException {
|
||||
try {
|
||||
// will be notified when the result comes back from service
|
||||
if (mSocketResult == null) wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(mTag, "checkSocketResult(): " + e);
|
||||
} finally {
|
||||
mControlSocketInUse = false;
|
||||
if ((mSocketResult == null) || (mSocketResult < 0)) {
|
||||
throw new IOException("socket error, result from service: "
|
||||
+ mSocketResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setSocketResult(int result)
|
||||
throws VpnConnectingError {
|
||||
if (mControlSocketInUse) {
|
||||
mSocketResult = result;
|
||||
notifyAll();
|
||||
} else if (result > 0) {
|
||||
// error from daemon
|
||||
throw new VpnConnectingError((result == AUTH_ERROR_CODE)
|
||||
? VpnManager.VPN_ERROR_AUTH
|
||||
: VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
private void setResultAndCloseControlSocket(int result)
|
||||
throws VpnConnectingError {
|
||||
setSocketResult(result);
|
||||
try {
|
||||
mKeepaliveSocket.shutdownInput();
|
||||
mKeepaliveSocket.shutdownOutput();
|
||||
mKeepaliveSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(mTag, "close keepalive socket", e);
|
||||
} finally {
|
||||
mKeepaliveSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the process to be in the expected state. The method returns
|
||||
* false if after the specified duration (in seconds), the process is still
|
||||
* not in the expected state.
|
||||
*/
|
||||
private boolean blockUntil(String expectedState, int waitTime) {
|
||||
String cmd = SVC_STATE_CMD_PREFIX + mServiceName;
|
||||
int sleepTime = 200; // ms
|
||||
int n = waitTime * 1000 / sleepTime;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (expectedState.equals(SystemProperties.get(cmd))) {
|
||||
Log.d(mTag, mServiceName + " is " + expectedState + " after "
|
||||
+ (i * sleepTime) + " msec");
|
||||
break;
|
||||
}
|
||||
sleep(sleepTime);
|
||||
}
|
||||
return expectedState.equals(SystemProperties.get(cmd));
|
||||
}
|
||||
|
||||
private void outputString(OutputStream out, String s) throws IOException {
|
||||
byte[] bytes = s.getBytes();
|
||||
out.write(bytes.length);
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
199
packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
Normal file
199
packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vpn;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.net.vpn.VpnManager;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Proxy to start, stop and interact with a VPN daemon.
|
||||
* The daemon is expected to accept connection through Unix domain socket.
|
||||
* When the proxy successfully starts the daemon, it will establish a socket
|
||||
* connection with the daemon, to both send commands to the daemon and receive
|
||||
* response and connecting error code from the daemon.
|
||||
*/
|
||||
class DaemonProxy {
|
||||
private static final int WAITING_TIME = 15; // sec
|
||||
|
||||
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
|
||||
private static final String SVC_START_CMD = "ctl.start";
|
||||
private static final String SVC_STOP_CMD = "ctl.stop";
|
||||
private static final String SVC_STATE_RUNNING = "running";
|
||||
private static final String SVC_STATE_STOPPED = "stopped";
|
||||
|
||||
private static final int END_OF_ARGUMENTS = 255;
|
||||
|
||||
private String mName;
|
||||
private LocalSocket mControlSocket;
|
||||
private String mTag;
|
||||
|
||||
/**
|
||||
* Creates a proxy of the specified daemon.
|
||||
* @param daemonName name of the daemon
|
||||
*/
|
||||
DaemonProxy(String daemonName) {
|
||||
mName = daemonName;
|
||||
mTag = "SProxy_" + daemonName;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
void start() throws IOException {
|
||||
String svc = mName;
|
||||
Log.d(mTag, "----- Stop the daemon just in case: " + mName);
|
||||
SystemProperties.set(SVC_STOP_CMD, mName);
|
||||
if (!blockUntil(SVC_STATE_STOPPED, 5)) {
|
||||
throw new IOException("cannot start service anew: " + svc
|
||||
+ ", it is still running");
|
||||
}
|
||||
|
||||
Log.d(mTag, "+++++ Start: " + svc);
|
||||
SystemProperties.set(SVC_START_CMD, svc);
|
||||
|
||||
if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
|
||||
throw new IOException("cannot start service: " + svc);
|
||||
} else {
|
||||
mControlSocket = createServiceSocket();
|
||||
}
|
||||
}
|
||||
|
||||
void sendCommand(String ...args) throws IOException {
|
||||
OutputStream out = getControlSocketOutput();
|
||||
for (String arg : args) outputString(out, arg);
|
||||
out.write(END_OF_ARGUMENTS);
|
||||
out.flush();
|
||||
|
||||
int result = getResultFromSocket(true);
|
||||
if (result != args.length) {
|
||||
throw new IOException("socket error, result from service: "
|
||||
+ result);
|
||||
}
|
||||
}
|
||||
|
||||
// returns 0 if nothing is in the receive buffer
|
||||
int getResultFromSocket() throws IOException {
|
||||
return getResultFromSocket(false);
|
||||
}
|
||||
|
||||
void closeControlSocket() {
|
||||
if (mControlSocket == null) return;
|
||||
try {
|
||||
mControlSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(mTag, "close control socket", e);
|
||||
} finally {
|
||||
mControlSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
String svc = mName;
|
||||
Log.d(mTag, "----- Stop: " + svc);
|
||||
SystemProperties.set(SVC_STOP_CMD, svc);
|
||||
boolean success = blockUntil(SVC_STATE_STOPPED, 5);
|
||||
Log.d(mTag, "stopping " + svc + ", success? " + success);
|
||||
}
|
||||
|
||||
boolean isStopped() {
|
||||
String cmd = SVC_STATE_CMD_PREFIX + mName;
|
||||
return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd));
|
||||
}
|
||||
|
||||
private int getResultFromSocket(boolean blocking) throws IOException {
|
||||
LocalSocket s = mControlSocket;
|
||||
if (s == null) return 0;
|
||||
InputStream in = s.getInputStream();
|
||||
if (!blocking && in.available() == 0) return 0;
|
||||
|
||||
int data = in.read();
|
||||
Log.d(mTag, "got data from control socket: " + data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private LocalSocket createServiceSocket() throws IOException {
|
||||
LocalSocket s = new LocalSocket();
|
||||
LocalSocketAddress a = new LocalSocketAddress(mName,
|
||||
LocalSocketAddress.Namespace.RESERVED);
|
||||
|
||||
// try a few times in case the service has not listen()ed
|
||||
IOException excp = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
s.connect(a);
|
||||
return s;
|
||||
} catch (IOException e) {
|
||||
Log.d(mTag, "service not yet listen()ing; try again");
|
||||
excp = e;
|
||||
sleep(500);
|
||||
}
|
||||
}
|
||||
throw excp;
|
||||
}
|
||||
|
||||
private OutputStream getControlSocketOutput() throws IOException {
|
||||
if (mControlSocket != null) {
|
||||
return mControlSocket.getOutputStream();
|
||||
} else {
|
||||
throw new IOException("no control socket available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the process to be in the expected state. The method returns
|
||||
* false if after the specified duration (in seconds), the process is still
|
||||
* not in the expected state.
|
||||
*/
|
||||
private boolean blockUntil(String expectedState, int waitTime) {
|
||||
String cmd = SVC_STATE_CMD_PREFIX + mName;
|
||||
int sleepTime = 200; // ms
|
||||
int n = waitTime * 1000 / sleepTime;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (expectedState.equals(SystemProperties.get(cmd))) {
|
||||
Log.d(mTag, mName + " is " + expectedState + " after "
|
||||
+ (i * sleepTime) + " msec");
|
||||
break;
|
||||
}
|
||||
sleep(sleepTime);
|
||||
}
|
||||
return expectedState.equals(SystemProperties.get(cmd));
|
||||
}
|
||||
|
||||
private void outputString(OutputStream out, String s) throws IOException {
|
||||
byte[] bytes = s.getBytes();
|
||||
out.write(bytes.length);
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void sleep(int msec) {
|
||||
try {
|
||||
Thread.currentThread().sleep(msec);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.io.IOException;
|
||||
* connection.
|
||||
*/
|
||||
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
||||
private static final String IPSEC_DAEMON = "racoon";
|
||||
private static final String IPSEC = "racoon";
|
||||
|
||||
@Override
|
||||
protected void connect(String serverIp, String username, String password)
|
||||
@@ -33,9 +33,9 @@ class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
||||
L2tpIpsecPskProfile p = getProfile();
|
||||
|
||||
// IPSEC
|
||||
AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
|
||||
ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
||||
p.getPresharedKey());
|
||||
DaemonProxy ipsec = startDaemon(IPSEC);
|
||||
ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT, p.getPresharedKey());
|
||||
ipsec.closeControlSocket();
|
||||
|
||||
sleep(2000); // 2 seconds
|
||||
|
||||
|
||||
@@ -25,15 +25,16 @@ import java.io.IOException;
|
||||
* The service that manages the certificate based L2TP-over-IPSec VPN connection.
|
||||
*/
|
||||
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
|
||||
private static final String IPSEC_DAEMON = "racoon";
|
||||
private static final String IPSEC = "racoon";
|
||||
|
||||
@Override
|
||||
protected void connect(String serverIp, String username, String password)
|
||||
throws IOException {
|
||||
// IPSEC
|
||||
AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
|
||||
ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
||||
DaemonProxy ipsec = startDaemon(IPSEC);
|
||||
ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
||||
getUserkeyPath(), getUserCertPath(), getCaCertPath());
|
||||
ipsec.closeControlSocket();
|
||||
|
||||
sleep(2000); // 2 seconds
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.Arrays;
|
||||
* A helper class for sending commands to the MTP daemon (mtpd).
|
||||
*/
|
||||
class MtpdHelper {
|
||||
private static final String MTPD_SERVICE = "mtpd";
|
||||
private static final String MTPD = "mtpd";
|
||||
private static final String VPN_LINKNAME = "vpn";
|
||||
private static final String PPP_ARGS_SEPARATOR = "";
|
||||
|
||||
@@ -37,7 +37,7 @@ class MtpdHelper {
|
||||
args.add(PPP_ARGS_SEPARATOR);
|
||||
addPppArguments(vpnService, args, serverIp, username, password);
|
||||
|
||||
AndroidServiceProxy mtpd = vpnService.startService(MTPD_SERVICE);
|
||||
DaemonProxy mtpd = vpnService.startDaemon(MTPD);
|
||||
mtpd.sendCommand(args.toArray(new String[args.size()]));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vpn;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Proxy to perform a command with arguments.
|
||||
*/
|
||||
public class NormalProcessProxy extends ProcessProxy {
|
||||
private Process mProcess;
|
||||
private String[] mArgs;
|
||||
private String mTag;
|
||||
|
||||
/**
|
||||
* Creates a proxy with the arguments.
|
||||
* @param args the argument list with the first one being the command
|
||||
*/
|
||||
public NormalProcessProxy(String ...args) {
|
||||
if ((args == null) || (args.length == 0)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
mArgs = args;
|
||||
mTag = "PProxy_" + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mArgs[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
if (isStopped()) return;
|
||||
getHostThread().interrupt();
|
||||
// TODO: not sure how to reliably kill a process
|
||||
mProcess.destroy();
|
||||
setState(ProcessState.STOPPING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performTask() throws IOException, InterruptedException {
|
||||
String[] args = mArgs;
|
||||
Log.d(mTag, "+++++ Execute: " + getEntireCommand());
|
||||
ProcessBuilder pb = new ProcessBuilder(args);
|
||||
setState(ProcessState.RUNNING);
|
||||
Process p = mProcess = pb.start();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
p.getInputStream()));
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if ((line == null) || isStopping()) break;
|
||||
Log.d(mTag, line);
|
||||
}
|
||||
|
||||
Log.d(mTag, "----- p.waitFor(): " + getName());
|
||||
p.waitFor();
|
||||
Log.d(mTag, "----- Done: " + getName());
|
||||
}
|
||||
|
||||
private CharSequence getEntireCommand() {
|
||||
String[] args = mArgs;
|
||||
StringBuilder sb = new StringBuilder(args[0]);
|
||||
for (int i = 1; i < args.length; i++) sb.append(' ').append(args[i]);
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.vpn;
|
||||
|
||||
import android.os.ConditionVariable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A proxy class that spawns a process to accomplish a certain task.
|
||||
*/
|
||||
public abstract class ProcessProxy {
|
||||
/**
|
||||
* Defines the interface to call back when the process is finished or an
|
||||
* error occurs during execution.
|
||||
*/
|
||||
public static interface Callback {
|
||||
/**
|
||||
* Called when the process is finished.
|
||||
* @param proxy the proxy that hosts the process
|
||||
*/
|
||||
void done(ProcessProxy proxy);
|
||||
|
||||
/**
|
||||
* Called when some error occurs.
|
||||
* @param proxy the proxy that hosts the process
|
||||
*/
|
||||
void error(ProcessProxy proxy, Throwable error);
|
||||
}
|
||||
|
||||
protected enum ProcessState {
|
||||
STOPPED, STARTING, RUNNING, STOPPING, ERROR
|
||||
}
|
||||
|
||||
private ProcessState mState = ProcessState.STOPPED;
|
||||
private ConditionVariable mLock = new ConditionVariable();
|
||||
private Thread mThread;
|
||||
|
||||
/**
|
||||
* Returns the name of the process.
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* Starts the process with a callback.
|
||||
* @param callback the callback to get notified when the process is finished
|
||||
* or an error occurs during execution
|
||||
* @throws IOException when the process is already running or failed to
|
||||
* start
|
||||
*/
|
||||
public synchronized void start(final Callback callback) throws IOException {
|
||||
if (!isStopped()) {
|
||||
throw new IOException("Process is already running: " + this);
|
||||
}
|
||||
mLock.close();
|
||||
setState(ProcessState.STARTING);
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
performTask();
|
||||
setState(ProcessState.STOPPED);
|
||||
mLock.open();
|
||||
if (callback != null) callback.done(ProcessProxy.this);
|
||||
} catch (Throwable e) {
|
||||
setState(ProcessState.ERROR);
|
||||
if (callback != null) callback.error(ProcessProxy.this, e);
|
||||
} finally {
|
||||
mThread = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.start();
|
||||
mThread = thread;
|
||||
if (!waitUntilRunning()) {
|
||||
throw new IOException("Failed to start the process: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process.
|
||||
* @throws IOException when the process is already running or failed to
|
||||
* start
|
||||
*/
|
||||
public synchronized void start() throws IOException {
|
||||
start(null);
|
||||
if (!waitUntilDone()) {
|
||||
throw new IOException("Failed to complete the process: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thread that hosts the process.
|
||||
*/
|
||||
public Thread getHostThread() {
|
||||
return mThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the current thread until the hosted process is finished.
|
||||
*
|
||||
* @return true if the process is finished normally; false if an error
|
||||
* occurs
|
||||
*/
|
||||
public boolean waitUntilDone() {
|
||||
while (!mLock.block(1000)) {
|
||||
if (isStopped() || isInError()) break;
|
||||
}
|
||||
return isStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the current thread until the hosted process is running.
|
||||
*
|
||||
* @return true if the process is running normally; false if the process
|
||||
* is in another state
|
||||
*/
|
||||
private boolean waitUntilRunning() {
|
||||
for (;;) {
|
||||
if (!isStarting()) break;
|
||||
}
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and destroys the process.
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Checks whether the process is finished.
|
||||
* @return true if the process is stopped
|
||||
*/
|
||||
public boolean isStopped() {
|
||||
return (mState == ProcessState.STOPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the process is being stopped.
|
||||
* @return true if the process is being stopped
|
||||
*/
|
||||
public boolean isStopping() {
|
||||
return (mState == ProcessState.STOPPING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the process is being started.
|
||||
* @return true if the process is being started
|
||||
*/
|
||||
public boolean isStarting() {
|
||||
return (mState == ProcessState.STARTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the process is running.
|
||||
* @return true if the process is running
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return (mState == ProcessState.RUNNING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether some error has occurred and the process is stopped.
|
||||
* @return true if some error has occurred and the process is stopped
|
||||
*/
|
||||
public boolean isInError() {
|
||||
return (mState == ProcessState.ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual task. Subclasses must make sure that the method
|
||||
* is blocked until the task is done or an error occurs.
|
||||
*/
|
||||
protected abstract void performTask()
|
||||
throws IOException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Sets the process state.
|
||||
* @param state the new state to be in
|
||||
*/
|
||||
protected void setState(ProcessState state) {
|
||||
mState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the current thread sleep for the specified time.
|
||||
* @param msec time to sleep in miliseconds
|
||||
*/
|
||||
protected void sleep(int msec) {
|
||||
try {
|
||||
Thread.currentThread().sleep(msec);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,14 +50,15 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
private static final String REMOTE_IP = "net.ipremote";
|
||||
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
|
||||
|
||||
private static final int AUTH_ERROR_CODE = 51;
|
||||
|
||||
private final String TAG = VpnService.class.getSimpleName();
|
||||
|
||||
E mProfile;
|
||||
VpnServiceBinder mContext;
|
||||
|
||||
private VpnState mState = VpnState.IDLE;
|
||||
private boolean mInError;
|
||||
private VpnConnectingError mError;
|
||||
private Throwable mError;
|
||||
|
||||
// connection settings
|
||||
private String mOriginalDns1;
|
||||
@@ -68,8 +69,8 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
|
||||
private long mStartTime; // VPN connection start time
|
||||
|
||||
// for helping managing multiple Android services
|
||||
private ServiceHelper mServiceHelper = new ServiceHelper();
|
||||
// for helping managing multiple daemons
|
||||
private DaemonHelper mDaemonHelper = new DaemonHelper();
|
||||
|
||||
// for helping showing, updating notification
|
||||
private NotificationHelper mNotification = new NotificationHelper();
|
||||
@@ -81,18 +82,11 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
String password) throws IOException;
|
||||
|
||||
/**
|
||||
* Tears down the VPN connection. The base class simply terminates all the
|
||||
* Android services. A subclass may need to do some clean-up before that.
|
||||
* Starts a VPN daemon.
|
||||
*/
|
||||
protected void disconnect() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an Android service defined in init.rc.
|
||||
*/
|
||||
protected AndroidServiceProxy startService(String serviceName)
|
||||
protected DaemonProxy startDaemon(String daemonName)
|
||||
throws IOException {
|
||||
return mServiceHelper.startService(serviceName);
|
||||
return mDaemonHelper.startDaemon(daemonName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,28 +103,6 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
return InetAddress.getByName(hostName).getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system property. The method is blocked until the value is
|
||||
* settled in.
|
||||
* @param name the name of the property
|
||||
* @param value the value of the property
|
||||
* @throws IOException if it fails to set the property within 2 seconds
|
||||
*/
|
||||
protected void setSystemProperty(String name, String value)
|
||||
throws IOException {
|
||||
SystemProperties.set(name, value);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
String v = SystemProperties.get(name);
|
||||
if (v.equals(value)) {
|
||||
return;
|
||||
} else {
|
||||
Log.d(TAG, "sys_prop: wait for " + name + " to settle in");
|
||||
sleep(400);
|
||||
}
|
||||
}
|
||||
throw new IOException("Failed to set system property: " + name);
|
||||
}
|
||||
|
||||
void setContext(VpnServiceBinder context, E profile) {
|
||||
mContext = context;
|
||||
mProfile = profile;
|
||||
@@ -153,44 +125,42 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "onConnect()", e);
|
||||
mError = newConnectingError(e);
|
||||
onError();
|
||||
onError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void onDisconnect(boolean cleanUpServices) {
|
||||
synchronized void onDisconnect() {
|
||||
try {
|
||||
Log.d(TAG, " disconnecting VPN...");
|
||||
mState = VpnState.DISCONNECTING;
|
||||
broadcastConnectivity(VpnState.DISCONNECTING);
|
||||
mNotification.showDisconnect();
|
||||
|
||||
// subclass implementation
|
||||
if (cleanUpServices) disconnect();
|
||||
|
||||
mServiceHelper.stop();
|
||||
mDaemonHelper.stopAll();
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "onDisconnect()", e);
|
||||
} finally {
|
||||
onFinalCleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void onError() {
|
||||
private void onError(Throwable error) {
|
||||
// error may occur during or after connection setup
|
||||
// and it may be due to one or all services gone
|
||||
mInError = true;
|
||||
switch (mState) {
|
||||
case CONNECTED:
|
||||
onDisconnect(true);
|
||||
break;
|
||||
|
||||
case CONNECTING:
|
||||
onDisconnect(false);
|
||||
break;
|
||||
if (mError != null) {
|
||||
Log.w(TAG, " multiple errors occur, record the last one: "
|
||||
+ error);
|
||||
}
|
||||
mError = error;
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
private void onError(int errorCode) {
|
||||
onError(new VpnConnectingError(errorCode));
|
||||
}
|
||||
|
||||
|
||||
private void onBeforeConnect() {
|
||||
mNotification.disableNotification();
|
||||
|
||||
@@ -201,41 +171,39 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
}
|
||||
|
||||
private void waitUntilConnectedOrTimedout() {
|
||||
// Run this in the background thread to not block UI
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
sleep(2000); // 2 seconds
|
||||
for (int i = 0; i < 60; i++) {
|
||||
if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
|
||||
onConnected();
|
||||
return;
|
||||
} else if (mState != VpnState.CONNECTING) {
|
||||
break;
|
||||
}
|
||||
sleep(500); // 0.5 second
|
||||
}
|
||||
|
||||
synchronized (VpnService.this) {
|
||||
if (mState == VpnState.CONNECTING) {
|
||||
Log.d(TAG, " connecting timed out !!");
|
||||
mError = newConnectingError(
|
||||
new IOException("Connecting timed out"));
|
||||
onError();
|
||||
}
|
||||
}
|
||||
sleep(2000); // 2 seconds
|
||||
for (int i = 0; i < 60; i++) {
|
||||
if (mState != VpnState.CONNECTING) {
|
||||
break;
|
||||
} else if (VPN_IS_UP.equals(
|
||||
SystemProperties.get(VPN_STATUS))) {
|
||||
onConnected();
|
||||
return;
|
||||
} else if (mDaemonHelper.anySocketError()) {
|
||||
return;
|
||||
}
|
||||
}).start();
|
||||
sleep(500); // 0.5 second
|
||||
}
|
||||
|
||||
synchronized (VpnService.this) {
|
||||
if (mState == VpnState.CONNECTING) {
|
||||
Log.d(TAG, " connecting timed out !!");
|
||||
onError(new IOException("Connecting timed out"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onConnected() {
|
||||
Log.d(TAG, "onConnected()");
|
||||
|
||||
mDaemonHelper.closeSockets();
|
||||
saveVpnDnsProperties();
|
||||
saveAndSetDomainSuffices();
|
||||
startConnectivityMonitor();
|
||||
|
||||
mState = VpnState.CONNECTED;
|
||||
broadcastConnectivity(VpnState.CONNECTED);
|
||||
|
||||
enterConnectivityLoop();
|
||||
}
|
||||
|
||||
private synchronized void onFinalCleanUp() {
|
||||
@@ -244,7 +212,7 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
if (mState == VpnState.IDLE) return;
|
||||
|
||||
// keep the notification when error occurs
|
||||
if (!mInError) mNotification.disableNotification();
|
||||
if (!anyError()) mNotification.disableNotification();
|
||||
|
||||
restoreOriginalDnsProperties();
|
||||
restoreOriginalDomainSuffices();
|
||||
@@ -255,37 +223,8 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
mContext.stopSelf();
|
||||
}
|
||||
|
||||
private VpnConnectingError newConnectingError(Throwable e) {
|
||||
return new VpnConnectingError(
|
||||
(e instanceof UnknownHostException)
|
||||
? VpnManager.VPN_ERROR_UNKNOWN_SERVER
|
||||
: VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
||||
}
|
||||
|
||||
private synchronized void onOneServiceGone() {
|
||||
switch (mState) {
|
||||
case IDLE:
|
||||
case DISCONNECTING:
|
||||
break;
|
||||
|
||||
default:
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onAllServicesGone() {
|
||||
switch (mState) {
|
||||
case IDLE:
|
||||
break;
|
||||
|
||||
case DISCONNECTING:
|
||||
// daemons are gone; now clean up everything
|
||||
onFinalCleanUp();
|
||||
break;
|
||||
|
||||
default:
|
||||
onError();
|
||||
}
|
||||
private boolean anyError() {
|
||||
return (mError != null);
|
||||
}
|
||||
|
||||
private void restoreOriginalDnsProperties() {
|
||||
@@ -341,46 +280,65 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
|
||||
private void broadcastConnectivity(VpnState s) {
|
||||
VpnManager m = new VpnManager(mContext);
|
||||
if ((s == VpnState.IDLE) && (mError != null)) {
|
||||
m.broadcastConnectivity(mProfile.getName(), s,
|
||||
mError.getErrorCode());
|
||||
Throwable err = mError;
|
||||
if ((s == VpnState.IDLE) && (err != null)) {
|
||||
if (err instanceof UnknownHostException) {
|
||||
m.broadcastConnectivity(mProfile.getName(), s,
|
||||
VpnManager.VPN_ERROR_UNKNOWN_SERVER);
|
||||
} else if (err instanceof VpnConnectingError) {
|
||||
m.broadcastConnectivity(mProfile.getName(), s,
|
||||
((VpnConnectingError) err).getErrorCode());
|
||||
} else {
|
||||
m.broadcastConnectivity(mProfile.getName(), s,
|
||||
VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
||||
}
|
||||
} else {
|
||||
m.broadcastConnectivity(mProfile.getName(), s);
|
||||
}
|
||||
}
|
||||
|
||||
private void startConnectivityMonitor() {
|
||||
private void enterConnectivityLoop() {
|
||||
mStartTime = System.currentTimeMillis();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Log.d(TAG, " +++++ connectivity monitor running");
|
||||
try {
|
||||
for (;;) {
|
||||
synchronized (VpnService.this) {
|
||||
if (mState != VpnState.CONNECTED) break;
|
||||
mNotification.update();
|
||||
checkConnectivity();
|
||||
VpnService.this.wait(1000); // 1 second
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "connectivity monitor", e);
|
||||
Log.d(TAG, " +++++ connectivity monitor running");
|
||||
try {
|
||||
for (;;) {
|
||||
synchronized (VpnService.this) {
|
||||
if (mState != VpnState.CONNECTED) break;
|
||||
mNotification.update();
|
||||
checkConnectivity();
|
||||
VpnService.this.wait(1000); // 1 second
|
||||
}
|
||||
Log.d(TAG, " ----- connectivity monitor stopped");
|
||||
}
|
||||
}).start();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "connectivity monitor", e);
|
||||
}
|
||||
Log.d(TAG, " ----- connectivity monitor stopped");
|
||||
}
|
||||
|
||||
private void checkConnectivity() {
|
||||
checkDnsProperties();
|
||||
if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
|
||||
onDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDnsProperties() {
|
||||
private boolean isLocalIpChanged() {
|
||||
// TODO
|
||||
if (!isDnsIntact()) {
|
||||
Log.w(TAG, " local IP changed");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDnsIntact() {
|
||||
String dns1 = SystemProperties.get(DNS1);
|
||||
if (!mVpnDns1.equals(dns1)) {
|
||||
Log.w(TAG, " dns being overridden by: " + dns1);
|
||||
onError();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,56 +349,64 @@ abstract class VpnService<E extends VpnProfile> {
|
||||
}
|
||||
}
|
||||
|
||||
private InetAddress toInetAddress(int addr) throws IOException {
|
||||
byte[] aa = new byte[4];
|
||||
for (int i= 0; i < aa.length; i++) {
|
||||
aa[i] = (byte) (addr & 0x0FF);
|
||||
addr >>= 8;
|
||||
}
|
||||
return InetAddress.getByAddress(aa);
|
||||
}
|
||||
private class DaemonHelper {
|
||||
private List<DaemonProxy> mDaemonList =
|
||||
new ArrayList<DaemonProxy>();
|
||||
|
||||
private class ServiceHelper implements ProcessProxy.Callback {
|
||||
private List<AndroidServiceProxy> mServiceList =
|
||||
new ArrayList<AndroidServiceProxy>();
|
||||
|
||||
// starts an Android service
|
||||
AndroidServiceProxy startService(String serviceName)
|
||||
synchronized DaemonProxy startDaemon(String daemonName)
|
||||
throws IOException {
|
||||
AndroidServiceProxy service = new AndroidServiceProxy(serviceName);
|
||||
mServiceList.add(service);
|
||||
service.start(this);
|
||||
return service;
|
||||
DaemonProxy daemon = new DaemonProxy(daemonName);
|
||||
mDaemonList.add(daemon);
|
||||
daemon.start();
|
||||
return daemon;
|
||||
}
|
||||
|
||||
// stops all the Android services
|
||||
void stop() {
|
||||
if (mServiceList.isEmpty()) {
|
||||
synchronized void stopAll() {
|
||||
if (mDaemonList.isEmpty()) {
|
||||
onFinalCleanUp();
|
||||
} else {
|
||||
for (AndroidServiceProxy s : mServiceList) s.stop();
|
||||
for (DaemonProxy s : mDaemonList) s.stop();
|
||||
}
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void done(ProcessProxy p) {
|
||||
Log.d(TAG, "service done: " + p.getName());
|
||||
commonCallback((AndroidServiceProxy) p);
|
||||
synchronized void closeSockets() {
|
||||
for (DaemonProxy s : mDaemonList) s.closeControlSocket();
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void error(ProcessProxy p, Throwable e) {
|
||||
Log.e(TAG, "service error: " + p.getName(), e);
|
||||
if (e instanceof VpnConnectingError) {
|
||||
mError = (VpnConnectingError) e;
|
||||
synchronized boolean anyDaemonStopped() {
|
||||
for (DaemonProxy s : mDaemonList) {
|
||||
if (s.isStopped()) {
|
||||
Log.w(TAG, " daemon gone: " + s.getName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
commonCallback((AndroidServiceProxy) p);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void commonCallback(AndroidServiceProxy service) {
|
||||
mServiceList.remove(service);
|
||||
onOneServiceGone();
|
||||
if (mServiceList.isEmpty()) onAllServicesGone();
|
||||
private int getResultFromSocket(DaemonProxy s) {
|
||||
try {
|
||||
return s.getResultFromSocket();
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean anySocketError() {
|
||||
for (DaemonProxy s : mDaemonList) {
|
||||
switch (getResultFromSocket(s)) {
|
||||
case 0:
|
||||
continue;
|
||||
|
||||
case AUTH_ERROR_CODE:
|
||||
onError(VpnManager.VPN_ERROR_AUTH);
|
||||
return true;
|
||||
|
||||
default:
|
||||
onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,11 +42,13 @@ public class VpnServiceBinder extends Service {
|
||||
|
||||
private final IBinder mBinder = new IVpnService.Stub() {
|
||||
public boolean connect(VpnProfile p, String username, String password) {
|
||||
android.util.Log.d("VpnServiceBinder", "becoming foreground");
|
||||
setForeground(true);
|
||||
return VpnServiceBinder.this.connect(p, username, password);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (mService != null) mService.onDisconnect(true);
|
||||
VpnServiceBinder.this.disconnect();
|
||||
}
|
||||
|
||||
public void checkStatus(VpnProfile p) {
|
||||
@@ -54,21 +56,39 @@ public class VpnServiceBinder extends Service {
|
||||
}
|
||||
};
|
||||
|
||||
public void onStart (Intent intent, int startId) {
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
super.onStart(intent, startId);
|
||||
setForeground(true);
|
||||
android.util.Log.d("VpnServiceBinder", "becomes a foreground service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private synchronized boolean connect(
|
||||
VpnProfile p, String username, String password) {
|
||||
private synchronized boolean connect(final VpnProfile p,
|
||||
final String username, final String password) {
|
||||
if (mService != null) return false;
|
||||
mService = createService(p);
|
||||
return mService.onConnect(username, password);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
mService = createService(p);
|
||||
mService.onConnect(username, password);
|
||||
}
|
||||
}).start();
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized void disconnect() {
|
||||
if (mService == null) return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
mService.onDisconnect();
|
||||
android.util.Log.d("VpnServiceBinder", "becoming background");
|
||||
setForeground(false);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private synchronized void checkStatus(VpnProfile p) {
|
||||
|
||||
Reference in New Issue
Block a user