Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff3bdca3 authored by Chia-chi Yeh's avatar Chia-chi Yeh
Browse files

The service part of the user space VPN support.

The dialogs will be in another change.

Change-Id: I0cdfd2ef21ffd40ee955b3cbde5ada65dbfdb0bc
parent 9eb58477
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.net;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;

import java.net.InetAddress;
@@ -756,4 +758,43 @@ public class ConnectivityManager {
        } catch (RemoteException e) {
        }
    }

    /**
     * Protect a socket from routing changes. This method is limited to VPN
     * applications, and it is always hidden to avoid direct use.
     * @hide
     */
    public void protectVpn(ParcelFileDescriptor socket) {
        try {
            mService.protectVpn(socket);
        } catch (RemoteException e) {
        }
    }

    /**
     * Prepare for a VPN application. This method is limited to VpnDialogs,
     * and it is always hidden to avoid direct use.
     * @hide
     */
    public String prepareVpn(String packageName) {
        try {
            return mService.prepareVpn(packageName);
        } catch (RemoteException e) {
            return null;
        }
    }

    /**
     * Configure a TUN interface and return its file descriptor. Parameters
     * are encoded and opaque to this class. This method is limited to VPN
     * applications, and it is always hidden to avoid direct use.
     * @hide
     */
    public ParcelFileDescriptor establishVpn(Bundle config) {
        try {
            return mService.establishVpn(config);
        } catch (RemoteException e) {
            return null;
        }
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@ import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkState;
import android.net.ProxyProperties;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;

/**
 * Interface that answers queries about, and allows changing, the
@@ -95,4 +97,10 @@ interface IConnectivityManager
    ProxyProperties getProxy();

    void setDataDependency(int networkType, boolean met);

    void protectVpn(in ParcelFileDescriptor socket);

    String prepareVpn(String packageName);

    ParcelFileDescriptor establishVpn(in Bundle config);
}
+8 −1
Original line number Diff line number Diff line
@@ -1330,7 +1330,7 @@
    <string name="permlab_vpn">intercept and modify all network traffic</string>
    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permdesc_vpn">Allows an application to intercept and
      inspect all network traffic, for example to establish a VPN connection.
      inspect all network traffic to establish a VPN connection.
      Malicious applications may monitor, redirect, or modify network packets
      without your knowledge.</string>

@@ -2725,6 +2725,13 @@
    <string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string>
    <string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string>

    <!-- Ticker text to show when VPN is active. -->
    <string name="vpn_ticker">Activating <xliff:g id="app">%s</xliff:g> VPN...</string>
    <!-- The title of the notification when VPN is active. -->
    <string name="vpn_title"><xliff:g id="app">%s</xliff:g> VPN is active</string>
    <!-- The text of the notification when VPN is active. -->
    <string name="vpn_text">VPN is connected to <xliff:g id="profile">%s</xliff:g>. Tap to manage the network.</string>

    <!-- Localized strings for WebView -->
    <!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
    <string name="upload_file">Choose file</string>
+68 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.net.RouteInfo;
import android.net.vpn.VpnManager;
import android.net.wifi.WifiStateTracker;
import android.os.Binder;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
@@ -55,6 +56,7 @@ import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -67,6 +69,8 @@ import android.util.SparseIntArray;

import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;

import com.google.android.collect.Lists;

import java.io.FileDescriptor;
@@ -103,6 +107,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
    private Tethering mTethering;
    private boolean mTetheringConfigValid = false;

    private Vpn mVpn;

    /** Currently active network rules by UID. */
    private SparseIntArray mUidRules = new SparseIntArray();

@@ -461,8 +467,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
                                 mTethering.getUpstreamIfaceRegexs().length != 0);

        mVpn = new Vpn(mContext, new VpnCallback());

        try {
            nmService.registerObserver(mTethering);
            nmService.registerObserver(mVpn);
        } catch (RemoteException e) {
            loge("Error registering observer :" + e);
        }
@@ -2358,6 +2367,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
    private void loge(String s) {
        Slog.e(TAG, s);
    }

    int convertFeatureToNetworkType(String feature){
        int networkType = -1;
        if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
@@ -2385,4 +2395,62 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        }
        return value;
    }

    // @see ConnectivityManager#protectVpn(ParcelFileDescriptor)
    // Permission checks are done in Vpn class.
    @Override
    public void protectVpn(ParcelFileDescriptor socket) {
        mVpn.protect(socket, getDefaultInterface());
    }

    // @see ConnectivityManager#prepareVpn(String)
    // Permission checks are done in Vpn class.
    @Override
    public String prepareVpn(String packageName) {
        return mVpn.prepare(packageName);
    }

    // @see ConnectivityManager#establishVpn(Bundle)
    // Permission checks are done in Vpn class.
    @Override
    public ParcelFileDescriptor establishVpn(Bundle config) {
        return mVpn.establish(config);
    }

    private String getDefaultInterface() {
        if (ConnectivityManager.isNetworkTypeValid(mActiveDefaultNetwork)) {
            NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
            if (tracker != null) {
                LinkProperties properties = tracker.getLinkProperties();
                if (properties != null) {
                    return properties.getInterfaceName();
                }
            }
        }
        throw new IllegalStateException("No default interface");
    }

    /**
     * Callback for VPN subsystem. Currently VPN is not adapted to the service
     * through NetworkStateTracker since it works differently. For example, it
     * needs to override DNS servers but never takes the default routes. It
     * relies on another data network, and it could keep existing connections
     * alive after reconnecting, switching between networks, or even resuming
     * from deep sleep. Calls from applications should be done synchronously
     * to avoid race conditions. As these are all hidden APIs, refactoring can
     * be done whenever a better abstraction is developed.
     */
    public class VpnCallback {

        private VpnCallback() {
        }

        public synchronized void override(String[] dnsServers) {
            // TODO: override DNS servers and http proxy.
        }

        public synchronized void restore() {
            // TODO: restore VPN changes.
        }
    }
}
+258 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.connectivity;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.INetworkManagementEventObserver;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.R;
import com.android.server.ConnectivityService.VpnCallback;

/**
 * @hide
 */
public class Vpn extends INetworkManagementEventObserver.Stub {

    private final static String TAG = "Vpn";
    private final static String VPN = android.Manifest.permission.VPN;

    private final Context mContext;
    private final VpnCallback mCallback;

    private String mPackageName;
    private String mInterfaceName;
    private String mDnsPropertyPrefix;

    public Vpn(Context context, VpnCallback callback) {
        mContext = context;
        mCallback = callback;
    }

    /**
     * Prepare for a VPN application.
     *
     * @param packageName The package name of the new VPN application.
     * @return The name of the current prepared package.
     */
    public synchronized String prepare(String packageName) {

        // TODO: Check if the caller is VpnDialogs.

        if (packageName == null) {
            return mPackageName;
        }

        // Check the permission of the given application.
        PackageManager pm = mContext.getPackageManager();
        if (pm.checkPermission(VPN, packageName) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException(packageName + " does not have " + VPN);
        }

        // Reset the interface and hide the notification.
        if (mInterfaceName != null) {
            nativeReset(mInterfaceName);
            mInterfaceName = null;
            hideNotification();
            // TODO: Send out a broadcast.
        }

        mPackageName = packageName;
        Log.i(TAG, "Prepared for " + packageName);
        return mPackageName;
    }

    /**
     * Protect a socket from routing changes by binding it to the given
     * interface. The socket is NOT closed by this method.
     *
     * @param socket The socket to be bound.
     * @param name The name of the interface.
     */
    public void protect(ParcelFileDescriptor socket, String name) {
        mContext.enforceCallingPermission(VPN, "protect");
        nativeProtect(socket.getFd(), name);
    }

    /**
     * Configure a TUN interface and return its file descriptor.
     *
     * @param configuration The parameters to configure the interface.
     * @return The file descriptor of the interface.
     */
    public synchronized ParcelFileDescriptor establish(Bundle config) {
        // Check the permission of the caller.
        mContext.enforceCallingPermission(VPN, "establish");

        // Check if the caller is already prepared.
        PackageManager pm = mContext.getPackageManager();
        ApplicationInfo app = null;
        try {
            app = pm.getApplicationInfo(mPackageName, 0);
        } catch (Exception e) {
            throw new SecurityException("Not prepared");
        }
        if (Binder.getCallingUid() != app.uid) {
            throw new SecurityException("Not prepared");
        }

        // Unpack the config.
        // TODO: move constants into VpnBuilder.
        String session = config.getString("session");
        String addresses = config.getString("addresses");
        String routes = config.getString("routes");
        String dnsServers = config.getString("dnsServers");

        // Create interface and configure addresses and routes.
        ParcelFileDescriptor descriptor = nativeConfigure(addresses, routes);

        // Replace the interface and abort if it fails.
        try {
            String interfaceName = nativeGetName(descriptor.getFd());

            if (mInterfaceName != null && !mInterfaceName.equals(interfaceName)) {
                nativeReset(mInterfaceName);
            }
            mInterfaceName = interfaceName;
        } catch (RuntimeException e) {
            try {
                descriptor.close();
            } catch (Exception ex) {
                // ignore
            }
            throw e;
        }

        dnsServers = (dnsServers == null) ? "" : dnsServers.trim();
        mCallback.override(dnsServers.isEmpty() ? null : dnsServers.split(" "));

        showNotification(pm, app, session);
        return descriptor;
    }

    public synchronized boolean onInterfaceRemoved(String name) {
        if (name.equals(mInterfaceName) && nativeCheck(name) == 0) {
            hideNotification();
            mInterfaceName = null;
            return true;
        }
        return false;
    }

    // INetworkManagementEventObserver.Stub
    public void interfaceLinkStatusChanged(String name, boolean up) {
    }

    // INetworkManagementEventObserver.Stub
    public void interfaceAdded(String name) {
    }

    // INetworkManagementEventObserver.Stub
    public synchronized void interfaceRemoved(String name) {
        if (name.equals(mInterfaceName) && nativeCheck(name) == 0) {
            hideNotification();
            mInterfaceName = null;
            mCallback.restore();
        }
    }

    private void showNotification(PackageManager pm, ApplicationInfo app, String session) {
        NotificationManager nm = (NotificationManager)
                mContext.getSystemService(Context.NOTIFICATION_SERVICE);

        if (nm != null) {
            // Load the icon and convert it into a bitmap.
            Drawable icon = app.loadIcon(pm);
            Bitmap bitmap = null;
            if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
                int width = mContext.getResources().getDimensionPixelSize(
                        android.R.dimen.notification_large_icon_width);
                int height = mContext.getResources().getDimensionPixelSize(
                        android.R.dimen.notification_large_icon_height);
                icon.setBounds(0, 0, width, height);
                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                icon.draw(new Canvas(bitmap));
            }

            // Load the label.
            String label = app.loadLabel(pm).toString();

            // If session is null, use the application name instead.
            if (session == null) {
                session = label;
            }

            // Build the intent.
            // TODO: move these into VpnBuilder.
            Intent intent = new Intent();
            intent.setClassName("com.android.vpndialogs",
                    "com.android.vpndialogs.ManageDialog");
            intent.putExtra("packageName", mPackageName);
            intent.putExtra("interfaceName", mInterfaceName);
            intent.putExtra("session", session);
            intent.putExtra("startTime", android.os.SystemClock.elapsedRealtime());
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

            // Build the notification.
            long identity = Binder.clearCallingIdentity();
            Notification notification = new Notification.Builder(mContext)
                    .setSmallIcon(R.drawable.vpn_connected)
                    .setLargeIcon(bitmap)
                    .setTicker(mContext.getString(R.string.vpn_ticker, label))
                    .setContentTitle(mContext.getString(R.string.vpn_title, label))
                    .setContentText(mContext.getString(R.string.vpn_text, session))
                    .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0))
                    .setDefaults(Notification.DEFAULT_ALL)
                    .setOngoing(true)
                    .getNotification();

            nm.notify(R.drawable.vpn_connected, notification);
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void hideNotification() {
        NotificationManager nm = (NotificationManager)
                mContext.getSystemService(Context.NOTIFICATION_SERVICE);

        if (nm != null) {
            long identity = Binder.clearCallingIdentity();
            nm.cancel(R.drawable.vpn_connected);
            Binder.restoreCallingIdentity(identity);
        }
    }

    private native ParcelFileDescriptor nativeConfigure(String addresses, String routes);
    private native String nativeGetName(int fd);
    private native void nativeReset(String name);
    private native int nativeCheck(String name);
    private native void nativeProtect(int fd, String name);
}
Loading