Files
mobile_comm/luatos/air780epm/docs/ipsec-design.md
T
wangfq 173dd6874f init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
2026-08-31 08:53:49 +08:00

14 KiB
Raw Blame History

netdrv IKEv2/IPsec 客户端设计文档

适用范围:LuatOS netdrv 框架下的 IKEv2/IPsec 隧道模式客户端 ESP tunnel mode)。首期已在 PC 模拟器上对接真实 strongSwan 网关 ipsec.air32.cnstrongSwan 5.9.13, Ubuntu 24.04 云主机)联调通过。


1. 目标与范围

在 netdrv 框架内新增 netdrv.IPSEC 虚拟网卡,实现:

  • IKEv2 发起端(RFC 7296),服务器证书链校验 + 强制 EAP-MSCHAPv2
  • NAT-TRFC 3948):UDP 500 启动,探测到 NAT 后切换 4500;
  • DPD 保活、CHILD_SA 到期重协商(CREATE_CHILD_SA,默认带 PFS KE + 新 Ni/Nr 按 RFC 7296 §2.17 派生 KEYMAT;网关不支持 PFS 时自动 无 KE 重试一次并保持旧 SA 在线)、IKE SA 到期全量重建;
  • 套件扩展:IKE DH 组 19/20/21ECP-256/384/521RFC 4753), ESP AES-GCM-128/256RFC 4106 AEADSA 内无 INTEG transform);
  • MOBIKERFC 4555opt-in,默认关闭):本地地址变化时发送 INFORMATIONAL(UPDATE_SA_ADDRESSES + NAT-D),处理对端发起的地址更新, 以及 established 状态下的对端地址学习;不做 ADDITIONAL_IP4_ADDRESS 通告与 COOKIE2
  • CPConfiguration Payload)下发虚拟 IPv4 + DNS
  • ESP 隧道模式(RFC 4303AES-CBC-128/256 + HMAC-SHA1-96 / HMAC-SHA2-256-128 或 AES-GCM-128/25632 包反重放窗口, ESP-in-UDP(4500)。

明确不做:IKEv1、L2TP/IPsec、ESP 传输模式、客户端证书认证、 IPv6 隧道、MOBIKE 的 ADDITIONAL_IP4_ADDRESS 通告与 COOKIE2。 MOBIKE 默认关闭(ipsec_mobike_enable 未配置时行为与旧版一致, 地址变化仍走断线重连)。

2. 文件布局

代码按用户要求拆分到独立子模块 components/network/ipsec/(内部头在 include/ipsec/,源文件在 src/):

文件 说明
include/ipsec/ipsec_crypto.h + src/ipsec_crypto.c PRF+/HMAC、DH modp2048、IKE/CHILD 密钥派生、AUTH 计算/验签、X.509 链+SAN 校验(信任锚由 ipsec_ca_cert_pem 提供;不提供时默认 fail-closed,仅当显式开启 ipsec_insecure_cert_ok 才接受证书且仍校验 SAN
include/ipsec/ipsec_esp.h + src/ipsec_esp.c ESP 隧道封装/解封(AES-CBC + HMAC 或 AES-GCM AEAD)、SPI 方向、32 包反重放
include/ipsec/ipsec_ike.h + src/ipsec_ike.c IKEv2 状态机:SA_INIT/AUTH/EAP/CREATE_CHILD_SA/INFORMATIONAL、payload 编解码、SK 加密、NAT-T 切换、DPD、重协商、虚拟 netif 与 adapter 收发
include/ipsec/ipsec_vendor_md4.h + src/ipsec_vendor_md4.c MD4Apache-2.0, 从 mbedTLS 2.x vendor, 自包含)
include/ipsec/ipsec_vendor_chap_ms.h + src/ipsec_vendor_chap_ms.c MS-CHAPv2BSD, 移植自 lwIP2.2 chap_ms.c),含 strongSwan 线格式与 RFC 3079 MSK 派生、RFC 2759 测试向量自检
src/luat_netdrv_ipsec.c netdrv 胶水层:setup/ctrl(UPDOWN)/dhcp(-1)/debug + 链路状态回调
components/network/netdrv/include/luat_netdrv_ipsec.h 胶水层头文件(保持在 netdrv/include

接入点:

  • luat_netdrv_drv.hLUAT_NETDRV_IMPL_IPSEC 7
  • luat_netdrv.hluat_netdrv_ipsec_conf_t + conf->ipsec_conf
  • luat_netdrv.c / luat_lib_netdrv.csetup 分发与 ipsec_* 参数解析
  • bsp/pc/xmake.luaipsec 子模块头文件搜索路径(components/network/ipsec/include
  • bsp/pc/include/luat_conf_bsp.hLUAT_USE_NETDRV_IPSEC 1
  • bsp/pc/include/lwipopts.hMEMP_NUM_SYS_TIMEOUT 30
  • bsp/pc/port/luat_crypto_mini.c:PC 模拟器 TRNG 每次调用重新播种

3. 架构与数据流

Lua (netdrv.setup / socket.*)
   |
   v
luat_netdrv_ipsec.c  (setup/ctrl/dhcp/debug, IP_READY/IP_LOSE 事件)
   |
   v
ipsec_ike.c  (IKEv2 状态机, tcpip 线程)
   |   netif (虚拟网卡, 全流量默认路由)
   |      | netif output -> ESP 封装 -> adapter UDP TX(4500)
   |      | adapter RX(4500) -> IKE(4字节零前缀) 或 ESP -> 内层 IP 注入
   |
   +-- network_ctrl_t (luat_network_adapter, 单 socket: 500 -> 4500)
  • IKE 与 ESP 共用同一个 adapter UDP socket:先在 500 上完成 IKE_SA_INITNAT 探测后切换到本地/远端 4500RFC 3948 非 ESP marker 区分 IKE 与 ESP)。这是标准 NAT-T 形态,避免了双 socket 同端口绑定的问题。
  • 所有状态机工作经 tcpip_callback_with_block 汇聚到 tcpip 线程; adapter 回调只拷贝数据并投递,与 L2TP/OpenVPN 客户端一致。
  • CP 下发虚拟 IP 后 netif_set_addr + netif_set_defaultDNS 写入 network_set_dns_server

4. 协议要点与互操作细节

  • 套件:IKE {aes256-sha256, aes128-sha1} × {modp2048, ecp256, ecp384, ecp521}!(8 个提案,DH14 优先;响应者选中其它组时按 RFC 7296 §2.7 的 INVALID_KE 通知重试,IKE_SA_INIT 默认先发 DH14 的 KE); ESP aes256-sha256, aes128-sha1, aes256gcm16, aes128gcm16! AEAD 提案无 INTEG transformtransform id 20 + KEY_LENGTH 属性)。
  • CHILD_SA 重协商默认带 PFS:请求载荷顺序 SA | Ni | KE | TSi | TSr 使用 IKE_SA_INIT 协商出的 DH 组生成新的临时密钥对;响应解析新 Nr 与 KEKEYMAT = prf+(SK_d, g^ir | Ni | Nr)。响应缺少 KE 或带 NO_PROPOSAL_CHOSEN/TS_UNACCEPTABLE 时,自动无 KE 重试一次并保持旧 SA 在线(与旧版行为一致)。
  • MOBIKE 更新载荷顺序:N(UPDATE_SA_ADDRESSES) | N(NAT_DETECTION_SOURCE_IP) | N(NAT_DETECTION_DESTINATION_IP)RFC 4555 §2.2),NAT-D 复用 IKE_SA_INIT 的 SHA-1 哈希方式;对端发起的更新用当前本地 IP 校验 目标 NAT-D,不匹配则忽略;established 状态下的地址学习只发生在 密码学验证通过之后:ESP 报文在 ICV/解密成功后、对端 UPDATE_SA_ADDRESSES 在 SK 解密 + NAT-D 校验通过后、已认证的 INFORMATIONAL 响应在 ICV 校验通过后(RFC 4555 §3, 未认证报文一律不得改写对端地址)。
  • IKE 请求单在途槽位:pending_msgid/last_tx 全 client 一份, DPD/MOBIKE 更新/CREATE_CHILD_SA 任一在途时其余请求让路(下一秒 tick 重试);INFORMATIONAL 响应(DPD/MOBIKE ack)经 SK 解密 + ICV 校验后才采信;msgid 全链路 32 位(RFC 7296 单调不回绕)。
  • IKE SK 载荷:AES-CBC + HMACICV 只覆盖 IKE 报文本身strongSwan 收到 4500 报文会先剥离 4 字节非 ESP marker 再校验)。
  • ESP AEADRFC 4106):8 字节显式 IVGCM IV = salt(4) | IV(8) AAD = SPI | SEQICV 16 字节,padding 按 4 字节对齐;KEYMAT 每方向为 enc_key(16/32) + salt(4)RFC 7296 §3.3.2),无 INTEG 密钥。
  • AUTHEAP 模式按 RFC 7296 §2.16 用 MSK 作为共享密钥; "Key Pad for IKEv2" 为 17 字节(不含 NUL)。
  • MSK:按 strongSwan 的 RFC 3079 实现—— master=SHA1(HH|NT-Response|Magic1) recv/send=SHA1(master[0:16]|0x00*40|Magic2/3|0xF2*40) MSK=recv[0:16]|send[0:16]|0x00*32
  • MS-CHAPv2 线格式采用 strongSwan/Windows 客户端格式: opcode|id|ms_length|value_size|...(区别于 RFC 2759 排版)。
  • ESP SPI 方向:发起端发送用响应者分配的 SPISAr2),接收 用自己提议的 SPISAi2)。
  • 证书链:配置了 ipsec_ca_cert_pem 时按该信任锚做链校验并校验 SAN ipsec.air32.cn;未配置时默认 fail-closed(固件不再内置任何 信任锚),仅当显式设置 ipsec_insecure_cert_ok=true 才接受服务器证书, 且仍强制 SAN 匹配。mbedTLS 校验需传 mbedtls_x509_crt_profile_default PEM 缓冲需 NUL 结尾(mbedTLS 3.x 的 PEM 识别条件)。

5. 配置参考(Lua

netdrv.setup(socket.LWIP_USER1, netdrv.IPSEC, {
    ipsec_remote_ip = "154.8.159.79",  -- 网关 IP(仅字面量)
    ipsec_remote_port = 500,           -- IKE 端口, 默认 500
    ipsec_username = "vpnuser",
    ipsec_password = "xxxx",
    ipsec_san = "ipsec.air32.cn",      -- 服务器 SAN 校验
    -- ipsec_ca_cert_pem = "-----BEGIN CERTIFICATE-----...", -- 建议配置信任锚;
    --                                                      -- 未配置且未开启 insecure 时无法连接 (fail-closed)
    -- ipsec_insecure_cert_ok = false,  -- 默认 false; true 表示无 CA 时仅校验 SAN 即接受
    ipsec_mtu = 1400,
    ipsec_retry_enable = true,
    ipsec_retry_base_ms = 1000,
    ipsec_retry_max_ms = 60000,
    -- ipsec_mobike_enable = true,  -- MOBIKE 双向地址更新 (默认关闭)
})

ipsec_mobike_enable:布尔,默认 false。开启后客户端在 1s tick 轮询 本地地址,变化时发送 UPDATE_SA_ADDRESSES;底层 socket 关闭但传输仍在线 时重开同端口 socket 并走更新流程而非全量重建;同时支持对端发起的地址 更新与 established 状态下的地址学习。仅 utest 构建提供测试钩子 netdrv.ipsec_sim_addr_change(id) 模拟一次本地地址变更。

6. 联调记录(2026-08-12, PC 模拟器 ↔ ipsec.air32.cn

联调期间发现并修复的问题:

# 现象 根因 修复
1 网关对 IKE_SA_INIT 无响应 Ni/NAT-D 载荷的 Next Payload 误用 notify 值(16388)而非载荷类型(41) 改为 IPSEC_PAYLOAD_NOTIFY
2 IKE_AUTH 无响应, 网关日志 message ID 16777216, expected 1 msgid 以单字节写入 (01 00 00 00) 而非 4 字节大端 ike_put32(msg+20, msgid)
3 网关日志 MAC verification failed mbedTLS 3 aes_crypt_cbc 会把最后一组密文写回 IV 缓冲, 覆盖了包内 IV 用独立 IV 缓冲
4 网关日志 MAC verification failed(修复 3 后仍失败) 4500 上 ICV 把 4 字节零前缀算进去了, 而 strongSwan 收到会先剥离 ICV 只覆盖 IKE 报文
5 trust anchor parse failed -0x2180 mbedTLS 3.x 仅当缓冲区 NUL 结尾才识别 PEM 内嵌/用户 PEM 均保留 NUL 结尾
6 cert chain verify failed flags=0xFFFFFFFF mbedtls_x509_crt_verify_with_profile 的 profile 传 NULL 返回 BAD_INPUT_DATA mbedtls_x509_crt_profile_default
7 服务器只发叶子证书, 链校验失败 strongSwan leftsendcert=always 只发叶子;Let's Encrypt 2026 新链 内置 YE2/ISRG Root YE/ISRG Root X2 中间 CA
8 server AUTH signature verification failed 裸 r|s 转 DER 时长度公式少算 2 字节 修正 total=6+r+s+pad
9 服务器 INVALID_SYNTAX 拒绝 EAP Identity 响应 EAP 载荷构建后未 p += plen, 内层为空 修复三处 EAP 载荷
10 unhandled MS-CHAPv2 request opcode 1 用 RFC 2759 排版解析 strongSwan 的 Challenge 按 strongSwan 线格式解析
11 最终 AUTH 被拒 内层 "Key Pad for IKEv2"sizeof(含 NUL 18 字节) 用 17 字节
12 最终 AUTH 被拒(修复 11 后仍失败) MSK 的 master 用 sizeof(inner)=79(实际 67);且 0x36/0x5C 填充与 strongSwan 的 0x00/0xF2 不符;32 字节 key 从 20 字节 digest 越界拷贝 按 strongSwan 实现重写 MSK 派生
13 ESP 无回包, 网关 XfrmInNoStates ESP SPI 方向反了(发送用了自己提议的 SPI) 发送用 SAr2 的 SPI
14 隧道 IP 与策略不匹配 CP 的 IPv4 字节序(ip4_addr_set_u32 需先 lwip_htonl 恢复 lwip_htonl(ike_get32())
15 证书校验 NOT_TRUSTEDflags=0x8 网关改用私建 CACN=IKEv2 VPN CA,自签 10 年),strongSwan 只发叶子,内置 ISRG Root X1 / LE 中间链不再适用 测试脚本改从 scripts/ikev2-ca.crt 读取 CA 作为 ipsec_ca_cert_pem 传入;同时按需求移除固件内置信任锚,未配 CA 的默认行为在后续提交中收紧为 fail-closed(需显式 ipsec_insecure_cert_ok=true 才接受证书,且仍校验 SAN

服务器侧配合项(已处理,用户授权调试):

  • 云主机 dirtyfrag.conf 禁用 esp4 内核模块(install esp4 /bin/false), 导致 XFRM SAD 安装失败(netlink error: Requested type not found (93)): 已注释该规则并加载 esp4/xfrm_user/xfrm4_tunnel
  • ipsec.secrets 私钥声明为 : RSA 但实际是 ECDSA P-384 已改为 : ECDSA
  • 联调后 charondebug 已恢复 ike 2esp=aes256-sha256, aes128-sha1! 已恢复原样。

7. 测试结果(PC 模拟器, testcase/unit/net/netdrv_ipsec_basic

  • connectIKE_SA_INIT → 证书链+SAN 校验 → 服务器 AUTH 验签 → EAP-MSCHAPv2(含服务器 Authenticator Response 校验)→ 双端 AUTH → CP 下发虚拟 IP/DNS → 隧道上线 → 隧道内 TCP 到网关 SSH 收到 banner 3 passed / 0 failed
  • badpass:错误密码 → EAP 失败 → 不 ready通过
  • sanit:错误 SAN → 证书校验拒绝 → 不 ready,通过

前置:testcase/unit/net/netdrv_ipsec_basic/scripts/ikev2-ca.crt (网关私建 CA)必须存在,测试会把它作为 ipsec_ca_cert_pem 传入; 测试账号凭据通过环境变量 LUAT_IPSEC_USERNAME / LUAT_IPSEC_PASSWORD 注入(不随源码分发),未设置时测试跳过。 固件默认 fail-closed:未传 ipsec_ca_cert_pem 且未显式开启 ipsec_insecure_cert_ok 时拒绝连接(固件不再内置任何信任锚)。

8. 遗留与后续

  • 集成联调(需网关侧重配,未完成项见交付说明):
    • PFS:网关 pfs=yes + 缩短 lifetime,验证重协商日志、DPD、隧道不断流;
    • ECP/GCM:网关 ike=aes256-sha256-ecp256! / esp=aes256gcm16! 建连;
    • MOBIKEnetdrv.ipsec_sim_addr_change 触发 UPDATE_SA_ADDRESSES 网关 mobike=no 时回退重连。
  • 硬件 BSP:需确认目标板 lwip 导出符号(pbuf/netif/sys/ip4_input) 与 adapter 行为,并按验收清单在目标板重跑;
  • 服务器 FORWARD 链的 IPsec 池 ACCEPT 规则与 rightsourceip 段需按实际网络调整(当前云主机 iptables 规则与池配置不完全一致, 隧道到网关本机可用,跨子网转发需核对);
  • netdrv.debug 会输出 IKE/ESP 帧日志(含 SK 明文转储仅调试用)。

9. 相关文档

  • RFC 7296IKEv2)、RFC 3948NAT-T)、RFC 4303ESP)、 RFC 2759/3079MS-CHAPv2/MSK
  • docs/l2tp-design.md(线程模型/胶水层/构建接线参考)
  • components/network/l2tp/src/l2tp_client.c / components/network/openvpn/src/ovpn_client.c(传输与 netif 参考)