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