blob: cf0aba44a77409e8b07774100bec134485567ae5 [file] [log] [blame]
Jeff Sharkey69ddab42012-08-25 00:05:46 -07001/*
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
17package com.android.server.net;
18
19import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.app.PendingIntent;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
Lorenzo Colitti0cb79032014-10-15 16:06:07 +090028import android.net.ConnectivityManager;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070029import android.net.LinkProperties;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070030import android.net.LinkAddress;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070031import android.net.NetworkInfo;
32import android.net.NetworkInfo.DetailedState;
33import android.net.NetworkInfo.State;
34import android.os.INetworkManagementService;
35import android.os.RemoteException;
36import android.security.Credentials;
37import android.security.KeyStore;
38import android.text.TextUtils;
39import android.util.Slog;
40
41import com.android.internal.R;
42import com.android.internal.net.VpnConfig;
43import com.android.internal.net.VpnProfile;
44import com.android.internal.util.Preconditions;
45import com.android.server.ConnectivityService;
Jeff Sharkey91c6a642012-09-06 18:33:14 -070046import com.android.server.EventLogTags;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070047import com.android.server.connectivity.Vpn;
48
Chad Brubaker4ca19e82013-06-14 11:16:51 -070049import java.util.List;
50
Jeff Sharkey69ddab42012-08-25 00:05:46 -070051/**
52 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
53 * connected and kicks off VPN connection, managing any required {@code netd}
54 * firewall rules.
55 */
56public class LockdownVpnTracker {
57 private static final String TAG = "LockdownVpnTracker";
58
59 /** Number of VPN attempts before waiting for user intervention. */
60 private static final int MAX_ERROR_COUNT = 4;
61
62 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080063
Jeff Sharkey580dd312012-08-29 22:27:39 -070064 private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080065 private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
Jeff Sharkey69ddab42012-08-25 00:05:46 -070066
67 private final Context mContext;
68 private final INetworkManagementService mNetService;
69 private final ConnectivityService mConnService;
70 private final Vpn mVpn;
71 private final VpnProfile mProfile;
72
73 private final Object mStateLock = new Object();
74
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080075 private final PendingIntent mConfigIntent;
76 private final PendingIntent mResetIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070077
78 private String mAcceptedEgressIface;
79 private String mAcceptedIface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070080 private List<LinkAddress> mAcceptedSourceAddr;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070081
82 private int mErrorCount;
83
84 public static boolean isEnabled() {
85 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
86 }
87
88 public LockdownVpnTracker(Context context, INetworkManagementService netService,
89 ConnectivityService connService, Vpn vpn, VpnProfile profile) {
90 mContext = Preconditions.checkNotNull(context);
91 mNetService = Preconditions.checkNotNull(netService);
92 mConnService = Preconditions.checkNotNull(connService);
93 mVpn = Preconditions.checkNotNull(vpn);
94 mProfile = Preconditions.checkNotNull(profile);
95
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080096 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
97 configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
98 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
99
Jeff Sharkey580dd312012-08-29 22:27:39 -0700100 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
101 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
102 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700103 }
104
105 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
106 @Override
107 public void onReceive(Context context, Intent intent) {
108 reset();
109 }
110 };
111
112 /**
113 * Watch for state changes to both active egress network, kicking off a VPN
114 * connection when ready, or setting firewall rules once VPN is connected.
115 */
116 private void handleStateChangedLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700117
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());
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900129
130 final String egressTypeName = (egressInfo == null) ?
131 null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
132 final String egressIface = (egressProp == null) ?
133 null : egressProp.getInterfaceName();
134 Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
135 " " + mAcceptedEgressIface + "->" + egressIface);
136
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700137 if (egressDisconnected || egressChanged) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700138 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700139 mAcceptedEgressIface = null;
140 mVpn.stopLegacyVpn();
141 }
Jeff Sharkey57666932013-04-30 17:01:57 -0700142 if (egressDisconnected) {
143 hideNotification();
144 return;
145 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700146
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700147 final int egressType = egressInfo.getType();
148 if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
149 EventLogTags.writeLockdownVpnError(egressType);
150 }
151
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700152 if (mErrorCount > MAX_ERROR_COUNT) {
153 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
154
155 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
156 if (mProfile.isValidLockdownProfile()) {
157 Slog.d(TAG, "Active network connected; starting VPN");
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700158 EventLogTags.writeLockdownVpnConnecting(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700159 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
160
161 mAcceptedEgressIface = egressProp.getInterfaceName();
Jeff Sharkey421fab82013-06-27 10:57:45 -0700162 try {
163 mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
164 } catch (IllegalStateException e) {
165 mAcceptedEgressIface = null;
166 Slog.e(TAG, "Failed to start VPN", e);
167 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
168 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700169 } else {
170 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
171 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
172 }
173
174 } else if (vpnInfo.isConnected() && vpnConfig != null) {
175 final String iface = vpnConfig.interfaze;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700176 final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700177
178 if (TextUtils.equals(iface, mAcceptedIface)
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700179 && sourceAddrs.equals(mAcceptedSourceAddr)) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700180 return;
181 }
182
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700183 Slog.d(TAG, "VPN connected using iface=" + iface +
184 ", sourceAddr=" + sourceAddrs.toString());
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700185 EventLogTags.writeLockdownVpnConnected(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700186 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
187
188 try {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700189 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700190
191 mNetService.setFirewallInterfaceRule(iface, true);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700192 for (LinkAddress addr : sourceAddrs) {
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900193 setFirewallEgressSourceRule(addr, true);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700194 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700195
196 mErrorCount = 0;
197 mAcceptedIface = iface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700198 mAcceptedSourceAddr = sourceAddrs;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700199 } catch (RemoteException e) {
200 throw new RuntimeException("Problem setting firewall rules", e);
201 }
202
203 mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
204 }
205 }
206
207 public void init() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700208 synchronized (mStateLock) {
209 initLocked();
210 }
211 }
212
213 private void initLocked() {
214 Slog.d(TAG, "initLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700215
Jeff Sharkey57666932013-04-30 17:01:57 -0700216 mVpn.setEnableTeardown(false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700217
218 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
219 mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
220
221 try {
222 // TODO: support non-standard port numbers
223 mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
224 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800225 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700226 } catch (RemoteException e) {
227 throw new RuntimeException("Problem setting firewall rules", e);
228 }
229
230 synchronized (mStateLock) {
231 handleStateChangedLocked();
232 }
233 }
234
235 public void shutdown() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700236 synchronized (mStateLock) {
237 shutdownLocked();
238 }
239 }
240
241 private void shutdownLocked() {
242 Slog.d(TAG, "shutdownLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700243
244 mAcceptedEgressIface = null;
245 mErrorCount = 0;
246
247 mVpn.stopLegacyVpn();
248 try {
249 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
250 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800251 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700252 } catch (RemoteException e) {
253 throw new RuntimeException("Problem setting firewall rules", e);
254 }
Jeff Sharkey580dd312012-08-29 22:27:39 -0700255 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700256 hideNotification();
257
258 mContext.unregisterReceiver(mResetReceiver);
Jeff Sharkey57666932013-04-30 17:01:57 -0700259 mVpn.setEnableTeardown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700260 }
261
262 public void reset() {
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900263 Slog.d(TAG, "reset()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700264 synchronized (mStateLock) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700265 // cycle tracker, reset error count, and trigger retry
266 shutdownLocked();
267 initLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700268 handleStateChangedLocked();
269 }
270 }
271
Jeff Sharkey580dd312012-08-29 22:27:39 -0700272 private void clearSourceRulesLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700273 try {
274 if (mAcceptedIface != null) {
275 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
276 mAcceptedIface = null;
277 }
278 if (mAcceptedSourceAddr != null) {
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700279 for (LinkAddress addr : mAcceptedSourceAddr) {
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900280 setFirewallEgressSourceRule(addr, false);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700281 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700282 mAcceptedSourceAddr = null;
283 }
284 } catch (RemoteException e) {
285 throw new RuntimeException("Problem setting firewall rules", e);
286 }
287 }
288
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900289 private void setFirewallEgressSourceRule(
290 LinkAddress address, boolean allow) throws RemoteException {
291 // Our source address based firewall rules must only cover our own source address, not the
292 // whole subnet
293 final String addrString = address.getAddress().getHostAddress();
294 mNetService.setFirewallEgressSourceRule(addrString, allow);
295 }
296
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900297 public void onNetworkInfoChanged() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700298 synchronized (mStateLock) {
299 handleStateChangedLocked();
300 }
301 }
302
303 public void onVpnStateChanged(NetworkInfo info) {
304 if (info.getDetailedState() == DetailedState.FAILED) {
305 mErrorCount++;
306 }
307 synchronized (mStateLock) {
308 handleStateChangedLocked();
309 }
310 }
311
312 public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700313 if (info.isConnected()) {
314 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
315 info = new NetworkInfo(info);
316 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
317 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700318 return info;
319 }
320
321 private void showNotification(int titleRes, int iconRes) {
Selim Cinek255dd042014-08-19 22:29:02 +0200322 final Notification.Builder builder = new Notification.Builder(mContext)
323 .setWhen(0)
324 .setSmallIcon(iconRes)
325 .setContentTitle(mContext.getString(titleRes))
326 .setContentText(mContext.getString(R.string.vpn_lockdown_config))
327 .setContentIntent(mConfigIntent)
328 .setPriority(Notification.PRIORITY_LOW)
329 .setOngoing(true)
330 .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
331 mResetIntent)
332 .setColor(mContext.getResources().getColor(
333 com.android.internal.R.color.system_notification_accent_color));
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800334
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700335 NotificationManager.from(mContext).notify(TAG, 0, builder.build());
336 }
337
338 private void hideNotification() {
339 NotificationManager.from(mContext).cancel(TAG, 0);
340 }
341}