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