katran 扩展:DNAT + SNAT 实现

3 阅读13分钟

❯ LB 使用 EIP1 的情况下,如果再配置 一对一 External IP 和 internal IP DNAT 是冲突的。 最多也就是 LB 使用 EIP1 的情况下,最多只有 SNAT 还可以再复用 EIP1。 LB 和 一对一 DNAT 本质上对于一个 EIP,肯定是 二选一的。 也就是说,LB + FIP 是伪需求。LB + SNAT 是正常需求。LB 可以共享 EIP,同时也能满足端口访问的需求,可以理解是 FIP 的替代。 分析这个说法的正确性

● 逐条分析

  1. "LB + 一对一 DNAT 对同一 EIP 是冲突的"

大体正确,但需精确化。

冲突发生在入方向决策逻辑:

  同一个包: dst = EIP:port
    LB 的决策:  → 从 N 个 backend 中选一个(一对多)
    FIP 的决策: → 固定发给 Pod A(一对一)
    ↑ 对同一个包,不可能同时执行两种决策

  但严格来说,冲突的粒度是端口级,不是 IP 级:

  ┌──────────────────────────────────┬─────────────────────────────┐
  │               场景               │          是否冲突           │
  ├──────────────────────────────────┼─────────────────────────────┤
  │ EIP:80 做 LB,EIP:80 做 1:1 DNAT │ 冲突 — 同一个包两种处理逻辑 │
  ├──────────────────────────────────┼─────────────────────────────┤
  │ EIP:80 做 LB,EIP:22 做 1:1 DNAT │ 理论上不冲突 — 按端口分流   │
  └──────────────────────────────────┴─────────────────────────────┘

但"EIP:80 走 LB,EIP:22 走特定 Pod"这种按端口拆分的模型,已经不是传统 FIP 的语义了。FIP 的定义是全端口 1:1 映射,在这个定义下,与 LB 确实互斥。

结论:在 FIP 的标准语义下,正确。


  1. "LB 使用 EIP1 的情况下,最多只有 SNAT 还可以再复用 EIP1"

正确。

原因:SNAT 是出方向操作,和 LB 的入方向在数据面上天然隔离:


  入方向:  ClientEIP:80  → katran LB 处理(VIP:service_port 匹配)
  出方向:  PodExternalSNAT 改写 src 为 EIP(不经过 katran)
  SNAT 回包: ExternalEIP:ephemeral_port → katran 查不到 → XDP_PASS → conntrack 还原

  SNAT 回包的 dst_port 是临时端口,不会命中 katran 的 VIP:service_port 表项,数据面零冲突。

  1. "LB + FIP 是伪需求"

对同一个 EIP 而言正确,但表述可以更精确。

准确的说法是:

  • 同一 EIP 上 LB + FIP = 伪需求 ✅ — 入方向逻辑互斥
  • 系统同时支持 LB 模式和 FIP 模式(不同 EIP)≠ 伪需求 — 有些 EIP 做 LB,有些做 FIP,这是合理的

所以"伪需求"针对的是"同一 EIP 同时做 LB 和 FIP",不是"系统不需要 FIP 功能"。


  1. "LB + SNAT 是正常需求"

完全正确。 这是非常普遍的场景:

  • Pod 通过 LB VIP 对外提供服务(入方向)
  • Pod 访问外部 API / DNS / 第三方服务时,需要以 VIP 为源 IP(出方向)
  • 外部服务对 VIP 做了 IP 白名单

  1. "LB 可以共享 EIP,可以理解是 FIP 的替代"

大部分场景正确,但不是完全替代。

对比:


  ┌─────────────────────┬─────────────┬───────────────────────┐
  │        能力         │     FIP     │       LB + SNAT       │
  ├─────────────────────┼─────────────┼───────────────────────┤
  │ 出方向有公网 IP     │ ✅ 1:1 SNAT │ ✅ N:1 SNAT 复用 VIP  │
  ├─────────────────────┼─────────────┼───────────────────────┤
  │ 入方向特定端口可达  │ ✅ 全端口   │ ✅ LB 配置的端口      │
  ├─────────────────────┼─────────────┼───────────────────────┤
  │ 入方向任意端口可达  │ ✅          │ ❌ 只有 LB 注册的端口 │
  ├─────────────────────┼─────────────┼───────────────────────┤
  │ 多 Pod 共享一个 EIP │ ❌ 独占     │ ✅                    │
  └─────────────────────┴─────────────┴───────────────────────┘

LB + SNAT 覆盖不了的场景:Pod 需要被外部以任意端口主动访问(游戏服务器、P2P、SIP 等)。这些场景仍然需要 FIP。

但在云原生主流场景下(Web 服务、微服务、gRPC),Pod 只暴露固定端口,LB + SNAT 确实足以替代 FIP,而且更节省公网 IP。


总结


  ┌─────────────────────────┬─────────────┬────────────────────────────────────────────────────────────┐
  │        原文观点         │    判定     │                            备注                            │
  ├─────────────────────────┼─────────────┼────────────────────────────────────────────────────────────┤
  │ LB + 1:1 DNAT 冲突      │ ✅ 正确     │ 严格说是端口级冲突,FIP 语义下等价于 IP 级                 │
  ├─────────────────────────┼─────────────┼────────────────────────────────────────────────────────────┤
  │ SNAT 可以复用 LB 的 EIP │ ✅ 正确     │ 入方向与出方向天然隔离                                     │
  ├─────────────────────────┼─────────────┼────────────────────────────────────────────────────────────┤
  │ LB + FIP 是伪需求       │ ⚠️ 需限定   │ 对同一 EIP 而言正确;系统仍可能需要 FIP 功能(用不同 EIP) │
  ├─────────────────────────┼─────────────┼────────────────────────────────────────────────────────────┤
  │ LB + SNAT 是正常需求    │ ✅ 正确     │ 业界通用模式                                               │
  ├─────────────────────────┼─────────────┼────────────────────────────────────────────────────────────┤
  │ LB 是 FIP 的替代        │ ⚠️ 部分正确 │ 覆盖主流场景,但全端口可达的场景仍需 FIP                   │
  └─────────────────────────┴─────────────┴────────────────────────────────────────────────────────────┘

❯ 是这样的。 LB 使用 EIP1 的情况下,如果再配置 一对一 External IP 和 internal IP DNAT 是冲突的。最多也就是 LB 使用 EIP1 的情况下,最多只有 SNAT 还可以再复用 EIP1。LB 和 一对一 DNAT 本质上对于一个 EIP,肯定是 二选一的。 也就是说,LB + FIP 是伪需求。梳理以上问题,再仔细评估下,输出最终方案。

● 分析确认

你的判断完全正确。对于同一个 EIP 的入方向:


  LB:      EIP:port  选一个 backend(一对多)
  1:1 DNAT: EIP      固定一个 Pod  (一对一)
             决策逻辑互斥,无法共存

  因此一个 EIP 只有两种互斥模式:

  ┌──────────┬─────────────────┬───────────────────────┬───────────────┐
     模式        入方向              出方向            Conntrack   
  ├──────────┼─────────────────┼───────────────────────┼───────────────┤
   LB 模式   katran 负载均衡  可选:SNAT 复用此 VIP  SNAT 部分需要 
  ├──────────┼─────────────────┼───────────────────────┼───────────────┤
   FIP 模式  1:1 DNAT         1:1 SNAT               不需要        
  └──────────┴─────────────────┴───────────────────────┴───────────────┘

LB + FIP 确实是伪需求,排除。

最终方案

统一架构


  NIC
   │
   ▼
  XDP (katran,不修改)
   ├─ 匹配 LB VIP:port → 封装 → XDP_TX → backend
   └─ 不匹配 → XDP_PASS
       │
       ▼
    TC ingress (新增,统一入口)
       ├─ fip_to_pod 命中 → 1:1 DNAT(无状态) ← FIP 模式
       ├─ snat_ct 命中   → reverse-DNAT(查 conntrack) ← LB+SNAT 回包
       └─ 都不命中       → TC_ACT_OK(正常上送内核)

    TC egress (新增,统一出口)
       ├─ pod_to_fip 命中    → 1:1 SNAT(无状态) ← FIP 模式
       ├─ snat_binding 命中  → N:1 SNAT(写 conntrack) ← LB+SNAT
       └─ 都不命中          → TC_ACT_OK(正常发出)

一共只需新增 2 个 TC BPF 程序,各自内含两条查表路径(FIP 路径 + LB SNAT 路径),互斥不冲突。

BPF Maps(共 5 个)


  // ========== FIP 模式(无状态,两个 map 互为反向) ==========

  // 入方向:FIP → Pod IP
  struct {
      __uint(type, BPF_MAP_TYPE_HASH);
      __type(key, struct ip_addr);       // FIP
      __type(value, struct ip_addr);     // Pod IP
      __uint(max_entries, MAX_FIPS);
  } fip_to_pod SEC(".maps");

  // 出方向:Pod IP → FIP
  struct {
      __uint(type, BPF_MAP_TYPE_HASH);
      __type(key, struct ip_addr);       // Pod IP
      __type(value, struct ip_addr);     // FIP
      __uint(max_entries, MAX_FIPS);
  } pod_to_fip SEC(".maps");

  // ========== LB SNAT 模式(有状态) ==========

  // Pod 子网 → 复用哪个 VIP
  struct {
      __uint(type, BPF_MAP_TYPE_LPM_TRIE);
      __type(key, struct v4_lpm_key);    // Pod 子网
      __type(value, __be32);             // VIP
      __uint(max_entries, 1024);
      __uint(map_flags, BPF_F_NO_PREALLOC);
  } snat_binding SEC(".maps");

  // Conntrack(LRU 自动淘汰,免 GC)
  struct {
      __uint(type, BPF_MAP_TYPE_LRU_HASH);
      __type(key, struct ct_key);        // (ext_ip, vip, ext_port, snat_port, proto)
      __type(value, struct ct_val);      // (pod_ip, orig_port)
      __uint(max_entries, 524288);
  } snat_ct SEC(".maps");

  // 端口游标(原子递增)
  struct {
      __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
      __type(key, __u32);
      __type(value, __u32);
      __uint(max_entries, 1);
  } port_alloc SEC(".maps");

  TC Ingress 程序(统一入方向)

  SEC("tc")
  int nat_ingress(struct __sk_buff *skb) {
      struct iphdr *iph = parse_ip(skb);
      if (!iph) return TC_ACT_OK;

      // --- 路径 1: FIP DNAT(无状态)---
      struct ip_addr dst_key = { .addr = iph->daddr };
      struct ip_addr *pod = bpf_map_lookup_elem(&fip_to_pod, &dst_key);
      if (pod) {
          rewrite_dst(skb, iph, pod->addr);  // 改 dst + 增量 csum
          return TC_ACT_OK;
      }

      // --- 路径 2: LB SNAT reverse-DNAT(查 conntrack)---
      struct ct_key ct = build_ct_key_from_reply(iph, skb);
      struct ct_val *val = bpf_map_lookup_elem(&snat_ct, &ct);
      if (val) {
          val->timestamp = bpf_ktime_get_ns();
          rewrite_dst_and_port(skb, iph, val->orig_src, val->orig_port);
          return TC_ACT_OK;
      }

      return TC_ACT_OK;  // 非 NAT 流量,正常上送
  }

  TC Egress 程序(统一出方向)

  SEC("tc")
  int nat_egress(struct __sk_buff *skb) {
      struct iphdr *iph = parse_ip(skb);
      if (!iph) return TC_ACT_OK;

      // --- 路径 1: FIP SNAT(无状态)---
      struct ip_addr src_key = { .addr = iph->saddr };
      struct ip_addr *fip = bpf_map_lookup_elem(&pod_to_fip, &src_key);
      if (fip) {
          rewrite_src(skb, iph, fip->addr);  // 改 src + 增量 csum
          return TC_ACT_OK;
      }

      // --- 路径 2: LB SNAT(写 conntrack)---
      struct v4_lpm_key lpm = { .prefixlen = 32, .addr = iph->saddr };
      __be32 *vip = bpf_map_lookup_elem(&snat_binding, &lpm);
      if (vip) {
          __u16 new_port = alloc_port();
          write_conntrack(skb, iph, *vip, new_port);
          rewrite_src_and_port(skb, iph, *vip, new_port);
          return TC_ACT_OK;
      }

      return TC_ACT_OK;  // 非 NAT 流量,正常发出
  }

控制面操作


  # LB 模式:EIP1 做负载均衡,后端 Pod 出方向复用 EIP1
  katran.addVip(eip="1.1.1.1", port=80, proto=TCP, backends=[...])
  katran.addVip(eip="1.1.1.1", port=443, proto=TCP, backends=[...])
  nat.addSnatBinding(pod_cidr="10.0.0.0/16", vip="1.1.1.1")

  # FIP 模式:EIP2 一对一绑定到某个 Pod
  nat.addFip(eip="2.2.2.2", pod_ip="10.0.1.5")

  # 互斥校验(控制面必须拒绝)
  katran.addVip(eip="2.2.2.2", ...)  → ERROR: EIP 已用于 FIP
  nat.addFip(eip="1.1.1.1", ...)     → ERROR: EIP 已用于 LB

最终全景


  每个 EIP 二选一:
  ┌─────────────────────────────────────────────────────┐
  │                  EIP = LB 模式                       │
  │                                                     │
  │  入: XDP(katran) → VIP:port 匹配 → 封装转发          │
  │  出: TC egress → snat_binding 匹配 → SNAT(有状态)    │
  │  回: XDP(miss) → TC ingress → snat_ct 匹配 → revDNAT │
  └─────────────────────────────────────────────────────┘
  ┌─────────────────────────────────────────────────────┐
  │                  EIP = FIP 模式                       │
  │                                                     │
  │  入: XDP(miss) → TC ingress → fip_to_pod → DNAT     │
  │  出: TC egress → pod_to_fip → SNAT                  │
  │  全程无状态,无 conntrack                              │
  └─────────────────────────────────────────────────────┘

  组件总计:
    katran XDP   — 已有,不修改
    TC ingress   — 新增 1 个程序(~120 行 C)
    TC egress    — 新增 1 个程序(~120 行 C)
    BPF maps     — 5 个(2 无状态 + 3 有状态)
    控制面       — EIP 模式互斥校验 + map CRUD
    

结论:katran 不支持 1:1 DNAT/SNAT(Floating IP)

katran 是一个纯粹的 L4 负载均衡器(DSR 模式),不具备为 Pod 配置独立 Floating External IP 的能力。以下是从代码层面的详细分析:


  1. katran 的核心数据面逻辑

从 katran/lib/bpf/balancer.bpf.c 的 process_packet() 函数可以看到,整个包处理流程是:


  入包 → 解析L3/L4 → VIP查表(vip_map) → 一致性哈希/LRU选real → IPIP/GUE封装 → XDP_TX

关键事实:

  • 没有任何 IP 地址重写操作。katran 从不修改原始包的 src/dst IP
  • 封装方式只有两种:IPIP 隧道 或 GUE 隧道(在原始包外面加一层 outer header)
  • 返回路径使用 DSR(Direct Server Return),即后端服务器直接回包给客户端,不经过 katran
  1. 几个容易混淆的特性分析

  ┌─────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────┐
  │            特性             │                                         实际作用                                         │            是否为 1:1 NAT             │
  ├─────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────┤
  │ LPM_SRC_LOOKUP              │ 根据源 IP 前缀选择目标 real server(而非一致性哈希),用于基于来源的路由策略             │ 否。只是改变选 real 的方式,不改写 IP │
  ├─────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────┤
  │ INLINE_DECAP                │ 对 IPIP/GUE 封装的入包做解封装,让本机能接收其他 katran 转发过来的包                     │ 否。只是剥掉外层隧道头                │
  ├─────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────┤
  │ encap_v4/encap_v6           │ 给原始包套一个新的 IP 头(IPIP 隧道),outer src 使用 172.16.0.0/10 前缀(不是原始 src) │ 否。是隧道封装,不是 SNAT             │
  ├─────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────┤
  │ LOCAL_DELIVERY_OPTIMIZATION │ 当 VIP 和 real 在同一台机器上时,直接 XDP_PASS 交给内核协议栈                            │ 否                                    │
  ├─────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────┤
  │ decap_dst map               │ 存放需要跨 PoP 解封装的目标地址白名单                                                    │ 否                                    │
  └─────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────┘
  1. 为什么 katran 架构上无法做 Floating IP

从架构根因来看:

  1. DSR 模式限制:katran 只处理入方向流量(client → VIP),回包(server → client)不经过 katran。要实现 SNAT,必须双向都经过 NAT 节点,这与 DSR 模式矛盾。

  2. XDP 层的限制:katran 运行在 XDP 层,在 XDP_TX 时直接将包从网卡发回,没有 conntrack 状态跟踪。1:1 NAT 需要 conntrack 来维护双向映射。

  3. 无 IP 重写代码:翻遍整个 BPF 数据面代码(balancer.bpf.c、pckt_encap.h),没有任何 iph->saddr = ... 或 iph->daddr = ... 这样的 IP 重写操作。所有的"地址变更"都是通过加/减隧道头实现的。

  4. 如果要实现 Floating IP,需要什么

要为 Pod 实现 FIP(类似公有云 EIP),需要:

  • 双向 NAT 节点:出方向 SNAT(Pod IP → FIP),入方向 DNAT(FIP → Pod IP)
  • Conntrack:跟踪连接状态以正确做反向映射
  • 典型实现:Linux 内核 netfilter/nftables、Cilium 的 BPF NAT、OVS、或 kube-proxy 的 iptables 规则

katran 的定位是 Facebook 数据中心的 L4 LB 前端(类似 LVS-TUN 模式),不是通用 NAT 网关。

约束条件清单


  ┌────────────────────────────────────────┬───────────────────────────────────────┬───────────────────┐
  │                  约束                  │                 原因                  │      执行层       │
  ├────────────────────────────────────────┼───────────────────────────────────────┼───────────────────┤
  │ 同一 EIP 不能同时有 LB 和 DNAT         │ 入方向决策冲突                        │ 控制面拒绝        │
  ├────────────────────────────────────────┼───────────────────────────────────────┼───────────────────┤
  │ DNAT 的 EIP 只能有一条对应的 SNAT 规则 │ 独占模式不做端口重映射,多 Pod 会冲突 │ 控制面校验        │
  ├────────────────────────────────────────┼───────────────────────────────────────┼───────────────────┤
  │ SNAT 端口从 32768-65535 分配           │ 避免与 LB 服务端口(通常 <32768)冲突 │ 数据面 port_alloc │
  ├────────────────────────────────────────┼───────────────────────────────────────┼───────────────────┤
  │ LB VIP 不用 port=0 通配                │ 否则 katran 会吃掉 SNAT 回包          │ 控制面校验        │
  └────────────────────────────────────────┴───────────────────────────────────────┴───────────────────┘
  

katran 扩展:DNAT + SNAT 最终实现方案

一、领域模型

三个独立原语,各自独立创建/删除:


  ┌─────────┬──────────────────┬──────┬────────────┬───────────┐
  │ 原语     │ 语义              │ 方向 │ 状态        │ 实现位置   │
  ├─────────┼──────────────────┼──────┼────────────┼───────────┤
  │ LB      │ EIP:port → N Pod │ 入   │ 已有        │ XDP(katran)│
  │ DNAT    │ EIP → 1 Pod      │ 入   │ 无状态      │ TC ingress │
  │ SNAT    │ Pod → EIP        │ 出+回 │ 视模式而定   │ TC egress  │
  └─────────┴──────────────────┴──────┴────────────┴───────────┘

二、EIP 组合规则


  LB + SNAT     ✅  服务对外 + Pod 出网复用 VIP
  DNAT + SNAT   ✅  等效 FIP(同一 Pod、同一 EIP)
  LB only       ✅
  SNAT only     ✅
  DNAT only     ⚠️  可创建,回包 src 非 EIP,对端可能不通
  LB + DNAT     ❌  入方向冲突,控制面拒绝

三、数据面架构


  NIC
   │
   ▼
  XDP (katran,不修改)
   ├─ VIP:port 命中 → 封装 → XDP_TX        ← LB
   └─ 未命中 → XDP_PASS
       │
       ▼
  TC ingress (新增)
   ├─ ① snat_ct 命中 → 还原 IP+端口        ← 共享 SNAT 回包
   ├─ ② dnat_map 命中 → 改写 dst IP        ← DNAT 入方向 / 独占 SNAT 回包
   └─ ③ 都不命中 → 放行

  TC egress (新增)
   ├─ snat_map 命中:
   │   ├─ dnat_map 有同 Pod 条目 → 独占模式:仅改 src IP,不动端口,无 ct
   │   └─ 否则 → 共享模式:改 src IP + 重映射端口 + 写 ct
   └─ 未命中 → 放行

  四、BPF Maps(4 个)

  // DNAT 域:EIP → Pod IP(入方向,无状态)
  struct bpf_map dnat_map {         // BPF_MAP_TYPE_HASH
      key:   struct ip_addr          // EIP
      value: struct ip_addr          // Pod IP
  };

  // SNAT 域:Pod IP → EIP(出方向配置)
  struct bpf_map snat_map {         // BPF_MAP_TYPE_HASH
      key:   struct ip_addr          // Pod IP
      value: struct ip_addr          // EIP
  };

  // SNAT 域:conntrack(共享模式专用,LRU 自动淘汰)
  struct bpf_map snat_ct {          // BPF_MAP_TYPE_LRU_HASH
      key:   {ext_ip, eip, ext_port, mapped_port, proto}   // 五元组
      value: {pod_ip, orig_port}                            // 原始信息
  };

  // SNAT 域:端口游标(per-CPU 避免锁竞争)
  struct bpf_map port_alloc {       // BPF_MAP_TYPE_PERCPU_ARRAY
      key:   __u32                   // 固定 0
      value: __u32                   // 当前游标,范围 32768-65535
  };

领域解耦:dnat_map 和 snat_map 互不引用。SNAT 的两种模式由 TC egress 运行时查两个 map 动态决定,无耦合标记位。

五、SNAT 两种模式的判定


  // TC egress 核心逻辑
  eip       = snat_map[pod_ip]           // 查 SNAT 域
  dnat_pod  = dnat_map[eip]              // 查 DNAT 域
  exclusive = dnat_pod && dnat_pod == pod_ip

  if exclusive → 独占模式(DNAT+SNAT 场景)
  else         → 共享模式(LB+SNAT / 纯 SNAT 场景)

  ┌──────┬───────────┬───────────────┬──────────────┬──────────┐
  │ 模式 │ 改 src IP │  改 src port  │ 写 conntrack │ 回包路径 │
  ├──────┼───────────┼───────────────┼──────────────┼──────────┤
  │ 独占 │ ✅        │ ❌ 保留原端口 │ ❌           │ dnat_map │
  ├──────┼───────────┼───────────────┼──────────────┼──────────┤
  │ 共享 │ ✅        │ ✅ 重映射     │ ✅           │ snat_ct  │
  └──────┴───────────┴───────────────┴──────────────┴──────────┘

为什么独占模式必须保留端口:DNAT 入方向连接的回包,src port 是服务端口(如 22),重映射会导致 Client 五元组不匹配,TCP RST。

六、全场景包路径


  LB + SNAT(共享模式)

  入站 LB:     Client → EIP:443 → katran 命中 → 封装 → Backend
  Pod 出站:    Pod:50000 → Ext:443 → egress 共享 SNAT → EIP:32800 + ct
  出站回包:    Ext:443 → EIP:32800 → katran 未命中 → PASS → ingress ct 命中 → Pod:50000

  DNAT + SNAT(独占模式 = FIP)

  入站 DNAT:   Client → EIP:22 → katran 未命中 → PASS → ingress dnat_map → Pod:22
  DNAT 回包:   Pod:22Client → egress 独占 SNAT → EIP:22(端口不变)
  Pod 出站:    Pod:50000 → Ext:443 → egress 独占 SNAT → EIP:50000(端口不变)
  出站回包:    Ext:443 → EIP:50000 → ingress dnat_map → Pod:50000

  纯 SNAT(共享模式)

  Pod 出站:    Pod:50000 → Ext:443 → egress 共享 SNAT → EIP:32800 + ct
  出站回包:    Ext:443 → EIP:32800 → ingress ct 命中 → Pod:50000

七、控制面 API


  addLbVip(eip, port, proto, backends[])   →  写 katran vip_map
  delLbVip(eip, port, proto)               →  删 katran vip_map

  addDnat(eip, pod_ip)                     →  校验 EIP 无 LB → 写 dnat_map
  delDnat(eip)                             →  删 dnat_map

  addSnat(pod_ip, eip)                     →  若 EIP 有 DNAT 则校验同 Pod → 写 snat_map
  delSnat(pod_ip)                          →  删 snat_map

八、约束条件

  1. 同一 EIP: LB 与 DNAT 互斥 ← 控制面拒绝
  2. DNAT 的 EIP: 最多一条 SNAT 规则且必须是同一 Pod ← 控制面校验
  3. 共享 SNAT 端口范围: 32768-65535 ← 避免与 LB 服务端口冲突
  4. LB VIP 不用 port=0 通配 ← 否则 katran 吃掉 SNAT 回包

九、工程量


  新增代码:
    TC ingress BPF    ~100  C
    TC egress BPF     ~120  C
    控制面 CRUD       ~200  C++/Go(map 操作 + 互斥校验)

  新增 BPF Maps:       4 
  修改 katran:         0