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