WG上的Babel动态路由title

2025-02-05date
Redundant network based on wireguard and babel
description

    之前尝试过使用静态的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 的原有配置格式。分为 netdevsnetworks

    {
      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。

    Notice

    注意以上配置中的两个 RouteTable=false,关闭wg的自动路由配置非常重要

    Tips

    据我所知有人在实际配置addresses中的参数时,需要加入对端的对应地址作为Peer参数才能正常运作,但在我的实践中只有删掉这个参数Babel才能正常处理路由。如果后期路由调试遇到问题请优先关注这个细节,具体导致差异的配置暂时未知,可能和我只配置IPv6有关系。

    Tips

    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 就够用:

    𝐶(𝑖,𝑗)=(𝑖+𝑗)(𝑖+𝑗+1)2+𝑗

    从我的网络结构算出来的大概长这样:

    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;
      };
      #...
    }
    

    Nix实现

    接着完成最后一块拼图,即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。

    network-topology-v1

    generated by nix-topology, definition

    Footnotes

    1. 在 WireGuard 构建的 Overlay Network 上跑 babel 路由协议

    2. Run Babeld over Wireguard 2

    3. Wireguard performance tuning

    ©2018-2025 Secirian | CC BY-SA 4.0