Android NDK开发详解连接性之VPN


Android 为开发者提供了创建虚拟专用网 (VPN) 解决方案的 API。阅读本指南后,您将了解如何为 Android 设备开发和测试自己的 VPN 客户端。

概览

VPN 可让实际不在网络中的设备安全地访问该网络。

Android 包含一个内置(PPTP 和 L2TP/IPSec)VPN 客户端,有时也称为旧版 VPN。Android 4.0(API 级别 14)引入了 API,以便应用开发者可以提供自己的 VPN 解决方案。您将 VPN 解决方案打包到应用中,然后用户安装到设备上。开发者构建 VPN 应用通常是出于以下原因之一:

提供内置客户端不支持的 VPN 协议。
帮助用户在不进行复杂配置的情况下连接到 VPN 服务。
本指南的其余部分将介绍如何开发 VPN 应用(包括始终开启和按应用开启的 VPN),不会介绍内置 VPN 客户端。

用户体验

Android 提供了一个界面 (UI) 来帮助用户配置、启动和停止您的 VPN 解决方案。系统界面还会让设备的使用者知道有效的 VPN 连接。对于 VPN 连接,Android 会显示以下界面组件:

在 VPN 应用首次变为活动状态之前,系统会显示一个连接请求对话框。该对话框会提示设备的使用者确认他们信任 VPN 并接受请求。
VPN 设置屏幕(“设置”>“网络和互联网”>“VPN”)显示用户接受了连接请求的 VPN 应用。有一个用于配置系统选项或忘记连接 VPN 的按钮。
连接处于活跃状态时,“快捷设置”栏会显示信息面板。点按标签会显示一个对话框,其中包含更多信息和指向“设置”的链接。
状态栏包含一个 VPN(钥匙)图标以表示有效连接。
您的应用还需要提供一个界面,以便设备的使用者能够配置您的服务选项。例如,您的解决方案可能需要捕获帐号身份验证设置。应用应该显示以下界面:

用于手动启动和停止连接的控件。始终开启的 VPN 可以在需要时连接,但允许用户在首次使用您的 VPN 时配置连接。
服务处于活动状态时发出的不可关闭通知。通知可以显示连接状态或提供更多信息,例如网络统计信息。点按该通知会将您的应用调入前台。在服务变为非活跃状态后移除通知。

VPN 服务

您的应用将用户(或工作资料)的系统网络连接到 VPN 网关。每个用户(或工作资料)可以运行不同的 VPN 应用。您需要创建 VPN 服务,系统使用该服务来启动和停止您的 VPN,并跟踪连接状态。您的 VPN 服务继承自 VpnService。

该服务还充当 VPN 网关连接及其本地设备接口的容器。您的服务实例调用 VpnService.Builder 方法来建立新的本地接口。
在这里插入图片描述

图 1.VpnService 如何将 Android 网络连接到 VPN 网关
显示 VpnService 如何在系统网络中创建本地 TUN 接口的块架构图。
您的应用会传输以下数据,用于将设备连接到 VPN 网关:

从本地接口的文件描述符中读取传出的 IP 数据包,对其进行加密,然后发送到 VPN 网关。
将传入的数据包(从 VPN 网关接收并解密)写入本地接口的文件描述符。
警告 :在与 VPN 网关之间传输数据时,您的应用必须使用强加密。
每个用户或工作资料只能有一项活动的服务。启动新服务会自动停止现有服务。

添加服务

如需将 VPN 服务添加到您的应用,请创建一项继承自 VpnService 的 Android 服务。在应用清单文件中声明 VPN 服务,并添加以下内容:

使用 BIND_VPN_SERVICE 权限保护服务,以便只有系统可以绑定到您的服务。
使用 “android.net.VpnService” intent 过滤器通告该服务,以便系统可以找到您的服务。
以下示例展示了如何在应用清单文件中声明服务:

<service android:name=".MyVpnService"
         android:permission="android.permission.BIND_VPN_SERVICE">
     <intent-filter>
         <action android:name="android.net.VpnService"/>
     </intent-filter>
</service>

现在您的应用声明了该服务,系统可以在需要时自动启动和停止应用的 VPN 服务。例如,系统会在运行始终开启的 VPN 时控制您的服务。

准备服务

如需准备好应用以成为用户当前的 VPN 服务,请调用 VpnService.prepare()。如果使用设备的用户尚未向您的应用授予权限,该方法会返回一个 activity intent。您将使用此 Intent 来启动询问权限的系统 Activity。系统会显示一个与其他权限对话框(例如相机或通讯录访问权限)类似的对话框。如果您的应用已准备就绪,该方法会返回 null。

只有一个应用可以是当前准备好的 VPN 服务。始终调用 VpnService.prepare(),因为自应用上次调用该方法以来,用户可能已将其他应用设置为 VPN 服务。如需了解详情,请参阅服务生命周期部分。

连接服务

服务运行后,您可以建立连接到 VPN 网关的新本地接口。如需请求权限并将您的服务连接到 VPN 网关,您需要按以下顺序完成步骤:

调用 VpnService.prepare() 以请求权限(需要时)。
调用 VpnService.protect() 以将应用的隧道套接字保留在系统 VPN 之外,并避免循环连接。
调用 DatagramSocket.connect() 以将应用的隧道套接字连接到 VPN 网关。
调用 VpnService.Builder 方法,以在设备上为 VPN 流量配置新的本地 TUN 接口。
调用 VpnService.Builder.establish(),以便系统建立本地 TUN 接口并开始通过该接口路由流量。
VPN 网关通常会在握手期间针对本地 TUN 接口提供设置建议。您的应用会调用 VpnService.Builder 方法来配置服务,如以下示例所示:

Kotlin

// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
val builder = Builder()

// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
val localTunnel = builder
        .addAddress("192.168.2.2", 24)
        .addRoute("0.0.0.0", 0)
        .addDnsServer("192.168.1.1")
        .establish()

Java

// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
VpnService.Builder builder = new VpnService.Builder();

// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
ParcelFileDescriptor localTunnel = builder
    .addAddress("192.168.2.2", 24)
    .addRoute("0.0.0.0", 0)
    .addDnsServer("192.168.1.1")
    .establish();

按应用划分的 VPN 部分中的示例显示了一个包含更多选项的 IPv6 配置。您需要先添加以下 VpnService.Builder 值,然后才能建立新接口:

addAddress()
添加至少一个 IPv4 或 IPv6 地址,以及系统指定为本地 TUN 接口地址的子网掩码。您的应用通常会在握手期间从 VPN 网关收到 IP 地址和子网掩码。
addRoute()
如果您希望系统通过 VPN 接口发送流量,请至少添加一个路由。路由按目标地址过滤。如需接受所有流量,请设置开放路由,例如 0.0.0.0/0 或 ::/0。
establish() 方法会返回一个 ParcelFileDescriptor 实例,供应用用来在接口缓冲区中读取和写入数据包。如果您的应用尚未准备就绪或有人撤消了权限,establish() 方法会返回 null。

服务生命周期

您的应用应跟踪系统所选 VPN 的状态以及所有活跃连接。更新应用的界面 (UI),确保设备的使用者了解所有变化。

启动服务
您的 VPN 服务可以通过以下方式启动:

您的应用启动服务,通常是因为用户点按了连接按钮。
系统启动服务,因为始终开启的 VPN 已开启。
您的应用通过将 intent 传递给 startService() 来启动 VPN 服务。如需了解详情,请阅读启动服务。

系统通过调用 onStartCommand() 在后台启动您的服务。不过,Android 对版本 8.0(API 级别 26)或更高版本的后台应用施加了限制。如果您支持这些 API 级别,则需要通过调用 Service.startForeground() 将服务转换为前台服务。如需了解详情,请参阅在前台运行服务。

停止服务
设备的使用者可以使用应用界面来停止服务。请停止服务,而不是仅关闭连接。当设备的使用者在“设置”应用的 VPN 屏幕中执行以下操作时,系统也会停止活动连接:

断开或忘记了 VPN 应用
为活动连接关闭始终开启的 VPN
系统会调用服务的 onRevoke() 方法,但此调用可能不会发生在主线程上。当系统调用此方法时,备用网络接口已在路由流量。您可以安全地处置以下资源:

通过调用 DatagramSocket.close() 关闭与 VPN 网关之间的受保护隧道套接字。
通过调用 ParcelFileDescriptor.close() 关闭 Parcel 文件描述符(无需排空它)。

始终开启的 VPN

Android 可以在设备启动时启动 VPN 服务,并在设备开启时使其保持运行状态。此功能称为“始终开启的 VPN”,适用于 Android 7.0(API 级别 24)或更高版本。虽然 Android 会维护服务生命周期,但 VPN 网关连接由您的 VPN 服务负责。始终开启的 VPN 还可以屏蔽不使用 VPN 的连接。

用户体验

在 Android 8.0 或更高版本中,系统会显示以下对话框,让设备的使用者知晓始终开启的 VPN:

当始终开启的 VPN 连接断开或无法连接时,用户会看到不可关闭的通知。点按该通知即会显示一个说明更多内容的对话框。当 VPN 重新连接或有人关闭始终开启的 VPN 选项时,通知会消失。
始终开启的 VPN 允许设备使用者屏蔽任何不使用 VPN 的网络连接。启用此选项后,“设置”应用会在 VPN 连接之前警告用户。“设置”应用会提示设备的使用者继续或取消。
由于系统(而非人)会启动和停止始终开启的连接,因此您需要调整应用的行为和界面:

停用所有断开连接的界面,因为系统和“设置”应用会控制连接。
在每次应用启动之间保存任何配置,并使用最新设置来配置连接。由于系统会按需启动您的应用,因此设备的使用者可能并不总是想要配置连接。
您还可以使用托管配置来配置连接。借助托管配置,IT 管理员可远程配置您的 VPN。

检测始终开启的 VPN

Android 不包含用于确认系统是否已启动 VPN 服务的 API。但是,当您的应用标记其启动的任何服务实例时,您可以假定系统为始终开启的 VPN 启动了未标记的服务。示例如下:

创建 Intent 实例以启动 VPN 服务。
通过在 Intent 中放置 extra 来标记该 VPN 服务。
在服务的 onStartCommand() 方法中,在 intent 参数的 extra 中查找标志。

屏蔽的连接

设备的使用者(或 IT 管理员)可以强制所有流量使用 VPN。 系统会屏蔽所有不使用 VPN 的网络流量。设备的使用者可以在“设置”的 VPN 选项面板中找到“禁止未使用 VPN 的连接”开关。

注意 :当非 VPN 流量被屏蔽时,不在允许列表或禁止列表中的应用会失去网络连接。在创建允许或禁止的列表时,请考虑警告用户。如需了解详情,请参阅下面的按应用实施 VPN 部分。

停用始终开启的 VPN

如果您的应用目前不支持始终开启的 VPN,您可以通过将 SERVICE_META_DATA_SUPPORTS_ALWAYS_ON 服务元数据设置为 false 来停用该功能(在 Android 8.1 或更高版本中)。以下应用清单示例展示了如何添加元数据元素:


<service android:name=".MyVpnService"
         android:permission="android.permission.BIND_VPN_SERVICE">
     <intent-filter>
         <action android:name="android.net.VpnService"/>
     </intent-filter>
     <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
             android:value=false/>
</service>

如果您的应用停用始终开启的 VPN,系统会在“设置”中停用选项界面控件。

按应用开启的 VPN

VPN 应用可以过滤允许哪些已安装的应用通过 VPN 连接发送流量。您可以创建允许列表或禁止列表,但不能同时创建两者。如果您未创建允许或禁止的列表,系统会通过 VPN 发送所有网络流量。

您的 VPN 应用必须先设置列表,然后建立连接。如果您需要更改列表,请建立新的 VPN 连接。将应用添加到列表时,必须在设备上安装应用。

Kotlin

// The apps that will have access to the VPN.
val appPackages = arrayOf(
        "com.android.chrome",
        "com.google.android.youtube",
        "com.example.a.missing.app")

// Loop through the app packages in the array and confirm that the app is
// installed before adding the app to the allowed list.
val builder = Builder()
for (appPackage in appPackages) {
    try {
        packageManager.getPackageInfo(appPackage, 0)
        builder.addAllowedApplication(appPackage)
    } catch (e: PackageManager.NameNotFoundException) {
        // The app isn't installed.
    }
}

// Complete the VPN interface config.
val localTunnel = builder
        .addAddress("2001:db8::1", 64)
        .addRoute("::", 0)
        .establish()

Java

// The apps that will have access to the VPN.
String[] appPackages = {
    "com.android.chrome",
    "com.google.android.youtube",
    "com.example.a.missing.app"};

// Loop through the app packages in the array and confirm that the app is
// installed before adding the app to the allowed list.
VpnService.Builder builder = new VpnService.Builder();
PackageManager packageManager = getPackageManager();
for (String appPackage: appPackages) {
  try {
    packageManager.getPackageInfo(appPackage, 0);
    builder.addAllowedApplication(appPackage);
  } catch (PackageManager.NameNotFoundException e) {
    // The app isn't installed.
  }
}

// Complete the VPN interface config.
ParcelFileDescriptor localTunnel = builder
    .addAddress("2001:db8::1", 64)
    .addRoute("::", 0)
    .establish();

允许的应用

如需将应用添加到允许列表中,请调用 VpnService.Builder.addAllowedApplication()。如果该列表包含一个或多个应用,则只有列表中的应用才会使用 VPN。所有其他应用(不在列表中)使用系统网络时,看似 VPN 未运行。当允许列表为空时,所有应用都将使用 VPN。

禁止的应用

如需将应用添加到禁止列表,请调用 VpnService.Builder.addDisallowedApplication()。不允许的应用使用系统网络,就像 VPN 未运行一样,而所有其他应用都会使用 VPN。

绕过 VPN

您的 VPN 可让应用绕过 VPN 并选择自己的网络。如需绕过 VPN,请在建立 VPN 接口时调用 VpnService.Builder.allowBypass()。启动 VPN 服务后,您将无法更改此值。如果应用未将其进程或套接字绑定到特定网络,应用的网络流量会继续通过 VPN 传输。

当有人屏蔽不通过 VPN 的流量时,绑定到特定网络的应用会无法连接。如需通过特定网络发送流量,应用会在连接套接字之前调用 ConnectivityManager.bindProcessToNetwork() 或 Network.bindSocket() 等方法。

示例代码

Android 开源项目包含一个名为 ToyVPN 的示例应用。此应用展示了如何设置和连接 VPN 服务。

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-11-11。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android NDK开发是指利用NDK(Native Development Kit)将C/C++开发的代码编译成so库,然后通过JNI(Java Native Interface)让Java程序调用。在Android开发中,默认使用的是Android SDK进行Java语言的开发,而对于一些需要使用C/C++的高性能计算、底层操作或跨平台需求的场景,可以使用NDK进行开发。 在Android Studio中进行NDK开发相对于Eclipse来说更加方便,特别是在Android Studio 3.0及以上版本中,配置更加简化,并引入了CMake等工具,使得开发更加便捷。首先要进行NDK开发,需要配置环境,包括导入NDK、LLDB和CMake等工具。可以通过打开Android Studio的SDK Manager,选择SDK Tools,在其中选中相应的工具进行导入。 在项目的build.gradle文件中,可以配置一些NDK相关的参数,例如编译版本、ABI过滤器等。其中,可以通过externalNativeBuild配置CMake的相关设置,包括CMakeLists.txt文件的路径和版本号。此外,在sourceSets.main中还可以设置jniLibs.srcDirs,指定so库的位置。 在进行NDK开发时,可以在jni文件夹中编写C/C++代码,并通过JNI调用相关的函数。通过JNI接口,可以实现Java与C/C++之间的相互调用,从而实现跨语言的开发。 综上所述,Android NDK开发是指利用NDK将C/C++开发的代码编译成so库,并通过JNI实现与Java的相互调用。在Android Studio中进行NDK开发相对方便,可以通过配置环境和相应的参数来进行开发。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值