blob: e25192525e3398c4acc2a105a4afbb10c668b663 [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;
28import android.net.LinkProperties;
29import android.net.NetworkInfo;
30import android.net.NetworkInfo.DetailedState;
31import android.net.NetworkInfo.State;
32import android.os.INetworkManagementService;
33import android.os.RemoteException;
34import android.security.Credentials;
35import android.security.KeyStore;
36import android.text.TextUtils;
37import android.util.Slog;
38
39import com.android.internal.R;
40import com.android.internal.net.VpnConfig;
41import com.android.internal.net.VpnProfile;
42import com.android.internal.util.Preconditions;
43import com.android.server.ConnectivityService;
Jeff Sharkey91c6a642012-09-06 18:33:14 -070044import com.android.server.EventLogTags;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070045import com.android.server.connectivity.Vpn;
46
47/**
48 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
49 * connected and kicks off VPN connection, managing any required {@code netd}
50 * firewall rules.
51 */
52public class LockdownVpnTracker {
53 private static final String TAG = "LockdownVpnTracker";
54
55 /** Number of VPN attempts before waiting for user intervention. */
56 private static final int MAX_ERROR_COUNT = 4;
57
58 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080059
Jeff Sharkey580dd312012-08-29 22:27:39 -070060 private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080061 private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
Jeff Sharkey69ddab42012-08-25 00:05:46 -070062
63 private final Context mContext;
64 private final INetworkManagementService mNetService;
65 private final ConnectivityService mConnService;
66 private final Vpn mVpn;
67 private final VpnProfile mProfile;
68
69 private final Object mStateLock = new Object();
70
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080071 private final PendingIntent mConfigIntent;
72 private final PendingIntent mResetIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070073
74 private String mAcceptedEgressIface;
75 private String mAcceptedIface;
76 private String mAcceptedSourceAddr;
77
78 private int mErrorCount;
79
80 public static boolean isEnabled() {
81 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
82 }
83
84 public LockdownVpnTracker(Context context, INetworkManagementService netService,
85 ConnectivityService connService, Vpn vpn, VpnProfile profile) {
86 mContext = Preconditions.checkNotNull(context);
87 mNetService = Preconditions.checkNotNull(netService);
88 mConnService = Preconditions.checkNotNull(connService);
89 mVpn = Preconditions.checkNotNull(vpn);
90 mProfile = Preconditions.checkNotNull(profile);
91
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080092 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
93 configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
94 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
95
Jeff Sharkey580dd312012-08-29 22:27:39 -070096 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
97 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
98 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
Jeff Sharkey69ddab42012-08-25 00:05:46 -070099 }
100
101 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
102 @Override
103 public void onReceive(Context context, Intent intent) {
104 reset();
105 }
106 };
107
108 /**
109 * Watch for state changes to both active egress network, kicking off a VPN
110 * connection when ready, or setting firewall rules once VPN is connected.
111 */
112 private void handleStateChangedLocked() {
113 Slog.d(TAG, "handleStateChanged()");
114
115 final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
116 final LinkProperties egressProp = mConnService.getActiveLinkProperties();
117
118 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
119 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
120
121 // Restart VPN when egress network disconnected or changed
122 final boolean egressDisconnected = egressInfo == null
123 || State.DISCONNECTED.equals(egressInfo.getState());
124 final boolean egressChanged = egressProp == null
125 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
126 if (egressDisconnected || egressChanged) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700127 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700128 mAcceptedEgressIface = null;
129 mVpn.stopLegacyVpn();
130 }
Jeff Sharkey57666932013-04-30 17:01:57 -0700131 if (egressDisconnected) {
132 hideNotification();
133 return;
134 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700135
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700136 final int egressType = egressInfo.getType();
137 if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
138 EventLogTags.writeLockdownVpnError(egressType);
139 }
140
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700141 if (mErrorCount > MAX_ERROR_COUNT) {
142 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
143
144 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
145 if (mProfile.isValidLockdownProfile()) {
146 Slog.d(TAG, "Active network connected; starting VPN");
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700147 EventLogTags.writeLockdownVpnConnecting(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700148 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
149
150 mAcceptedEgressIface = egressProp.getInterfaceName();
151 mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
152
153 } else {
154 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
155 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
156 }
157
158 } else if (vpnInfo.isConnected() && vpnConfig != null) {
159 final String iface = vpnConfig.interfaze;
160 final String sourceAddr = vpnConfig.addresses;
161
162 if (TextUtils.equals(iface, mAcceptedIface)
163 && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) {
164 return;
165 }
166
167 Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr);
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700168 EventLogTags.writeLockdownVpnConnected(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700169 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
170
171 try {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700172 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700173
174 mNetService.setFirewallInterfaceRule(iface, true);
175 mNetService.setFirewallEgressSourceRule(sourceAddr, true);
176
177 mErrorCount = 0;
178 mAcceptedIface = iface;
179 mAcceptedSourceAddr = sourceAddr;
180 } catch (RemoteException e) {
181 throw new RuntimeException("Problem setting firewall rules", e);
182 }
183
184 mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
185 }
186 }
187
188 public void init() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700189 synchronized (mStateLock) {
190 initLocked();
191 }
192 }
193
194 private void initLocked() {
195 Slog.d(TAG, "initLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700196
197 mVpn.setEnableNotifications(false);
Jeff Sharkey57666932013-04-30 17:01:57 -0700198 mVpn.setEnableTeardown(false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700199
200 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
201 mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
202
203 try {
204 // TODO: support non-standard port numbers
205 mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
206 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800207 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700208 } catch (RemoteException e) {
209 throw new RuntimeException("Problem setting firewall rules", e);
210 }
211
212 synchronized (mStateLock) {
213 handleStateChangedLocked();
214 }
215 }
216
217 public void shutdown() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700218 synchronized (mStateLock) {
219 shutdownLocked();
220 }
221 }
222
223 private void shutdownLocked() {
224 Slog.d(TAG, "shutdownLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700225
226 mAcceptedEgressIface = null;
227 mErrorCount = 0;
228
229 mVpn.stopLegacyVpn();
230 try {
231 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
232 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800233 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700234 } catch (RemoteException e) {
235 throw new RuntimeException("Problem setting firewall rules", e);
236 }
Jeff Sharkey580dd312012-08-29 22:27:39 -0700237 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700238 hideNotification();
239
240 mContext.unregisterReceiver(mResetReceiver);
241 mVpn.setEnableNotifications(true);
Jeff Sharkey57666932013-04-30 17:01:57 -0700242 mVpn.setEnableTeardown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700243 }
244
245 public void reset() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700246 synchronized (mStateLock) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700247 // cycle tracker, reset error count, and trigger retry
248 shutdownLocked();
249 initLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700250 handleStateChangedLocked();
251 }
252 }
253
Jeff Sharkey580dd312012-08-29 22:27:39 -0700254 private void clearSourceRulesLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700255 try {
256 if (mAcceptedIface != null) {
257 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
258 mAcceptedIface = null;
259 }
260 if (mAcceptedSourceAddr != null) {
261 mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false);
262 mAcceptedSourceAddr = null;
263 }
264 } catch (RemoteException e) {
265 throw new RuntimeException("Problem setting firewall rules", e);
266 }
267 }
268
269 public void onNetworkInfoChanged(NetworkInfo info) {
270 synchronized (mStateLock) {
271 handleStateChangedLocked();
272 }
273 }
274
275 public void onVpnStateChanged(NetworkInfo info) {
276 if (info.getDetailedState() == DetailedState.FAILED) {
277 mErrorCount++;
278 }
279 synchronized (mStateLock) {
280 handleStateChangedLocked();
281 }
282 }
283
284 public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700285 if (info.isConnected()) {
286 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
287 info = new NetworkInfo(info);
288 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
289 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700290 return info;
291 }
292
293 private void showNotification(int titleRes, int iconRes) {
294 final Notification.Builder builder = new Notification.Builder(mContext);
295 builder.setWhen(0);
296 builder.setSmallIcon(iconRes);
297 builder.setContentTitle(mContext.getString(titleRes));
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800298 builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
299 builder.setContentIntent(mConfigIntent);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700300 builder.setPriority(Notification.PRIORITY_LOW);
301 builder.setOngoing(true);
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800302 builder.addAction(
303 R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
304
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700305 NotificationManager.from(mContext).notify(TAG, 0, builder.build());
306 }
307
308 private void hideNotification() {
309 NotificationManager.from(mContext).cancel(TAG, 0);
310 }
311}