让我搜索 Android VPN 开启和关闭的相关代码:
现在让我获取 VPN 连接和断开的关键方法:
让我搜索更具体的内容:
完美!现在让我总结 Android VPN 的开启和关闭步骤:
Android VPN 开启和关闭的完整步骤
VPN 类型
Android 支持两种类型的 VPN:
- VpnService-based VPN (第三方 VPN 应用)
- Platform VPN (系统内置的 IKEv2/IPSec VPN)
一、VpnService-based VPN 开启步骤
1.1 应用层操作
// VPN 应用代码
public class MyVpnService extends VpnService {
// 步骤1: 准备 VPN (获取用户授权)
Intent intent = VpnService.prepare(getApplicationContext());
if (intent != null) {
// 需要用户授权,启动系统授权 Activity
startActivityForResult(intent, REQUEST_VPN_PERMISSION);
} else {
// 已授权,直接启动
startVpn();
}
private void startVpn() {
// 步骤2: 创建 VPN 配置
VpnService.Builder builder = new VpnService.Builder();
builder.setSession("My VPN")
.addAddress("10.0.0.2", 24) // VPN 内部 IP
.addRoute("0.0.0.0", 0) // 路由所有流量
.addDnsServer("8.8.8.8") // DNS 服务器
.setMtu(1500);
// 步骤3: 建立 VPN 连接
ParcelFileDescriptor vpnInterface = builder.establish();
// 步骤4: 开始转发数据
// 从 vpnInterface 读取数据 -> 加密 -> 发送到 VPN 服务器
// 从 VPN 服务器接收 -> 解密 -> 写入 vpnInterface
}
}
1.2 系统层操作流程
┌─────────────────────────────────────────────────────────┐
│ VPN 应用 │
├─────────────────────────────────────────────────────────┤
│ 1. VpnService.prepare() - 检查授权 │
│ 2. Builder.establish() - 建立 VPN │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ ConnectivityService / Vpn.java │
├─────────────────────────────────────────────────────────┤
│ 3. prepare() - 验证权限和包名 │
│ 4. establish() - 建立 VPN 接口 │
└─────────────────────────────────────────────────────────┘
1.3 Vpn.establish() 详细步骤
public synchronized ParcelFileDescriptor establish(VpnConfig config) {
// Check if the caller is already prepared.
if (Binder.getCallingUid() != mOwnerUID) {
return null;
}
// Check to ensure consent hasn't been revoked since we were prepared.
if (!isVpnServicePreConsented(mContext, mPackage)) {
return null;
}
// Check if the service is properly declared.
Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
intent.setClassName(mPackage, config.user);
final long token = Binder.clearCallingIdentity();
try {
// Restricted users are not allowed to create VPNs, they are tied to Owner
enforceNotRestrictedUser();
final PackageManager packageManager = mUserIdContext.getPackageManager();
if (packageManager == null) {
throw new IllegalStateException("Cannot get PackageManager.");
}
final ResolveInfo info = packageManager.resolveService(intent, 0 /* flags */);
if (info == null) {
throw new SecurityException("Cannot find " + config.user);
}
if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(token);
}
// Save the old config in case we need to go back.
VpnConfig oldConfig = mConfig;
String oldInterface = mInterface;
Connection oldConnection = mConnection;
NetworkAgent oldNetworkAgent = mNetworkAgent;
Set<Range<Integer>> oldUsers = mNetworkCapabilities.getUids();
// Configure the interface. Abort if any of these steps fails.
final ParcelFileDescriptor tun = mDeps.adoptFd(this, config.mtu);
try {
final String interfaze = mDeps.jniGetName(this, tun.getFd());
// TEMP use the old jni calls until there is support for netd address setting
StringBuilder builder = new StringBuilder();
for (LinkAddress address : config.addresses) {
builder.append(" ");
builder.append(address);
}
if (mDeps.jniSetAddresses(this, interfaze, builder.toString()) < 1) {
throw new IllegalArgumentException("At least one address must be specified");
}
Connection connection = new Connection();
if (!mContext.bindServiceAsUser(intent, connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUserId))) {
throw new IllegalStateException("Cannot bind " + config.user);
}
mConnection = connection;
mInterface = interfaze;
// Fill more values.
config.user = mPackage;
config.interfaze = mInterface;
config.startTime = SystemClock.elapsedRealtime();
mConfig = config;
// Set up forwarding and DNS rules.
// First attempt to do a seamless handover that only changes the interface name and
// parameters. If that fails, disconnect.
if (oldConfig != null
&& updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) {
// Update underlying networks if it is changed.
if (!Arrays.equals(oldConfig.underlyingNetworks, config.underlyingNetworks)) {
setUnderlyingNetworks(config.underlyingNetworks);
}
} else {
startNewNetworkAgent(oldNetworkAgent, "establish");
}
if (oldConnection != null) {
mContext.unbindService(oldConnection);
}
if (oldInterface != null && !oldInterface.equals(interfaze)) {
关键步骤:
- 权限检查: 验证调用者 UID 和授权状态
- 创建 TUN 设备:
adoptFd()创建虚拟网络接口 - 配置接口: 设置 IP 地址、路由、DNS
- 绑定服务:
bindServiceAsUser()绑定 VPN 服务 - 注册 NetworkAgent: 向 ConnectivityService 注册网络
- 设置路由规则: 通过 netd 配置 UID 路由
二、Always-On VPN 开启步骤
2.1 设置 Always-On VPN
// 系统设置或管理员操作
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
// 设置 Always-On VPN
boolean success = cm.setAlwaysOnVpnPackageForUser(
userId,
"com.example.vpn", // VPN 包名
true, // lockdown mode (阻止非 VPN 流量)
Arrays.asList("com.example.allowedapp") // 白名单应用
);
2.2 系统启动 Always-On VPN
public boolean startAlwaysOnVpn() {
final String alwaysOnPackage;
synchronized (this) {
alwaysOnPackage = getAlwaysOnPackage();
// Skip if there is no service to start.
if (alwaysOnPackage == null) {
return true;
}
// Remove always-on VPN if it's not supported.
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
setAlwaysOnPackage(null, false, null);
return false;
}
// Skip if the service is already established. This isn't bulletproof: it's not bound
// until after establish(), so if it's mid-setup onStartCommand will be sent twice,
// which may restart the connection.
if (getNetworkInfo().isConnected()) {
return true;
}
}
final long oldId = Binder.clearCallingIdentity();
try {
// Prefer VPN profiles, if any exist.
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage);
if (profile != null) {
startVpnProfilePrivileged(profile, alwaysOnPackage);
// If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
// correctly parsed, and the VPN has started running in a different thread. The only
// other possibility is that the above call threw an exception, which will be
// caught below, and returns false (clearing the always-on VPN). Once started, the
// Platform VPN cannot permanently fail, and is resilient to temporary failures. It
// will continue retrying until shut down by the user, or always-on is toggled off.
return true;
}
// Tell the OS that background services in this app need to be allowed for
// a short time, so we can bootstrap the VPN service.
DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN,
"vpn");
// Start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
serviceIntent.setPackage(alwaysOnPackage);
try {
return mUserIdContext.startService(serviceIntent) != null;
} catch (RuntimeException e) {
Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
}
} catch (Exception e) {
Log.e(TAG, "Error starting always-on VPN", e);
return false;
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
三、VPN 关闭步骤
3.1 应用层关闭
// VPN 应用代码
public class MyVpnService extends VpnService {
public void stopVpn() {
// 步骤1: 关闭 VPN 接口
if (vpnInterface != null) {
try {
vpnInterface.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close VPN interface", e);
}
}
// 步骤2: 停止服务
stopSelf();
}
}
3.2 系统层断开
/** Prepare the VPN for the given package. Does not perform permission checks. */
@GuardedBy("this")
private void prepareInternal(String newPackage) {
final long token = Binder.clearCallingIdentity();
try {
// Reset the interface.
if (mInterface != null) {
mStatusIntent = null;
agentDisconnect();
jniReset(mInterface);
mInterface = null;
private void agentDisconnect() {
updateState(DetailedState.DISCONNECTED, "agentDisconnect");
}
断开步骤:
- 注销 NetworkAgent:
networkAgent.unregister() - 重置网络接口:
jniReset(mInterface) - 解绑服务:
unbindService(mConnection) - 清除路由规则: 移除 UID 路由规则
- 更新状态: 设置为 DISCONNECTED
四、完整流程图
开启 VPN
┌─────────────────────────────────────────────────────────┐
│ 应用层 │
├─────────────────────────────────────────────────────────┤
│ 1. VpnService.prepare() - 请求授权 │
│ ↓ │
│ 2. 用户授权 (如果需要) │
│ ↓ │
│ 3. Builder.setSession() │
│ Builder.addAddress() │
│ Builder.addRoute() │
│ Builder.addDnsServer() │
│ ↓ │
│ 4. builder.establish() ← 返回 ParcelFileDescriptor │
└─────────────────────────────────────────────────────────┘
↓ Binder IPC
┌─────────────────────────────────────────────────────────┐
│ Vpn.java (System Server) │
├─────────────────────────────────────────────────────────┤
│ 5. prepare() - 验证权限 │
│ ↓ │
│ 6. establish(VpnConfig) │
│ ├─ 创建 TUN 设备 (adoptFd) │
│ ├─ 配置 IP 地址 (jniSetAddresses) │
│ ├─ 绑定 VPN 服务 (bindServiceAsUser) │
│ ├─ 创建 NetworkAgent │
│ └─ startNewNetworkAgent() │
│ ↓ │
│ 7. agentConnect() │
│ ├─ 注册 NetworkAgent │
│ └─ 设置 LinkProperties/NetworkCapabilities │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ ConnectivityService │
├─────────────────────────────────────────────────────────┤
│ 8. 注册 VPN 网络 │
│ ├─ 设置网络优先级 │
│ └─ 通知应用网络可用 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Netd │
├─────────────────────────────────────────────────────────┤
│ 9. 添加 UID 路由规则 │
│ ├─ networkAddUidRanges() │
│ ├─ ip rule add uidrange ... │
│ └─ 配置路由表 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Kernel Network Stack │
├─────────────────────────────────────────────────────────┤
│ 10. 应用流量路由到 VPN 接口 │
│ 根据 fwmark 和 UID 规则转发数据包 │
└─────────────────────────────────────────────────────────┘
关闭 VPN
┌─────────────────────────────────────────────────────────┐
│ 应用层 │
├─────────────────────────────────────────────────────────┤
│ 1. vpnInterface.close() │
│ ↓ │
│ 2. stopSelf() 或用户断开 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Vpn.java (System Server) │
├─────────────────────────────────────────────────────────┤
│ 3. agentDisconnect() │
│ ├─ networkAgent.unregister() │
│ ├─ updateState(DISCONNECTED) │
│ └─ jniReset(mInterface) │
│ ↓ │
│ 4. prepareInternal(null) 或 prepareInternal(LEGACY_VPN)│
│ ├─ 解绑服务 (unbindService) │
│ ├─ 清除配置 (mConfig = null) │
│ └─ 重置接口 (mInterface = null) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ ConnectivityService │
├─────────────────────────────────────────────────────────┤
│ 5. 注销 VPN 网络 │
│ └─ 通知应用网络断开 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Netd │
├─────────────────────────────────────────────────────────┤
│ 6. 移除 UID 路由规则 │
│ ├─ networkRemoveUidRanges() │
│ └─ ip rule del ... │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Kernel Network Stack │
├─────────────────────────────────────────────────────────┤
│ 7. 应用流量恢复使用默认网络 │
└─────────────────────────────────────────────────────────┘
关键组件总结
| 组件 | 职责 | 关键方法 |
|---|---|---|
| VpnService (应用) | VPN 应用基类 | prepare(), Builder.establish() |
| Vpn.java (系统) | VPN 管理核心 | prepare(), establish(), agentConnect(), agentDisconnect() |
| ConnectivityService | 网络管理服务 | 注册/注销 NetworkAgent |
| NetworkAgent | 网络代理 | 向系统报告网络状态 |
| Netd | 网络守护进程 | 配置路由规则和 iptables |
| TUN 设备 | 虚拟网络接口 | 转发用户空间和内核之间的数据包 |
VPN 的核心就是通过 TUN 设备 + UID 路由规则 实现应用流量的重定向!