• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.vpn;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.net.vpn.VpnManager;
24 import android.net.vpn.VpnProfile;
25 import android.net.vpn.VpnState;
26 import android.os.SystemProperties;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import java.io.IOException;
31 import java.io.Serializable;
32 import java.net.DatagramSocket;
33 import java.net.InetAddress;
34 import java.net.NetworkInterface;
35 import java.net.UnknownHostException;
36 
37 /**
38  * The service base class for managing a type of VPN connection.
39  */
40 abstract class VpnService<E extends VpnProfile> implements Serializable {
41     static final long serialVersionUID = 1L;
42     private static final boolean DBG = true;
43     private static final int NOTIFICATION_ID = 1;
44 
45     private static final String DNS1 = "net.dns1";
46     private static final String DNS2 = "net.dns2";
47     private static final String VPN_DNS1 = "vpn.dns1";
48     private static final String VPN_DNS2 = "vpn.dns2";
49     private static final String VPN_STATUS = "vpn.status";
50     private static final String VPN_IS_UP = "ok";
51     private static final String VPN_IS_DOWN = "down";
52 
53     private static final String REMOTE_IP = "net.ipremote";
54     private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
55 
56     private final String TAG = VpnService.class.getSimpleName();
57 
58     // FIXME: profile is only needed in connecting phase, so we can just save
59     // the profile name and service class name for recovery
60     E mProfile;
61     transient VpnServiceBinder mContext;
62 
63     private VpnState mState = VpnState.IDLE;
64     private Throwable mError;
65 
66     // connection settings
67     private String mOriginalDns1;
68     private String mOriginalDns2;
69     private String mOriginalDomainSuffices;
70     private String mLocalIp;
71     private String mLocalIf;
72 
73     private long mStartTime; // VPN connection start time
74 
75     // for helping managing daemons
76     private VpnDaemons mDaemons = new VpnDaemons();
77 
78     // for helping showing, updating notification
79     private transient NotificationHelper mNotification;
80 
81     /**
82      * Establishes a VPN connection with the specified username and password.
83      */
connect(String serverIp, String username, String password)84     protected abstract void connect(String serverIp, String username,
85             String password) throws IOException;
86 
87     /**
88      * Returns the daemons management class for this service object.
89      */
getDaemons()90     protected VpnDaemons getDaemons() {
91         return mDaemons;
92     }
93 
94     /**
95      * Returns the VPN profile associated with the connection.
96      */
getProfile()97     protected E getProfile() {
98         return mProfile;
99     }
100 
101     /**
102      * Returns the IP address of the specified host name.
103      */
getIp(String hostName)104     protected String getIp(String hostName) throws IOException {
105         return InetAddress.getByName(hostName).getHostAddress();
106     }
107 
setContext(VpnServiceBinder context, E profile)108     void setContext(VpnServiceBinder context, E profile) {
109         mProfile = profile;
110         recover(context);
111     }
112 
recover(VpnServiceBinder context)113     void recover(VpnServiceBinder context) {
114         mContext = context;
115         mNotification = new NotificationHelper();
116 
117         if (VpnState.CONNECTED.equals(mState)) {
118             Log.i("VpnService", "     recovered: " + mProfile.getName());
119             startConnectivityMonitor();
120         }
121     }
122 
getState()123     VpnState getState() {
124         return mState;
125     }
126 
onConnect(String username, String password)127     synchronized boolean onConnect(String username, String password) {
128         try {
129             setState(VpnState.CONNECTING);
130 
131             mDaemons.stopAll();
132             String serverIp = getIp(getProfile().getServerName());
133             saveLocalIpAndInterface(serverIp);
134             onBeforeConnect();
135             connect(serverIp, username, password);
136             waitUntilConnectedOrTimedout();
137             return true;
138         } catch (Throwable e) {
139             onError(e);
140             return false;
141         }
142     }
143 
onDisconnect()144     synchronized void onDisconnect() {
145         try {
146             Log.i(TAG, "disconnecting VPN...");
147             setState(VpnState.DISCONNECTING);
148             mNotification.showDisconnect();
149 
150             mDaemons.stopAll();
151         } catch (Throwable e) {
152             Log.e(TAG, "onDisconnect()", e);
153         } finally {
154             onFinalCleanUp();
155         }
156     }
157 
onError(Throwable error)158     private void onError(Throwable error) {
159         // error may occur during or after connection setup
160         // and it may be due to one or all services gone
161         if (mError != null) {
162             Log.w(TAG, "   multiple errors occur, record the last one: "
163                     + error);
164         }
165         Log.e(TAG, "onError()", error);
166         mError = error;
167         onDisconnect();
168     }
169 
onError(int errorCode)170     private void onError(int errorCode) {
171         onError(new VpnConnectingError(errorCode));
172     }
173 
174 
onBeforeConnect()175     private void onBeforeConnect() throws IOException {
176         mNotification.disableNotification();
177 
178         SystemProperties.set(VPN_DNS1, "");
179         SystemProperties.set(VPN_DNS2, "");
180         SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
181         if (DBG) {
182             Log.d(TAG, "       VPN UP: " + SystemProperties.get(VPN_STATUS));
183         }
184     }
185 
waitUntilConnectedOrTimedout()186     private void waitUntilConnectedOrTimedout() throws IOException {
187         sleep(2000); // 2 seconds
188         for (int i = 0; i < 80; i++) {
189             if (mState != VpnState.CONNECTING) {
190                 break;
191             } else if (VPN_IS_UP.equals(
192                     SystemProperties.get(VPN_STATUS))) {
193                 onConnected();
194                 return;
195             } else {
196                 int err = mDaemons.getSocketError();
197                 if (err != 0) {
198                     onError(err);
199                     return;
200                 }
201             }
202             sleep(500); // 0.5 second
203         }
204 
205         if (mState == VpnState.CONNECTING) {
206             onError(new IOException("Connecting timed out"));
207         }
208     }
209 
onConnected()210     private synchronized void onConnected() throws IOException {
211         if (DBG) Log.d(TAG, "onConnected()");
212 
213         mDaemons.closeSockets();
214         saveOriginalDns();
215         saveAndSetDomainSuffices();
216 
217         mStartTime = System.currentTimeMillis();
218 
219         // Correct order to make sure VpnService doesn't break when killed:
220         // (1) set state to CONNECTED
221         // (2) save states
222         // (3) set DNS
223         setState(VpnState.CONNECTED);
224         saveSelf();
225         setVpnDns();
226 
227         startConnectivityMonitor();
228     }
229 
saveSelf()230     private void saveSelf() throws IOException {
231         mContext.saveStates();
232     }
233 
onFinalCleanUp()234     private synchronized void onFinalCleanUp() {
235         if (DBG) Log.d(TAG, "onFinalCleanUp()");
236 
237         if (mState == VpnState.IDLE) return;
238 
239         // keep the notification when error occurs
240         if (!anyError()) mNotification.disableNotification();
241 
242         restoreOriginalDns();
243         restoreOriginalDomainSuffices();
244         setState(VpnState.IDLE);
245 
246         // stop the service itself
247         SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
248         mContext.removeStates();
249         mContext.stopSelf();
250     }
251 
anyError()252     private boolean anyError() {
253         return (mError != null);
254     }
255 
restoreOriginalDns()256     private void restoreOriginalDns() {
257         // restore only if they are not overridden
258         String vpnDns1 = SystemProperties.get(VPN_DNS1);
259         if (vpnDns1.equals(SystemProperties.get(DNS1))) {
260             Log.i(TAG, String.format("restore original dns prop: %s --> %s",
261                     SystemProperties.get(DNS1), mOriginalDns1));
262             Log.i(TAG, String.format("restore original dns prop: %s --> %s",
263                     SystemProperties.get(DNS2), mOriginalDns2));
264             SystemProperties.set(DNS1, mOriginalDns1);
265             SystemProperties.set(DNS2, mOriginalDns2);
266         }
267     }
268 
saveOriginalDns()269     private void saveOriginalDns() {
270         mOriginalDns1 = SystemProperties.get(DNS1);
271         mOriginalDns2 = SystemProperties.get(DNS2);
272         Log.i(TAG, String.format("save original dns prop: %s, %s",
273                 mOriginalDns1, mOriginalDns2));
274     }
275 
setVpnDns()276     private void setVpnDns() {
277         String vpnDns1 = SystemProperties.get(VPN_DNS1);
278         String vpnDns2 = SystemProperties.get(VPN_DNS2);
279         SystemProperties.set(DNS1, vpnDns1);
280         SystemProperties.set(DNS2, vpnDns2);
281         Log.i(TAG, String.format("set vpn dns prop: %s, %s",
282                 vpnDns1, vpnDns2));
283     }
284 
saveAndSetDomainSuffices()285     private void saveAndSetDomainSuffices() {
286         mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
287         Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices);
288         String list = mProfile.getDomainSuffices();
289         if (!TextUtils.isEmpty(list)) {
290             SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
291         }
292     }
293 
restoreOriginalDomainSuffices()294     private void restoreOriginalDomainSuffices() {
295         Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
296         SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
297     }
298 
setState(VpnState newState)299     private void setState(VpnState newState) {
300         mState = newState;
301         broadcastConnectivity(newState);
302     }
303 
broadcastConnectivity(VpnState s)304     private void broadcastConnectivity(VpnState s) {
305         VpnManager m = new VpnManager(mContext);
306         Throwable err = mError;
307         if ((s == VpnState.IDLE) && (err != null)) {
308             if (err instanceof UnknownHostException) {
309                 m.broadcastConnectivity(mProfile.getName(), s,
310                         VpnManager.VPN_ERROR_UNKNOWN_SERVER);
311             } else if (err instanceof VpnConnectingError) {
312                 m.broadcastConnectivity(mProfile.getName(), s,
313                         ((VpnConnectingError) err).getErrorCode());
314             } else if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
315                 m.broadcastConnectivity(mProfile.getName(), s,
316                         VpnManager.VPN_ERROR_CONNECTION_LOST);
317             } else {
318                 m.broadcastConnectivity(mProfile.getName(), s,
319                         VpnManager.VPN_ERROR_CONNECTION_FAILED);
320             }
321         } else {
322             m.broadcastConnectivity(mProfile.getName(), s);
323         }
324     }
325 
startConnectivityMonitor()326     private void startConnectivityMonitor() {
327         new Thread(new Runnable() {
328             public void run() {
329                 Log.i(TAG, "VPN connectivity monitor running");
330                 try {
331                     for (int i = 10; ; i--) {
332                         long now = System.currentTimeMillis();
333 
334                         boolean heavyCheck = i == 0;
335                         synchronized (VpnService.this) {
336                             if (mState != VpnState.CONNECTED) break;
337                             mNotification.update(now);
338 
339                             if (heavyCheck) {
340                                 i = 10;
341                                 if (checkConnectivity()) checkDns();
342                             }
343                             long t = 1000L - System.currentTimeMillis() + now;
344                             if (t > 100L) VpnService.this.wait(t);
345                         }
346                     }
347                 } catch (InterruptedException e) {
348                     onError(e);
349                 }
350                 Log.i(TAG, "VPN connectivity monitor stopped");
351             }
352         }).start();
353     }
354 
saveLocalIpAndInterface(String serverIp)355     private void saveLocalIpAndInterface(String serverIp) throws IOException {
356         DatagramSocket s = new DatagramSocket();
357         int port = 80; // arbitrary
358         s.connect(InetAddress.getByName(serverIp), port);
359         InetAddress localIp = s.getLocalAddress();
360         mLocalIp = localIp.getHostAddress();
361         NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp);
362         mLocalIf = (localIf == null) ? null : localIf.getName();
363         if (TextUtils.isEmpty(mLocalIf)) {
364             throw new IOException("Local interface is empty!");
365         }
366         if (DBG) {
367             Log.d(TAG, "  Local IP: " + mLocalIp + ", if: " + mLocalIf);
368         }
369     }
370 
371     // returns false if vpn connectivity is broken
checkConnectivity()372     private boolean checkConnectivity() {
373         if (mDaemons.anyDaemonStopped() || isLocalIpChanged()) {
374             onError(new IOException("Connectivity lost"));
375             return false;
376         } else {
377             return true;
378         }
379     }
380 
checkDns()381     private void checkDns() {
382         String dns1 = SystemProperties.get(DNS1);
383         String vpnDns1 = SystemProperties.get(VPN_DNS1);
384         if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) {
385             // dhcp expires?
386             setVpnDns();
387         }
388     }
389 
isLocalIpChanged()390     private boolean isLocalIpChanged() {
391         try {
392             InetAddress localIp = InetAddress.getByName(mLocalIp);
393             NetworkInterface localIf =
394                     NetworkInterface.getByInetAddress(localIp);
395             if (localIf == null || !mLocalIf.equals(localIf.getName())) {
396                 Log.w(TAG, "       local If changed from " + mLocalIf
397                         + " to " + localIf);
398                 return true;
399             } else {
400                 return false;
401             }
402         } catch (IOException e) {
403             Log.w(TAG, "isLocalIpChanged()", e);
404             return true;
405         }
406     }
407 
sleep(int ms)408     protected void sleep(int ms) {
409         try {
410             Thread.currentThread().sleep(ms);
411         } catch (InterruptedException e) {
412         }
413     }
414 
415     private class DaemonHelper implements Serializable {
416     }
417 
418     // Helper class for showing, updating notification.
419     private class NotificationHelper {
update(long now)420         void update(long now) {
421             String title = getNotificationTitle(true);
422             Notification n = new Notification(R.drawable.vpn_connected, title,
423                     mStartTime);
424             n.setLatestEventInfo(mContext, title,
425                     getConnectedNotificationMessage(now),
426                     prepareNotificationIntent());
427             n.flags |= Notification.FLAG_NO_CLEAR;
428             n.flags |= Notification.FLAG_ONGOING_EVENT;
429             enableNotification(n);
430         }
431 
showDisconnect()432         void showDisconnect() {
433             String title = getNotificationTitle(false);
434             Notification n = new Notification(R.drawable.vpn_disconnected,
435                     title, System.currentTimeMillis());
436             n.setLatestEventInfo(mContext, title,
437                     getDisconnectedNotificationMessage(),
438                     prepareNotificationIntent());
439             n.flags |= Notification.FLAG_AUTO_CANCEL;
440             disableNotification();
441             enableNotification(n);
442         }
443 
disableNotification()444         void disableNotification() {
445             ((NotificationManager) mContext.getSystemService(
446                     Context.NOTIFICATION_SERVICE)).cancel(NOTIFICATION_ID);
447         }
448 
enableNotification(Notification n)449         private void enableNotification(Notification n) {
450             ((NotificationManager) mContext.getSystemService(
451                     Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, n);
452         }
453 
prepareNotificationIntent()454         private PendingIntent prepareNotificationIntent() {
455             return PendingIntent.getActivity(mContext, 0,
456                     new VpnManager(mContext).createSettingsActivityIntent(), 0);
457         }
458 
getNotificationTitle(boolean connected)459         private String getNotificationTitle(boolean connected) {
460             String formatString = connected
461                     ? mContext.getString(
462                             R.string.vpn_notification_title_connected)
463                     : mContext.getString(
464                             R.string.vpn_notification_title_disconnected);
465             return String.format(formatString, mProfile.getName());
466         }
467 
getFormattedTime(int duration)468         private String getFormattedTime(int duration) {
469             int hours = duration / 3600;
470             StringBuilder sb = new StringBuilder();
471             if (hours > 0) sb.append(hours).append(':');
472             sb.append(String.format("%02d:%02d", (duration % 3600 / 60),
473                     (duration % 60)));
474             return sb.toString();
475         }
476 
getConnectedNotificationMessage(long now)477         private String getConnectedNotificationMessage(long now) {
478             return getFormattedTime((int) (now - mStartTime) / 1000);
479         }
480 
getDisconnectedNotificationMessage()481         private String getDisconnectedNotificationMessage() {
482             return mContext.getString(
483                     R.string.vpn_notification_hint_disconnected);
484         }
485     }
486 }
487