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