Loading... # eBPF/XDP 构建二层直接服务器返回负载均衡器教程 # 一、概述 ## 1. 简介 ### A. 是什么 本教程讲解如何使用 eBPF/XDP 技术从零开始构建一个二层 Direct Server Return(DSR)负载均衡器。DSR 是一种高级负载均衡模式,允许后端服务器直接响应客户端请求,无需通过负载均衡器回传响应流量。 ### B. 为什么学 - 解决 NAT 模式负载均衡器的性能瓶颈问题 - 掌握 eBPF/XDP 在网络编程中的实际应用 - 理解 DSR 架构在云原生环境中的价值 ### C. 学完能做什么 - 使用 eBPF/XDP 实现二层 DSR 负载均衡 - 理解虚拟 IP(VIP)的工作原理和配置方法 - 掌握 MAC 地址重写技术在数据平面加速中的应用 ## 2. 前置知识 ### A. 必备技能 - Linux 网络基础(IP、MAC、ARP 协议) - 基本的 C/Go 编程能力 - 了解 eBPF/XDP 基本概念 ### B. 推荐知识 - 完成前序教程:NAT 模式 XDP 负载均衡器 - 了解网络协议栈的分层模型 # 二、背景知识 ## 1. NAT 模式的局限性 在之前的教程中,我们构建了基于 NAT 的 XDP 负载均衡器。虽然该方案可以正常工作,但存在以下问题: ### A. 资源消耗 负载均衡器需要处理双向流量(请求和响应),这会导致: - 更高的 CPU 和内存占用 - 可能成为系统瓶颈 ### B. 流量不对称 入站流量通常远小于出站流量: - 搜索查询或 AI 提示只有几字节 - 搜索结果或 AI 响应可能有数千字节 ### C. 客户端信息丢失 由于 NAT 负载均衡器重写了数据包头,后端服务器: - 无法感知真实客户端 IP - 难以维护用户会话 - 无法按源 IP 记录请求日志 ## 2. Direct Server Return 概念 DSR 通过让后端服务器直接响应客户端来解决上述问题。有多种 DSR 实现方式: - Layer 2 DSR(本教程重点) - IP-in-IP 封装 - GRE 隧道 - 基于 IP 或 TCP 头部字段的变体 # 三、实验环境 ## 1. 网络拓扑 实验环境包含 5 个节点,分布在两个网络中: | 节点 | 网络 | IP 地址 | |------|------|---------| | lb | 192.168.178.0/24 | 192.168.178.10/24 | | backend-01 | 192.168.178.0/24 | 192.168.178.11/24 | | backend-02 | 192.168.178.0/24 | 192.168.178.12/24 | | client | 10.0.0.0/16 | 10.0.0.20/16 | | gateway | 跨网络 | 192.168.178.2/24 + 10.0.0.2/16 | ## 2. DSR 原理 与传统 NAT 负载均衡不同,DSR 保留原始客户端 IP,并允许后端直接响应客户端,在回程路径上绕过负载均衡器。 核心问题: 1. 后端如何看到客户端 IP(数据包经过了负载均衡器)? 2. 后端知道响应哪个客户端吗? 3. 如何确保客户端从同一 IP 收到响应? 简而言之,二层 DSR 通过在负载均衡器上仅重写数据包的 MAC 地址(保持 IP 头不变),并依靠负载均衡器和后端服务器之间的虚拟 IP 来实现。 # 四、核心概念 ## 1. 虚拟 IP(VIP) 在任何网络设置中,当客户端向特定端点发送请求时,期望响应来自相同的 IP 地址。如果响应来自不同的 IP,客户端网络栈会认为出错并丢弃数据包。 在 NAT 负载均衡中,这会造成问题,因为客户端请求总是通过负载均衡器,后者将其转发给具有不同 IP 的后端节点。 ### A. VIP 解决方案 为了解决这个问题,负载均衡器和后端节点需要共享相同的 IP,确保客户端始终认为响应来自其最初联系的地址。 这个共享 IP 就是虚拟 IP(VIP)。 ### B. VIP 特性 与物理 IP 不同,VIP 不绑定到特定接口或节点。相反,它是由多个节点使用的共享地址,用于处理同一服务的流量。 ## 2. VIP 配置 ### A. 负载均衡器配置 在负载均衡器的主接口(eth0)上配置 VIP: ```bash sudo ip addr add 192.168.178.15/32 dev eth0 ``` ### B. 后端服务器配置 在两个后端服务器上,在环回接口(lo)上配置 VIP: ```bash sudo ip addr add 192.168.178.15/32 dev lo ``` ### C. ARP 隔离 两个节点具有相同 IP 会引起混乱。因此,我们将 VIP 分配给后端节点的 lo 接口。我们不希望后端通过 ARP 广告虚拟 IP。 网络中没有人应该知道后端上存在 VIP,否则客户端可能绕过负载均衡器直接连接。 为防止后端节点响应 lo 接口上 VIP 地址的 ARP 请求(即广告),在每个后端节点上运行以下命令: ```bash # 仅当目标 IP 分配给接收请求的接口时才响应 ARP 请求 # (防止在 eth0 上广告 VIP) sudo sysctl -w net.ipv4.conf.eth0.arp_ignore=1 # 发送 ARP 请求时,仅使用分配给出站接口的地址 # (防止节点在 ARP 中泄露 VIP 作为源 IP) sudo sysctl -w net.ipv4.conf.eth0.arp_announce=2 ``` ### D. 工作原理 负载均衡器实际上不需要知道后端节点上存在 VIP。它只需要后端的 MAC 地址在二层转发数据包。 当后端接收数据包时,它会解封装数据包并将虚拟 IP 识别为自己的 IP(在 lo 接口上配置),即使该 IP 永远不会在网络上广告。 这也解释了为什么这个概念称为二层 DSR——负载均衡器仅使用后端的 MAC 地址到达后端节点,如果后端位于不同的(二层)网络,这将无法工作。 ## 3. DSR 流程 ```mermaid sequenceDiagram participant C as 客户端 participant G as 网关 participant LB as 负载均衡器 participant B as 后端服务器 C->>G: 请求到 VIP G->>LB: 路由到负载均衡器 LB->>LB: 选择后端(哈希) LB->>B: 重写 MAC 地址(保持 IP) Note over B: VIP 配置在 lo 接口 B->>G: 直接响应客户端 G->>C: 转发响应 ```  当客户端连接到 VIP 时: 1. 网关将流量路由到负载均衡器 2. 负载均衡器通过重写(源和)目标 MAC 地址将数据包转发到后端 3. 后端在 lo 接口上配置了 VIP,它将数据包识别为自己的并处理 ## 4. 前置准备 在下一步之前,需要在负载均衡器上启用 IP 转发并填充 ARP 表,以便 bpf_fib_lookup 可以解析数据包目标: ```bash # 启用 IP 转发 sudo sysctl -w net.ipv4.ip_forward=1 # 填充 ARP 表 sudo ping -c1 192.168.178.11 # backend-01 sudo ping -c1 192.168.178.12 # backend-02 ``` # 五、实现细节 ## 1. MAC 地址重写 现在我们理解了如何避免客户端困惑(总是收到来自相同 IP 的响应)。但还有一个问题:负载均衡器如何知道将流量发送到哪个后端——特别是当它们共享(相同)VIP 时? 在二层 DSR 负载均衡中,我们实际上只需要在负载均衡器中更新 MAC 地址,以便数据包在二层正确传递。 ### A. 后端选择 使用简单哈希选择后端: ```c // 选择后端 struct four_tuple_t four_tuple; four_tuple.src_ip = ip->saddr; four_tuple.dst_ip = ip->daddr; four_tuple.src_port = tcp->source; four_tuple.dst_port = tcp->dest; four_tuple.protocol = IPPROTO_TCP; __u32 key = xdp_hash_tuple(&four_tuple) % NUM_BACKENDS; struct endpoint *backend = bpf_map_lookup_elem(&backends, &key); if (!backend) { return XDP_ABORTED; } ``` ### B. FIB 查询 执行 FIB 查询获取转发信息: ```c // 执行 FIB 查询 struct bpf_fib_lookup fib = {}; int rc = fib_lookup_v4_full(ctx, &fib, ip->daddr, backend->ip, bpf_ntohs(ip->tot_len)); if (rc != BPF_FIB_LKUP_RET_SUCCESS) { log_fib_error(rc); return XDP_ABORTED; } ``` ### C. MAC 地址重写 仅更新 MAC 地址: ```c // 我们只需要更新 MAC 地址 // 后端需要在 lo 上有虚拟 IP(与负载均衡器相同) // 源 IP 保留为客户端,因此后端将直接响应它 __builtin_memcpy(eth->h_source, fib.smac, ETH_ALEN); __builtin_memcpy(eth->h_dest, fib.dmac, ETH_ALEN); ``` ### D. 关键点 这里很酷的事情是无需在负载均衡器中维护任何连接跟踪状态(与 NAT 负载均衡器不同)。我们只需基于通过 bpf_fib_lookup 在 fib_lookup_v4_full 中检索的 MAC 地址将数据包重定向到选定的后端节点,后端根据 IP 头直接响应客户端。 负载均衡器甚至不触碰 IP 头,这意味着后端仍然接收原始客户端源 IP 并可以直接回复它,绕过负载均衡器。 ## 2. 源 MAC 地址说明 源 MAC 地址也需要更新,以确保后端将响应正确发送回网关(而不是直接发送给负载均衡器)。 # 六、运行负载均衡器 ## 1. 启动后端服务 在两个后端服务器上启动 HTTP 服务器,显式绑定到 VIP 上监听请求: ```bash python3 -m http.server 8000 --bind 192.168.178.15 ``` ## 2. 编译并运行负载均衡器 在 lab 目录下的 lb 标签页中: ```bash go generate go build sudo ./lb -i eth0 --backends 192.168.178.11,192.168.178.12 ``` 参数说明: - `-i eth0`:指定网络接口 - `--backends`:后端服务器 IP 列表(逗号分隔) ## 3. 测试负载均衡 从客户端标签页查询 VIP: ```bash curl http://192.168.178.15:8000 ``` 确认请求确实是从客户端接收的(IP 在行首): ``` 10.0.0.20 - - [01/Jan/2026 16:03:45] "GET / HTTP/1.1" 200 - ``` ## 4. 验证数据流向 ### A. 使用 tcpdump 在两个后端上运行 tcpdump 查看 MAC 地址: ```bash sudo tcpdump -i eth0 -n -t -e -q tcp port 8000 ``` 预期输出(简化): ``` LB_MAC > BACKEND_MAC, IPv4, length 74: CLIENT_IP/PORT > BACKEND_IP/PORT: tcp 0 # 负载均衡器 -> 后端(同时保留客户端源 IP) BACKEND_MAC > GATEWAY_MAC, IPv4, length 74: BACKEND_IP/PORT > CLIENT_IP/PORT: tcp 0 # 后端 -> 网关(其中客户端 IP 是目标) ``` ### B. 使用 bpftool 检查负载均衡器日志: ```bash sudo bpftool prog trace ``` # 七、架构图 ```mermaid graph TB subgraph 客户端网络 C[客户端<br/>10.0.0.20] GW[网关<br/>10.0.0.2/192.168.178.2] end subgraph 服务器网络 VIP[虚拟 IP<br/>192.168.178.15] LB[负载均衡器<br/>192.168.178.10] BE1[后端-01<br/>192.168.178.11<br/>VIP on lo] BE2[后端-02<br/>192.168.178.12<br/>VIP on lo] end C -->|请求到 VIP| GW GW -->|路由| LB LB -->|重写 MAC| BE1 LB -->|重写 MAC| BE2 BE1 -->|直接响应| GW BE2 -->|直接响应| GW GW --> C style VIP fill:#f9f,stroke:#333,stroke-width:2px ```  # 八、局限性 二层 DSR 仅在负载均衡器和后端位于同一子网(即具有直接二层连接)时才能真正工作。 如果负载均衡器尝试将流量重定向到不同二层网络中的后端: 1. 它会将目标 MAC 地址设置为网关接口(尝试退出当前二层网络) 2. 由于只有负载均衡器上的 VIP 在整个网络上广告,网关会将数据包发回负载均衡器 3. 因为这是网关知道的该 VIP 的唯一路由 4. 这会导致环路 ### A. 约束 - 要求后端与负载均衡器共享同一网络 - 负载均衡器和后端节点托管在同一子网会增加完全故障的风险 ### B. 解决方案 这是 IPIP DSR 负载均衡发挥作用的地方——将在后续教程中介绍。 # 九、总结 ## 1. 核心要点 - DSR 允许后端直接响应客户端,绕过负载均衡器 - 二层 DSR 通过重写 MAC 地址实现,保持 IP 头不变 - VIP 配置在负载均衡器的实接口和后端的 lo 接口上 - 需要 ARP 隔离防止后端广告 VIP ## 2. 优势 - 负载均衡器资源消耗降低 - 后端可以看到真实客户端 IP - 更适合出站流量大的场景 ## 3. 局限 - 要求负载均衡器和后端在同一二层网络 - 跨网络部署需要 IPIP 或 GRE 等隧道技术 *** ## 参考资料 1. [Building an eBPF/XDP L2 Direct Server Return Load Balancer from Scratch](https://labs.iximiuz.com/tutorials/xdp-dsr-layer2-lb-92b02f3e) 最后修改:2026 年 02 月 06 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏