Merge change 9114 into donut

* changes:
  Add state saving mechanism to support proc restart
This commit is contained in:
Android (Google) Code Review
2009-07-30 04:46:00 -07:00
10 changed files with 271 additions and 104 deletions

View File

@ -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);

View File

@ -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);
}
} }

View File

@ -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());

View File

@ -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);
}
} }

View File

@ -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() {

View File

@ -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);
}
} }

View File

@ -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;

View File

@ -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:

View File

@ -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;
}
} }

View File

@ -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";