Home | History | Annotate | Download | only in net
      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 
     91     public static boolean isEnabled() {
     92         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
     93     }
     94 
     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      */
    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 
    201     public void init() {
    202         synchronized (mStateLock) {
    203             initLocked();
    204         }
    205     }
    206 
    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 
    219     public void shutdown() {
    220         synchronized (mStateLock) {
    221             shutdownLocked();
    222         }
    223     }
    224 
    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 
    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 
    249     public void onNetworkInfoChanged() {
    250         synchronized (mStateLock) {
    251             handleStateChangedLocked();
    252         }
    253     }
    254 
    255     public void onVpnStateChanged(NetworkInfo info) {
    256         if (info.getDetailedState() == DetailedState.FAILED) {
    257             mErrorCount++;
    258         }
    259         synchronized (mStateLock) {
    260             handleStateChangedLocked();
    261         }
    262     }
    263 
    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 
    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 
    289     private void hideNotification() {
    290         NotificationManager.from(mContext).cancel(null, SystemMessage.NOTE_VPN_STATUS);
    291     }
    292 }
    293