• 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.messages.nano.SystemMessageProto.SystemMessage;
49 import com.android.internal.net.VpnConfig;
50 import com.android.internal.net.VpnProfile;
51 import com.android.internal.notification.SystemNotificationChannels;
52 import com.android.internal.util.Preconditions;
53 import com.android.server.ConnectivityService;
54 import com.android.server.EventLogTags;
55 import com.android.server.connectivity.Vpn;
56 
57 import java.util.List;
58 
59 /**
60  * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
61  * connected and kicks off VPN connection, managing any required {@code netd}
62  * firewall rules.
63  */
64 public class LockdownVpnTracker {
65     private static final String TAG = "LockdownVpnTracker";
66 
67     /** Number of VPN attempts before waiting for user intervention. */
68     private static final int MAX_ERROR_COUNT = 4;
69 
70     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
71 
72     private static final int ROOT_UID = 0;
73 
74     private final Context mContext;
75     private final INetworkManagementService mNetService;
76     private final ConnectivityService mConnService;
77     private final Vpn mVpn;
78     private final VpnProfile mProfile;
79 
80     private final Object mStateLock = new Object();
81 
82     private final PendingIntent mConfigIntent;
83     private final PendingIntent mResetIntent;
84 
85     private String mAcceptedEgressIface;
86     private String mAcceptedIface;
87     private List<LinkAddress> mAcceptedSourceAddr;
88 
89     private int mErrorCount;
90 
isEnabled()91     public static boolean isEnabled() {
92         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
93     }
94 
LockdownVpnTracker(Context context, INetworkManagementService netService, ConnectivityService connService, Vpn vpn, VpnProfile profile)95     public LockdownVpnTracker(Context context, INetworkManagementService netService,
96             ConnectivityService connService, Vpn vpn, VpnProfile profile) {
97         mContext = Preconditions.checkNotNull(context);
98         mNetService = Preconditions.checkNotNull(netService);
99         mConnService = Preconditions.checkNotNull(connService);
100         mVpn = Preconditions.checkNotNull(vpn);
101         mProfile = Preconditions.checkNotNull(profile);
102 
103         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
104         mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
105 
106         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
107         resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
108         mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
109     }
110 
111     private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
112         @Override
113         public void onReceive(Context context, Intent intent) {
114             reset();
115         }
116     };
117 
118     /**
119      * Watch for state changes to both active egress network, kicking off a VPN
120      * connection when ready, or setting firewall rules once VPN is connected.
121      */
handleStateChangedLocked()122     private void handleStateChangedLocked() {
123 
124         final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
125         final LinkProperties egressProp = mConnService.getActiveLinkProperties();
126 
127         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
128         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
129 
130         // Restart VPN when egress network disconnected or changed
131         final boolean egressDisconnected = egressInfo == null
132                 || State.DISCONNECTED.equals(egressInfo.getState());
133         final boolean egressChanged = egressProp == null
134                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
135 
136         final String egressTypeName = (egressInfo == null) ?
137                 null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
138         final String egressIface = (egressProp == null) ?
139                 null : egressProp.getInterfaceName();
140         Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
141                 " " + mAcceptedEgressIface + "->" + egressIface);
142 
143         if (egressDisconnected || egressChanged) {
144             mAcceptedEgressIface = null;
145             mVpn.stopLegacyVpnPrivileged();
146         }
147         if (egressDisconnected) {
148             hideNotification();
149             return;
150         }
151 
152         final int egressType = egressInfo.getType();
153         if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
154             EventLogTags.writeLockdownVpnError(egressType);
155         }
156 
157         if (mErrorCount > MAX_ERROR_COUNT) {
158             showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
159 
160         } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
161             if (mProfile.isValidLockdownProfile()) {
162                 Slog.d(TAG, "Active network connected; starting VPN");
163                 EventLogTags.writeLockdownVpnConnecting(egressType);
164                 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
165 
166                 mAcceptedEgressIface = egressProp.getInterfaceName();
167                 try {
168                     // Use the privileged method because Lockdown VPN is initiated by the system, so
169                     // no additional permission checks are necessary.
170                     mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
171                 } catch (IllegalStateException e) {
172                     mAcceptedEgressIface = null;
173                     Slog.e(TAG, "Failed to start VPN", e);
174                     showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
175                 }
176             } else {
177                 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
178                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
179             }
180 
181         } else if (vpnInfo.isConnected() && vpnConfig != null) {
182             final String iface = vpnConfig.interfaze;
183             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
184 
185             if (TextUtils.equals(iface, mAcceptedIface)
186                   && sourceAddrs.equals(mAcceptedSourceAddr)) {
187                 return;
188             }
189 
190             Slog.d(TAG, "VPN connected using iface=" + iface +
191                     ", sourceAddr=" + sourceAddrs.toString());
192             EventLogTags.writeLockdownVpnConnected(egressType);
193             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
194 
195             final NetworkInfo clone = new NetworkInfo(egressInfo);
196             augmentNetworkInfo(clone);
197             mConnService.sendConnectedBroadcast(clone);
198         }
199     }
200 
init()201     public void init() {
202         synchronized (mStateLock) {
203             initLocked();
204         }
205     }
206 
initLocked()207     private void initLocked() {
208         Slog.d(TAG, "initLocked()");
209 
210         mVpn.setEnableTeardown(false);
211         mVpn.setLockdown(true);
212 
213         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
214         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
215 
216         handleStateChangedLocked();
217     }
218 
shutdown()219     public void shutdown() {
220         synchronized (mStateLock) {
221             shutdownLocked();
222         }
223     }
224 
shutdownLocked()225     private void shutdownLocked() {
226         Slog.d(TAG, "shutdownLocked()");
227 
228         mAcceptedEgressIface = null;
229         mErrorCount = 0;
230 
231         mVpn.stopLegacyVpnPrivileged();
232         mVpn.setLockdown(false);
233         hideNotification();
234 
235         mContext.unregisterReceiver(mResetReceiver);
236         mVpn.setEnableTeardown(true);
237     }
238 
reset()239     public void reset() {
240         Slog.d(TAG, "reset()");
241         synchronized (mStateLock) {
242             // cycle tracker, reset error count, and trigger retry
243             shutdownLocked();
244             initLocked();
245             handleStateChangedLocked();
246         }
247     }
248 
onNetworkInfoChanged()249     public void onNetworkInfoChanged() {
250         synchronized (mStateLock) {
251             handleStateChangedLocked();
252         }
253     }
254 
onVpnStateChanged(NetworkInfo info)255     public void onVpnStateChanged(NetworkInfo info) {
256         if (info.getDetailedState() == DetailedState.FAILED) {
257             mErrorCount++;
258         }
259         synchronized (mStateLock) {
260             handleStateChangedLocked();
261         }
262     }
263 
augmentNetworkInfo(NetworkInfo info)264     public void augmentNetworkInfo(NetworkInfo info) {
265         if (info.isConnected()) {
266             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
267             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
268         }
269     }
270 
showNotification(int titleRes, int iconRes)271     private void showNotification(int titleRes, int iconRes) {
272         final Notification.Builder builder =
273                 new Notification.Builder(mContext, SystemNotificationChannels.VPN)
274                         .setWhen(0)
275                         .setSmallIcon(iconRes)
276                         .setContentTitle(mContext.getString(titleRes))
277                         .setContentText(mContext.getString(R.string.vpn_lockdown_config))
278                         .setContentIntent(mConfigIntent)
279                         .setOngoing(true)
280                         .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
281                                 mResetIntent)
282                         .setColor(mContext.getColor(
283                                 com.android.internal.R.color.system_notification_accent_color));
284 
285         NotificationManager.from(mContext).notify(null, SystemMessage.NOTE_VPN_STATUS,
286                 builder.build());
287     }
288 
hideNotification()289     private void hideNotification() {
290         NotificationManager.from(mContext).cancel(null, SystemMessage.NOTE_VPN_STATUS);
291     }
292 }
293