• 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 
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.net.LinkProperties;
29 import android.net.NetworkInfo;
30 import android.net.NetworkInfo.DetailedState;
31 import android.net.NetworkInfo.State;
32 import android.os.INetworkManagementService;
33 import android.os.RemoteException;
34 import android.security.Credentials;
35 import android.security.KeyStore;
36 import android.text.TextUtils;
37 import android.util.Slog;
38 
39 import com.android.internal.R;
40 import com.android.internal.net.VpnConfig;
41 import com.android.internal.net.VpnProfile;
42 import com.android.internal.util.Preconditions;
43 import com.android.server.ConnectivityService;
44 import com.android.server.EventLogTags;
45 import 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  */
52 public 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";
59 
60     private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
61     private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
62 
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 
71     private final PendingIntent mConfigIntent;
72     private final PendingIntent mResetIntent;
73 
74     private String mAcceptedEgressIface;
75     private String mAcceptedIface;
76     private String mAcceptedSourceAddr;
77 
78     private int mErrorCount;
79 
isEnabled()80     public static boolean isEnabled() {
81         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
82     }
83 
LockdownVpnTracker(Context context, INetworkManagementService netService, ConnectivityService connService, Vpn vpn, VpnProfile profile)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 
92         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
93         configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
94         mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
95 
96         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
97         resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
98         mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
99     }
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      */
handleStateChangedLocked()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) {
127             clearSourceRulesLocked();
128             mAcceptedEgressIface = null;
129             mVpn.stopLegacyVpn();
130         }
131         if (egressDisconnected) {
132             hideNotification();
133             return;
134         }
135 
136         final int egressType = egressInfo.getType();
137         if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
138             EventLogTags.writeLockdownVpnError(egressType);
139         }
140 
141         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");
147                 EventLogTags.writeLockdownVpnConnecting(egressType);
148                 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);
168             EventLogTags.writeLockdownVpnConnected(egressType);
169             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
170 
171             try {
172                 clearSourceRulesLocked();
173 
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 
init()188     public void init() {
189         synchronized (mStateLock) {
190             initLocked();
191         }
192     }
193 
initLocked()194     private void initLocked() {
195         Slog.d(TAG, "initLocked()");
196 
197         mVpn.setEnableNotifications(false);
198         mVpn.setEnableTeardown(false);
199 
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);
207             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
208         } catch (RemoteException e) {
209             throw new RuntimeException("Problem setting firewall rules", e);
210         }
211 
212         synchronized (mStateLock) {
213             handleStateChangedLocked();
214         }
215     }
216 
shutdown()217     public void shutdown() {
218         synchronized (mStateLock) {
219             shutdownLocked();
220         }
221     }
222 
shutdownLocked()223     private void shutdownLocked() {
224         Slog.d(TAG, "shutdownLocked()");
225 
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);
233             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
234         } catch (RemoteException e) {
235             throw new RuntimeException("Problem setting firewall rules", e);
236         }
237         clearSourceRulesLocked();
238         hideNotification();
239 
240         mContext.unregisterReceiver(mResetReceiver);
241         mVpn.setEnableNotifications(true);
242         mVpn.setEnableTeardown(true);
243     }
244 
reset()245     public void reset() {
246         synchronized (mStateLock) {
247             // cycle tracker, reset error count, and trigger retry
248             shutdownLocked();
249             initLocked();
250             handleStateChangedLocked();
251         }
252     }
253 
clearSourceRulesLocked()254     private void clearSourceRulesLocked() {
255         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 
onNetworkInfoChanged(NetworkInfo info)269     public void onNetworkInfoChanged(NetworkInfo info) {
270         synchronized (mStateLock) {
271             handleStateChangedLocked();
272         }
273     }
274 
onVpnStateChanged(NetworkInfo info)275     public void onVpnStateChanged(NetworkInfo info) {
276         if (info.getDetailedState() == DetailedState.FAILED) {
277             mErrorCount++;
278         }
279         synchronized (mStateLock) {
280             handleStateChangedLocked();
281         }
282     }
283 
augmentNetworkInfo(NetworkInfo info)284     public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
285         if (info.isConnected()) {
286             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
287             info = new NetworkInfo(info);
288             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
289         }
290         return info;
291     }
292 
showNotification(int titleRes, int iconRes)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));
298         builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
299         builder.setContentIntent(mConfigIntent);
300         builder.setPriority(Notification.PRIORITY_LOW);
301         builder.setOngoing(true);
302         builder.addAction(
303                 R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
304 
305         NotificationManager.from(mContext).notify(TAG, 0, builder.build());
306     }
307 
hideNotification()308     private void hideNotification() {
309         NotificationManager.from(mContext).cancel(TAG, 0);
310     }
311 }
312