Skip to content

DET44:不查表,算出来

前三章的所有插件都在做同一件事:查表。包进来,查会话表;查不到,新建一条。 这一章的 det44 反其道而行。

换一种思路

映射不是"分配"出来的,是出来的。给定内网地址,公网地址和端口区间是一个纯函数的输出; 反过来,给定公网地址和端口,也能算回内网地址。

这个性质带来一个运营商最看重的能力:可追溯性。下一篇专门讲它。

配置

应用配置

bash
./labctl reset base
./labctl apply base 09

三条命令:

bash
vppctl det44 plugin enable
vppctl det44 add in 10.10.0.0/24 out 203.0.113.100/32
vppctl set interface det44 inside host-eth1 outside host-eth2

命令形态和 nat44 有三处不同

text
  1. 给的是**前缀**,不是地址池
     nat44:  nat44 add address 203.0.113.100 - 203.0.113.110
     det44:  det44 add in <内网前缀> out <公网前缀>

  2. 内外侧关键字是 inside/outside,不是 in/out
     nat44:  set interface nat44 in  host-eth1 out     host-eth2
     det44:  set interface det44 inside host-eth1 outside host-eth2

  3. 没有"地址池"的概念,也就没有 twice-nat、静态映射、负载均衡这些东西

算出来的配额

bash
./labctl vppctl base show det44 mappings
text
NAT44 deterministic mappings:
 in 10.10.0.0/24 out 203.0.113.100/32
  outside address sharing ratio: 256
  number of ports per inside host: 252
  sessions number: 0

两个数字决定了一切:

text
  可用端口 64512(1024 - 65535)
  ÷ 共享比 256(一个 /24 有 256 台主机,对 1 个公网地址)
  = 每台主机 252 个端口

算法

端口块的起点是一个简单公式:

text
  端口起点 = 1024 + 主机序号 × 每主机端口数

亲自验证

bash
./labctl vppctl base det44 forward 10.10.0.11

实测与公式逐个吻合:

text
  内网地址       预期            实际
  ───────────   ─────────────   ─────────────────────────────
  10.10.0.0     <1024-1275>     203.0.113.100:<1024-1275>
  10.10.0.1     <1276-1527>     203.0.113.100:<1276-1527>
  10.10.0.11    <3796-4047>     203.0.113.100:<3796-4047>
  10.10.0.12    <4048-4299>     203.0.113.100:<4048-4299>
  10.10.0.255   <65284-65535>   203.0.113.100:<65284-65535>

10.10.0.11 的序号是 11,1024 + 11 × 252 = 3796。分毫不差。

多个公网地址时

地址也是算出来的:

text
  公网地址 = 公网前缀起点 + (主机序号 ÷ 共享比)
  端口起点 = 1024 + (主机序号 mod 共享比) × 每主机端口数

/24 内网配到 /28 公网(16 个地址,共享比 16):

text
  10.10.0.0    → 203.0.113.96:<1024-5055>
  10.10.0.15   → 203.0.113.96:<61504-65535>     ← 第 16 台,仍是 .96
  10.10.0.16   → 203.0.113.97:<1024-5055>       ← 第 17 台,换到 .97
  10.10.0.31   → 203.0.113.97:<61504-65535>
  10.10.0.32   → 203.0.113.98:<1024-5055>
  10.10.0.255  → 203.0.113.111:<61504-65535>

每 16 台主机换一个地址,端口区间在每个地址内重新从 1024 开始。

真实流量确实落在算出来的块里

text
  h1 的块:203.0.113.100:<3796-4047>
  h2 的块:203.0.113.100:<4048-4299>

  h1 实测端口:3995 3881 3813      ← 全在 3796-4047
  h2 实测端口:4127 4295           ← 全在 4048-4299

共享比就是全部的取舍

DET44 只有一个可调参数:共享比。它由内外前缀的大小之比决定。

text
  内网 /24(256 台)      公网          共享比    每主机端口   能撑的并发
  ────────────────────   ───────────   ───────   ─────────   ────────────
  10.10.0.0/24           /32  (1 个)      256        252      每台 ~2 个应用
  10.10.0.0/24           /30  (4 个)       64       1008      每台 ~10 个应用
  10.10.0.0/24           /28 (16 个)       16       4032      每台很宽裕
  10.10.0.0/24           /24 (256个)        1      64512      等于一人一地址

端口配额是硬的,用完就断

这是 DET44 和前面所有插件最本质的区别。

nat44-ed 里某台主机端口用完了,还能从地址池里另一个地址借。 DET44 不能借 —— 每台主机的端口块是算死的,用完就是用完, 新连接直接失败,哪怕隔壁主机的块空着一大半。

252 个端口听起来不少,但一个现代网页可能就开几十条连接。 按 252 算,一台设备同时开两三个页面就到顶了。

所以共享比是个必须认真算的账,不是随手填的。

怎么估共享比

text
  每主机端口数 = 64512 / 共享比
  需要的端口数 ≈ 用户峰值并发会话数 × 安全系数(建议 1.5~2)

  例:用户峰值 150 条并发会话,取系数 2 → 需要 300 端口
      64512 / 300 ≈ 215  →  共享比取 128(向下取 2 的幂)
      即:每 128 个用户配 1 个公网地址

比 nat44 的估算更严格 —— nat44 里"某个用户偶尔超标"能被池子吸收, DET44 里超标就是直接失败。

会话表还在,但角色变了

DET44 仍然有会话表:

bash
./labctl vppctl base show det44 sessions
text
NAT44 deterministic sessions:
  in 10.10.0.11:38 out 203.0.113.100:3834 external host 203.0.113.11:0
     state: icmp-active expire: 111

但它的角色完全不同了:

text
  nat44-ed/ei 的会话表      det44 的会话表
  ──────────────────────    ──────────────────────
  承担地址端口的分配决策       只记录活跃连接
  查不到就要新建(慢路径)     地址端口早就算好了
  表满 = 服务中断            表只影响回程匹配

超时配置是独立的一套:

bash
vppctl show det44 timeouts
vppctl set det44 timeouts udp 120 tcp established 3600 tcp transitory 120 icmp 30
vppctl set det44 timeouts reset

值和 nat44 默认一样(UDP 300 / TCP 已建立 7440 / TCP 中间态 240 / ICMP 60), 但是分开配的 —— set nat timeout 不影响 det44。

一个会让 VPP 崩溃的配置

内网前缀不能超过 /19

实测在 VPP 26.06 上,det44 add 的内网前缀一旦大到 /18(16384 台主机), VPP 会当场段错误崩溃。而且是执行命令的瞬间就崩,不需要有流量。

阶梯测试结果:

text
  内网前缀   主机数    每主机端口   结果
  ────────   ───────   ─────────   ──────────
  /24          256        252      OK
  /23          512        126      OK
  /22         1024         63      OK
  /20         4096         15      OK
  /19         8192          7      OK
  /18        16384          3      VPP 崩溃

崩溃栈显示问题出在添加映射时的内存分配,而不是转发路径:

text
#4  clib_mem_heap_alloc_aligned
#5  _vec_alloc_internal
#6  det44_out2in_node_fn_x86_64_v4     ← det44 插件
#8  vlib_cli_input                     ← 由 CLI 命令直接触发

不是端口数太少的问题。 我们验证过:/18 内网配 /28 公网 (每主机 63 个端口,和 /22 → /32 那次一样宽裕)同样崩溃。 所以驱动因素是主机数,det44 会为前缀内的每一台主机预分配状态。

实践建议:一条 det44 add 的内网前缀不要超过 /19。 需要覆盖更大的地址空间,就拆成多条映射:

bash
vppctl det44 add in 10.10.0.0/19  out 203.0.113.0/27
vppctl det44 add in 10.10.32.0/19 out 203.0.113.32/27

崩了怎么恢复

bash
./labctl down base && ./labctl up base

容器会以 Exited (134) 停下(134 = 128 + 6,SIGABRT), restart-policy: no 意味着不会自动重启。

更麻烦的一个坑:det44 会污染接口,且无法在线清理

做完 det44 实验必须重建容器,否则 nat44 全部失效

这个坑比上面那个崩溃更阴险,因为它不报错

DET44 在启用时会往接口的 feature arc 上注册 det44-in2out 处理节点。 问题是:

text
  det44 plugin disable                → 接口列表清空了,但 feature 节点还在
  set interface det44 ... del         → 不但没摘掉,计数反而 +1
  再 enable 再 disable                → 计数继续累积

实测过程:

bash
vppctl show interface features host-eth1 | grep -c det44-in2out
text
  初始                    0
  配好 det44 后            1
  执行 set interface ... del   2      ← 变多了
  det44 plugin disable         0      ← 有时能清掉,有时清不掉

残留的 det44-in2out抢在 nat44 前面拦包。后果是:

text
  ./labctl apply base 01      → 配置成功
  ./labctl vppctl base show nat44 interfaces
      NAT44 interfaces:
       host-eth1 in
       host-eth2 out           → 看起来完全正常

  实际流量                     → 全部不通

配置一切正常、计数器也没有异常,但流量就是不走。 第一次遇到时几乎不可能猜到是上一轮 det44 实验的残留。

没有在线清理的办法。 唯一的解法是重建容器:

bash
./labctl down base && ./labctl up base

更糟的是,在残留状态下继续操作 det44 和 nat44 的组合命令, 还可能直接把 VPP 打崩(崩溃栈同样指向 vlib_cli_input)。

本教程的实验脚本已经加了防护

reset.sh 每次都会检查这个残留,脏了就直接中止并告诉你怎么恢复:

text
✗ 接口上残留着 det44 的 feature 节点。

  这是 VPP 26.06 的一个 bug:det44 插件关闭后,它注册在接口上的
  det44-in2out 节点不会被摘掉,会继续抢在 nat44 前面拦包。
  症状是 nat44-ed 的实验全部不通,但 show 出来的配置一切正常。

  没有在线清理的办法,必须重建容器:

      ./down.sh && ./up.sh

第 09 步的 apply 和 verify 也都会提醒你这一点。

对生产环境的启示

不要指望在一台运行中的 VPP 上"试试 det44,不合适再换回 nat44"。 在 26.06 上,这个切换需要重启 VPP 进程

选型必须在上线前定好。真要评估,用一台独立设备或实验环境。

和 nat44 的对照

text
  ┌──────────────┬────────────────────────┬────────────────────────┐
  │              │ nat44-ed / nat44-ei    │ det44                  │
  ├──────────────┼────────────────────────┼────────────────────────┤
  │ 映射来源      │ 查表分配                │ 算法计算                │
  │ 端口配额      │ 弹性,可从池里借         │ 固定,用完就断           │
  │ 可追溯性      │ 靠日志(TB 级存储)      │ 一条命令算出来           │
  │ 状态开销      │ 每会话都要存分配决策      │ 只存活跃连接             │
  │ 静态映射      │ 有                     │ 无                     │
  │ twice-NAT    │ ED 有                  │ 无                     │
  │ 负载均衡      │ ED 有                  │ 无                     │
  │ VRF 多租户    │ ED 有                  │ 无(只有 inside/outside vrf)│
  │ 会话高可用    │ EI 有                  │ 无                     │
  └──────────────┴────────────────────────┴────────────────────────┘

DET44 是一个功能极简、专为一个目的服务的插件。 它放弃了几乎所有花哨能力,换来可追溯性和极低的状态开销。

小结

核心思想映射用算法算出来,不查表分配
配置det44 add in <内网前缀> out <公网前缀> + set interface det44 inside/outside
端口公式1024 + 主机序号 × (64512 / 共享比)
唯一可调参数共享比,由内外前缀大小之比决定
最大限制端口配额固定,用完不能借,超标直接失败
致命坑一内网前缀 ≥ /18 会让 VPP 崩溃,拆成多条 /19
致命坑二会往接口注册 feature 节点且无法在线摘除,用完必须重建 VPP
功能缺失无静态映射、无 twice-NAT、无负载均衡、无 HA

下一篇:可追溯性 —— DET44 存在的全部理由。

实验环境基于 netlab + containerlab + VPP 26.06