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