基于WireGuard组网,利用GRE+Bonding实现负载均衡

本文主要关注基于ip命令的配置, 尽量避免使用ifconfig, route, brctl等传统命令, 尽量避开使用systemd-network等网络管理器.

本文基于 利用OSPF协议实现WireGuard高可用 并假设已存在一个由WireGuard安全点对点连接组成的网络, 且网络中运行一种IGP协议(例如OSPF).

OSPF Network Diagram

网络拓扑如上图. 其中点对点链路均使用/30网段, 各路由器均运行OSPF协议. 现在想利用Router 10.65.1.1, 10.65.1.2 两台机器实现Router 10.65.2.210.65.2.1 流量最大化通信.

由于WireGuard本身运行在L3/IP层, 且官方版本不支持设置mac地址(有魔改版据说做到了), 我们无法利用Linux本身提供的Bonding功能来做原生负载均衡. 因此可以在 10.65.2.210.65.2.1 之间分别搭建两条经过不同路由的GRE隧道, 然后在两侧分别将两个GRE端口绑定起来.

GRE Tunnel Diagram

需要注意的是, GRE隧道分为GRE和GRETAP, 其中GRE也是运行在L3的, GRETAP则是运行在L2的. 尽管GRE没有加密功能, 但由于外层隧道本身是加密的, 所以不会有安全问题, 也避免了多次加密带来的性能损耗.

首先加载必要的kernel module (不过这一步似乎可以省略, 因为新建gre设备的时候会自动加载)

1
2
modprobe ip_gre
modprobe bonding

创建GRE隧道

在Router 10.65.2.2上:

1
2
3
4
5
6
7
ip link add gre1 type gretap local 10.65.0.2 remote 10.65.0.6 ttl 255
ip addr add 10.66.0.1/24 dev gre1
ip link set dev gre1 up

ip link add gre2 type gretap local 10.65.0.14 remote 10.65.0.10 ttl 255
ip addr add 10.66.1.1/24 dev gre2
ip link set dev gre2 up

在Router 10.65.2.1上:

1
2
3
4
5
6
7
ip link add gre1 type gretap local 10.65.0.6 remote 10.65.0.2
ip addr add 10.66.0.2/24 dev gre1
ip link set dev gre1 up

ip link add gre2 type gretap local 10.65.0.10 remote 10.65.0.14
ip addr add 10.66.1.2/24 dev gre2
ip link set dev gre2 up

此时两侧应该可以通过gre隧道ping通:

1
2
3
PING 10.66.0.2 (10.66.0.2) 56(84) bytes of data.
64 bytes from 10.66.0.2: icmp_seq=1 ttl=64 time=...
...

创建Bonding

注意: 向bonding添加slave时, 对应的设备状态不能为up.

在Router 10.65.2.2上:

1
2
3
4
5
6
7
8
9
10
11
12
13
ip link add bond0 type bond
ip link set dev bond0 type bond mode balance-rr
ip addr add 10.67.0.1/24 dev bond0

ip link set dev gre1 down
ip link set dev gre1 master bond0
ip link set dev gre1 up

ip link set dev gre2 down
ip link set dev gre2 master bond0
ip link set dev gre2 up

ip link set dev bond0 up

这里, 由于需求是尽量使用带宽, 这里采用了balance-rr模式, 即平均分配入流量到两个接口上. 此外还有 active-backup, balance-xor, broadcase, 802.3ad, balance-tlb, balance-alb 等模式.

在Router 10.65.2.1上:

1
2
3
4
5
6
7
8
9
10
11
12
13
ip link add bond0 type bond
ip link set dev bond0 type bond mode balance-rr
ip addr add 10.67.0.2/24 dev bond0

ip link set dev gre1c down
ip link set dev gre1c master bond0
ip link set dev gre1c up

ip link set dev gre2c down
ip link set dev gre2c master bond0
ip link set dev gre2c up

ip link set dev bond0 up

此时两侧应该可以通过bond0的地址ping通:

1
2
3
PING 10.67.0.1 (10.67.0.1) 56(84) bytes of data.
64 bytes from 10.67.0.1: icmp_seq=1 ttl=64 time=...
...

不知道为什么, 在两侧bond0都启动完成后, 如果只从一侧开始ping刚开始并不能ping通, 如果此时从另一侧也开始ping, 那么两侧从此都可以互相ping通. 推测可能是没有给bond0设置miimon等参数导致的. (MIIMON是Media Independent Interface Monitoring的缩写)

bonding的状态可以通过 /proc/net/bonding/bond0 获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)

Bonding Mode: load balancing (round-robin)
MII Status: up
MII Polling Interval (ms): 0
Up Delay (ms): 0
Down Delay (ms): 0
Peer Notification Delay (ms): 0

Slave Interface: gre1
MII Status: up
Speed: Unknown
Duplex: Unknown
Link Failure Count: 0
Permanent HW addr: ...
Slave queue ID: 0

Slave Interface: gre2
MII Status: up
Speed: Unknown
Duplex: Unknown
Link Failure Count: 0
Permanent HW addr: ...
Slave queue ID: 0

至此已基本搭建完毕. 在两侧通过bond0的地址使用iperf3进行测速, 实测可以达到几乎双倍的速度.

另外, 由于底层网络基于WG+OSPF, 当网络中有节点掉线的时, bond接口会有短暂的丢包(实际观测看要>50%, 几乎65%) 经过一段时间OSPF完成收敛后(默认配置下大约45秒), bond接口就会恢复正常. 推测如果bond接口本身配置了miimon可能在bond层会先剔除掉超时的slave.

最后我们来计算一下开销:

GRE with WireGuard Packet

只考虑IPv4的情况下, 从外到内分别是:

  • 外层IPv4, 20 bytes
  • UDP, 8 bytes
  • WireGuard, 32 bytes
  • 内层IPv4, 20 bytes
  • GRE, 4 bytes
  • 以太网帧头部, 14 bytes (因为用的是L2 GRETAP)

最终在基础MTU=1500的前提下, 最内层MTU还剩下1402. 如果外层内层均更换为IPv6, 由于IPv6 header为40 bytes, 那么最后留给最内层的MTU还剩下1362, 距离IPv6要求的最低1280还有一点空间.

最开始给gre隧道挂到bridge下面了, 结果两边bridge一开直接回环网络风暴… = =||

参考资料

GRE bridging, IPsec and NFQUEUE

SETUP GRE TUNNEL ON UBUNTU 20 LINUX SERVER

Syntax for changing the bond mode of an interface

ip-link(8) — Linux manual page

networking:bonding [Wiki]

Bonding - Debian Wiki

7.7. Using Channel Bonding

10.5 Configuring Network Interface Bonding

Switch flooding when bonding interfaces in Linux

A Beginner’s Guide to Generic Routing Encapsulation

How to create a GRE tunnel on Linux