开启关闭vpn的步骤

9 阅读5分钟

让我搜索 Android VPN 开启和关闭的相关代码:

现在让我获取 VPN 连接和断开的关键方法:

让我搜索更具体的内容:

完美!现在让我总结 Android VPN 的开启和关闭步骤:

Android VPN 开启和关闭的完整步骤

VPN 类型

Android 支持两种类型的 VPN:

  1. VpnService-based VPN (第三方 VPN 应用)
  2. 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)) {

关键步骤:

  1. 权限检查: 验证调用者 UID 和授权状态
  2. 创建 TUN 设备: adoptFd() 创建虚拟网络接口
  3. 配置接口: 设置 IP 地址、路由、DNS
  4. 绑定服务: bindServiceAsUser() 绑定 VPN 服务
  5. 注册 NetworkAgent: 向 ConnectivityService 注册网络
  6. 设置路由规则: 通过 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");
}

断开步骤:

  1. 注销 NetworkAgent: networkAgent.unregister()
  2. 重置网络接口: jniReset(mInterface)
  3. 解绑服务: unbindService(mConnection)
  4. 清除路由规则: 移除 UID 路由规则
  5. 更新状态: 设置为 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 路由规则 实现应用流量的重定向!