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.net.VpnConfig;
     49 import com.android.internal.net.VpnProfile;
     50 import com.android.internal.util.Preconditions;
     51 import com.android.server.ConnectivityService;
     52 import com.android.server.EventLogTags;
     53 import com.android.server.connectivity.Vpn;
     54 
     55 import java.util.List;
     56 
     57 /**
     58  * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
     59  * connected and kicks off VPN connection, managing any required {@code netd}
     60  * firewall rules.
     61  */
     62 public class LockdownVpnTracker {
     63     private static final String TAG = "LockdownVpnTracker";
     64 
     65     /** Number of VPN attempts before waiting for user intervention. */
     66     private static final int MAX_ERROR_COUNT = 4;
     67 
     68     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
     69 
     70     private static final int ROOT_UID = 0;
     71 
     72     private final Context mContext;
     73     private final INetworkManagementService mNetService;
     74     private final ConnectivityService mConnService;
     75     private final Vpn mVpn;
     76     private final VpnProfile mProfile;
     77 
     78     private final Object mStateLock = new Object();
     79 
     80     private final PendingIntent mConfigIntent;
     81     private final PendingIntent mResetIntent;
     82 
     83     private String mAcceptedEgressIface;
     84     private String mAcceptedIface;
     85     private List<LinkAddress> mAcceptedSourceAddr;
     86 
     87     private int mErrorCount;
     88 
     89     public static boolean isEnabled() {
     90         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
     91     }
     92 
     93     public LockdownVpnTracker(Context context, INetworkManagementService netService,
     94             ConnectivityService connService, Vpn vpn, VpnProfile profile) {
     95         mContext = Preconditions.checkNotNull(context);
     96         mNetService = Preconditions.checkNotNull(netService);
     97         mConnService = Preconditions.checkNotNull(connService);
     98         mVpn = Preconditions.checkNotNull(vpn);
     99         mProfile = Preconditions.checkNotNull(profile);
    100 
    101         final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
    102         mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
    103 
    104         final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
    105         resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    106         mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
    107     }
    108 
    109     private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
    110         @Override
    111         public void onReceive(Context context, Intent intent) {
    112             reset();
    113         }
    114     };
    115 
    116     /**
    117      * Watch for state changes to both active egress network, kicking off a VPN
    118      * connection when ready, or setting firewall rules once VPN is connected.
    119      */
    120     private void handleStateChangedLocked() {
    121 
    122         final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
    123         final LinkProperties egressProp = mConnService.getActiveLinkProperties();
    124 
    125         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
    126         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
    127 
    128         // Restart VPN when egress network disconnected or changed
    129         final boolean egressDisconnected = egressInfo == null
    130                 || State.DISCONNECTED.equals(egressInfo.getState());
    131         final boolean egressChanged = egressProp == null
    132                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
    133 
    134         final String egressTypeName = (egressInfo == null) ?
    135                 null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
    136         final String egressIface = (egressProp == null) ?
    137                 null : egressProp.getInterfaceName();
    138         Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
    139                 " " + mAcceptedEgressIface + "->" + egressIface);
    140 
    141         if (egressDisconnected || egressChanged) {
    142             clearSourceRulesLocked();
    143             mAcceptedEgressIface = null;
    144             mVpn.stopLegacyVpnPrivileged();
    145         }
    146         if (egressDisconnected) {
    147             hideNotification();
    148             return;
    149         }
    150 
    151         final int egressType = egressInfo.getType();
    152         if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
    153             EventLogTags.writeLockdownVpnError(egressType);
    154         }
    155 
    156         if (mErrorCount > MAX_ERROR_COUNT) {
    157             showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    158 
    159         } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
    160             if (mProfile.isValidLockdownProfile()) {
    161                 Slog.d(TAG, "Active network connected; starting VPN");
    162                 EventLogTags.writeLockdownVpnConnecting(egressType);
    163                 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
    164 
    165                 mAcceptedEgressIface = egressProp.getInterfaceName();
    166                 try {
    167                     // Use the privileged method because Lockdown VPN is initiated by the system, so
    168                     // no additional permission checks are necessary.
    169                     mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
    170                 } catch (IllegalStateException e) {
    171                     mAcceptedEgressIface = null;
    172                     Slog.e(TAG, "Failed to start VPN", e);
    173                     showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    174                 }
    175             } else {
    176                 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
    177                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
    178             }
    179 
    180         } else if (vpnInfo.isConnected() && vpnConfig != null) {
    181             final String iface = vpnConfig.interfaze;
    182             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
    183 
    184             if (TextUtils.equals(iface, mAcceptedIface)
    185                   && sourceAddrs.equals(mAcceptedSourceAddr)) {
    186                 return;
    187             }
    188 
    189             Slog.d(TAG, "VPN connected using iface=" + iface +
    190                     ", sourceAddr=" + sourceAddrs.toString());
    191             EventLogTags.writeLockdownVpnConnected(egressType);
    192             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
    193 
    194             try {
    195                 clearSourceRulesLocked();
    196 
    197                 mNetService.setFirewallInterfaceRule(iface, true);
    198                 for (LinkAddress addr : sourceAddrs) {
    199                     setFirewallEgressSourceRule(addr, true);
    200                 }
    201 
    202                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
    203                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
    204 
    205                 mErrorCount = 0;
    206                 mAcceptedIface = iface;
    207                 mAcceptedSourceAddr = sourceAddrs;
    208             } catch (RemoteException e) {
    209                 throw new RuntimeException("Problem setting firewall rules", e);
    210             }
    211 
    212             final NetworkInfo clone = new NetworkInfo(egressInfo);
    213             augmentNetworkInfo(clone);
    214             mConnService.sendConnectedBroadcast(clone);
    215         }
    216     }
    217 
    218     public void init() {
    219         synchronized (mStateLock) {
    220             initLocked();
    221         }
    222     }
    223 
    224     private void initLocked() {
    225         Slog.d(TAG, "initLocked()");
    226 
    227         mVpn.setEnableTeardown(false);
    228 
    229         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
    230         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
    231 
    232         try {
    233             // TODO: support non-standard port numbers
    234             mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
    235             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
    236             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
    237         } catch (RemoteException e) {
    238             throw new RuntimeException("Problem setting firewall rules", e);
    239         }
    240 
    241         handleStateChangedLocked();
    242     }
    243 
    244     public void shutdown() {
    245         synchronized (mStateLock) {
    246             shutdownLocked();
    247         }
    248     }
    249 
    250     private void shutdownLocked() {
    251         Slog.d(TAG, "shutdownLocked()");
    252 
    253         mAcceptedEgressIface = null;
    254         mErrorCount = 0;
    255 
    256         mVpn.stopLegacyVpnPrivileged();
    257         try {
    258             mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
    259             mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
    260             mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
    261         } catch (RemoteException e) {
    262             throw new RuntimeException("Problem setting firewall rules", e);
    263         }
    264         clearSourceRulesLocked();
    265         hideNotification();
    266 
    267         mContext.unregisterReceiver(mResetReceiver);
    268         mVpn.setEnableTeardown(true);
    269     }
    270 
    271     public void reset() {
    272         Slog.d(TAG, "reset()");
    273         synchronized (mStateLock) {
    274             // cycle tracker, reset error count, and trigger retry
    275             shutdownLocked();
    276             initLocked();
    277             handleStateChangedLocked();
    278         }
    279     }
    280 
    281     private void clearSourceRulesLocked() {
    282         try {
    283             if (mAcceptedIface != null) {
    284                 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
    285                 mAcceptedIface = null;
    286             }
    287             if (mAcceptedSourceAddr != null) {
    288                 for (LinkAddress addr : mAcceptedSourceAddr) {
    289                     setFirewallEgressSourceRule(addr, false);
    290                 }
    291 
    292                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
    293                 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
    294 
    295                 mAcceptedSourceAddr = null;
    296             }
    297         } catch (RemoteException e) {
    298             throw new RuntimeException("Problem setting firewall rules", e);
    299         }
    300     }
    301 
    302     private void setFirewallEgressSourceRule(
    303             LinkAddress address, boolean allow) throws RemoteException {
    304         // Our source address based firewall rules must only cover our own source address, not the
    305         // whole subnet
    306         final String addrString = address.getAddress().getHostAddress();
    307         mNetService.setFirewallEgressSourceRule(addrString, allow);
    308     }
    309 
    310     public void onNetworkInfoChanged() {
    311         synchronized (mStateLock) {
    312             handleStateChangedLocked();
    313         }
    314     }
    315 
    316     public void onVpnStateChanged(NetworkInfo info) {
    317         if (info.getDetailedState() == DetailedState.FAILED) {
    318             mErrorCount++;
    319         }
    320         synchronized (mStateLock) {
    321             handleStateChangedLocked();
    322         }
    323     }
    324 
    325     public void augmentNetworkInfo(NetworkInfo info) {
    326         if (info.isConnected()) {
    327             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
    328             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
    329         }
    330     }
    331 
    332     private void showNotification(int titleRes, int iconRes) {
    333         final Notification.Builder builder = new Notification.Builder(mContext)
    334                 .setWhen(0)
    335                 .setSmallIcon(iconRes)
    336                 .setContentTitle(mContext.getString(titleRes))
    337                 .setContentText(mContext.getString(R.string.vpn_lockdown_config))
    338                 .setContentIntent(mConfigIntent)
    339                 .setPriority(Notification.PRIORITY_LOW)
    340                 .setOngoing(true)
    341                 .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
    342                         mResetIntent)
    343                 .setColor(mContext.getColor(
    344                         com.android.internal.R.color.system_notification_accent_color));
    345 
    346         NotificationManager.from(mContext).notify(TAG, 0, builder.build());
    347     }
    348 
    349     private void hideNotification() {
    350         NotificationManager.from(mContext).cancel(TAG, 0);
    351     }
    352 }
    353