vpn_sdp 2.0.9 copy "vpn_sdp: ^2.0.9" to clipboard
vpn_sdp: ^2.0.9 copied to clipboard

SDP 版本 VPN 接入 Flutter 插件,基于零信任 SDP 安全隧道 SDK 封装,提供 iOS/Android 双平台 VPN 连接管理能力。

vpn_sdp #

SDP 版本 VPN 接入 Flutter 插件,基于零信任 SDP 安全隧道 SDK 封装,提供 iOS/Android 双平台 VPN 连接管理能力。

如果此文档显示不正常,请点击《VPN_SDP Flutter插件文档》跳转至语雀查看

一、使用前准备 #

注:在使用插件之前,需要大概过一下SDP厂商提供的接入文档,熟悉VPN的基本原理和使用流程,此插件依据厂商提供的《 零信任SDP移动终端(SDK)安全隧道接入方案V1.0 》文档和Demo基础上编写而成

1.1. iOS 接入流程 #

1.1.1 插件引入(自动集成)

在 Flutter 主工程的 pubspec.yaml 中添加依赖:

dependencies:
  vpn_sdp: ^2.0.9

执行 flutter pub get 后,iOS 端通过 CocoaPods 自动集成。插件的 vpn_sdp.podspec 已声明以下 vendored_frameworks(共 18 个),pod install 时自动引入主 Target:

框架 说明
AiSDPsdk.framework SDP 厂商核心 SDK(Swift),提供 VPN 连接/认证/透传能力
Alamofire.framework Swift 网络库(SDK 内部依赖)
NIO.framework SwiftNIO 网络框架(SDK 内部依赖)
NIOCore / NIOPosix / NIOHTTP1 / NIOEmbedded / NIOTLS / NIOSSL SwiftNIO 子模块
NIOConcurrencyHelpers / _NIODataStructures SwiftNIO 并发与数据结构
CNIOAtomics / CNIODarwin / CNIOHTTPParser / CNIOLinux / CNIOWindows SwiftNIO C 层桥接
CNIOBoringSSL / CNIOBoringSSLShims BoringSSL 加密库桥接

同时 podspec 还声明了 资源文件 AiSDPsdkBundle.bundle(内含 SDP 隧道证书:client.crtclient.keycontrollerca.crtgatewayca.crt),也会随 Pod 自动拷贝到主 Target。

注意:以上框架和资源由 CocoaPods 自动管理,无需手动拖拽文件到 Xcode

1.1.1.1 CocoaPods 1.17.0 兼容性修复(重要)

已知问题:CocoaPods 1.17.0 存在缺陷——vendored_frameworks 的文件引用虽已创建,但未自动加入 pod target 的链接阶段,导致 pod install 后编译时出现 Undefined symbol: _OBJC_CLASS_$__TtC8AiSDPsdk11MacInfoTool 等符号未定义错误。

修复方式:在主工程 ios/Podfile 末尾的 post_install 中添加 hook,确保每次 pod installAiSDPsdk.framework 被正确链接:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
  end

  # 修复 CocoaPods 1.17.0 vendored_frameworks 链接缺失
  vpn_target = installer.pods_project.targets.find { |t| t.name == 'vpn_sdp' }
  if vpn_target
    ai_ref = installer.pods_project.files.find { |f| f.path&.end_with?('AiSDPsdk.framework') }
    if ai_ref
      link_phase = vpn_target.frameworks_build_phase
      unless link_phase.files.any? { |bf| bf.file_ref == ai_ref }
        link_phase.add_file_reference(ai_ref)
      end
    end
  end
end

适用范围:CocoaPods 1.17.x 版本。如果后续 CocoaPods 修复此缺陷或升级到其他版本后问题消失,可移除此 hook。

1.1.2 Swift 编译器版本兼容(重要)

已知问题AiSDPsdk.framework 以预编译二进制形式提供,.swiftmodule 为二进制格式,严格绑定编译时的 Swift 版本。当 Xcode 升级后,若新的 Swift 编译器版本与 SDK 编译时的版本不兼容,Tun 扩展(Network Extension)在 import AiSDPsdk 时会出现 Module compiled with Swift X cannot be imported by the Swift Y compiler 错误。

修复方式:在 SDK framework 的模块目录中提供 .swiftinterface 文本接口文件,使编译器在加载 .swiftmodule 失败时自动回退读取。详见 IOS_SDK_INTEGRATION_GUIDE.md 问题 3。

长期建议:联系 SDK 提供方在编译时加入 BUILD_LIBRARY_FOR_DISTRIBUTION=YES,以自动生成 .swiftinterface,彻底消除 Swift 版本兼容问题。

1.1.3 手动配置步骤(Xcode 操作)

以下步骤无法通过 CocoaPods 自动化,必须在 Xcode 中手动完成:

(1)创建 Network Extension Target
  • Xcode 打开 ios/Runner.xcworkspace(注意是 .xcworkspace,不是 .xcodeproj
  • 左侧目录选中 Runner 项目 → 点击 TARGETS 区域底部 "+" 按钮
  • 搜索并选择 "Network Extension" 模板
  • Product Name 建议命名为 Tun(与后续 providerBundleId 对应)
  • 编程语言选择 Swift(即使是 OC 项目也选择 Swift)
  • 按向导完成创建
(2)配置 Capabilities

主 Target(Runner)扩展 Target(Tun) 都需要配置:

  • 在 Signing & Capabilities 选项卡中,点击 "+ Capability"
  • 添加 Network Extensions,勾选 Packet Tunnel
  • 添加 Personal VPN
(3)配置 entitlements

确保扩展 Target 的 .entitlements 文件包含:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.developer.networking.networkextension</key>
    <array>
        <string>packet-tunnel-provider</string>
    </array>
    <key>com.apple.developer.networking.vpn.api</key>
    <array>
        <string>allow-vpn</string>
    </array>
    <key>com.apple.security.application-groups</key>
    <array>
        <string>group.com.yourcompany.yourapp</string>
    </array>
</dict>
</plist>
(4)配置扩展 Target 的 Framework 依赖

扩展 Target(Tun)不受 CocoaPods 管理,需要手动添加 AiSDPsdk.framework

  • 在扩展 Target(Tun)→ General → Frameworks, Libraries, and Embedded Content
  • 点击 "+" 添加 AiSDPsdk.framework(从主工程的 Pods 中选择)
  • 将 Embed 设置为 "Do Not Embed"
  • 如果编译提示缺少其他 framework(Alamofire、NIO 等),同样方式添加并设置为 "Do Not Embed"
(5)编译选项配置
  • 主 Target(Runner)→ Build Settings → Build Options → Enable Bitcode 设置为 NO
(6)编写 PacketTunnelProvider

打开扩展 Target 的 PacketTunnelProvider.swift,替换为:

import NetworkExtension
import AiSDPsdk

class PacketTunnelProvider: PacketTunnelProviderSDK {}

Info.plist 验证:确保扩展 Target 的 Info.plistNSExtensionPointIdentifiercom.apple.networkextension.packet-tunnelNSExtensionPrincipalClass$(PRODUCT_MODULE_NAME).PacketTunnelProvider

1.1.4 iOS 集成架构总览

┌─────────────────────────────────────────────────────┐
│  Flutter App (pubspec.yaml)                         │
│  dependencies:                                      │
│    vpn_sdp: ^2.0.9                                  │
└──────────────────┬──────────────────────────────────┘
                   │ flutter pub get
                   ▼
┌─────────────────────────────────────────────────────┐
│  CocoaPods (Podfile)                                │
│  pod 'vpn_sdp'  ←── vpn_sdp.podspec                 │
│    ├─ vendored_frameworks (18个 .framework)          │
│    │   ├─ AiSDPsdk.framework (核心SDK)              │
│    │   ├─ Alamofire.framework                       │
│    │   └─ SwiftNIO 系列 (16个)                       │
│    └─ resources                                     │
│        └─ AiSDPsdkBundle.bundle (隧道证书)           │
└──────────────────┬──────────────────────────────────┘
                   │ pod install (自动,无需手动拖拽)
                   ▼
┌─────────────────────────────────────────────────────┐
│  Xcode 手动配置 (不可自动化)                          │
│  1. 创建 Network Extension Target (如 Tun)           │
│  2. 主Target + 扩展Target 添加 Capabilities:         │
│     - Network Extensions (Packet Tunnel)            │
│     - Personal VPN                                  │
│  3. 扩展Target 手动添加 AiSDPsdk.framework            │
│     Embed: Do Not Embed                             │
│  4. Enable Bitcode → NO                             │
│  5. PacketTunnelProvider 继承 PacketTunnelProviderSDK│
└─────────────────────────────────────────────────────┘

1.2. Android 接入 #

在应用启动之初,需要请求android.permission.BLUETOOTH_CONNECT权限,否则所有关于vpn连接的请求都将出现如下异常:

D/UDP     ( 6317): 认证通道创建异常: Need android.permission.BLUETOOTH_CONNECT permission for AttributionSource { uid = 10421, packageName = com.gzdict.vpn_sdp_example, attributionTag = null, token = android.os.BinderProxy@37f742a, next = null }: getName
[   +2 ms] W/System.err( 6317): java.lang.SecurityException: Need android.permission.BLUETOOTH_CONNECT permission for AttributionSource { uid = 10421, packageName = com.gzdict.vpn_sdp_example, attributionTag = null, token = android.os.BinderProxy@37f742a, next = null }: getName
[        ] W/System.err( 6317): 	at android.os.Parcel.createExceptionOrNull(Parcel.java:2438)
[   +2 ms] W/System.err( 6317): 	at android.os.Parcel.createException(Parcel.java:2422)
[        ] W/System.err( 6317): 	at android.os.Parcel.readException(Parcel.java:2405)
[        ] W/System.err( 6317): 	at android.os.Parcel.readException(Parcel.java:2347)
[        ] W/System.err( 6317): 	at android.bluetooth.IBluetoothManager$Stub$Proxy.getName(IBluetoothManager.java:1194)
[        ] W/System.err( 6317): 	at android.bluetooth.BluetoothAdapter.getName(BluetoothAdapter.java:2345)
[        ] W/System.err( 6317): 	at com.aisec.sdp.util.InterfaceV2Method.verifyV2(InterfaceV2Method.java:61)
[        ] W/System.err( 6317): 	at com.aisec.sdp.thread.UdpAuthV2Thread.run(UdpAuthV2Thread.java:111)

关于权限请求,建议使用permission_handler插件处理,例如:

  void _requestPermission() async {
    if (!Platform.isAndroid) {
      return;
    }
    var result = await Permission.bluetoothConnect.request();
    if (!result.isGranted) {
      /// 无权限,不可操作
    }
  }

接入插件,第一次调用连接方法成功后,会出现如下弹窗;点击确认后,状态栏会出现小钥匙的图标,此时就代表接入成功

1.3. Android 依赖说明(compileOnly) #

本插件的 Android 端依赖(SDP 厂商 SDK aar、fastjson、dnsjava、bcpkix-jdk15to18、sunjce_provider.jar)均以 compileOnly 方式引入,不会随插件打包进宿主工程。宿主 App 需要自行集成这些依赖,否则运行时会抛出 NoClassDefFoundError / ClassNotFoundException 异常。

插件仓库中已不再附带 android/libs 目录(原包含 sdp-release-3.8.7.131_20240725.aarsunjce_provider.jar),且插件 android/build.gradle不声明任何本地文件依赖,以上依赖必须由宿主工程完整提供,否则宿主工程编译时会因缺少文件导致构建失败(如 JetifyTransform 报错)。如需本地开发,请向 SDP 厂商获取对应的 aar 放入宿主工程的 libs 目录,并在宿主工程的 build.gradle 中配置 flatDir 仓库及相应依赖(参考以下示例):

repositories {
    flatDir { dirs 'libs' }
}

dependencies {
    implementation(name: 'sdp-release-3.8.7.131_20240725', ext: 'aar')
    implementation 'com.alibaba:fastjson:1.2.70'
    implementation 'dnsjava:dnsjava:2.1.7'
    implementation 'org.bouncycastle:bcpkix-jdk15to18:1.68'
    implementation files('libs/sunjce_provider.jar')
}

二、接口说明 #

2.1. verifyV3 #

特别注意:

  1. 在Android端,此方法会同[proxyApiV2]以及[reconnect]方法互斥
  2. 在iOS端,此方法会同[reconnect]方法互斥

2.1.1. VPN三方认证登录,因为SDP需要知晓是否认证成功,所以需要外围系统对返回格式进行改造:

参数 说明
code 【0:成功 ;1:失败; 2:需要下一步认证】
desc 描述
nextcmd 二次认证方式:sms email otp
userName 账号
data 接口需要返回给APP的数据,SDP会做透传

2.1.2. 参数如下:

114.135.115.224
55840
reqUrl 第三方的认证请求URL
method http调用方式,传递post、get post
header 使用json格式
reqType
请求方式:【1,2】,含义如下
1:健值对格式
2:body方式
2

当传输方式选择1:使用json格式传递健值对
当传输方式选择2:使用字符串,SDP会透传
function string
timeout Duration 防止插件异常导致结果无返回,设置一个超时时间 15秒

2.2. proxyApiV2 #

接口透传,返回透传结果,透传返回值亦可通过[messageStream]监听,code为[MethodCode.proxyApi]

  • 在Android端,此方法会同[verifyV3]以及[reconnect]方法互斥

2.2.1. 对第三方接口进行透传,

参数说明:

reqUrl 第三方的数据请求URL
method http调用方式,传递post、get
header 使用json格式
reqType
请求方式:【1,2】,含义如下
1:健值对格式
2:body方式

当传输方式选择1:使用json格式传递健值对
当传输方式选择2:使用字符串,SDP会透传

#

2.3. reconnect vpn重连,无参数

  • 在Android端,此方法会同[verifyV3]以及[proxyApiV2]方法互斥
  • 在iOS端,此方法会同[verifyV3]方法互斥

2.4. disconnect #

断开vpn连接,之后[messageStream]会接收到[RltCode.disconnect]消息。

2.5. messageStream #

监听消息,返回[SdpResult],其中code和data字段如下:

{
  "code":0, #状态码
  "data": {} #返回值,可能是json格式,也可能是字符串
}

2.6. setUp #

初始化参数,仅iOS有效

变量名 类型 描述 默认值
sn String 设备序列号 000000000000
vpnLocalizedDesc String vpn描述,用于在系统设置Vpn列表中显示 VPN_SDP
mac String 设备mac 00:00:00:00:00:00
providerBundleId String NetworkExtension的bundleId,例如com.example.vpn.tun

三、示例 #

3.1.1. 初始化

# 依赖vpn_sdp
vpn_sdp: ^2.0.9

// 实例化vpn插件
final _vpnSdpPlugin = VpnSdp();

/// 如果有iOS,必须先调用此方法
_vpnSdpPlugin.setUp(
  providerBundleId: "com.gzdict.gzyqdev.Tun",
);

/// Android端请求权限
if (!Platform.isAndroid) {
      return;
    }
    var result = await Permission.bluetoothConnect.request();
    if (!result.isGranted) {
      /// 无权限,不可操作
    }

3.2.2. 连接

var url = "${netDio.options.baseUrl}/km/sso/sdpLogin";
var msgCode = await _msgCode.rsaEncode();
var password = await _password.rsaEncode();
var body = {
  "username": _username,
  "password": password,
  "captchaCode": msgCode,
};
// 在此调用verifyV3开启vpn
var result = await _vpnSdpPlugin
  .verifyV3(username: _username, reqUrl: url, body: body, func: "yxt")
  .toDialogRequest()
  .start();
if (result != null) {
  _transformData(result);
}

3.2.3. 断开连接

// 手动断开连接,一般在退出登录时调用
await _vpnSdpPlugin.disconnect();

3.2.4. 重连

手动重连,SDK内设置了主动重连功能,一般无需调用,仅在某些特殊情况提供此功能
await _vpnSdpPlugin.reconnect()

3.2.5. 透传

var url = "${netDio.options.baseUrl}/km/sso/sdpVerify";
var password = await _password.rsaEncode();
var data = {
  "username": _username,
  "password": password,
  "captchaType": 0,
};
var result = await _vpnSdpPlugin
  .proxyApiV2(
  reqUrl: url,
  body: data,
)
  .toDialogRequest()
  .start();
if (result != null) {
  SmartDialog.showToast(result.toString());
}

3.2.6. 监听vpn返回的所有状态


StreamSubscription? _subscription;

.....
  
_subscription = _vpnSdpPlugin.messageStream.listen(
  (data) {
    setState(() {
      _resultStr = "$data";
    });
  },
  onError: (error) {
    setState(() {
      _resultStr = "$error";
    });
  },
);

四、错误码 #

以下错误均SDP提供,可能存在不完整情况,仅做参考;

4.1. Android 错误码 #

code 错误提示 报错原因 备注
1001 认证失败 认证接口返回的报错 第三方APPServer错误都在此处提示
1003 创建虚拟网卡失败 创建虚拟网卡失败 再认证成功后或者断链重新创建VPN失败情况会出现
1015 无法连接到服务器 连接SDP服务器超时 (网络不通环境下会报此错误)
1016 异常中断 SDP服务端交互异常
1019 服务器连接超时 连接SDP服务器超时 (网络不通环境下会报此错误)
1025 连接失败 SDP控制器断开连接 (网络不通环境下会报此错误)
1040 验证读取超时异常 无法连接到SDP服务器 (网络不通环境下会报此错误)
1045 认证成功,vpn通道已存在 重复创建VPN 如果手机已存在VPN也会报此错
9999 重新连接中...连接状态:true/false 正在进行重连 (10S检测一次,检测到连接失败就开始重连)
else 后端返回的错误 交互异常

4.2. iOS 错误码 #

调用SDK连接的时候,新增回调方法TunConnectedWithCode的回调处理,红色code码为常见错误

(原TunConnected回调也保留,只不过没有code )

code(后续补充) 错误提示 报错原因 备注
1001 获取控制器地址或端口为空 服务器地址或端口设置 参数未初始化,或者是服务端配置的控制器地址未空
1002
敲门包发送异常,请稍后重试! 网络不通,SPA敲门超时 (网络不通环境下会报此错误)
1003 连接超时 敲门包发送超时了
这种一般网络是正常的,但是SDP的敲门包发送出现问题,因为有些运营商会拦截SDP的敲门包;或者网络不好,敲门包发送超时了
1004 敲门超时 敲门包发送超时 (网络不通环境下会报此错误)
1005 连接关闭 SDK与服务端的连接断开了 一般是用户主动关闭连接,或者网络断开,服务被关闭的时候
2001 连接服务器失败 与SDP控制器连接失败 可能是控制器地址连接超时或sdp服务出现异常了
2002 创建虚拟网卡失败 网关连接超时 多次重连也会存在此异常,或者sdk的refreshkey过期了,sdk重连也会发生
2004 会话过期,请重新登录 refreshkey到期了
管理平台设置的会话到期时间一般是8小时,重连sdk会话已到期,需重新登录
2003 敲门绑定失败 敲门bind失败 网络异常了,建议切换网络或重启手机
3001 虚拟网卡启动配置错误 配置vpn出现异常 一般是系统出现异常了,建议重启手机
3002 vpn异常断开 VPN被其他app或人为关闭 app之间抢占vpn;或进入设置页手动关闭vpn
else 后端返回的错误 SDP服务端或者第三方认证服务错误

调用SDK发送透传请求,调用proxyApi/proxyApiV2回调

code 提示信息(Any对象) 报错原因 备注
1002 敲门包发送异常,请稍后重试! 敲门包发送超时了 这种一般网络是正常的,但是SDP的敲门包发送出现问题,因为有些运营商会拦截SDP的敲门包;或者网络不好,敲门包发送超时了
1004 敲门超时 敲门包发送超时 (网络不通环境下会报此错误)
else 后端返回的错误 SDP服务端或者第三方认证服务错误
0
likes
110
points
110
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

SDP 版本 VPN 接入 Flutter 插件,基于零信任 SDP 安全隧道 SDK 封装,提供 iOS/Android 双平台 VPN 连接管理能力。

Repository

License

MIT (license)

Dependencies

flutter, package_info, permission_handler, plugin_platform_interface

More

Packages that depend on vpn_sdp

Packages that implement vpn_sdp