OSDN Git Service

am 64d6bacf: am 2f4ad45d: Merge "DO NOT MERGE Restore calling identity before checkin...
[android-x86/frameworks-base.git] / services / core / java / com / android / server / net / LockdownVpnTracker.java
1 /*
2  * Copyright (C) 2012 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.net;
18
19 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
20
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.net.LinkProperties;
29 import android.net.LinkAddress;
30 import android.net.NetworkInfo;
31 import android.net.NetworkInfo.DetailedState;
32 import android.net.NetworkInfo.State;
33 import android.os.INetworkManagementService;
34 import android.os.RemoteException;
35 import android.security.Credentials;
36 import android.security.KeyStore;
37 import android.text.TextUtils;
38 import android.util.Slog;
39
40 import com.android.internal.R;
41 import com.android.internal.net.VpnConfig;
42 import com.android.internal.net.VpnProfile;
43 import com.android.internal.util.Preconditions;
44 import com.android.server.ConnectivityService;
45 import com.android.server.EventLogTags;
46 import com.android.server.connectivity.Vpn;
47
48 import java.util.List;
49
50 /**
51  * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
52  * connected and kicks off VPN connection, managing any required {@code netd}
53  * firewall rules.
54  */
55 public class LockdownVpnTracker {
56     private static final String TAG = "LockdownVpnTracker";
57
58     /** Number of VPN attempts before waiting for user intervention. */
59     private static final int MAX_ERROR_COUNT = 4;
60
61     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
62
63     private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
64     private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
65
66     private final Context mContext;
67     private final INetworkManagementService mNetService;
68     private final ConnectivityService mConnService;
69     private final Vpn mVpn;
70     private final VpnProfile mProfile;
71
72     private final Object mStateLock = new Object();
73
74     private final PendingIntent mConfigIntent;
75     private final PendingIntent mResetIntent;
76
77     private String mAcceptedEgressIface;
78     private String mAcceptedIface;
79     private List<LinkAddress> mAcceptedSourceAddr;
80
81     private int mErrorCount;
82
83     public static boolean isEnabled() {
84         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
85     }
86
87     public LockdownVpnTracker(Context context, INetworkManagementService netService,
88             ConnectivityService connService, Vpn vpn, VpnProfile profile) {
89         mContext = Preconditions.checkNotNull(context);
90         mNetService = Preconditions.checkNotNull(netService);
91         mConnService = Preconditions.checkNotNull(connService);
92         mVpn = Preconditions.checkNotNull(vpn);
93         mProfile = Preconditions.checkNotNull(profile);
94
95         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
96         configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
97         mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
98
99         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
100         resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
101         mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
102     }
103
104     private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
105         @Override
106         public void onReceive(Context context, Intent intent) {
107             reset();
108         }
109     };
110
111     /**
112      * Watch for state changes to both active egress network, kicking off a VPN
113      * connection when ready, or setting firewall rules once VPN is connected.
114      */
115     private void handleStateChangedLocked() {
116         Slog.d(TAG, "handleStateChanged()");
117
118         final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
119         final LinkProperties egressProp = mConnService.getActiveLinkProperties();
120
121         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
122         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
123
124         // Restart VPN when egress network disconnected or changed
125         final boolean egressDisconnected = egressInfo == null
126                 || State.DISCONNECTED.equals(egressInfo.getState());
127         final boolean egressChanged = egressProp == null
128                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
129         if (egressDisconnected || egressChanged) {
130             clearSourceRulesLocked();
131             mAcceptedEgressIface = null;
132             mVpn.stopLegacyVpn();
133         }
134         if (egressDisconnected) {
135             hideNotification();
136             return;
137         }
138
139         final int egressType = egressInfo.getType();
140         if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
141             EventLogTags.writeLockdownVpnError(egressType);
142         }
143
144         if (mErrorCount > MAX_ERROR_COUNT) {
145             showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
146
147         } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
148             if (mProfile.isValidLockdownProfile()) {
149                 Slog.d(TAG, "Active network connected; starting VPN");
150                 EventLogTags.writeLockdownVpnConnecting(egressType);
151                 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
152
153                 mAcceptedEgressIface = egressProp.getInterfaceName();
154                 try {
155                     mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
156                 } catch (IllegalStateException e) {
157                     mAcceptedEgressIface = null;
158                     Slog.e(TAG, "Failed to start VPN", e);
159                     showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
160                 }
161             } else {
162                 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
163                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
164             }
165
166         } else if (vpnInfo.isConnected() && vpnConfig != null) {
167             final String iface = vpnConfig.interfaze;
168             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
169
170             if (TextUtils.equals(iface, mAcceptedIface)
171                   && sourceAddrs.equals(mAcceptedSourceAddr)) {
172                 return;
173             }
174
175             Slog.d(TAG, "VPN connected using iface=" + iface +
176                     ", sourceAddr=" + sourceAddrs.toString());
177             EventLogTags.writeLockdownVpnConnected(egressType);
178             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
179
180             try {
181                 clearSourceRulesLocked();
182
183                 mNetService.setFirewallInterfaceRule(iface, true);
184                 for (LinkAddress addr : sourceAddrs) {
185                     mNetService.setFirewallEgressSourceRule(addr.toString(), true);
186                 }
187
188                 mErrorCount = 0;
189                 mAcceptedIface = iface;
190                 mAcceptedSourceAddr = sourceAddrs;
191             } catch (RemoteException e) {
192                 throw new RuntimeException("Problem setting firewall rules", e);
193             }
194
195             mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
196         }
197     }
198
199     public void init() {
200         synchronized (mStateLock) {
201             initLocked();
202         }
203     }
204
205     private void initLocked() {
206         Slog.d(TAG, "initLocked()");
207
208         mVpn.setEnableNotifications(false);
209         mVpn.setEnableTeardown(false);
210
211         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
212         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
213
214         try {
215             // TODO: support non-standard port numbers
216             mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
217             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
218             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
219         } catch (RemoteException e) {
220             throw new RuntimeException("Problem setting firewall rules", e);
221         }
222
223         synchronized (mStateLock) {
224             handleStateChangedLocked();
225         }
226     }
227
228     public void shutdown() {
229         synchronized (mStateLock) {
230             shutdownLocked();
231         }
232     }
233
234     private void shutdownLocked() {
235         Slog.d(TAG, "shutdownLocked()");
236
237         mAcceptedEgressIface = null;
238         mErrorCount = 0;
239
240         mVpn.stopLegacyVpn();
241         try {
242             mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
243             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
244             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
245         } catch (RemoteException e) {
246             throw new RuntimeException("Problem setting firewall rules", e);
247         }
248         clearSourceRulesLocked();
249         hideNotification();
250
251         mContext.unregisterReceiver(mResetReceiver);
252         mVpn.setEnableNotifications(true);
253         mVpn.setEnableTeardown(true);
254     }
255
256     public void reset() {
257         synchronized (mStateLock) {
258             // cycle tracker, reset error count, and trigger retry
259             shutdownLocked();
260             initLocked();
261             handleStateChangedLocked();
262         }
263     }
264
265     private void clearSourceRulesLocked() {
266         try {
267             if (mAcceptedIface != null) {
268                 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
269                 mAcceptedIface = null;
270             }
271             if (mAcceptedSourceAddr != null) {
272                 for (LinkAddress addr : mAcceptedSourceAddr) {
273                     mNetService.setFirewallEgressSourceRule(addr.toString(), false);
274                 }
275                 mAcceptedSourceAddr = null;
276             }
277         } catch (RemoteException e) {
278             throw new RuntimeException("Problem setting firewall rules", e);
279         }
280     }
281
282     public void onNetworkInfoChanged(NetworkInfo info) {
283         synchronized (mStateLock) {
284             handleStateChangedLocked();
285         }
286     }
287
288     public void onVpnStateChanged(NetworkInfo info) {
289         if (info.getDetailedState() == DetailedState.FAILED) {
290             mErrorCount++;
291         }
292         synchronized (mStateLock) {
293             handleStateChangedLocked();
294         }
295     }
296
297     public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
298         if (info.isConnected()) {
299             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
300             info = new NetworkInfo(info);
301             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
302         }
303         return info;
304     }
305
306     private void showNotification(int titleRes, int iconRes) {
307         final Notification.Builder builder = new Notification.Builder(mContext);
308         builder.setWhen(0);
309         builder.setSmallIcon(iconRes);
310         builder.setContentTitle(mContext.getString(titleRes));
311         builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
312         builder.setContentIntent(mConfigIntent);
313         builder.setPriority(Notification.PRIORITY_LOW);
314         builder.setOngoing(true);
315         builder.addAction(
316                 R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
317
318         NotificationManager.from(mContext).notify(TAG, 0, builder.build());
319     }
320
321     private void hideNotification() {
322         NotificationManager.from(mContext).cancel(TAG, 0);
323     }
324 }