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