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             clearSourceRulesLocked();
    145             mAcceptedEgressIface = null;
    146             mVpn.stopLegacyVpnPrivileged();
    147         }
    148         if (egressDisconnected) {
    149             hideNotification();
    150             return;
    151         }
    152 
    153         final int egressType = egressInfo.getType();
    154         if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
    155             EventLogTags.writeLockdownVpnError(egressType);
    156         }
    157 
    158         if (mErrorCount > MAX_ERROR_COUNT) {
    159             showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    160 
    161         } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
    162             if (mProfile.isValidLockdownProfile()) {
    163                 Slog.d(TAG, "Active network connected; starting VPN");
    164                 EventLogTags.writeLockdownVpnConnecting(egressType);
    165                 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
    166 
    167                 mAcceptedEgressIface = egressProp.getInterfaceName();
    168                 try {
    169                     // Use the privileged method because Lockdown VPN is initiated by the system, so
    170                     // no additional permission checks are necessary.
    171                     mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
    172                 } catch (IllegalStateException e) {
    173                     mAcceptedEgressIface = null;
    174                     Slog.e(TAG, "Failed to start VPN", e);
    175                     showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    176                 }
    177             } else {
    178                 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
    179                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    180             }
    181 
    182         } else if (vpnInfo.isConnected() && vpnConfig != null) {
    183             final String iface = vpnConfig.interfaze;
    184             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
    185 
    186             if (TextUtils.equals(iface, mAcceptedIface)
    187                   && sourceAddrs.equals(mAcceptedSourceAddr)) {
    188                 return;
    189             }
    190 
    191             Slog.d(TAG, "VPN connected using iface=" + iface +
    192                     ", sourceAddr=" + sourceAddrs.toString());
    193             EventLogTags.writeLockdownVpnConnected(egressType);
    194             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
    195 
    196             try {
    197                 clearSourceRulesLocked();
    198 
    199                 mNetService.setFirewallInterfaceRule(iface, true);
    200                 for (LinkAddress addr : sourceAddrs) {
    201                     setFirewallEgressSourceRule(addr, true);
    202                 }
    203 
    204                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
    205                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
    206 
    207                 mErrorCount = 0;
    208                 mAcceptedIface = iface;
    209                 mAcceptedSourceAddr = sourceAddrs;
    210             } catch (RemoteException e) {
    211                 throw new RuntimeException("Problem setting firewall rules", e);
    212             }
    213 
    214             final NetworkInfo clone = new NetworkInfo(egressInfo);
    215             augmentNetworkInfo(clone);
    216             mConnService.sendConnectedBroadcast(clone);
    217         }
    218     }
    219 
    220     public void init() {
    221         synchronized (mStateLock) {
    222             initLocked();
    223         }
    224     }
    225 
    226     private void initLocked() {
    227         Slog.d(TAG, "initLocked()");
    228 
    229         mVpn.setEnableTeardown(false);
    230 
    231         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
    232         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
    233 
    234         try {
    235             // TODO: support non-standard port numbers
    236             mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
    237             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
    238             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
    239         } catch (RemoteException e) {
    240             throw new RuntimeException("Problem setting firewall rules", e);
    241         }
    242 
    243         handleStateChangedLocked();
    244     }
    245 
    246     public void shutdown() {
    247         synchronized (mStateLock) {
    248             shutdownLocked();
    249         }
    250     }
    251 
    252     private void shutdownLocked() {
    253         Slog.d(TAG, "shutdownLocked()");
    254 
    255         mAcceptedEgressIface = null;
    256         mErrorCount = 0;
    257 
    258         mVpn.stopLegacyVpnPrivileged();
    259         try {
    260             mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
    261             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
    262             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
    263         } catch (RemoteException e) {
    264             throw new RuntimeException("Problem setting firewall rules", e);
    265         }
    266         clearSourceRulesLocked();
    267         hideNotification();
    268 
    269         mContext.unregisterReceiver(mResetReceiver);
    270         mVpn.setEnableTeardown(true);
    271     }
    272 
    273     public void reset() {
    274         Slog.d(TAG, "reset()");
    275         synchronized (mStateLock) {
    276             // cycle tracker, reset error count, and trigger retry
    277             shutdownLocked();
    278             initLocked();
    279             handleStateChangedLocked();
    280         }
    281     }
    282 
    283     private void clearSourceRulesLocked() {
    284         try {
    285             if (mAcceptedIface != null) {
    286                 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
    287                 mAcceptedIface = null;
    288             }
    289             if (mAcceptedSourceAddr != null) {
    290                 for (LinkAddress addr : mAcceptedSourceAddr) {
    291                     setFirewallEgressSourceRule(addr, false);
    292                 }
    293 
    294                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
    295                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
    296 
    297                 mAcceptedSourceAddr = null;
    298             }
    299         } catch (RemoteException e) {
    300             throw new RuntimeException("Problem setting firewall rules", e);
    301         }
    302     }
    303 
    304     private void setFirewallEgressSourceRule(
    305             LinkAddress address, boolean allow) throws RemoteException {
    306         // Our source address based firewall rules must only cover our own source address, not the
    307         // whole subnet
    308         final String addrString = address.getAddress().getHostAddress();
    309         mNetService.setFirewallEgressSourceRule(addrString, allow);
    310     }
    311 
    312     public void onNetworkInfoChanged() {
    313         synchronized (mStateLock) {
    314             handleStateChangedLocked();
    315         }
    316     }
    317 
    318     public void onVpnStateChanged(NetworkInfo info) {
    319         if (info.getDetailedState() == DetailedState.FAILED) {
    320             mErrorCount++;
    321         }
    322         synchronized (mStateLock) {
    323             handleStateChangedLocked();
    324         }
    325     }
    326 
    327     public void augmentNetworkInfo(NetworkInfo info) {
    328         if (info.isConnected()) {
    329             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
    330             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
    331         }
    332     }
    333 
    334     private void showNotification(int titleRes, int iconRes) {
    335         final Notification.Builder builder =
    336                 new Notification.Builder(mContext, SystemNotificationChannels.VPN)
    337                         .setWhen(0)
    338                         .setSmallIcon(iconRes)
    339                         .setContentTitle(mContext.getString(titleRes))
    340                         .setContentText(mContext.getString(R.string.vpn_lockdown_config))
    341                         .setContentIntent(mConfigIntent)
    342                         .setOngoing(true)
    343                         .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
    344                                 mResetIntent)
    345                         .setColor(mContext.getColor(
    346                                 com.android.internal.R.color.system_notification_accent_color));
    347 
    348         NotificationManager.from(mContext).notify(null, SystemMessage.NOTE_VPN_STATUS,
    349                 builder.build());
    350     }
    351 
    352     private void hideNotification() {
    353         NotificationManager.from(mContext).cancel(null, SystemMessage.NOTE_VPN_STATUS);
    354     }
    355 }
    356