summaryrefslogtreecommitdiffstats
path: root/packages/VpnServices/src/com/android/server/vpn/VpnService.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/VpnServices/src/com/android/server/vpn/VpnService.java')
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/VpnService.java469
1 files changed, 254 insertions, 215 deletions
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 87bd780..b107c7d 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -28,7 +28,11 @@ import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
+import java.io.Serializable;
+import java.net.DatagramSocket;
+import java.net.Socket;
import java.net.InetAddress;
+import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
@@ -36,7 +40,9 @@ import java.util.List;
/**
* 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 String DNS1 = "net.dns1";
@@ -50,29 +56,34 @@ abstract class VpnService<E extends VpnProfile> {
private static final String REMOTE_IP = "net.ipremote";
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
+ private static final int CHALLENGE_ERROR_CODE = 5;
+ private static final int REMOTE_HUNG_UP_ERROR_CODE = 7;
+ private static final int AUTH_ERROR_CODE = 51;
+
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;
- VpnServiceBinder mContext;
+ transient VpnServiceBinder mContext;
private VpnState mState = VpnState.IDLE;
- private boolean mInError;
- private VpnConnectingError mError;
+ private Throwable mError;
// connection settings
private String mOriginalDns1;
private String mOriginalDns2;
- private String mVpnDns1 = "";
- private String mVpnDns2 = "";
private String mOriginalDomainSuffices;
+ private String mLocalIp;
+ private String mLocalIf;
private long mStartTime; // VPN connection start time
- // for helping managing multiple Android services
- private ServiceHelper mServiceHelper = new ServiceHelper();
+ // for helping managing multiple daemons
+ private DaemonHelper mDaemonHelper = new DaemonHelper();
// for helping showing, updating notification
- private NotificationHelper mNotification = new NotificationHelper();
+ private transient NotificationHelper mNotification;
/**
* Establishes a VPN connection with the specified username and password.
@@ -80,19 +91,21 @@ abstract class VpnService<E extends VpnProfile> {
protected abstract void connect(String serverIp, String username,
String password) throws IOException;
+ protected abstract void stopPreviouslyRunDaemons();
+
/**
- * Tears down the VPN connection. The base class simply terminates all the
- * Android services. A subclass may need to do some clean-up before that.
+ * Starts a VPN daemon.
*/
- protected void disconnect() {
+ protected DaemonProxy startDaemon(String daemonName)
+ throws IOException {
+ return mDaemonHelper.startDaemon(daemonName);
}
/**
- * Starts an Android service defined in init.rc.
+ * Stops a VPN daemon.
*/
- protected AndroidServiceProxy startService(String serviceName)
- throws IOException {
- return mServiceHelper.startService(serviceName);
+ protected void stopDaemon(String daemonName) {
+ new DaemonProxy(daemonName).stop();
}
/**
@@ -109,31 +122,23 @@ abstract class VpnService<E extends VpnProfile> {
return InetAddress.getByName(hostName).getHostAddress();
}
- /**
- * Sets the system property. The method is blocked until the value is
- * settled in.
- * @param name the name of the property
- * @param value the value of the property
- * @throws IOException if it fails to set the property within 2 seconds
- */
- protected void setSystemProperty(String name, String value)
- throws IOException {
- SystemProperties.set(name, value);
- for (int i = 0; i < 5; i++) {
- String v = SystemProperties.get(name);
- if (v.equals(value)) {
- return;
- } else {
- Log.d(TAG, "sys_prop: wait for " + name + " to settle in");
- sleep(400);
- }
- }
- throw new IOException("Failed to set system property: " + name);
+ void setContext(VpnServiceBinder context, E profile) {
+ mProfile = profile;
+ recover(context);
}
- void setContext(VpnServiceBinder context, E profile) {
+ void recover(VpnServiceBinder context) {
mContext = context;
- mProfile = profile;
+ 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() {
@@ -145,189 +150,161 @@ abstract class VpnService<E extends VpnProfile> {
mState = VpnState.CONNECTING;
broadcastConnectivity(VpnState.CONNECTING);
+ stopPreviouslyRunDaemons();
String serverIp = getIp(getProfile().getServerName());
-
+ saveLocalIpAndInterface(serverIp);
onBeforeConnect();
connect(serverIp, username, password);
waitUntilConnectedOrTimedout();
return true;
} catch (Throwable e) {
- Log.e(TAG, "onConnect()", e);
- mError = newConnectingError(e);
- onError();
+ onError(e);
return false;
}
}
- synchronized void onDisconnect(boolean cleanUpServices) {
+ synchronized void onDisconnect() {
try {
- Log.d(TAG, " disconnecting VPN...");
+ Log.i(TAG, "disconnecting VPN...");
mState = VpnState.DISCONNECTING;
broadcastConnectivity(VpnState.DISCONNECTING);
mNotification.showDisconnect();
- // subclass implementation
- if (cleanUpServices) disconnect();
-
- mServiceHelper.stop();
+ mDaemonHelper.stopAll();
} catch (Throwable e) {
Log.e(TAG, "onDisconnect()", e);
+ } finally {
onFinalCleanUp();
}
}
- synchronized void onError() {
+ private void onError(Throwable error) {
// error may occur during or after connection setup
// and it may be due to one or all services gone
- mInError = true;
- switch (mState) {
- case CONNECTED:
- onDisconnect(true);
- break;
-
- case CONNECTING:
- onDisconnect(false);
- break;
+ if (mError != null) {
+ Log.w(TAG, " multiple errors occur, record the last one: "
+ + error);
}
+ Log.e(TAG, "onError()", error);
+ mError = error;
+ onDisconnect();
+ }
+
+ private void onError(int errorCode) {
+ onError(new VpnConnectingError(errorCode));
}
- private void onBeforeConnect() {
+
+ private void onBeforeConnect() throws IOException {
mNotification.disableNotification();
- SystemProperties.set(VPN_DNS1, "-");
- SystemProperties.set(VPN_DNS2, "-");
+ SystemProperties.set(VPN_DNS1, "");
+ SystemProperties.set(VPN_DNS2, "");
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() {
- // Run this in the background thread to not block UI
- new Thread(new Runnable() {
- public void run() {
- sleep(2000); // 2 seconds
- for (int i = 0; i < 60; i++) {
- if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
- onConnected();
- return;
- } else if (mState != VpnState.CONNECTING) {
- break;
- }
- sleep(500); // 0.5 second
- }
+ private void waitUntilConnectedOrTimedout() throws IOException {
+ sleep(2000); // 2 seconds
+ for (int i = 0; i < 60; i++) {
+ if (mState != VpnState.CONNECTING) {
+ break;
+ } else if (VPN_IS_UP.equals(
+ SystemProperties.get(VPN_STATUS))) {
+ onConnected();
+ return;
+ } else if (mDaemonHelper.anySocketError()) {
+ return;
+ }
+ 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();
- }
- }
+ synchronized (VpnService.this) {
+ if (mState == VpnState.CONNECTING) {
+ onError(new IOException("Connecting timed out"));
}
- }).start();
+ }
}
- private synchronized void onConnected() {
- Log.d(TAG, "onConnected()");
+ private synchronized void onConnected() throws IOException {
+ if (DBG) Log.d(TAG, "onConnected()");
- saveVpnDnsProperties();
+ mDaemonHelper.closeSockets();
+ saveOriginalDns();
saveAndSetDomainSuffices();
- startConnectivityMonitor();
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);
+
+ enterConnectivityLoop();
+ }
+
+ private void saveSelf() throws IOException {
+ mContext.saveStates();
}
private synchronized void onFinalCleanUp() {
- Log.d(TAG, "onFinalCleanUp()");
+ if (DBG) Log.d(TAG, "onFinalCleanUp()");
if (mState == VpnState.IDLE) return;
// keep the notification when error occurs
- if (!mInError) mNotification.disableNotification();
+ if (!anyError()) mNotification.disableNotification();
- restoreOriginalDnsProperties();
+ restoreOriginalDns();
restoreOriginalDomainSuffices();
mState = VpnState.IDLE;
broadcastConnectivity(VpnState.IDLE);
// stop the service itself
+ mContext.removeStates();
mContext.stopSelf();
}
- private VpnConnectingError newConnectingError(Throwable e) {
- return new VpnConnectingError(
- (e instanceof UnknownHostException)
- ? VpnManager.VPN_ERROR_UNKNOWN_SERVER
- : VpnManager.VPN_ERROR_CONNECTION_FAILED);
- }
-
- private synchronized void onOneServiceGone() {
- switch (mState) {
- case IDLE:
- case DISCONNECTING:
- break;
-
- default:
- onError();
- }
+ private boolean anyError() {
+ return (mError != null);
}
- 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 restoreOriginalDns() {
// restore only if they are not overridden
- if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
- Log.d(TAG, String.format("restore original dns prop: %s --> %s",
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ if (vpnDns1.equals(SystemProperties.get(DNS1))) {
+ Log.i(TAG, String.format("restore original dns prop: %s --> %s",
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.set(DNS1, mOriginalDns1);
SystemProperties.set(DNS2, mOriginalDns2);
}
}
- private void saveVpnDnsProperties() {
- mOriginalDns1 = mOriginalDns2 = "";
- for (int i = 0; i < 5; i++) {
- mVpnDns1 = SystemProperties.get(VPN_DNS1);
- mVpnDns2 = SystemProperties.get(VPN_DNS2);
- if (mOriginalDns1.equals(mVpnDns1)) {
- Log.d(TAG, "wait for vpn dns to settle in..." + i);
- sleep(200);
- } else {
- mOriginalDns1 = SystemProperties.get(DNS1);
- mOriginalDns2 = SystemProperties.get(DNS2);
- SystemProperties.set(DNS1, mVpnDns1);
- SystemProperties.set(DNS2, mVpnDns2);
- Log.d(TAG, String.format("save original dns prop: %s, %s",
- 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 saveOriginalDns() {
+ mOriginalDns1 = SystemProperties.get(DNS1);
+ mOriginalDns2 = SystemProperties.get(DNS2);
+ Log.i(TAG, String.format("save original dns prop: %s, %s",
+ mOriginalDns1, mOriginalDns2));
+ }
+
+ private void setVpnDns() {
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ String vpnDns2 = SystemProperties.get(VPN_DNS2);
+ SystemProperties.set(DNS1, vpnDns1);
+ SystemProperties.set(DNS2, vpnDns2);
+ Log.i(TAG, String.format("set vpn dns prop: %s, %s",
+ vpnDns1, vpnDns2));
}
private void saveAndSetDomainSuffices() {
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();
if (!TextUtils.isEmpty(list)) {
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
@@ -335,112 +312,174 @@ abstract class VpnService<E extends VpnProfile> {
}
private void restoreOriginalDomainSuffices() {
- Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices);
+ Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
}
private void broadcastConnectivity(VpnState s) {
VpnManager m = new VpnManager(mContext);
- if ((s == VpnState.IDLE) && (mError != null)) {
- m.broadcastConnectivity(mProfile.getName(), s,
- mError.getErrorCode());
+ Throwable err = mError;
+ if ((s == VpnState.IDLE) && (err != null)) {
+ if (err instanceof UnknownHostException) {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ VpnManager.VPN_ERROR_UNKNOWN_SERVER);
+ } else if (err instanceof VpnConnectingError) {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ ((VpnConnectingError) err).getErrorCode());
+ } else {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ VpnManager.VPN_ERROR_CONNECTION_FAILED);
+ }
} else {
m.broadcastConnectivity(mProfile.getName(), s);
}
}
- private void startConnectivityMonitor() {
- mStartTime = System.currentTimeMillis();
-
- new Thread(new Runnable() {
- public void run() {
- Log.d(TAG, " +++++ connectivity monitor running");
- try {
- for (;;) {
- synchronized (VpnService.this) {
- if (mState != VpnState.CONNECTED) break;
- mNotification.update();
- checkConnectivity();
- VpnService.this.wait(1000); // 1 second
- }
+ private void enterConnectivityLoop() {
+ Log.i(TAG, "VPN connectivity monitor running");
+ try {
+ for (;;) {
+ synchronized (VpnService.this) {
+ if (mState != VpnState.CONNECTED || !checkConnectivity()) {
+ break;
}
- } catch (InterruptedException e) {
- Log.e(TAG, "connectivity monitor", e);
+ mNotification.update();
+ checkDns();
+ VpnService.this.wait(1000); // 1 second
}
- Log.d(TAG, " ----- connectivity monitor stopped");
}
- }).start();
+ } catch (InterruptedException e) {
+ onError(e);
+ }
+ Log.i(TAG, "VPN connectivity monitor stopped");
}
- private void checkConnectivity() {
- checkDnsProperties();
+ 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);
+ }
}
- private void checkDnsProperties() {
+ // returns false if vpn connectivity is broken
+ private boolean checkConnectivity() {
+ if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
+ onDisconnect();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private void checkDns() {
String dns1 = SystemProperties.get(DNS1);
- if (!mVpnDns1.equals(dns1)) {
- Log.w(TAG, " dns being overridden by: " + dns1);
- onError();
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) {
+ // dhcp expires?
+ setVpnDns();
}
}
- protected void sleep(int ms) {
+ private boolean isLocalIpChanged() {
try {
- Thread.currentThread().sleep(ms);
- } catch (InterruptedException e) {
+ InetAddress localIp = InetAddress.getByName(mLocalIp);
+ NetworkInterface localIf =
+ NetworkInterface.getByInetAddress(localIp);
+ if (localIf == null || !mLocalIf.equals(localIf.getName())) {
+ Log.w(TAG, " local If changed from " + mLocalIf
+ + " to " + localIf);
+ return true;
+ } else {
+ return false;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "isLocalIpChanged()", e);
+ return true;
}
}
- private InetAddress toInetAddress(int addr) throws IOException {
- byte[] aa = new byte[4];
- for (int i= 0; i < aa.length; i++) {
- aa[i] = (byte) (addr & 0x0FF);
- addr >>= 8;
+ protected void sleep(int ms) {
+ try {
+ Thread.currentThread().sleep(ms);
+ } catch (InterruptedException e) {
}
- return InetAddress.getByAddress(aa);
}
- private class ServiceHelper implements ProcessProxy.Callback {
- private List<AndroidServiceProxy> mServiceList =
- new ArrayList<AndroidServiceProxy>();
+ private class DaemonHelper implements Serializable {
+ private List<DaemonProxy> mDaemonList =
+ new ArrayList<DaemonProxy>();
- // starts an Android service
- AndroidServiceProxy startService(String serviceName)
+ synchronized DaemonProxy startDaemon(String daemonName)
throws IOException {
- AndroidServiceProxy service = new AndroidServiceProxy(serviceName);
- mServiceList.add(service);
- service.start(this);
- return service;
+ DaemonProxy daemon = new DaemonProxy(daemonName);
+ mDaemonList.add(daemon);
+ daemon.start();
+ return daemon;
}
- // stops all the Android services
- void stop() {
- if (mServiceList.isEmpty()) {
+ synchronized void stopAll() {
+ if (mDaemonList.isEmpty()) {
onFinalCleanUp();
} else {
- for (AndroidServiceProxy s : mServiceList) s.stop();
+ for (DaemonProxy s : mDaemonList) s.stop();
}
}
- //@Override
- public void done(ProcessProxy p) {
- Log.d(TAG, "service done: " + p.getName());
- commonCallback((AndroidServiceProxy) p);
+ synchronized void closeSockets() {
+ for (DaemonProxy s : mDaemonList) s.closeControlSocket();
}
- //@Override
- public void error(ProcessProxy p, Throwable e) {
- Log.e(TAG, "service error: " + p.getName(), e);
- if (e instanceof VpnConnectingError) {
- mError = (VpnConnectingError) e;
+ synchronized boolean anyDaemonStopped() {
+ for (DaemonProxy s : mDaemonList) {
+ if (s.isStopped()) {
+ Log.w(TAG, " VPN daemon gone: " + s.getName());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getResultFromSocket(DaemonProxy s) {
+ try {
+ return s.getResultFromSocket();
+ } catch (IOException e) {
+ return -1;
}
- commonCallback((AndroidServiceProxy) p);
}
- private void commonCallback(AndroidServiceProxy service) {
- mServiceList.remove(service);
- onOneServiceGone();
- if (mServiceList.isEmpty()) onAllServicesGone();
+ synchronized boolean anySocketError() {
+ for (DaemonProxy s : mDaemonList) {
+ switch (getResultFromSocket(s)) {
+ case 0:
+ continue;
+
+ case AUTH_ERROR_CODE:
+ onError(VpnManager.VPN_ERROR_AUTH);
+ 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:
+ onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
+ return true;
+ }
+ }
+ return false;
}
}