• 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.net.NetworkCapabilities.TRANSPORT_VPN;
20 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
21 import static android.provider.Settings.ACTION_VPN_SETTINGS;
22 
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.Notification;
27 import android.app.NotificationManager;
28 import android.app.PendingIntent;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.net.ConnectivityManager;
32 import android.net.LinkAddress;
33 import android.net.LinkProperties;
34 import android.net.Network;
35 import android.net.NetworkInfo;
36 import android.net.NetworkRequest;
37 import android.os.Handler;
38 import android.text.TextUtils;
39 import android.util.Log;
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.server.connectivity.Vpn;
46 
47 import java.util.List;
48 import java.util.Objects;
49 
50 /**
51  * State tracker for legacy lockdown VPN. Watches for physical networks to be
52  * connected and kicks off VPN connection.
53  */
54 public class LockdownVpnTracker {
55     private static final String TAG = "LockdownVpnTracker";
56 
57     public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
58 
59     @NonNull private final Context mContext;
60     @NonNull private final ConnectivityManager mCm;
61     @NonNull private final NotificationManager mNotificationManager;
62     @NonNull private final Handler mHandler;
63     @NonNull private final Vpn mVpn;
64     @NonNull private final VpnProfile mProfile;
65 
66     @NonNull private final Object mStateLock = new Object();
67 
68     @NonNull private final PendingIntent mConfigIntent;
69     @NonNull private final PendingIntent mResetIntent;
70 
71     @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback();
72     @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback();
73 
74     private class NetworkCallback extends ConnectivityManager.NetworkCallback {
75         private Network mNetwork = null;
76         private LinkProperties mLinkProperties = null;
77 
78         @Override
onLinkPropertiesChanged(Network network, LinkProperties lp)79         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
80             boolean networkChanged = false;
81             if (!network.equals(mNetwork)) {
82                 // The default network just changed.
83                 mNetwork = network;
84                 networkChanged = true;
85             }
86             mLinkProperties = lp;
87             // Backwards compatibility: previously, LockdownVpnTracker only responded to connects
88             // and disconnects, not LinkProperties changes on existing networks.
89             if (networkChanged) {
90                 synchronized (mStateLock) {
91                     handleStateChangedLocked();
92                 }
93             }
94         }
95 
96         @Override
onLost(Network network)97         public void onLost(Network network) {
98             // The default network has gone down.
99             mNetwork = null;
100             mLinkProperties = null;
101             synchronized (mStateLock) {
102                 handleStateChangedLocked();
103             }
104         }
105 
getNetwork()106         public Network getNetwork() {
107             return mNetwork;
108         }
109 
getLinkProperties()110         public LinkProperties getLinkProperties() {
111             return mLinkProperties;
112         }
113     }
114 
115     private class VpnNetworkCallback extends NetworkCallback {
116         @Override
onAvailable(Network network)117         public void onAvailable(Network network) {
118             synchronized (mStateLock) {
119                 handleStateChangedLocked();
120             }
121         }
122         @Override
onLost(Network network)123         public void onLost(Network network) {
124             onAvailable(network);
125         }
126     }
127 
128     @Nullable
129     private String mAcceptedEgressIface;
130 
LockdownVpnTracker(@onNull Context context, @NonNull Handler handler, @NonNull Vpn vpn, @NonNull VpnProfile profile)131     public LockdownVpnTracker(@NonNull Context context,
132             @NonNull Handler handler,
133             @NonNull Vpn vpn,
134             @NonNull VpnProfile profile) {
135         mContext = Objects.requireNonNull(context);
136         mCm = mContext.getSystemService(ConnectivityManager.class);
137         mHandler = Objects.requireNonNull(handler);
138         mVpn = Objects.requireNonNull(vpn);
139         mProfile = Objects.requireNonNull(profile);
140         mNotificationManager = mContext.getSystemService(NotificationManager.class);
141 
142         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
143         mConfigIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, configIntent,
144                 PendingIntent.FLAG_IMMUTABLE);
145 
146         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
147         resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
148         mResetIntent = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, resetIntent,
149                 PendingIntent.FLAG_IMMUTABLE);
150     }
151 
152     /**
153      * Watch for state changes to both active egress network, kicking off a VPN
154      * connection when ready, or setting firewall rules once VPN is connected.
155      */
handleStateChangedLocked()156     private void handleStateChangedLocked() {
157         final Network network = mDefaultNetworkCallback.getNetwork();
158         final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
159 
160         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
161         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
162 
163         // Restart VPN when egress network disconnected or changed
164         final boolean egressDisconnected = (network == null);
165         final boolean egressChanged = egressProp == null
166                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
167 
168         final String egressIface = (egressProp == null) ?
169                 null : egressProp.getInterfaceName();
170         Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface);
171 
172         if (egressDisconnected || egressChanged) {
173             mAcceptedEgressIface = null;
174             mVpn.stopVpnRunnerPrivileged();
175         }
176         if (egressDisconnected) {
177             hideNotification();
178             return;
179         }
180 
181         // At this point, |network| is known to be non-null.
182         if (!vpnInfo.isConnectedOrConnecting()) {
183             if (!mProfile.isValidLockdownProfile()) {
184                 Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
185                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
186                 return;
187             }
188 
189             Log.d(TAG, "Active network connected; starting VPN");
190             showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
191 
192             mAcceptedEgressIface = egressIface;
193             try {
194                 // Use the privileged method because Lockdown VPN is initiated by the system, so
195                 // no additional permission checks are necessary.
196                 //
197                 // Pass in the underlying network here because the legacy VPN is, in fact, tightly
198                 // coupled to a given underlying network and cannot provide mobility. This makes
199                 // things marginally more correct in two ways:
200                 //
201                 // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra
202                 //    CONNECTED broadcast for the underlying network type. The underlying type comes
203                 //    from here. LTT *could* assume that the underlying network is the default
204                 //    network, but that might introduce a race condition if, say, the VPN starts
205                 //    connecting on cell, but when the connection succeeds and the agent is
206                 //    registered, the default network is now wifi.
207                 // 2. If no underlying network is passed in, then CS will assume the underlying
208                 //    network is the system default. So, if the VPN  is up and underlying network
209                 //    (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
210                 //    changed to match the new default network (e.g., cell).
211                 mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp);
212             } catch (IllegalStateException e) {
213                 mAcceptedEgressIface = null;
214                 Log.e(TAG, "Failed to start VPN", e);
215                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
216             }
217         } else if (vpnInfo.isConnected() && vpnConfig != null) {
218             final String iface = vpnConfig.interfaze;
219             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
220 
221             Log.d(TAG, "VPN connected using iface=" + iface
222                     + ", sourceAddr=" + sourceAddrs.toString());
223             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
224         }
225     }
226 
init()227     public void init() {
228         synchronized (mStateLock) {
229             initLocked();
230         }
231     }
232 
initLocked()233     private void initLocked() {
234         Log.d(TAG, "initLocked()");
235 
236         mVpn.setEnableTeardown(false);
237         mVpn.setLockdown(true);
238         mCm.setLegacyLockdownVpnEnabled(true);
239         handleStateChangedLocked();
240 
241         mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
242         final NetworkRequest vpnRequest = new NetworkRequest.Builder()
243                 .clearCapabilities()
244                 .addTransportType(TRANSPORT_VPN)
245                 .build();
246         mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler);
247     }
248 
shutdown()249     public void shutdown() {
250         synchronized (mStateLock) {
251             shutdownLocked();
252         }
253     }
254 
shutdownLocked()255     private void shutdownLocked() {
256         Log.d(TAG, "shutdownLocked()");
257 
258         mAcceptedEgressIface = null;
259 
260         mVpn.stopVpnRunnerPrivileged();
261         mVpn.setLockdown(false);
262         mCm.setLegacyLockdownVpnEnabled(false);
263         hideNotification();
264 
265         mVpn.setEnableTeardown(true);
266         mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
267         mCm.unregisterNetworkCallback(mVpnNetworkCallback);
268     }
269 
270     /**
271      * Reset VPN lockdown tracker. Called by ConnectivityService when receiving
272      * {@link #ACTION_LOCKDOWN_RESET} pending intent.
273      */
reset()274     public void reset() {
275         Log.d(TAG, "reset()");
276         synchronized (mStateLock) {
277             // cycle tracker, reset error count, and trigger retry
278             shutdownLocked();
279             initLocked();
280             handleStateChangedLocked();
281         }
282     }
283 
showNotification(int titleRes, int iconRes)284     private void showNotification(int titleRes, int iconRes) {
285         final Notification.Builder builder =
286                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN)
287                         .setWhen(0)
288                         .setSmallIcon(iconRes)
289                         .setContentTitle(mContext.getString(titleRes))
290                         .setContentText(mContext.getString(R.string.vpn_lockdown_config))
291                         .setContentIntent(mConfigIntent)
292                         .setOngoing(true)
293                         .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
294                                 mResetIntent)
295                         .setColor(mContext.getColor(
296                                 com.android.internal.R.color.system_notification_accent_color));
297 
298         mNotificationManager.notify(null /* tag */, SystemMessage.NOTE_VPN_STATUS,
299                 builder.build());
300     }
301 
hideNotification()302     private void hideNotification() {
303         mNotificationManager.cancel(null, SystemMessage.NOTE_VPN_STATUS);
304     }
305 }
306