Merge change 9114 into donut
* changes: Add state saving mechanism to support proc restart
This commit is contained in:
@ -25,6 +25,7 @@ import android.util.Log;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy to start, stop and interact with a VPN daemon.
|
* Proxy to start, stop and interact with a VPN daemon.
|
||||||
@ -33,7 +34,10 @@ import java.io.OutputStream;
|
|||||||
* connection with the daemon, to both send commands to the daemon and receive
|
* connection with the daemon, to both send commands to the daemon and receive
|
||||||
* response and connecting error code from the daemon.
|
* response and connecting error code from the daemon.
|
||||||
*/
|
*/
|
||||||
class DaemonProxy {
|
class DaemonProxy implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final boolean DBG = true;
|
||||||
|
|
||||||
private static final int WAITING_TIME = 15; // sec
|
private static final int WAITING_TIME = 15; // sec
|
||||||
|
|
||||||
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
|
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
|
||||||
@ -45,8 +49,8 @@ class DaemonProxy {
|
|||||||
private static final int END_OF_ARGUMENTS = 255;
|
private static final int END_OF_ARGUMENTS = 255;
|
||||||
|
|
||||||
private String mName;
|
private String mName;
|
||||||
private LocalSocket mControlSocket;
|
|
||||||
private String mTag;
|
private String mTag;
|
||||||
|
private transient LocalSocket mControlSocket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a proxy of the specified daemon.
|
* Creates a proxy of the specified daemon.
|
||||||
@ -63,14 +67,8 @@ class DaemonProxy {
|
|||||||
|
|
||||||
void start() throws IOException {
|
void start() throws IOException {
|
||||||
String svc = mName;
|
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);
|
Log.i(mTag, "Start VPN daemon: " + svc);
|
||||||
SystemProperties.set(SVC_START_CMD, svc);
|
SystemProperties.set(SVC_START_CMD, svc);
|
||||||
|
|
||||||
if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
|
if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
|
||||||
@ -103,7 +101,7 @@ class DaemonProxy {
|
|||||||
try {
|
try {
|
||||||
mControlSocket.close();
|
mControlSocket.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(mTag, "close control socket", e);
|
Log.w(mTag, "close control socket", e);
|
||||||
} finally {
|
} finally {
|
||||||
mControlSocket = null;
|
mControlSocket = null;
|
||||||
}
|
}
|
||||||
@ -111,10 +109,10 @@ class DaemonProxy {
|
|||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
String svc = mName;
|
String svc = mName;
|
||||||
Log.d(mTag, "----- Stop: " + svc);
|
Log.i(mTag, "Stop VPN daemon: " + svc);
|
||||||
SystemProperties.set(SVC_STOP_CMD, svc);
|
SystemProperties.set(SVC_STOP_CMD, svc);
|
||||||
boolean success = blockUntil(SVC_STATE_STOPPED, 5);
|
boolean success = blockUntil(SVC_STATE_STOPPED, 5);
|
||||||
Log.d(mTag, "stopping " + svc + ", success? " + success);
|
if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isStopped() {
|
boolean isStopped() {
|
||||||
@ -129,7 +127,7 @@ class DaemonProxy {
|
|||||||
if (!blocking && in.available() == 0) return 0;
|
if (!blocking && in.available() == 0) return 0;
|
||||||
|
|
||||||
int data = in.read();
|
int data = in.read();
|
||||||
Log.d(mTag, "got data from control socket: " + data);
|
Log.i(mTag, "got data from control socket: " + data);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -146,7 +144,7 @@ class DaemonProxy {
|
|||||||
s.connect(a);
|
s.connect(a);
|
||||||
return s;
|
return s;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(mTag, "service not yet listen()ing; try again");
|
if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
|
||||||
excp = e;
|
excp = e;
|
||||||
sleep(500);
|
sleep(500);
|
||||||
}
|
}
|
||||||
@ -173,8 +171,10 @@ class DaemonProxy {
|
|||||||
int n = waitTime * 1000 / sleepTime;
|
int n = waitTime * 1000 / sleepTime;
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (expectedState.equals(SystemProperties.get(cmd))) {
|
if (expectedState.equals(SystemProperties.get(cmd))) {
|
||||||
Log.d(mTag, mName + " is " + expectedState + " after "
|
if (DBG) {
|
||||||
+ (i * sleepTime) + " msec");
|
Log.d(mTag, mName + " is " + expectedState + " after "
|
||||||
|
+ (i * sleepTime) + " msec");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sleep(sleepTime);
|
sleep(sleepTime);
|
||||||
|
@ -45,4 +45,10 @@ class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
|
|||||||
(p.isSecretEnabled() ? p.getSecretString() : null),
|
(p.isSecretEnabled() ? p.getSecretString() : null),
|
||||||
username, password);
|
username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopPreviouslyRunDaemons() {
|
||||||
|
stopDaemon(IPSEC);
|
||||||
|
stopDaemon(MtpdHelper.MTPD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,12 @@ class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
|
|||||||
username, password);
|
username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopPreviouslyRunDaemons() {
|
||||||
|
stopDaemon(IPSEC);
|
||||||
|
stopDaemon(MtpdHelper.MTPD);
|
||||||
|
}
|
||||||
|
|
||||||
private String getCaCertPath() {
|
private String getCaCertPath() {
|
||||||
return CertTool.getInstance().getCaCertificate(
|
return CertTool.getInstance().getCaCertificate(
|
||||||
getProfile().getCaCertificate());
|
getProfile().getCaCertificate());
|
||||||
|
@ -35,4 +35,9 @@ class L2tpService extends VpnService<L2tpProfile> {
|
|||||||
(p.isSecretEnabled() ? p.getSecretString() : null),
|
(p.isSecretEnabled() ? p.getSecretString() : null),
|
||||||
username, password);
|
username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopPreviouslyRunDaemons() {
|
||||||
|
stopDaemon(MtpdHelper.MTPD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,26 +24,33 @@ 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 = "mtpd";
|
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 = "";
|
||||||
|
|
||||||
static void sendCommand(VpnService<?> vpnService, String protocol,
|
static void sendCommand(VpnService<?> vpnService, String protocol,
|
||||||
String serverIp, String port, String secret, String username,
|
String serverIp, String port, String secret, String username,
|
||||||
String password) throws IOException {
|
String password) throws IOException {
|
||||||
|
sendCommand(vpnService, protocol, serverIp, port, secret, username,
|
||||||
|
password, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendCommand(VpnService<?> vpnService, String protocol,
|
||||||
|
String serverIp, String port, String secret, String username,
|
||||||
|
String password, boolean encryption) throws IOException {
|
||||||
ArrayList<String> args = new ArrayList<String>();
|
ArrayList<String> args = new ArrayList<String>();
|
||||||
args.addAll(Arrays.asList(protocol, serverIp, port));
|
args.addAll(Arrays.asList(protocol, serverIp, port));
|
||||||
if (secret != null) args.add(secret);
|
if (secret != null) args.add(secret);
|
||||||
args.add(PPP_ARGS_SEPARATOR);
|
args.add(PPP_ARGS_SEPARATOR);
|
||||||
addPppArguments(vpnService, args, serverIp, username, password);
|
addPppArguments(args, serverIp, username, password, encryption);
|
||||||
|
|
||||||
DaemonProxy mtpd = vpnService.startDaemon(MTPD);
|
DaemonProxy mtpd = vpnService.startDaemon(MTPD);
|
||||||
mtpd.sendCommand(args.toArray(new String[args.size()]));
|
mtpd.sendCommand(args.toArray(new String[args.size()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addPppArguments(VpnService<?> vpnService,
|
private static void addPppArguments(ArrayList<String> args, String serverIp,
|
||||||
ArrayList<String> args, String serverIp, String username,
|
String username, String password, boolean encryption)
|
||||||
String password) throws IOException {
|
throws IOException {
|
||||||
args.addAll(Arrays.asList(
|
args.addAll(Arrays.asList(
|
||||||
"linkname", VPN_LINKNAME,
|
"linkname", VPN_LINKNAME,
|
||||||
"name", username,
|
"name", username,
|
||||||
@ -52,6 +59,9 @@ class MtpdHelper {
|
|||||||
"idle", "1800",
|
"idle", "1800",
|
||||||
"mtu", "1400",
|
"mtu", "1400",
|
||||||
"mru", "1400"));
|
"mru", "1400"));
|
||||||
|
if (encryption) {
|
||||||
|
args.add("+mppe");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MtpdHelper() {
|
private MtpdHelper() {
|
||||||
|
@ -26,11 +26,17 @@ import java.io.IOException;
|
|||||||
class PptpService extends VpnService<PptpProfile> {
|
class PptpService extends VpnService<PptpProfile> {
|
||||||
static final String PPTP_DAEMON = "pptp";
|
static final String PPTP_DAEMON = "pptp";
|
||||||
static final String PPTP_PORT = "1723";
|
static final String PPTP_PORT = "1723";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connect(String serverIp, String username, String password)
|
protected void connect(String serverIp, String username, String password)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
PptpProfile p = getProfile();
|
||||||
MtpdHelper.sendCommand(this, PPTP_DAEMON, serverIp, PPTP_PORT, null,
|
MtpdHelper.sendCommand(this, PPTP_DAEMON, serverIp, PPTP_PORT, null,
|
||||||
username, password);
|
username, password, p.isEncryptionEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopPreviouslyRunDaemons() {
|
||||||
|
stopDaemon(MtpdHelper.MTPD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,11 @@ import android.text.TextUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.Socket;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -36,7 +40,9 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* The service base class for managing a type of VPN connection.
|
* The service base class for managing a type of VPN connection.
|
||||||
*/
|
*/
|
||||||
abstract class VpnService<E extends VpnProfile> {
|
abstract class VpnService<E extends VpnProfile> implements Serializable {
|
||||||
|
protected static final long serialVersionUID = 1L;
|
||||||
|
private static final boolean DBG = true;
|
||||||
private static final int NOTIFICATION_ID = 1;
|
private static final int NOTIFICATION_ID = 1;
|
||||||
|
|
||||||
private static final String DNS1 = "net.dns1";
|
private static final String DNS1 = "net.dns1";
|
||||||
@ -50,12 +56,16 @@ 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 CHALLENGE_ERROR_CODE = 5;
|
||||||
|
private static final int REMOTE_HUNG_UP_ERROR_CODE = 7;
|
||||||
private static final int AUTH_ERROR_CODE = 51;
|
private static final int AUTH_ERROR_CODE = 51;
|
||||||
|
|
||||||
private final String TAG = VpnService.class.getSimpleName();
|
private final String TAG = VpnService.class.getSimpleName();
|
||||||
|
|
||||||
|
// FIXME: profile is only needed in connecting phase, so we can just save
|
||||||
|
// the profile name and service class name for recovery
|
||||||
E mProfile;
|
E mProfile;
|
||||||
VpnServiceBinder mContext;
|
transient VpnServiceBinder mContext;
|
||||||
|
|
||||||
private VpnState mState = VpnState.IDLE;
|
private VpnState mState = VpnState.IDLE;
|
||||||
private Throwable mError;
|
private Throwable mError;
|
||||||
@ -63,9 +73,9 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
// connection settings
|
// connection settings
|
||||||
private String mOriginalDns1;
|
private String mOriginalDns1;
|
||||||
private String mOriginalDns2;
|
private String mOriginalDns2;
|
||||||
private String mVpnDns1 = "";
|
|
||||||
private String mVpnDns2 = "";
|
|
||||||
private String mOriginalDomainSuffices;
|
private String mOriginalDomainSuffices;
|
||||||
|
private String mLocalIp;
|
||||||
|
private String mLocalIf;
|
||||||
|
|
||||||
private long mStartTime; // VPN connection start time
|
private long mStartTime; // VPN connection start time
|
||||||
|
|
||||||
@ -73,7 +83,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
private DaemonHelper mDaemonHelper = new DaemonHelper();
|
private DaemonHelper mDaemonHelper = new DaemonHelper();
|
||||||
|
|
||||||
// for helping showing, updating notification
|
// for helping showing, updating notification
|
||||||
private NotificationHelper mNotification = new NotificationHelper();
|
private transient NotificationHelper mNotification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a VPN connection with the specified username and password.
|
* Establishes a VPN connection with the specified username and password.
|
||||||
@ -81,6 +91,8 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
protected abstract void connect(String serverIp, String username,
|
protected abstract void connect(String serverIp, String username,
|
||||||
String password) throws IOException;
|
String password) throws IOException;
|
||||||
|
|
||||||
|
protected abstract void stopPreviouslyRunDaemons();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a VPN daemon.
|
* Starts a VPN daemon.
|
||||||
*/
|
*/
|
||||||
@ -89,6 +101,13 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
return mDaemonHelper.startDaemon(daemonName);
|
return mDaemonHelper.startDaemon(daemonName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops a VPN daemon.
|
||||||
|
*/
|
||||||
|
protected void stopDaemon(String daemonName) {
|
||||||
|
new DaemonProxy(daemonName).stop();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the VPN profile associated with the connection.
|
* Returns the VPN profile associated with the connection.
|
||||||
*/
|
*/
|
||||||
@ -104,8 +123,22 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setContext(VpnServiceBinder context, E profile) {
|
void setContext(VpnServiceBinder context, E profile) {
|
||||||
mContext = context;
|
|
||||||
mProfile = profile;
|
mProfile = profile;
|
||||||
|
recover(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recover(VpnServiceBinder context) {
|
||||||
|
mContext = context;
|
||||||
|
mNotification = new NotificationHelper();
|
||||||
|
|
||||||
|
if (VpnState.CONNECTED.equals(mState)) {
|
||||||
|
Log.i("VpnService", " recovered: " + mProfile.getName());
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
enterConnectivityLoop();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VpnState getState() {
|
VpnState getState() {
|
||||||
@ -117,14 +150,14 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
mState = VpnState.CONNECTING;
|
mState = VpnState.CONNECTING;
|
||||||
broadcastConnectivity(VpnState.CONNECTING);
|
broadcastConnectivity(VpnState.CONNECTING);
|
||||||
|
|
||||||
|
stopPreviouslyRunDaemons();
|
||||||
String serverIp = getIp(getProfile().getServerName());
|
String serverIp = getIp(getProfile().getServerName());
|
||||||
|
saveLocalIpAndInterface(serverIp);
|
||||||
onBeforeConnect();
|
onBeforeConnect();
|
||||||
connect(serverIp, username, password);
|
connect(serverIp, username, password);
|
||||||
waitUntilConnectedOrTimedout();
|
waitUntilConnectedOrTimedout();
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "onConnect()", e);
|
|
||||||
onError(e);
|
onError(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -132,7 +165,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
|
|
||||||
synchronized void onDisconnect() {
|
synchronized void onDisconnect() {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, " disconnecting VPN...");
|
Log.i(TAG, "disconnecting VPN...");
|
||||||
mState = VpnState.DISCONNECTING;
|
mState = VpnState.DISCONNECTING;
|
||||||
broadcastConnectivity(VpnState.DISCONNECTING);
|
broadcastConnectivity(VpnState.DISCONNECTING);
|
||||||
mNotification.showDisconnect();
|
mNotification.showDisconnect();
|
||||||
@ -152,6 +185,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
Log.w(TAG, " multiple errors occur, record the last one: "
|
Log.w(TAG, " multiple errors occur, record the last one: "
|
||||||
+ error);
|
+ error);
|
||||||
}
|
}
|
||||||
|
Log.e(TAG, "onError()", error);
|
||||||
mError = error;
|
mError = error;
|
||||||
onDisconnect();
|
onDisconnect();
|
||||||
}
|
}
|
||||||
@ -161,16 +195,18 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onBeforeConnect() {
|
private void onBeforeConnect() throws IOException {
|
||||||
mNotification.disableNotification();
|
mNotification.disableNotification();
|
||||||
|
|
||||||
SystemProperties.set(VPN_DNS1, "-");
|
SystemProperties.set(VPN_DNS1, "");
|
||||||
SystemProperties.set(VPN_DNS2, "-");
|
SystemProperties.set(VPN_DNS2, "");
|
||||||
SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
|
SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
|
||||||
Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS));
|
if (DBG) {
|
||||||
|
Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitUntilConnectedOrTimedout() {
|
private void waitUntilConnectedOrTimedout() throws IOException {
|
||||||
sleep(2000); // 2 seconds
|
sleep(2000); // 2 seconds
|
||||||
for (int i = 0; i < 60; i++) {
|
for (int i = 0; i < 60; i++) {
|
||||||
if (mState != VpnState.CONNECTING) {
|
if (mState != VpnState.CONNECTING) {
|
||||||
@ -187,39 +223,49 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
|
|
||||||
synchronized (VpnService.this) {
|
synchronized (VpnService.this) {
|
||||||
if (mState == VpnState.CONNECTING) {
|
if (mState == VpnState.CONNECTING) {
|
||||||
Log.d(TAG, " connecting timed out !!");
|
|
||||||
onError(new IOException("Connecting timed out"));
|
onError(new IOException("Connecting timed out"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onConnected() {
|
private synchronized void onConnected() throws IOException {
|
||||||
Log.d(TAG, "onConnected()");
|
if (DBG) Log.d(TAG, "onConnected()");
|
||||||
|
|
||||||
mDaemonHelper.closeSockets();
|
mDaemonHelper.closeSockets();
|
||||||
saveVpnDnsProperties();
|
saveOriginalDns();
|
||||||
saveAndSetDomainSuffices();
|
saveAndSetDomainSuffices();
|
||||||
|
|
||||||
mState = VpnState.CONNECTED;
|
mState = VpnState.CONNECTED;
|
||||||
|
mStartTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// set DNS after saving the states in case the process gets killed
|
||||||
|
// before states are saved
|
||||||
|
saveSelf();
|
||||||
|
setVpnDns();
|
||||||
broadcastConnectivity(VpnState.CONNECTED);
|
broadcastConnectivity(VpnState.CONNECTED);
|
||||||
|
|
||||||
enterConnectivityLoop();
|
enterConnectivityLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveSelf() throws IOException {
|
||||||
|
mContext.saveStates();
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void onFinalCleanUp() {
|
private synchronized void onFinalCleanUp() {
|
||||||
Log.d(TAG, "onFinalCleanUp()");
|
if (DBG) Log.d(TAG, "onFinalCleanUp()");
|
||||||
|
|
||||||
if (mState == VpnState.IDLE) return;
|
if (mState == VpnState.IDLE) return;
|
||||||
|
|
||||||
// keep the notification when error occurs
|
// keep the notification when error occurs
|
||||||
if (!anyError()) mNotification.disableNotification();
|
if (!anyError()) mNotification.disableNotification();
|
||||||
|
|
||||||
restoreOriginalDnsProperties();
|
restoreOriginalDns();
|
||||||
restoreOriginalDomainSuffices();
|
restoreOriginalDomainSuffices();
|
||||||
mState = VpnState.IDLE;
|
mState = VpnState.IDLE;
|
||||||
broadcastConnectivity(VpnState.IDLE);
|
broadcastConnectivity(VpnState.IDLE);
|
||||||
|
|
||||||
// stop the service itself
|
// stop the service itself
|
||||||
|
mContext.removeStates();
|
||||||
mContext.stopSelf();
|
mContext.stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,46 +273,38 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
return (mError != null);
|
return (mError != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreOriginalDnsProperties() {
|
private void restoreOriginalDns() {
|
||||||
// restore only if they are not overridden
|
// restore only if they are not overridden
|
||||||
if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
|
String vpnDns1 = SystemProperties.get(VPN_DNS1);
|
||||||
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
|
if (vpnDns1.equals(SystemProperties.get(DNS1))) {
|
||||||
|
Log.i(TAG, String.format("restore original dns prop: %s --> %s",
|
||||||
SystemProperties.get(DNS1), mOriginalDns1));
|
SystemProperties.get(DNS1), mOriginalDns1));
|
||||||
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
|
Log.i(TAG, String.format("restore original dns prop: %s --> %s",
|
||||||
SystemProperties.get(DNS2), mOriginalDns2));
|
SystemProperties.get(DNS2), mOriginalDns2));
|
||||||
SystemProperties.set(DNS1, mOriginalDns1);
|
SystemProperties.set(DNS1, mOriginalDns1);
|
||||||
SystemProperties.set(DNS2, mOriginalDns2);
|
SystemProperties.set(DNS2, mOriginalDns2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveVpnDnsProperties() {
|
private void saveOriginalDns() {
|
||||||
mOriginalDns1 = mOriginalDns2 = "";
|
mOriginalDns1 = SystemProperties.get(DNS1);
|
||||||
for (int i = 0; i < 5; i++) {
|
mOriginalDns2 = SystemProperties.get(DNS2);
|
||||||
mVpnDns1 = SystemProperties.get(VPN_DNS1);
|
Log.i(TAG, String.format("save original dns prop: %s, %s",
|
||||||
mVpnDns2 = SystemProperties.get(VPN_DNS2);
|
mOriginalDns1, mOriginalDns2));
|
||||||
if (mOriginalDns1.equals(mVpnDns1)) {
|
}
|
||||||
Log.d(TAG, "wait for vpn dns to settle in..." + i);
|
|
||||||
sleep(200);
|
private void setVpnDns() {
|
||||||
} else {
|
String vpnDns1 = SystemProperties.get(VPN_DNS1);
|
||||||
mOriginalDns1 = SystemProperties.get(DNS1);
|
String vpnDns2 = SystemProperties.get(VPN_DNS2);
|
||||||
mOriginalDns2 = SystemProperties.get(DNS2);
|
SystemProperties.set(DNS1, vpnDns1);
|
||||||
SystemProperties.set(DNS1, mVpnDns1);
|
SystemProperties.set(DNS2, vpnDns2);
|
||||||
SystemProperties.set(DNS2, mVpnDns2);
|
Log.i(TAG, String.format("set vpn dns prop: %s, %s",
|
||||||
Log.d(TAG, String.format("save original dns prop: %s, %s",
|
vpnDns1, vpnDns2));
|
||||||
mOriginalDns1, mOriginalDns2));
|
|
||||||
Log.d(TAG, String.format("set vpn dns prop: %s, %s",
|
|
||||||
mVpnDns1, mVpnDns2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(TAG, "saveVpnDnsProperties(): DNS not updated??");
|
|
||||||
mOriginalDns1 = mVpnDns1 = SystemProperties.get(DNS1);
|
|
||||||
mOriginalDns2 = mVpnDns2 = SystemProperties.get(DNS2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndSetDomainSuffices() {
|
private void saveAndSetDomainSuffices() {
|
||||||
mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
|
mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
|
||||||
Log.d(TAG, "save original dns search: " + mOriginalDomainSuffices);
|
Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices);
|
||||||
String list = mProfile.getDomainSuffices();
|
String list = mProfile.getDomainSuffices();
|
||||||
if (!TextUtils.isEmpty(list)) {
|
if (!TextUtils.isEmpty(list)) {
|
||||||
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
|
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
|
||||||
@ -274,7 +312,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void restoreOriginalDomainSuffices() {
|
private void restoreOriginalDomainSuffices() {
|
||||||
Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices);
|
Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
|
||||||
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
|
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,46 +336,73 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enterConnectivityLoop() {
|
private void enterConnectivityLoop() {
|
||||||
mStartTime = System.currentTimeMillis();
|
Log.i(TAG, "VPN connectivity monitor running");
|
||||||
|
|
||||||
Log.d(TAG, " +++++ connectivity monitor running");
|
|
||||||
try {
|
try {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
synchronized (VpnService.this) {
|
synchronized (VpnService.this) {
|
||||||
if (mState != VpnState.CONNECTED) break;
|
if (mState != VpnState.CONNECTED || !checkConnectivity()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
mNotification.update();
|
mNotification.update();
|
||||||
checkConnectivity();
|
checkDns();
|
||||||
VpnService.this.wait(1000); // 1 second
|
VpnService.this.wait(1000); // 1 second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(TAG, "connectivity monitor", e);
|
onError(e);
|
||||||
}
|
}
|
||||||
Log.d(TAG, " ----- connectivity monitor stopped");
|
Log.i(TAG, "VPN connectivity monitor stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkConnectivity() {
|
private void saveLocalIpAndInterface(String serverIp) throws IOException {
|
||||||
|
DatagramSocket s = new DatagramSocket();
|
||||||
|
int port = 80; // arbitrary
|
||||||
|
s.connect(InetAddress.getByName(serverIp), port);
|
||||||
|
InetAddress localIp = s.getLocalAddress();
|
||||||
|
mLocalIp = localIp.getHostAddress();
|
||||||
|
NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp);
|
||||||
|
mLocalIf = (localIf == null) ? null : localIf.getName();
|
||||||
|
if (TextUtils.isEmpty(mLocalIf)) {
|
||||||
|
throw new IOException("Local interface is empty!");
|
||||||
|
}
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, " Local IP: " + mLocalIp + ", if: " + mLocalIf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns false if vpn connectivity is broken
|
||||||
|
private boolean checkConnectivity() {
|
||||||
if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
|
if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
|
||||||
onDisconnect();
|
onDisconnect();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDns() {
|
||||||
|
String dns1 = SystemProperties.get(DNS1);
|
||||||
|
String vpnDns1 = SystemProperties.get(VPN_DNS1);
|
||||||
|
if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) {
|
||||||
|
// dhcp expires?
|
||||||
|
setVpnDns();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLocalIpChanged() {
|
private boolean isLocalIpChanged() {
|
||||||
// TODO
|
try {
|
||||||
if (!isDnsIntact()) {
|
InetAddress localIp = InetAddress.getByName(mLocalIp);
|
||||||
Log.w(TAG, " local IP changed");
|
NetworkInterface localIf =
|
||||||
return true;
|
NetworkInterface.getByInetAddress(localIp);
|
||||||
} else {
|
if (localIf == null || !mLocalIf.equals(localIf.getName())) {
|
||||||
return false;
|
Log.w(TAG, " local If changed from " + mLocalIf
|
||||||
}
|
+ " to " + localIf);
|
||||||
}
|
return true;
|
||||||
|
} else {
|
||||||
private boolean isDnsIntact() {
|
return false;
|
||||||
String dns1 = SystemProperties.get(DNS1);
|
}
|
||||||
if (!mVpnDns1.equals(dns1)) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, " dns being overridden by: " + dns1);
|
Log.w(TAG, "isLocalIpChanged()", e);
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,7 +414,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DaemonHelper {
|
private class DaemonHelper implements Serializable {
|
||||||
private List<DaemonProxy> mDaemonList =
|
private List<DaemonProxy> mDaemonList =
|
||||||
new ArrayList<DaemonProxy>();
|
new ArrayList<DaemonProxy>();
|
||||||
|
|
||||||
@ -376,7 +441,7 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
synchronized boolean anyDaemonStopped() {
|
synchronized boolean anyDaemonStopped() {
|
||||||
for (DaemonProxy s : mDaemonList) {
|
for (DaemonProxy s : mDaemonList) {
|
||||||
if (s.isStopped()) {
|
if (s.isStopped()) {
|
||||||
Log.w(TAG, " daemon gone: " + s.getName());
|
Log.w(TAG, " VPN daemon gone: " + s.getName());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,6 +466,14 @@ abstract class VpnService<E extends VpnProfile> {
|
|||||||
onError(VpnManager.VPN_ERROR_AUTH);
|
onError(VpnManager.VPN_ERROR_AUTH);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case CHALLENGE_ERROR_CODE:
|
||||||
|
onError(VpnManager.VPN_ERROR_CHALLENGE);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case REMOTE_HUNG_UP_ERROR_CODE:
|
||||||
|
onError(VpnManager.VPN_ERROR_REMOTE_HUNG_UP);
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
|
||||||
return true;
|
return true;
|
||||||
|
@ -27,23 +27,31 @@ import android.net.vpn.VpnManager;
|
|||||||
import android.net.vpn.VpnProfile;
|
import android.net.vpn.VpnProfile;
|
||||||
import android.net.vpn.VpnState;
|
import android.net.vpn.VpnState;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service class for managing a VPN connection. It implements the
|
* The service class for managing a VPN connection. It implements the
|
||||||
* {@link IVpnService} binder interface.
|
* {@link IVpnService} binder interface.
|
||||||
*/
|
*/
|
||||||
public class VpnServiceBinder extends Service {
|
public class VpnServiceBinder extends Service {
|
||||||
private final String TAG = VpnServiceBinder.class.getSimpleName();
|
private static final String TAG = VpnServiceBinder.class.getSimpleName();
|
||||||
|
private static final boolean DBG = true;
|
||||||
|
|
||||||
|
private static final String STATES_FILE_PATH = "/data/misc/vpn/.states";
|
||||||
|
|
||||||
// The actual implementation is delegated to the VpnService class.
|
// The actual implementation is delegated to the VpnService class.
|
||||||
private VpnService<? extends VpnProfile> mService;
|
private VpnService<? extends VpnProfile> mService;
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +64,13 @@ public class VpnServiceBinder extends Service {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
checkSavedStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart(Intent intent, int startId) {
|
public void onStart(Intent intent, int startId) {
|
||||||
super.onStart(intent, startId);
|
super.onStart(intent, startId);
|
||||||
@ -66,14 +81,30 @@ public class VpnServiceBinder extends Service {
|
|||||||
return mBinder;
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void saveStates() throws IOException {
|
||||||
|
if (DBG) Log.d("VpnServiceBinder", " saving states");
|
||||||
|
ObjectOutputStream oos =
|
||||||
|
new ObjectOutputStream(new FileOutputStream(STATES_FILE_PATH));
|
||||||
|
oos.writeObject(mService);
|
||||||
|
oos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeStates() {
|
||||||
|
try {
|
||||||
|
new File(STATES_FILE_PATH).delete();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (DBG) Log.d("VpnServiceBinder", " remove states: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized boolean connect(final VpnProfile p,
|
private synchronized boolean connect(final VpnProfile p,
|
||||||
final String username, final String password) {
|
final String username, final String password) {
|
||||||
if (mService != null) return false;
|
if (mService != null) return false;
|
||||||
|
final VpnService s = mService = createService(p);
|
||||||
|
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
mService = createService(p);
|
s.onConnect(username, password);
|
||||||
mService.onConnect(username, password);
|
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
return true;
|
return true;
|
||||||
@ -81,12 +112,11 @@ public class VpnServiceBinder extends Service {
|
|||||||
|
|
||||||
private synchronized void disconnect() {
|
private synchronized void disconnect() {
|
||||||
if (mService == null) return;
|
if (mService == null) return;
|
||||||
|
final VpnService s = mService;
|
||||||
|
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
mService.onDisconnect();
|
s.onDisconnect();
|
||||||
android.util.Log.d("VpnServiceBinder", "becoming background");
|
|
||||||
setForeground(false);
|
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@ -100,6 +130,21 @@ public class VpnServiceBinder extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkSavedStates() {
|
||||||
|
try {
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
|
||||||
|
STATES_FILE_PATH));
|
||||||
|
mService = (VpnService<? extends VpnProfile>) ois.readObject();
|
||||||
|
mService.recover(this);
|
||||||
|
ois.close();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// do nothing
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.i("VpnServiceBinder", "recovery error, remove states: " + e);
|
||||||
|
removeStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private VpnService<? extends VpnProfile> createService(VpnProfile p) {
|
private VpnService<? extends VpnProfile> createService(VpnProfile p) {
|
||||||
switch (p.getType()) {
|
switch (p.getType()) {
|
||||||
case L2TP:
|
case L2TP:
|
||||||
|
@ -22,9 +22,21 @@ package android.net.vpn;
|
|||||||
*/
|
*/
|
||||||
public class PptpProfile extends VpnProfile {
|
public class PptpProfile extends VpnProfile {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private boolean mEncryption = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VpnType getType() {
|
public VpnType getType() {
|
||||||
return VpnType.PPTP;
|
return VpnType.PPTP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables/disables the encryption for PPTP tunnel.
|
||||||
|
*/
|
||||||
|
public void setEncryptionEnabled(boolean enabled) {
|
||||||
|
mEncryption = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncryptionEnabled() {
|
||||||
|
return mEncryption;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,10 @@ public class VpnManager {
|
|||||||
public static final int VPN_ERROR_CONNECTION_FAILED = 2;
|
public static final int VPN_ERROR_CONNECTION_FAILED = 2;
|
||||||
/** Error code to indicate the server is not known. */
|
/** Error code to indicate the server is not known. */
|
||||||
public static final int VPN_ERROR_UNKNOWN_SERVER = 3;
|
public static final int VPN_ERROR_UNKNOWN_SERVER = 3;
|
||||||
|
/** Error code to indicate an error from challenge response. */
|
||||||
|
public static final int VPN_ERROR_CHALLENGE = 4;
|
||||||
|
/** Error code to indicate an error of remote server hanging up. */
|
||||||
|
public static final int VPN_ERROR_REMOTE_HUNG_UP = 5;
|
||||||
private static final int VPN_ERROR_NO_ERROR = 0;
|
private static final int VPN_ERROR_NO_ERROR = 0;
|
||||||
|
|
||||||
public static final String PROFILES_PATH = "/data/misc/vpn/profiles";
|
public static final String PROFILES_PATH = "/data/misc/vpn/profiles";
|
||||||
|
Reference in New Issue
Block a user