summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/net/ConnectivityManager.java41
-rw-r--r--core/java/android/net/IConnectivityManager.aidl8
-rwxr-xr-xcore/res/res/values/strings.xml9
-rw-r--r--services/java/com/android/server/ConnectivityService.java68
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java258
-rw-r--r--services/jni/Android.mk1
-rw-r--r--services/jni/com_android_server_connectivity_Vpn.cpp450
-rw-r--r--services/jni/onload.cpp2
8 files changed, 836 insertions, 1 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index ea750da..3025462 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -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;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 07f6cec..7f3775d 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -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);
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 65a1e44..87d5654 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -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>
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index e3d4c45..c6f4c20 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -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.
+ }
+ }
}
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
new file mode 100644
index 0000000..b754dba
--- /dev/null
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -0,0 +1,258 @@
+/*
+ * 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);
+}
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index a1c3283..f33920d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -16,6 +16,7 @@ LOCAL_SRC_FILES:= \
com_android_server_UsbHostManager.cpp \
com_android_server_VibratorService.cpp \
com_android_server_location_GpsLocationProvider.cpp \
+ com_android_server_connectivity_Vpn.cpp \
onload.cpp
LOCAL_C_INCLUDES += \
diff --git a/services/jni/com_android_server_connectivity_Vpn.cpp b/services/jni/com_android_server_connectivity_Vpn.cpp
new file mode 100644
index 0000000..374fd3b
--- /dev/null
+++ b/services/jni/com_android_server_connectivity_Vpn.cpp
@@ -0,0 +1,450 @@
+/*
+ * 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.
+ */
+
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "VpnJni"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/route.h>
+#include <linux/ipv6_route.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_util_Binder.h"
+
+namespace android
+{
+
+static inline void init_sockaddr(sockaddr *sa) {
+ ((sockaddr_in *)sa)->sin_family = AF_INET;
+ ((sockaddr_in *)sa)->sin_port = 0;
+}
+
+static inline in_addr_t *as_in_addr(sockaddr *sa) {
+ return &((sockaddr_in *)sa)->sin_addr.s_addr;
+}
+
+static inline in_addr_t *as_in_addr(sockaddr_storage *ss) {
+ return &((sockaddr_in *)ss)->sin_addr.s_addr;
+}
+
+static inline in6_addr *as_in6_addr(sockaddr_storage *ss) {
+ return &((sockaddr_in6 *)&ss)->sin6_addr;
+}
+
+//------------------------------------------------------------------------------
+
+#define SYSTEM_ERROR -1
+#define BAD_ARGUMENT -2
+
+static int create_interface(char *name, int *index)
+{
+ int tun = open("/dev/tun", O_RDWR);
+ int inet4 = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifreq ifr4;
+ memset(&ifr4, 0, sizeof(ifr4));
+
+ // Allocate interface.
+ ifr4.ifr_flags = IFF_TUN;
+ if (ioctl(tun, TUNSETIFF, &ifr4)) {
+ LOGE("Cannot allocate TUN: %s", strerror(errno));
+ goto error;
+ }
+
+ // Activate interface.
+ ifr4.ifr_flags = IFF_UP;
+ if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) {
+ LOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno));
+ goto error;
+ }
+
+ // Get interface index.
+ if (ioctl(inet4, SIOGIFINDEX, &ifr4)) {
+ LOGE("Cannot get index of %s: %s", ifr4.ifr_name, strerror(errno));
+ goto error;
+ }
+
+ strcpy(name, ifr4.ifr_name);
+ *index = ifr4.ifr_ifindex;
+ close(inet4);
+ return tun;
+
+error:
+ close(tun);
+ close(inet4);
+ return SYSTEM_ERROR;
+}
+
+static int set_addresses(const char *name, int index, const char *addresses)
+{
+ int inet4 = socket(AF_INET, SOCK_DGRAM, 0);
+ int inet6 = socket(AF_INET6, SOCK_DGRAM, 0);
+
+ ifreq ifr4;
+ memset(&ifr4, 0, sizeof(ifr4));
+ strcpy(ifr4.ifr_name, name);
+ init_sockaddr(&ifr4.ifr_addr);
+
+ in6_ifreq ifr6;
+ memset(&ifr6, 0, sizeof(ifr6));
+ ifr6.ifr6_ifindex = index;
+
+ char address[65];
+ int prefix;
+
+ int chars;
+ int count = 0;
+
+ while (sscanf(addresses, " %64[^/]/%d %n", address, &prefix, &chars) == 2) {
+ addresses += chars;
+
+ if (strchr(address, ':')) {
+ // Add an IPv6 address.
+ if (inet_pton(AF_INET6, address, &ifr6.ifr6_addr) != 1 ||
+ prefix < 0 || prefix > 128) {
+ count = BAD_ARGUMENT;
+ break;
+ }
+
+ ifr6.ifr6_prefixlen = prefix;
+ if (ioctl(inet6, SIOCSIFADDR, &ifr6)) {
+ count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
+ break;
+ }
+ } else {
+ // Add an IPv4 address.
+ if (inet_pton(AF_INET, address, as_in_addr(&ifr4.ifr_addr)) != 1 ||
+ prefix < 0 || prefix > 32) {
+ count = BAD_ARGUMENT;
+ break;
+ }
+
+ if (count) {
+ sprintf(ifr4.ifr_name, "%s:%d", name, count);
+ }
+ if (ioctl(inet4, SIOCSIFADDR, &ifr4)) {
+ count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
+ break;
+ }
+
+ in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0;
+ *as_in_addr(&ifr4.ifr_addr) = htonl(mask);
+ if (ioctl(inet4, SIOCSIFNETMASK, &ifr4)) {
+ count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
+ break;
+ }
+ }
+ LOGV("Address added on %s: %s/%d", name, address, prefix);
+ ++count;
+ }
+
+ if (count == BAD_ARGUMENT) {
+ LOGE("Invalid address: %s/%d", address, prefix);
+ } else if (count == SYSTEM_ERROR) {
+ LOGE("Cannot add address: %s/%d: %s", address, prefix, strerror(errno));
+ } else if (*addresses) {
+ LOGE("Invalid address: %s", addresses);
+ count = BAD_ARGUMENT;
+ }
+
+ close(inet4);
+ close(inet6);
+ return count;
+}
+
+static int set_routes(const char *name, int index, const char *routes)
+{
+ int inet4 = socket(AF_INET, SOCK_DGRAM, 0);
+ int inet6 = socket(AF_INET6, SOCK_DGRAM, 0);
+
+ rtentry rt4;
+ memset(&rt4, 0, sizeof(rt4));
+ rt4.rt_dev = (char *)name;
+ rt4.rt_flags = RTF_UP;
+ init_sockaddr(&rt4.rt_dst);
+ init_sockaddr(&rt4.rt_genmask);
+ init_sockaddr(&rt4.rt_gateway);
+
+ in6_rtmsg rt6;
+ memset(&rt6, 0, sizeof(rt6));
+ rt6.rtmsg_ifindex = index;
+ rt6.rtmsg_flags = RTF_UP;
+
+ char address[65];
+ int prefix;
+ char gateway[65];
+
+ int chars;
+ int count = 0;
+
+ while (sscanf(routes, " %64[^/]/%d>%64[^ ] %n",
+ address, &prefix, gateway, &chars) == 3) {
+ routes += chars;
+
+ if (strchr(address, ':')) {
+ // Add an IPv6 route.
+ if (inet_pton(AF_INET6, gateway, &rt6.rtmsg_gateway) != 1 ||
+ inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 ||
+ prefix < 0 || prefix > 128) {
+ count = BAD_ARGUMENT;
+ break;
+ }
+
+ rt6.rtmsg_dst_len = prefix;
+ if (memcmp(&rt6.rtmsg_gateway, &in6addr_any, sizeof(in6addr_any))) {
+ rt6.rtmsg_flags |= RTF_GATEWAY;
+ }
+ if (ioctl(inet6, SIOCADDRT, &rt6)) {
+ count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
+ break;
+ }
+ } else {
+ // Add an IPv4 route.
+ if (inet_pton(AF_INET, gateway, as_in_addr(&rt4.rt_gateway)) != 1 ||
+ inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 ||
+ prefix < 0 || prefix > 32) {
+ count = BAD_ARGUMENT;
+ break;
+ }
+
+ in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0;
+ *as_in_addr(&rt4.rt_genmask) = htonl(mask);
+ if (*as_in_addr(&rt4.rt_gateway)) {
+ rt4.rt_flags |= RTF_GATEWAY;
+ }
+ if (ioctl(inet4, SIOCADDRT, &rt4)) {
+ count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
+ break;
+ }
+ }
+ LOGV("Route added on %s: %s/%d -> %s", name, address, prefix, gateway);
+ ++count;
+ }
+
+ if (count == BAD_ARGUMENT) {
+ LOGE("Invalid route: %s/%d -> %s", address, prefix, gateway);
+ } else if (count == SYSTEM_ERROR) {
+ LOGE("Cannot add route: %s/%d -> %s: %s",
+ address, prefix, gateway, strerror(errno));
+ } else if (*routes) {
+ LOGE("Invalid route: %s", routes);
+ count = BAD_ARGUMENT;
+ }
+
+ close(inet4);
+ close(inet6);
+ return count;
+}
+
+static int get_interface_name(char *name, int tun)
+{
+ ifreq ifr4;
+ if (ioctl(tun, TUNGETIFF, &ifr4)) {
+ LOGE("Cannot get interface name: %s", strerror(errno));
+ return SYSTEM_ERROR;
+ }
+ strcpy(name, ifr4.ifr_name);
+ return 0;
+}
+
+static int reset_interface(const char *name)
+{
+ int inet4 = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifreq ifr4;
+ ifr4.ifr_flags = 0;
+ strncpy(ifr4.ifr_name, name, IFNAMSIZ);
+
+ if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) {
+ LOGE("Cannot reset %s: %s", name, strerror(errno));
+ close(inet4);
+ return SYSTEM_ERROR;
+ }
+ close(inet4);
+ return 0;
+}
+
+static int check_interface(const char *name)
+{
+ int inet4 = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifreq ifr4;
+ strncpy(ifr4.ifr_name, name, IFNAMSIZ);
+
+ if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) {
+ LOGE("Cannot check %s: %s", name, strerror(errno));
+ ifr4.ifr_flags = 0;
+ }
+ close(inet4);
+ return ifr4.ifr_flags;
+}
+
+static int bind_to_interface(int fd, const char *name)
+{
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) {
+ LOGE("Cannot bind socket to %s: %s", name, strerror(errno));
+ return SYSTEM_ERROR;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+
+static void throwException(JNIEnv *env, int error, const char *message)
+{
+ if (error == SYSTEM_ERROR) {
+ jniThrowException(env, "java/lang/IllegalStateException", message);
+ } else {
+ jniThrowException(env, "java/lang/IllegalArgumentException", message);
+ }
+}
+
+static jobject configure(JNIEnv *env, jobject thiz,
+ jstring jAddresses, jstring jRoutes)
+{
+ char name[IFNAMSIZ];
+ int index;
+ int tun = create_interface(name, &index);
+ if (tun < 0) {
+ throwException(env, tun, "Cannot create interface");
+ return NULL;
+ }
+ LOGD("%s is created", name);
+
+ const char *addresses;
+ const char *routes;
+ int count;
+
+ // Addresses are required.
+ addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL;
+ if (!addresses) {
+ jniThrowNullPointerException(env, "address");
+ goto error;
+ }
+ count = set_addresses(name, index, addresses);
+ env->ReleaseStringUTFChars(jAddresses, addresses);
+ if (count <= 0) {
+ throwException(env, count, "Cannot set address");
+ goto error;
+ }
+ LOGD("Configured %d address(es) on %s", count, name);
+
+ // Routes are optional.
+ routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL;
+ if (routes) {
+ count = set_routes(name, index, routes);
+ env->ReleaseStringUTFChars(jRoutes, routes);
+ if (count < 0) {
+ throwException(env, count, "Cannot set route");
+ goto error;
+ }
+ LOGD("Configured %d route(s) on %s", count, name);
+ }
+
+ return newParcelFileDescriptor(env, jniCreateFileDescriptor(env, tun));
+
+error:
+ close(tun);
+ LOGD("%s is destroyed", name);
+ return NULL;
+}
+
+static jstring getName(JNIEnv *env, jobject thiz, jint fd)
+{
+ char name[IFNAMSIZ];
+ if (get_interface_name(name, fd) < 0) {
+ throwException(env, SYSTEM_ERROR, "Cannot get interface name");
+ return NULL;
+ }
+ return env->NewStringUTF(name);
+}
+
+static void reset(JNIEnv *env, jobject thiz, jstring jName)
+{
+ const char *name = jName ?
+ env->GetStringUTFChars(jName, NULL) : NULL;
+ if (!name) {
+ jniThrowNullPointerException(env, "name");
+ return;
+ }
+ if (reset_interface(name) < 0) {
+ throwException(env, SYSTEM_ERROR, "Cannot reset interface");
+ } else {
+ LOGD("%s is deactivated", name);
+ }
+ env->ReleaseStringUTFChars(jName, name);
+}
+
+static jint check(JNIEnv *env, jobject thiz, jstring jName)
+{
+ const char *name = jName ?
+ env->GetStringUTFChars(jName, NULL) : NULL;
+ if (!name) {
+ jniThrowNullPointerException(env, "name");
+ return 0;
+ }
+ int flags = check_interface(name);
+ env->ReleaseStringUTFChars(jName, name);
+ return flags;
+}
+
+static void protect(JNIEnv *env, jobject thiz, jint fd, jstring jName)
+{
+ const char *name = jName ?
+ env->GetStringUTFChars(jName, NULL) : NULL;
+ if (!name) {
+ jniThrowNullPointerException(env, "name");
+ return;
+ }
+ if (bind_to_interface(fd, name) < 0) {
+ throwException(env, SYSTEM_ERROR, "Cannot protect socket");
+ }
+ env->ReleaseStringUTFChars(jName, name);
+}
+
+//------------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+ {"nativeConfigure", "(Ljava/lang/String;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", (void *)configure},
+ {"nativeGetName", "(I)Ljava/lang/String;", (void *)getName},
+ {"nativeReset", "(Ljava/lang/String;)V", (void *)reset},
+ {"nativeCheck", "(Ljava/lang/String;)I", (void *)check},
+ {"nativeProtect", "(ILjava/lang/String;)V", (void *)protect},
+};
+
+int register_android_server_connectivity_Vpn(JNIEnv *env)
+{
+ return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn",
+ gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 469e818..9dff48b 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -34,6 +34,7 @@ int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
+int register_android_server_connectivity_Vpn(JNIEnv* env);
};
using namespace android;
@@ -63,6 +64,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
+ register_android_server_connectivity_Vpn(env);
return JNI_VERSION_1_4;
}