Skip to content
Diffumist
Go back

浅尝 DN42

大概 5 年前就听说过 DN42,不过因为没有相对稳定的 VPS 和某些心智负担一直没有尝试,最近周末心血来潮就尝试了一下,简单记录一下流程。

DN42 ASN

DN42/registry 开 pull request 申请,注册账号之后最好添加 ssh 和 pgp public keys,pipeline ci 在合并前会验证 commit signature,Kioubit oauth 认证时也会用到。

PR 里需要提交以下 object:

可以参考 registry 里已有的提交,照葫芦画瓢就行。

DN42 DNS

比较通用的做法是用 IATA 机场代码 来命名 DNS,这样换 IDC 服务商时也无需向外部 peer 重定向 DNS 名称,我用了 dnscontrol 来管理 DNS,最好为公网 v4 和 v6 单独加 endpoint,双栈 DNS 记录可能会在 peer 时遇到线路问题。

写两个 helper func,ENDPOINT_RECORDS 为公网 DNS 记录,DN42_ENDPOINT_RECORDS 为 DN42 网络 DNS 记录。

function ENDPOINT_RECORDS(host, names, modifiers) {
  var extraModifiers = modifiers || [];
  DISALLOW_CF_PROXY_ON("ENDPOINT_RECORDS", extraModifiers);
  var records = HOST_RECORDS(host, names, [CF_PROXY_OFF].concat(extraModifiers));

  names.forEach(function (name) {
    if (host.ipv4) {
      records.push(A.apply(null, ["v4." + name, host.ipv4].concat([CF_PROXY_OFF], extraModifiers)));
    }
    if (host.ipv6) {
      records.push(
        AAAA.apply(null, ["v6." + name, host.ipv6].concat([CF_PROXY_OFF], extraModifiers)),
      );
    }
  });

  return records;
}

function DN42_HOST_RECORDS(host, names, modifiers) {
  var extraModifiers = modifiers || [];
  DISALLOW_CF_PROXY_ON("DN42_HOST_RECORDS", extraModifiers);
  var dn42Names = names.map(function (name) {
    return name + ".dn42";
  });

  return HOST_RECORDS(host, dn42Names, [CF_PROXY_OFF].concat(extraModifiers));
}
// DN42
DN42_ENDPOINT_RECORDS(DN42_HOSTS.ams0, ["ams-0"]),
ENDPOINT_RECORDS(HOSTS.ams0, ["ams-0"]),

预期得到的 DNS 记录长这样:

DN42 mesh

由于我用 NixOS,用到了 @prince213 的 nix-dn42 modules 来初始化 bird BGP 和 babel IGP。内部 mesh 基于 WireGuard 全互联 + babel,不使用 OSPF(babel 更适合隧道 mesh)。

nix-dn42 的最小启动配置:

# flake.nix
inputs.nix-dn42.url = "git+https://git.sr.ht/~prince213/nix-dn42";
inputs.dn42-registry = {
    url = "git+https://git.lantian.pub/backup/dn42-registry.git";
    flake = false;
};

# 节点配置
networking.dn42 = {
  enable = true;
  asn = 4242420642;

  ipv4 = {
    address = "172.22.64.65";       # 本节点 IPv4
    pool = "172.22.64.64/27";       # IPv4 段
  };
  ipv6 = {
    address = "fd22:1056:95a4:1::1";
    pool = "fd22:1056:95a4::/48";
  };

  roa = {
    enable = true;
    registry = inputs.dn42-registry; # ROA 数据源
  };

  babel = {
    enable = true;
    interfaces = {
      wg-ams0-sjc0 = { openFirewall = true; };
      # ... 其他 mesh link
    };
  };
};

services.bird = {
  enable = true;
  autoReload = true;
};

ROA(Route Origin Authorization)用来验证收到的路由前缀是否由注册的 ASN 宣告,防止路由劫持。nix-dn42 会从 dn42-registry checkout 生成 ROA 表,bird import filter 里会自动拒绝 ROA 不合规的路由。

把 nix-dn42 和 dn42-registry 加到 flake inputs,填好 ASN / IP / ROA / babel,bird 就会自动拉起。剩下的工作就是搭好节点间的 WireGuard tunnel,让 babel 在 interface 上跑起来。

DN42 peer

nix-dn42 没有管理外部 peer 的 options,所以写了一个 dn42-peer nixos module 来管理各机器上的 eBGP peers。

写完 nixos module 之后管理外部 peer 就很方便了:

my.services.dn42-peers.example-peer = {
  asn = 4242429999;
  listenPort = 29999;
  endpoint = "peer.example.com";
  peerPort = 20642;
  publicKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=";
  peerLinkLocal = "fe80::9999";
  mtu = 1420;
};

每个 peer 会自动生成:一个 WireGuard interface + systemd-networkd 配置 + bird BGP protocol(link-local + extended-next-hop)。

端口通常约定:2 + 对方 ASN 后 4 位

peer 时需要和对方确认的内容有:

模块默认复用 dn42_wg_private_key(和 mesh 同一把 key);如果对端要求独立 key,可以通过 privateKeyFile 覆盖。

Other Problem

之后实际配置中遇到了 peer 后 v4 路由黑洞的问题,而且 all.rp_filterdefault.rp_filter 均无法缓解,调查发现 systemd 带了段 50-default.conf,发行版打包可能会直接打进去。

# Source route verification
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.*.rp_filter = 2
-net.ipv4.conf.all.rp_filter

这会导致所有新创建的 wg peer interface rp_filter=2,要求「内核主路由表认为源地址可达」,但 dn42 数据面路由经常会变化,不对称路由下最好直接设置为:

net.ipv4.conf.all.rp_filter =  0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.*.rp_filter = 0


Previous Post
随想——关于身份标签
Next Post
反工具化角色被处理成工具:叙事的自我证伪