FileDocCategorySizeDatePackage
LockdownVpnTracker.javaAPI DocAndroid 5.1 API13484Thu Mar 12 22:22:42 GMT 2015com.android.server.net

LockdownVpnTracker

public class LockdownVpnTracker extends Object
State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be connected and kicks off VPN connection, managing any required {@code netd} firewall rules.

Fields Summary
private static final String
TAG
private static final int
MAX_ERROR_COUNT
Number of VPN attempts before waiting for user intervention.
private static final String
ACTION_LOCKDOWN_RESET
private static final String
ACTION_VPN_SETTINGS
private static final String
EXTRA_PICK_LOCKDOWN
private static final int
ROOT_UID
private final android.content.Context
mContext
private final android.os.INetworkManagementService
mNetService
private final com.android.server.ConnectivityService
mConnService
private final com.android.server.connectivity.Vpn
mVpn
private final com.android.internal.net.VpnProfile
mProfile
private final Object
mStateLock
private final android.app.PendingIntent
mConfigIntent
private final android.app.PendingIntent
mResetIntent
private String
mAcceptedEgressIface
private String
mAcceptedIface
private List
mAcceptedSourceAddr
private int
mErrorCount
private android.content.BroadcastReceiver
mResetReceiver
Constructors Summary
public LockdownVpnTracker(android.content.Context context, android.os.INetworkManagementService netService, com.android.server.ConnectivityService connService, com.android.server.connectivity.Vpn vpn, com.android.internal.net.VpnProfile profile)

        mContext = Preconditions.checkNotNull(context);
        mNetService = Preconditions.checkNotNull(netService);
        mConnService = Preconditions.checkNotNull(connService);
        mVpn = Preconditions.checkNotNull(vpn);
        mProfile = Preconditions.checkNotNull(profile);

        final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
        configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
        mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);

        final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
        resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
    
Methods Summary
public android.net.NetworkInfoaugmentNetworkInfo(android.net.NetworkInfo info)

        if (info.isConnected()) {
            final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
            info = new NetworkInfo(info);
            info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
        }
        return info;
    
private voidclearSourceRulesLocked()

        try {
            if (mAcceptedIface != null) {
                mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
                mAcceptedIface = null;
            }
            if (mAcceptedSourceAddr != null) {
                for (LinkAddress addr : mAcceptedSourceAddr) {
                    setFirewallEgressSourceRule(addr, false);
                }

                mNetService.setFirewallUidRule(ROOT_UID, false);
                mNetService.setFirewallUidRule(Os.getuid(), false);

                mAcceptedSourceAddr = null;
            }
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }
    
private voidhandleStateChangedLocked()
Watch for state changes to both active egress network, kicking off a VPN connection when ready, or setting firewall rules once VPN is connected.


                                 
       

        final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
        final LinkProperties egressProp = mConnService.getActiveLinkProperties();

        final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
        final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();

        // Restart VPN when egress network disconnected or changed
        final boolean egressDisconnected = egressInfo == null
                || State.DISCONNECTED.equals(egressInfo.getState());
        final boolean egressChanged = egressProp == null
                || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());

        final String egressTypeName = (egressInfo == null) ?
                null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
        final String egressIface = (egressProp == null) ?
                null : egressProp.getInterfaceName();
        Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
                " " + mAcceptedEgressIface + "->" + egressIface);

        if (egressDisconnected || egressChanged) {
            clearSourceRulesLocked();
            mAcceptedEgressIface = null;
            mVpn.stopLegacyVpnPrivileged();
        }
        if (egressDisconnected) {
            hideNotification();
            return;
        }

        final int egressType = egressInfo.getType();
        if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
            EventLogTags.writeLockdownVpnError(egressType);
        }

        if (mErrorCount > MAX_ERROR_COUNT) {
            showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);

        } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
            if (mProfile.isValidLockdownProfile()) {
                Slog.d(TAG, "Active network connected; starting VPN");
                EventLogTags.writeLockdownVpnConnecting(egressType);
                showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);

                mAcceptedEgressIface = egressProp.getInterfaceName();
                try {
                    // Use the privileged method because Lockdown VPN is initiated by the system, so
                    // no additional permission checks are necessary.
                    mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
                } catch (IllegalStateException e) {
                    mAcceptedEgressIface = null;
                    Slog.e(TAG, "Failed to start VPN", e);
                    showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
                }
            } else {
                Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
                showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
            }

        } else if (vpnInfo.isConnected() && vpnConfig != null) {
            final String iface = vpnConfig.interfaze;
            final List<LinkAddress> sourceAddrs = vpnConfig.addresses;

            if (TextUtils.equals(iface, mAcceptedIface)
                  && sourceAddrs.equals(mAcceptedSourceAddr)) {
                return;
            }

            Slog.d(TAG, "VPN connected using iface=" + iface +
                    ", sourceAddr=" + sourceAddrs.toString());
            EventLogTags.writeLockdownVpnConnected(egressType);
            showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);

            try {
                clearSourceRulesLocked();

                mNetService.setFirewallInterfaceRule(iface, true);
                for (LinkAddress addr : sourceAddrs) {
                    setFirewallEgressSourceRule(addr, true);
                }

                mNetService.setFirewallUidRule(ROOT_UID, true);
                mNetService.setFirewallUidRule(Os.getuid(), true);

                mErrorCount = 0;
                mAcceptedIface = iface;
                mAcceptedSourceAddr = sourceAddrs;
            } catch (RemoteException e) {
                throw new RuntimeException("Problem setting firewall rules", e);
            }

            mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
        }
    
private voidhideNotification()

        NotificationManager.from(mContext).cancel(TAG, 0);
    
public voidinit()

        synchronized (mStateLock) {
            initLocked();
        }
    
private voidinitLocked()

        Slog.d(TAG, "initLocked()");

        mVpn.setEnableTeardown(false);

        final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);

        try {
            // TODO: support non-standard port numbers
            mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }

        synchronized (mStateLock) {
            handleStateChangedLocked();
        }
    
public static booleanisEnabled()


        
        return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
    
public voidonNetworkInfoChanged()

        synchronized (mStateLock) {
            handleStateChangedLocked();
        }
    
public voidonVpnStateChanged(android.net.NetworkInfo info)

        if (info.getDetailedState() == DetailedState.FAILED) {
            mErrorCount++;
        }
        synchronized (mStateLock) {
            handleStateChangedLocked();
        }
    
public voidreset()

        Slog.d(TAG, "reset()");
        synchronized (mStateLock) {
            // cycle tracker, reset error count, and trigger retry
            shutdownLocked();
            initLocked();
            handleStateChangedLocked();
        }
    
private voidsetFirewallEgressSourceRule(android.net.LinkAddress address, boolean allow)

        // Our source address based firewall rules must only cover our own source address, not the
        // whole subnet
        final String addrString = address.getAddress().getHostAddress();
        mNetService.setFirewallEgressSourceRule(addrString, allow);
    
private voidshowNotification(int titleRes, int iconRes)

        final Notification.Builder builder = new Notification.Builder(mContext)
                .setWhen(0)
                .setSmallIcon(iconRes)
                .setContentTitle(mContext.getString(titleRes))
                .setContentText(mContext.getString(R.string.vpn_lockdown_config))
                .setContentIntent(mConfigIntent)
                .setPriority(Notification.PRIORITY_LOW)
                .setOngoing(true)
                .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
                        mResetIntent)
                .setColor(mContext.getResources().getColor(
                        com.android.internal.R.color.system_notification_accent_color));

        NotificationManager.from(mContext).notify(TAG, 0, builder.build());
    
public voidshutdown()

        synchronized (mStateLock) {
            shutdownLocked();
        }
    
private voidshutdownLocked()

        Slog.d(TAG, "shutdownLocked()");

        mAcceptedEgressIface = null;
        mErrorCount = 0;

        mVpn.stopLegacyVpnPrivileged();
        try {
            mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }
        clearSourceRulesLocked();
        hideNotification();

        mContext.unregisterReceiver(mResetReceiver);
        mVpn.setEnableTeardown(true);