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