从零构建 eBPF/XDP 二层直连返回负载均衡器技术教程

一、概述

1. 简介

A. 是什么

本教程介绍如何使用 eBPF 和 XDP(eXpress Data Path)从零开始构建一个二层(Layer 2)Direct Server Return(DSR)负载均衡器。DSR 是一种高性能负载均衡技术,允许后端服务器直接响应客户端请求,绕过负载均衡器的回程路径。

B. 为什么学

  • 理解现代高性能负载均衡的核心原理
  • 掌握 eBPF/XDP 在网络编程中的应用
  • 学习 DSR 技术如何解决传统 NAT 负载均衡的性能瓶颈
  • 深入理解二层网络转发机制

C. 学完能做什么

  • 使用 eBPF/XDP 编写二层负载均衡器
  • 配置虚拟 IP(VIP)实现 DSR 架构
  • 理解 MAC 地址重写与 IP 转发的区别
  • 部署高性能、低延迟的负载均衡解决方案

2. 前置知识

A. 必备技能

  • Linux 网络基础(IP、MAC、ARP 协议)
  • 基本 C 语言编程能力
  • 网络命令行工具使用(ip、tcpdump、curl)

B. 推荐知识

  • eBPF 和 XDP 基础概念
  • NAT 负载均衡原理
  • 二层网络与三层网络的区别

二、背景知识

1. NAT 负载均衡的局限性

在之前的教程中,我们构建了基于 NAT 的 XDP 负载均衡器。虽然这种方案可以正常工作,但存在以下问题:

A. 资源消耗

  • 负载均衡器需要处理双向流量(请求和响应)
  • 入站流量通常远小于出站流量
  • 例如:搜索查询或 AI 提示只有几字节,但响应可能有几 KB 甚至更多

B. 性能瓶颈

  • 负载均衡器成为网络瓶颈
  • 高流量场景下资源消耗显著

C. 客户端信息丢失

  • 后端服务器无法看到真实客户端 IP
  • NAT 负载均衡器重写了数据包头
  • 后端无法基于源 IP 进行会话管理或日志记录

2. DSR 解决方案

Direct Server Return(DSR)概念被引入以克服上述限制。DSR 有多种实现方式:

  • Layer 2 DSR(本教程重点)
  • IP-in-IP 封装
  • GRE(Generic Routing Encapsulation)封装
  • 基于 IP 或 TCP 头部字段的变体

三、网络拓扑

1. 实验环境

本教程使用一个包含五个节点的网络拓扑,分布在两个不同的网络中:

节点列表:
- lb:           192.168.178.10/24(负载均衡器)
- backend-01:   192.168.178.11/24(后端服务器 1)
- backend-02:   192.168.178.12/24(后端服务器 2)
- client:       10.0.0.20/16(客户端)
- gateway:      192.168.178.2/24 和 10.0.0.2/16(网关)

2. 架构图

graph TB
    C[客户端<br/>10.0.0.20/16] -->|请求| GW[网关<br/>192.168.178.2<br/>10.0.0.2]
    GW -->|转发| LB[负载均衡器<br/>192.168.178.10<br/>VIP: 192.168.178.15]
    LB -->|MAC重写| B1[后端服务器1<br/>192.168.178.11<br/>VIP on lo]
    LB -->|MAC重写| B2[后端服务器2<br/>192.168.178.12<br/>VIP on lo]
    B1 -->|直接响应| C
    B2 -.->|直接响应| C

DSR L2 负载均衡架构图

四、核心概念

1. DSR L2 负载均衡原理

与 NAT 负载均衡不同,DSR 保留原始客户端 IP,并允许后端服务器直接响应客户端,绕过负载均衡器的回程路径。

为了理解这是如何实现的,我们需要回答三个核心问题:

  1. 后端如何看到客户端 IP?

    • 数据包通过负载均衡器时,IP 头部保持不变
    • 只有 MAC 地址被重写
  2. 后端知道响应哪个客户端吗?

    • 后端看到原始客户端 IP,可以正常响应
  3. 如何确保客户端接收来自同一 IP 的响应?

    • 通过虚拟 IP(VIP)实现,负载均衡器和后端共享同一 IP

2. 虚拟 IP(VIP)

A. 什么是 VIP

在任何网络设置中,当客户端向特定端点发送请求时,期望响应来自相同的 IP 地址。如果响应来自不同的 IP,客户端的网络栈会认为出错并丢弃数据包。

虚拟 IP(Virtual IP,VIP)不是绑定到特定接口或节点的物理 IP,而是由多个节点共享的地址,用于处理同一服务的流量。

B. VIP 配置

在负载均衡器(lb 节点)上配置 VIP:

sudo ip addr add 192.168.178.15/32 dev eth0

在后端服务器(backend-01 和 backend-02)上配置 VIP:

sudo ip addr add 192.168.178.15/32 dev lo

C. 为什么后端使用 lo 接口

两个节点配置相同的 IP 不会混淆客户端或网关吗?这正是我们将 VIP 分配到后端节点的 lo 接口的原因。我们不希望后端广播虚拟 IP(通过 ARP)。

换句话说,网络中没人应该知道 VIP 存在于后端节点上,否则客户端可能绕过负载均衡器直接连接。

D. ARP 配置

为防止后端节点响应 lo 接口上 VIP 地址的 ARP 请求(即广播),在每个后端节点上运行以下命令:

# 仅当目标 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

3. 二层转发原理

负载均衡器不需要知道后端节点上存在 VIP。它只需要后端的 MAC 地址在二层转发数据包。当后端接收到数据包时,它解封装并识别虚拟 IP 为自己的 IP(配置在 lo 接口上),即使该 IP 从未在网络上广播。

这也正是这个概念被称为二层 DSR 的原因——负载均衡器仅使用后端的 MAC 地址到达后端节点,如果后端位于不同的(二层)网络中,这将无法工作。

sequenceDiagram
    participant C as 客户端
    participant G as 网关
    participant L as 负载均衡器
    participant B as 后端服务器

    C->>G: 请求 VIP (192.168.178.15)
    G->>L: 转发到负载均衡器
    Note over L: 1. 哈希选择后端<br/>2. 重写 MAC 地址<br/>3. IP 头保持不变
    L->>B: 转发(MAC 重写,源 IP 保留)
    Note over B: 识别 VIP 为自己的地址
    B->>C: 直接响应(绕过负载均衡器)

DSR L2 负载均衡时序图

五、实现步骤

1. 准备工作

A. 启用 IP 转发

在负载均衡器(lb 节点)上启用 IP 转发并填充 ARP 表:

sudo sysctl -w net.ipv4.ip_forward=1

# ping 后端节点以填充 ARP 表(bpf_fib_lookup 需要此步骤)
sudo ping -c1 192.168.178.11  # backend-01 节点/真实 IP
sudo ping -c1 192.168.178.12  # backend-02 节点/真实 IP

B. 启动 HTTP 服务器

在两个后端服务器(backend-01 和 backend-02)上启动 HTTP 服务器,显式绑定到 VIP:

python3 -m http.server 8000 --bind 192.168.178.15

2. XDP 负载均衡器代码

A. 核心 MAC 地址重写逻辑

在二层 DSR 负载均衡中,我们只需要在负载均衡器中更新 MAC 地址,以便数据包在二层正确传递:

// 使用简单哈希选择后端
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;
}

// 执行 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;
}

// 我们只需要更新 MAC 地址
// 后端需要在 lo 接口上有虚拟 IP(与负载均衡器相同)
// 源 IP 保留为客户端 IP,因此后端将直接响应客户端
__builtin_memcpy(eth->h_source, fib.smac, ETH_ALEN);
__builtin_memcpy(eth->h_dest, fib.dmac, ETH_ALEN);

B. 关键要点

  1. 无需连接跟踪

    • 负载均衡器不需要维护任何连接跟踪状态(与 NAT 负载均衡不同)
    • 简单地基于 MAC 地址重定向数据包
  2. 保留 IP 头部

    • 负载均衡器甚至不接触 IP 头部
    • 后端仍接收原始客户端源 IP
    • 后端可以直接回复客户端,绕过负载均衡器
  3. FIB 查找

    • 通过 bpf_fib_lookup 获取 MAC 地址
    • 使用 fib_lookup_v4_full 执行完整查找

3. 编译和运行

A. 编译负载均衡器

cd lab
go generate
go build

B. 运行负载均衡器

sudo ./lb -i eth0 --backends 192.168.178.11,192.168.178.12

参数说明:

  • -i eth0: 指定网络接口
  • --backends: 后端服务器真实 IP 列表(逗号分隔)

C. 测试负载均衡

从客户端节点查询 VIP:

curl http://192.168.178.15:8000

D. 验证流量

在后端服务器上查看 HTTP 服务器日志,确认请求确实来自客户端 IP:

10.0.0.20 - - [01/Jan/2026 16:03:45] "GET / HTTP/1.1" 200 -

E. 抓包验证 MAC 地址

在两个后端服务器上运行 tcpdump 查看 MAC 地址:

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 为目标地址)

F. 查看负载均衡器日志

sudo bpftool prog trace

六、技术限制

1. 二层网络要求

DSR L2 负载均衡仅在负载均衡器和后端服务器位于同一子网(即具有直接二层连接)时才能正常工作。

如果负载均衡器尝试将流量重定向到不同二层网络中的后端:

  1. 负载均衡器会将目标 MAC 地址设置为网关接口(试图退出当前二层网络)
  2. 由于只有负载均衡器上的 VIP 在网络上广播
  3. 网关会将数据包发回负载均衡器——因为这是它知道的该 VIP 的唯一路由
  4. 导致环路
graph TB
    LB[负载均衡器] -->|目标MAC=网关| GW[网关]
    GW -->|VIP路由回LB| LB
    LB -.环路.-> GW

    style LB fill:#f9f,stroke:#333,stroke-width:2px
    style GW fill:#ff9,stroke:#333,stroke-width:2px

DSR L2 跨网络问题

2. 架构约束

  • 要求后端服务器与负载均衡器共享同一网络
  • 增加整体故障风险——网络故障导致全部服务不可用

3. 解决方案

这正是 IPIP DSR 负载均衡发挥作用的地方,将在后续教程中介绍。

七、总结

1. 关键要点

A. DSR 优势

  • 后端服务器直接响应客户端,绕过负载均衡器
  • 保留原始客户端 IP,便于会话管理和日志记录
  • 减轻负载均衡器负担,提高整体性能

B. 二层 DSR 实现原理

  • 仅重写 MAC 地址,IP 头部保持不变
  • 通过虚拟 IP(VIP)实现 IP 一致性
  • 后端在 lo 接口配置 VIP,不参与 ARP 广播

C. 技术限制

  • 负载均衡器与后端必须在同一二层网络
  • 跨网络场景需要使用 IPIP 或 GRE 等封装技术

2. 下一步学习

  • IPIP DSR 负载均衡(跨网络场景)
  • GRE DSR 负载均衡
  • 高级哈希算法与一致性哈希
  • 健康检查与故障转移

参考资料

  1. Building an eBPF/XDP L2 Direct Server Return Load Balancer from Scratch
最后修改:2026 年 01 月 22 日
如果觉得我的文章对你有用,请随意赞赏