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.
|
* connection.
|
||||||
*/
|
*/
|
||||||
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
||||||
private static final String IPSEC_DAEMON = "racoon";
|
private static final String IPSEC = "racoon";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connect(String serverIp, String username, String password)
|
protected void connect(String serverIp, String username, String password)
|
||||||
@@ -33,9 +33,9 @@ class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
|||||||
L2tpIpsecPskProfile p = getProfile();
|
L2tpIpsecPskProfile p = getProfile();
|
||||||
|
|
||||||
// IPSEC
|
// IPSEC
|
||||||
AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
|
DaemonProxy ipsec = startDaemon(IPSEC);
|
||||||
ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT, p.getPresharedKey());
|
||||||
p.getPresharedKey());
|
ipsec.closeControlSocket();
|
||||||
|
|
||||||
sleep(2000); // 2 seconds
|
sleep(2000); // 2 seconds
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ import java.io.IOException;
|
|||||||
* The service that manages the certificate based L2TP-over-IPSec VPN connection.
|
* The service that manages the certificate based L2TP-over-IPSec VPN connection.
|
||||||
*/
|
*/
|
||||||
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
|
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
|
||||||
private static final String IPSEC_DAEMON = "racoon";
|
private static final String IPSEC = "racoon";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connect(String serverIp, String username, String password)
|
protected void connect(String serverIp, String username, String password)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// IPSEC
|
// IPSEC
|
||||||
AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON);
|
DaemonProxy ipsec = startDaemon(IPSEC);
|
||||||
ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT,
|
||||||
getUserkeyPath(), getUserCertPath(), getCaCertPath());
|
getUserkeyPath(), getUserCertPath(), getCaCertPath());
|
||||||
|
ipsec.closeControlSocket();
|
||||||
|
|
||||||
sleep(2000); // 2 seconds
|
sleep(2000); // 2 seconds
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.util.Arrays;
|
|||||||
* A helper class for sending commands to the MTP daemon (mtpd).
|
* A helper class for sending commands to the MTP daemon (mtpd).
|
||||||
*/
|
*/
|
||||||
class MtpdHelper {
|
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 VPN_LINKNAME = "vpn";
|
||||||
private static final String PPP_ARGS_SEPARATOR = "";
|
private static final String PPP_ARGS_SEPARATOR = "";
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class MtpdHelper {
|
|||||||
args.add(PPP_ARGS_SEPARATOR);
|
args.add(PPP_ARGS_SEPARATOR);
|
||||||
addPppArguments(vpnService, args, serverIp, username, password);
|
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()]));
|
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 REMOTE_IP = "net.ipremote";
|
||||||
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
|
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();
|
private final String TAG = VpnService.class.getSimpleName();
|
||||||
|
|
||||||
E mProfile;
|
E mProfile;
|
||||||
VpnServiceBinder mContext;
|
VpnServiceBinder mContext;
|
||||||
|
|
||||||
private VpnState mState = VpnState.IDLE;
|
private VpnState mState = VpnState.IDLE;
|
||||||
private boolean mInError;
|
private Throwable mError;
|
||||||
private VpnConnectingError mError;
|
|
||||||
|
|
||||||
// connection settings
|
// connection settings
|
||||||
private String mOriginalDns1;
|
private String mOriginalDns1;
|
||||||
@@ -68,8 +69,8 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
|
|
||||||
private long mStartTime; // VPN connection start time
|
private long mStartTime; // VPN connection start time
|
||||||
|
|
||||||
// for helping managing multiple Android services
|
// for helping managing multiple daemons
|
||||||
private ServiceHelper mServiceHelper = new ServiceHelper();
|
private DaemonHelper mDaemonHelper = new DaemonHelper();
|
||||||
|
|
||||||
// for helping showing, updating notification
|
// for helping showing, updating notification
|
||||||
private NotificationHelper mNotification = new NotificationHelper();
|
private NotificationHelper mNotification = new NotificationHelper();
|
||||||
@@ -81,18 +82,11 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
String password) throws IOException;
|
String password) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tears down the VPN connection. The base class simply terminates all the
|
* Starts a VPN daemon.
|
||||||
* Android services. A subclass may need to do some clean-up before that.
|
|
||||||
*/
|
*/
|
||||||
protected void disconnect() {
|
protected DaemonProxy startDaemon(String daemonName)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an Android service defined in init.rc.
|
|
||||||
*/
|
|
||||||
protected AndroidServiceProxy startService(String serviceName)
|
|
||||||
throws IOException {
|
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();
|
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) {
|
void setContext(VpnServiceBinder context, E profile) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mProfile = profile;
|
mProfile = profile;
|
||||||
@@ -153,44 +125,42 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "onConnect()", e);
|
Log.e(TAG, "onConnect()", e);
|
||||||
mError = newConnectingError(e);
|
onError(e);
|
||||||
onError();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onDisconnect(boolean cleanUpServices) {
|
synchronized void onDisconnect() {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, " disconnecting VPN...");
|
Log.d(TAG, " disconnecting VPN...");
|
||||||
mState = VpnState.DISCONNECTING;
|
mState = VpnState.DISCONNECTING;
|
||||||
broadcastConnectivity(VpnState.DISCONNECTING);
|
broadcastConnectivity(VpnState.DISCONNECTING);
|
||||||
mNotification.showDisconnect();
|
mNotification.showDisconnect();
|
||||||
|
|
||||||
// subclass implementation
|
mDaemonHelper.stopAll();
|
||||||
if (cleanUpServices) disconnect();
|
|
||||||
|
|
||||||
mServiceHelper.stop();
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "onDisconnect()", e);
|
Log.e(TAG, "onDisconnect()", e);
|
||||||
|
} finally {
|
||||||
onFinalCleanUp();
|
onFinalCleanUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onError() {
|
private void onError(Throwable error) {
|
||||||
// error may occur during or after connection setup
|
// error may occur during or after connection setup
|
||||||
// and it may be due to one or all services gone
|
// and it may be due to one or all services gone
|
||||||
mInError = true;
|
if (mError != null) {
|
||||||
switch (mState) {
|
Log.w(TAG, " multiple errors occur, record the last one: "
|
||||||
case CONNECTED:
|
+ error);
|
||||||
onDisconnect(true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CONNECTING:
|
|
||||||
onDisconnect(false);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
mError = error;
|
||||||
|
onDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onError(int errorCode) {
|
||||||
|
onError(new VpnConnectingError(errorCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onBeforeConnect() {
|
private void onBeforeConnect() {
|
||||||
mNotification.disableNotification();
|
mNotification.disableNotification();
|
||||||
|
|
||||||
@@ -201,41 +171,39 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void waitUntilConnectedOrTimedout() {
|
private void waitUntilConnectedOrTimedout() {
|
||||||
// Run this in the background thread to not block UI
|
sleep(2000); // 2 seconds
|
||||||
new Thread(new Runnable() {
|
for (int i = 0; i < 60; i++) {
|
||||||
public void run() {
|
if (mState != VpnState.CONNECTING) {
|
||||||
sleep(2000); // 2 seconds
|
break;
|
||||||
for (int i = 0; i < 60; i++) {
|
} else if (VPN_IS_UP.equals(
|
||||||
if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
|
SystemProperties.get(VPN_STATUS))) {
|
||||||
onConnected();
|
onConnected();
|
||||||
return;
|
return;
|
||||||
} else if (mState != VpnState.CONNECTING) {
|
} else if (mDaemonHelper.anySocketError()) {
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).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() {
|
private synchronized void onConnected() {
|
||||||
Log.d(TAG, "onConnected()");
|
Log.d(TAG, "onConnected()");
|
||||||
|
|
||||||
|
mDaemonHelper.closeSockets();
|
||||||
saveVpnDnsProperties();
|
saveVpnDnsProperties();
|
||||||
saveAndSetDomainSuffices();
|
saveAndSetDomainSuffices();
|
||||||
startConnectivityMonitor();
|
|
||||||
|
|
||||||
mState = VpnState.CONNECTED;
|
mState = VpnState.CONNECTED;
|
||||||
broadcastConnectivity(VpnState.CONNECTED);
|
broadcastConnectivity(VpnState.CONNECTED);
|
||||||
|
|
||||||
|
enterConnectivityLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onFinalCleanUp() {
|
private synchronized void onFinalCleanUp() {
|
||||||
@@ -244,7 +212,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
if (mState == VpnState.IDLE) return;
|
if (mState == VpnState.IDLE) return;
|
||||||
|
|
||||||
// keep the notification when error occurs
|
// keep the notification when error occurs
|
||||||
if (!mInError) mNotification.disableNotification();
|
if (!anyError()) mNotification.disableNotification();
|
||||||
|
|
||||||
restoreOriginalDnsProperties();
|
restoreOriginalDnsProperties();
|
||||||
restoreOriginalDomainSuffices();
|
restoreOriginalDomainSuffices();
|
||||||
@@ -255,37 +223,8 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
mContext.stopSelf();
|
mContext.stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private VpnConnectingError newConnectingError(Throwable e) {
|
private boolean anyError() {
|
||||||
return new VpnConnectingError(
|
return (mError != null);
|
||||||
(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 void restoreOriginalDnsProperties() {
|
private void restoreOriginalDnsProperties() {
|
||||||
@@ -341,46 +280,65 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
|
|
||||||
private void broadcastConnectivity(VpnState s) {
|
private void broadcastConnectivity(VpnState s) {
|
||||||
VpnManager m = new VpnManager(mContext);
|
VpnManager m = new VpnManager(mContext);
|
||||||
if ((s == VpnState.IDLE) && (mError != null)) {
|
Throwable err = mError;
|
||||||
m.broadcastConnectivity(mProfile.getName(), s,
|
if ((s == VpnState.IDLE) && (err != null)) {
|
||||||
mError.getErrorCode());
|
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 {
|
} else {
|
||||||
m.broadcastConnectivity(mProfile.getName(), s);
|
m.broadcastConnectivity(mProfile.getName(), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startConnectivityMonitor() {
|
private void enterConnectivityLoop() {
|
||||||
mStartTime = System.currentTimeMillis();
|
mStartTime = System.currentTimeMillis();
|
||||||
|
|
||||||
new Thread(new Runnable() {
|
Log.d(TAG, " +++++ connectivity monitor running");
|
||||||
public void run() {
|
try {
|
||||||
Log.d(TAG, " +++++ connectivity monitor running");
|
for (;;) {
|
||||||
try {
|
synchronized (VpnService.this) {
|
||||||
for (;;) {
|
if (mState != VpnState.CONNECTED) break;
|
||||||
synchronized (VpnService.this) {
|
mNotification.update();
|
||||||
if (mState != VpnState.CONNECTED) break;
|
checkConnectivity();
|
||||||
mNotification.update();
|
VpnService.this.wait(1000); // 1 second
|
||||||
checkConnectivity();
|
|
||||||
VpnService.this.wait(1000); // 1 second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "connectivity monitor", e);
|
|
||||||
}
|
}
|
||||||
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() {
|
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);
|
String dns1 = SystemProperties.get(DNS1);
|
||||||
if (!mVpnDns1.equals(dns1)) {
|
if (!mVpnDns1.equals(dns1)) {
|
||||||
Log.w(TAG, " dns being overridden by: " + 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 {
|
private class DaemonHelper {
|
||||||
byte[] aa = new byte[4];
|
private List<DaemonProxy> mDaemonList =
|
||||||
for (int i= 0; i < aa.length; i++) {
|
new ArrayList<DaemonProxy>();
|
||||||
aa[i] = (byte) (addr & 0x0FF);
|
|
||||||
addr >>= 8;
|
|
||||||
}
|
|
||||||
return InetAddress.getByAddress(aa);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ServiceHelper implements ProcessProxy.Callback {
|
synchronized DaemonProxy startDaemon(String daemonName)
|
||||||
private List<AndroidServiceProxy> mServiceList =
|
|
||||||
new ArrayList<AndroidServiceProxy>();
|
|
||||||
|
|
||||||
// starts an Android service
|
|
||||||
AndroidServiceProxy startService(String serviceName)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
AndroidServiceProxy service = new AndroidServiceProxy(serviceName);
|
DaemonProxy daemon = new DaemonProxy(daemonName);
|
||||||
mServiceList.add(service);
|
mDaemonList.add(daemon);
|
||||||
service.start(this);
|
daemon.start();
|
||||||
return service;
|
return daemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// stops all the Android services
|
synchronized void stopAll() {
|
||||||
void stop() {
|
if (mDaemonList.isEmpty()) {
|
||||||
if (mServiceList.isEmpty()) {
|
|
||||||
onFinalCleanUp();
|
onFinalCleanUp();
|
||||||
} else {
|
} else {
|
||||||
for (AndroidServiceProxy s : mServiceList) s.stop();
|
for (DaemonProxy s : mDaemonList) s.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//@Override
|
synchronized void closeSockets() {
|
||||||
public void done(ProcessProxy p) {
|
for (DaemonProxy s : mDaemonList) s.closeControlSocket();
|
||||||
Log.d(TAG, "service done: " + p.getName());
|
|
||||||
commonCallback((AndroidServiceProxy) p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//@Override
|
synchronized boolean anyDaemonStopped() {
|
||||||
public void error(ProcessProxy p, Throwable e) {
|
for (DaemonProxy s : mDaemonList) {
|
||||||
Log.e(TAG, "service error: " + p.getName(), e);
|
if (s.isStopped()) {
|
||||||
if (e instanceof VpnConnectingError) {
|
Log.w(TAG, " daemon gone: " + s.getName());
|
||||||
mError = (VpnConnectingError) e;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
commonCallback((AndroidServiceProxy) p);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commonCallback(AndroidServiceProxy service) {
|
private int getResultFromSocket(DaemonProxy s) {
|
||||||
mServiceList.remove(service);
|
try {
|
||||||
onOneServiceGone();
|
return s.getResultFromSocket();
|
||||||
if (mServiceList.isEmpty()) onAllServicesGone();
|
} 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() {
|
private final IBinder mBinder = new IVpnService.Stub() {
|
||||||
public boolean connect(VpnProfile p, String username, String password) {
|
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);
|
return VpnServiceBinder.this.connect(p, username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
if (mService != null) mService.onDisconnect(true);
|
VpnServiceBinder.this.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkStatus(VpnProfile p) {
|
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);
|
super.onStart(intent, startId);
|
||||||
setForeground(true);
|
|
||||||
android.util.Log.d("VpnServiceBinder", "becomes a foreground service");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return mBinder;
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean connect(
|
private synchronized boolean connect(final VpnProfile p,
|
||||||
VpnProfile p, String username, String password) {
|
final String username, final String password) {
|
||||||
if (mService != null) return false;
|
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) {
|
private synchronized void checkStatus(VpnProfile p) {
|
||||||
|
|||||||
Reference in New Issue
Block a user