WG上的Babel动态路由title
之前尝试过使用静态的Wireguard路由定义来实现故障转移之类的东西,当然预期是用caddy内建的fallback来实现的,可不能指望路由真的自动迁移。 但是我折腾傻了也没搞明白这个wg到底怎么处理,在群友的建议下直接改造成了动态路由的 Overlay Network,Still based on wireguard.
为什么依然用wireguard而不是基于IPSec的 ranet 或者同时支持 IPSec 和 Wireguard 的 rait 呢? 因为没有合规性和非常极端的安全要求所以不想用比较古老而且性能稍微弱点的IPSec; 因为不喜欢go所以不想选择后者(主要是它们两个文档对于不熟悉IPSec的人来说太稀缺了)。
但是呢 Wireguard 它不支持 Multicast 而且我们使用 babel 进行路由,
所以不能采用 Wiregurad 本身基于目的地址的端口复用1,
于是就只能每个 Wireguard Interface 单个 Peer,那么如果要构建类似 full mesh 的网络就需要每个 node 上存在 nodes - 1
个interface 2。
庆幸的是我参与组网的机器全部是nixos,一切可以优雅而高效地处理,不用手动创建和定义每个接口网络。
要做的是在每个node上创建 n - 1
个具有唯一 peer 的 wireguard interface,然后再给它们配置地址使其互相一一连接,再把 babel 跑起来;
但是这些个地址有点讲究,babel要求 IPv6 link local address才能运行,
而 Wireguard 设备本身是不具备ll addr的,那我们就需要手动配置一下。
回顾nixos上配置Wireguard,其实基本符合 sd-networkd 的原有配置格式。分为 netdevs
和 networks
:
{
systemd.network = {
netdevs.wg2 = {
netdevConfig = {
Kind = "wireguard";
Name = "wg2";
MTUBytes = "1380";
};
wireguardConfig = {
PrivateKeyFile = config.vaultix.secrets.wgab.path;
ListenPort = 51821;
RouteTable = false;
};
wireguardPeers = [
{
PublicKey = "49xNnrpNKHAvYCDikO3XhiK94sUaSQ4leoCnTOQjWno=";
AllowedIPs = [ "::/0" "0.0.0.0/0"];
RouteTable = false;
}
];
};
networks."10-wg2" = {
# match the netdevs.wg2.netdevConfig.Name
matchConfig.Name = "wg2";
addresses = [
{
# address, identical across different WireGuard interfaces on the same machine.
Address = "fdcc::2/128";
}
# also ipv4 addr
{
# link local address, identical across different WireGuard interfaces on the same machine,
Address = "fe80::216:3eff:fe15:ec52/64";
Scope = "link";
}
];
networkConfig = {
DHCP = false;
};
};
};
}
Scope = "link"
的 address 就是 link local addr 了(其实不写它也会自动标注成 ll, 因为 fe80::/10
),这是必需的地址,其他的可以配一两个IPv4/v6地址,我这里只配了v6。
注意以上配置中的两个 RouteTable=false
,关闭wg的自动路由配置非常重要
据我所知有人在实际配置addresses中的参数时,需要加入对端的对应地址作为Peer参数才能正常运作,但在我的实践中只有删掉这个参数Babel才能正常处理路由。如果后期路由调试遇到问题请优先关注这个细节,具体导致差异的配置暂时未知,可能和我只配置IPv6有关系。
WireGuard 接口上的 MTU 应比其隧道数据包所经过的以太网接口的 MTU 小 60 字节(使用 IPv4 传输隧道数据包时;使用 IPv6 时小 80 字节)。隧道 TCP 数据包的 MSS 应再小 40 字节(当数据包本身使用 IPv4 时;使用 IPv6 时为 60 字节)3
上文的 link local address 可以用以下nushell脚本生成,或者使用参考2的python脚本。
$"fe80::(random binary 8 | encode hex --lower | split chars | window 4 --stride 4 | each {str join} | str join ':')"
babeld
的配置很简单,
# if you're using nixpkgs' module, this should be auto configured by module
# skip-kernel-setup true
local-path /var/run/babeld/ro.sock
router-id f2:3c:95:50:a1:73 # mac address or, read the doc, machine unique
# every wg interfaces needed.
interface wg-0 type tunnel rtt-max 256
interface wg-1 type tunnel rtt-max 256
interface wg-2 type tunnel rtt-max 256
# ...
redistribute ip fdcc::/64 ge 64 le 128 local allow
redistribute proto 42
redistribute local deny
当然如果想要收敛更快速一点请参阅文档进行详细配置。
需要注意本地的每个参与组网的wg interface都得写上,具体配置见doc。
上文的 router-id
每个机器都唯一,一般查一下mac地址填上就好了,推荐手动配置。也可以用如下生成:
random binary 6 | encode hex --lower | split chars | window 2 --stride 2 | each {str join} | str join ':'
刚刚提到过每台机器上都需要创建 nodes - 1
的interfaces, 也就是需要不停重复添加 netdevs 和 networks 配置,是个人都得累死,那么引入 nixos module 自动处理吧。
不过在这之前我们还有个问题。
唯一端口分配
因为每个node与另一node连接都需要占用双方一个端口,为统一管理,我需要预设所有node一对一连接所使用的端口唯一(AB连接时使用同一端口号),并且nodes数量变动不改变已有端口配对。 为解决这个问题我给每个机器递增唯一ID, 然后使用康托尔配对将两个机器的ID即两个自然数映射到唯一的自然数, 作为index,之后再加上一个 base value 就是特定两台机器的全局唯一端口号。
因为我的机器数量比较少(小于10)所以原始的 Cantor Pairing 就够用:
从我的网络结构算出来的大概长这样:
nix-repl> :p (lib.conn {})
{
abhoth = {
azasos = 51850;
eihort = 51825;
hastur = 51814;
kaambl = 51819;
yidhra = 51832;
};
azasos = {
abhoth = 51850;
eihort = 51833;
hastur = 51820;
kaambl = 51826;
yidhra = 51841;
};
eihort = {
abhoth = 51825;
azasos = 51833;
hastur = 51805;
kaambl = 51808;
yidhra = 51818;
};
#...
}
接着完成最后一块拼图,即wg接口和地址配置的自动化。
生成
Nix实现, 由于我的配置还涉及反审查和NAT下的机器的自动处理,可能会稍微复杂一点(填了半天真值表)。
回顾我们已经得到的信息,
- peer 双端的hostname
- peer双端端口
- 双端密钥
- 双端内网地址
- peer的Endpoint
要素俱全,然后生成所有对端的iface和地址配置,见如上实现。我推荐将地址信息和唯一id什么的统一管理,方便删改。
babeld
的调试可以通过:
echo 'dump'|sudo nc -U /var/run/babeld/ro.sock
来完成。
bird
这里是之后迁移的bird配置,用以替代babeld
{
services.bird = {
enable = true;
config = ''
log syslog all;
debug protocols all;
router id 10.0.0.1; # machine unique
protocol device {}
protocol direct {
ipv6;
};
define SELFSET = [ fdcc::/64+ ];
function is_self_net() -> bool
{
return net ~ SELFSET;
}
protocol kernel {
ipv6 {
import none;
export filter {
if source = RTS_STATIC then reject;
accept;
};
};
};
protocol babel {
interface "wg-*" {
type tunnel;
extended next hop yes;
};
ipv6 {
export where is_self_net();
};
};
'';
};
}
在我的六台机器全联通状态下,预期的路由是:
> sudo birdc show babel route
BIRD 3.0.1 ready.
babel1:
Prefix Nexthop Interface Metric F Seqno Expires
fdcc::6/128 fe80::216:3eff:fe0f:37d8 wg-hastur 199 4 41.909
fdcc::6/128 fe80::216:3eff:fe4f:e85f wg-eihort 196 4 42.489
fdcc::6/128 fe80::216:3eff:fe15:ec52 wg-abhoth 195 4 55.647
fdcc::6/128 fe80::216:3eff:fe6c:5a57 wg-azasos 96 * 4 47.541
fdcc::6/128 fe80::216:3eff:fe41:49c6 wg-yidhra 200 4 54.505
# 手动分隔
fdcc::3/128 fe80::216:3eff:fe0f:37d8 wg-hastur 199 + 185 41.909
fdcc::3/128 fe80::216:3eff:fe4f:e85f wg-eihort 99 * 185 42.489
fdcc::3/128 fe80::216:3eff:fe15:ec52 wg-abhoth 192 + 185 55.647
fdcc::3/128 fe80::216:3eff:fe6c:5a57 wg-azasos 193 185 47.541
fdcc::3/128 fe80::216:3eff:fe41:49c6 wg-yidhra 202 185 54.505
fdcc::1/128 fe80::216:3eff:fe0f:37d8 wg-hastur 103 * 203 46.935
fdcc::1/128 fe80::216:3eff:fe4f:e85f wg-eihort 195 + 203 47.036
fdcc::1/128 fe80::216:3eff:fe15:ec52 wg-abhoth 288 203 55.647
fdcc::1/128 fe80::216:3eff:fe6c:5a57 wg-azasos 194 + 203 47.541
fdcc::1/128 fe80::216:3eff:fe41:49c6 wg-yidhra 200 + 203 54.505
fdcc::4/128 fe80::216:3eff:fe0f:37d8 wg-hastur 199 110 41.909
fdcc::4/128 fe80::216:3eff:fe4f:e85f wg-eihort 195 110 42.489
fdcc::4/128 fe80::216:3eff:fe15:ec52 wg-abhoth 192 110 55.647
fdcc::4/128 fe80::216:3eff:fe6c:5a57 wg-azasos 193 110 47.541
fdcc::4/128 fe80::216:3eff:fe41:49c6 wg-yidhra 104 * 110 54.505
fdcc::5/128 fe80::216:3eff:fe0f:37d8 wg-hastur 199 463 41.909
fdcc::5/128 fe80::216:3eff:fe4f:e85f wg-eihort 195 464 45.468
fdcc::5/128 fe80::216:3eff:fe15:ec52 wg-abhoth 96 * 464 55.647
fdcc::5/128 fe80::216:3eff:fe6c:5a57 wg-azasos 192 464 47.541
fdcc::5/128 fe80::216:3eff:fe41:49c6 wg-yidhra 200 464 54.505
F
为星号代表已选择的路由。如果有哪个peer断开了,首先metric会上涨,到达阈值后自动切换到metric较小的路由(via other interface/peer)。
bird的调试比babeld舒适太多,如果设备性能不是特别寒酸建议还是跑bird。
generated by nix-topology, definition