note/dn42

DN42

DN42 是一个大型的去中心化的虚拟网络。比起其他的去中心化网络,DN42 注重的不是隐私和匿名,而是在 WireGuard、OpenVPN、Tinc 等 VPN 之上模拟一个真实的互联网。因此你会发现 DN42 中会用到 BGP、OSPF、DNS、SSL 等一系列现实中因特网的技术。比起真实的因特网,DN42 AS 间建立 peering 前不需要大费周张地架设信道或向 IXP 缴纳昂贵的会费——一切连接都是在 VPN 之上的。此外也正是因为这点,虽然真实的因特网设计上是去中心化的,但实际上的因特网组网多往大企业靠拢。而 DN42 只需要一台能够连接到因特网的机器就能参与对接,因此可以十分网状化。

简而言之你可以在 DN42 中以很低的成本享受到 role play 一个网络运营商的快乐。

申请 ASN 和 IP

DN42 的 ASN 分配范围是 AS4242420000-AS4242423999。IPv4 地址分配于 RFC1918 中的 172.20.0.0/14 中,IPv6 地址根据 RFC4193 分配一个 ULA 使用,因此连接到 DN42 并不会与因特网产生冲突。

DN42 Git 注册一个帐号,在 dashboard 右边就可以看见 dn42/registry 仓库,我们需要在这个仓库里创建几个文件。

  • data/mntner/<YOU>-MNT: 网络维护者的验证信息。
  • data/person/<YOU>-DN42: 网络维护者的联系信息。
  • data/organisation/ORG-<ORG>: 如果以一个组织的身份加入 DN42,则需要编辑此文件。
  • data/aut-num/AS424242<ASN>: 你要注册的 AS 号。
  • data/inet{,6}num/<IP>_<PREFIX>: 你要注册的 IPv4/IPv6 前缀。
  • data/route{,6}/<IP>_<PREFIX>: 将你的 AS 号和 IP 关联起来,在 ROA 中允许这个 AS 播这段 IP。

具体的格式可在 wiki 查看。IPv4 一般分配一个 /27,要注意有些地址是保留的,建议直接在 DN42 Free Explorer 挑一个顺眼的。IPv6 在 fd00::/8 下随机选择一个 /48 就可以了。

编辑好后用 ./check-my-stuff <YOU>-MNT 检查格式是否正确。提交时要用自己 mntner 对象中的 PGP 钥匙签名,git commit --gpg-sign=<keyid>。一个 pull request 中只能包含一个提交,如果你 commit 了多次记得 squash commit。

Peering

有了 ASN 和 IP,就可以开始 peering 了。以下是找 peer 的方式:

  • DN42 PingFinder。输入你的明网 IP 地址,便会有许多服务器 ping 你,按照延迟排序,并附上他们的联系方式。
  • KioubitYuuta 和 chrismoos(ssh://cli@cli.dn42.tech9.io) 的自动 peer 系统。
  • 我的 peer 信息页面。你可以在这里找到我的节点信息,配置好你的那边,然后发送邮件至 chuang at melty dot land 并附上你的节点信息,与我建立 peering。

在 DN42 中建立 peering 分为两步:建立 VPN 隧道和建立 BGP 会话。

建立隧道

目前 WireGuard 是 DN42 中使用较为广泛的 VPN 方案,它简单、无状态和安全。WireGuard 在 Linux 5.6 以后已经包含在了内核中,只需安装 wireguard-tools 就可以操作内核配置 WireGuard。

由于 WireGuard 会在内部路由,也就是发往 WireGuard interface 的流量会在根据 IP 找到对应的 allowed-ips 然后发往对应的 wg peer,因此单个 WireGuard 中的 allowed-ips 不能重叠。为了能让 BGP 配置路由,必须要每个 peer 一个 WireGuard interface。我采用的配置方式是在 systemd service unit 中运行脚本,在脚本中调用 wg 配置。这是 systemd unit:

# /etc/systemd/system/dn42-burble.service
[Unit]
After=network-online.target
Description=WireGuard DN42 Tunnel - burble
Requires=network-online.target

[Service]
ExecStart=/usr/local/bin/dn42-burble-start
ExecStopPost=/usr/local/bin/dn42-burble-post-stop
RemainAfterExit=true
Type=oneshot

[Install]
WantedBy=multi-user.target

这是启动脚本:

#!/bin/bash -e
/bin/ip link add dev "dn42-burble" type wireguard
/bin/wg set "dn42-burble" listen-port "22601" \
                          private-key "/run/secrets/wireguard" \
                          peer "oGk4IqY4+zDarNPQEULC/M/8dWnw8ylngTurH2UlR2k=" \
                          allowed-ips "172.16.0.0/12,10.0.0.0/8,fd00::/8,fe80::/10" \
                          endpoint "dn42-sg-sin2.burble.com:23632"
/bin/ip link set up dev "dn42-burble"
/bin/ip address add dev "dn42-burble" "172.23.36.33" peer "172.20.129.181/32"
/bin/ip address add dev "dn42-burble" "fe80::3632" peer "fe80::42:2601:37:1/64"

这是停止脚本:

#!/bin/bash -e
/bin/ip link delete dev "dn42-burble"
  • burble 是我要 peer 的网络的维护者的昵称。dn42-burble 是接口名。
  • listen-port 后面是本机监听的 UDP 端口,是提前与对方协商好的。
  • private-key 后面是这台机器的私钥文件,可以用 wg genkey > file 生成一个。
  • peer 后面是对方的公钥。
  • allowed-ips 后面是可路由至 peer 的 IP 地址,发往这个 interface 的 IP 包如果目的地址在其中则转发给这个 peer。这里填了 DN42 的 IPv4、NeoNetwork 的 IPv4、DN42 IPv6 为 ULA 所以有 fd00::/8、以及 IPv6 link-local 地址。
  • endpoint 后面是对方的明网 IP 和端口。
  • 172.23.36.33 是本机的 IPv4 隧道地址,可以是任何地址,只要与对方协商好即可。但通常使用你的 DN42 IP。
  • 172.20.129.181/32 是对方的 IPv4 隧道地址和这个隧道的掩码。
  • fe80::3632 是本机的 IPv6 隧道地址,是与对方协商好的。因为 IPv6 link-local 是 interface wise 的,可以十分有效地避免 IP 重叠,因此常常用一个 link-local 地址。我喜欢使用 fe80::<ASN 后四位> link-local 地址作为隧道地址。
  • fe80::42:2601:37:1/64 是对方的 IPv6 隧道地址和这个隧道的掩码。

如果你更喜欢使用 wg-quick 配置的话,请一定记住设置 Table = off 避免 wg-quick 对 allowed-ips 创建路由,并手动添加隧道地址。具体的配置可以在 wiki 上找到。

建立 BGP 会话

Bird2 是 Linux 上一款好用的 BGP daemon。DN42 wiki 上有一份很通用的 bird2 配置,使用这个配置的 dnpeers 模板,即可通过追加这样的配置建立 BGP 会话:

protocol bgp burble from dnpeers {
    neighbor 172.20.129.181 as 4242422601;
}
protocol bgp burble_v6 from dnpeers {
    neighbor fe80::42:2601:37:1 % 'dn42-burble' as 4242422601;
}

因为对方提供的是一个 link-local 地址,因此这里要指定 interface 名称。通过对示例配置的 dnpeer 模板进行一些更改,即可支持 extended next hop(需要 kernel >= 5.2):

 template bgp dnpeers {
     local as OWNAS;
     path metric 1;
 
     ipv4 {
+        extended next hop on;

这么做可以将 IPv4 地址通过 IPv6 接口路由,这样就只需要建立一个 BGP 会话了:

protocol bgp burble_v6 from dnpeers {
    neighbor fe80::42:2601:37:1 % 'dn42-burble' as 4242422601;
}

内网

上面只将一个节点连入了 DN42,但因特网是“网络的网络”,要模拟一个因特网,必须将我们的网络建立起来。要将路由器连接起来,首先要按照与上面相同的办法建立隧道。然后要配置路由,当然可以直接配置路由表,手动选路,但这在节点较多时十分麻烦。直接 full mesh 网络往往也无法得到最佳的选路方案(如 A-C 延迟高,但 A-B-C 延迟低),因此最好的方法还是使用路由协议交换路由表。BGP 是网络间的路由协议,而 IGP 则用于在网络中选路。常用的 IGP 有 OSPF、RIP,还有一些比较新的 IGP,如 Babel。这里使用 Bird2 的 OSPF。

protocol ospf v3 igp_ospf {
    ipv4 {
        import where source != RTS_BGP && is_self_net() ;
        export where source != RTS_BGP && is_self_net() ;
    };
    area {
        interface "node-cranberry" { cost 76; };
        interface "node-chokeberry" { cost 222; };
        interface "node-dummy" { stub; };
    };
}
protocol ospf v3 igp_ospf_v6 {
    ipv6 {
        import where source != RTS_BGP && is_self_net_v6() ;
        export where source != RTS_BGP && is_self_net_v6() ;
    };
    area 0 {
        # 因为隧道网络 cost 由 endpoint 决定,所以和上面一样
    };
}

OSPF 使用 cost 选路,如果希望包走某条路,只需要将这条路上的 cost 调低一点。如果希望 OSPF 通过延迟来选路,那么只需要将 cost 设置为延迟。这里的 node-dummy 为一个 dummy interface 或者 loopback interface,是用来确保有 interface down 了后 OSPF 不会乱选 router ID。创建方法为:

ip link add dev node-dummy type dummy
ip link set up dev node-dummy
ip address add dev node-dummy 172.23.36.33
ip address add dev node-dummy fd76:bcbe:1a52::1/128

Systemd unit:

# /etc/systemd/system/node-dummy.service
[Unit]
After=network-online.target
Description=OSPF dummy interface
Requires=network.target

[Service]
ExecStart=/usr/local/bin/node-dummy-start
RemainAfterExit=true
Type=oneshot

[Install]
WantedBy=multi-user.target

这样内网的路由是配好了。但如果有不止一个节点运行 BGP daemon 与其他网络 peering,为了在这些 BGP daemon 间交换外部网络的 BGP 路由信息,还需要建立 iBGP。iBGP 是对方和自己的 ASN 相同的 BGP,这种 BGP 只能传递一次,因此网络内所有 BGP daemon 直接都要两两建立起 iBGP。

template bgp nodes {
    local as OWNAS;
    ipv4 {
        extended next hop on;
        next hop self;
        import where source = RTS_BGP && is_valid_network() && !is_self_net();
        export where source = RTS_BGP && is_valid_network() && !is_self_net();
    };
    ipv6 {
        next hop self;
        import where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
        export where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6();
    };
}
protocol bgp node_chokeberry_v6 from nodes {
    neighbor fd76:bcbe:1a52::3 as OWNAS;
}

DNS

DN42 有自己的 TLD .dn42,要解析这个 TLD 的域名需要一些配置。

曾经 DN42 有 root servers,但现在已经没有了。现在的 DN42 直接提供 recursive servers 服务。你可以直接在 resolv.conf 中将 nameserver 设为这里列出的一个 recursive server。但这样你对明网 DNS 的查询也会发送到这些 recursive server 上。为了安全和降低延迟,最好自己运行一个 recursive server (也叫 resolver)。这里介绍两个 resolver 的使用方法。

Dnsmasq

Dnsmasq 是 Linux 下配起来最简单的 DNS resolver,/etc/dnsmasq.conf

# 明网 DNS
server=1.1.1.1
server=1.0.0.1

# DN42 DNS
server=/dn42/172.20.0.53
server=/neo/172.20.0.53
server=/20.172.in-addr.arpa/172.20.0.53
server=/21.172.in-addr.arpa/172.20.0.53
server=/22.172.in-addr.arpa/172.20.0.53
server=/23.172.in-addr.arpa/172.20.0.53
server=/10.in-addr.arpa/172.20.0.53
server=/d.f.ip6.arpa/fd42:d42:d42:54::1

如果你没有使用任何网络管理器,编辑 /etc/resolv.conf

nameserver 127.0.0.1

如果你使用 dhcpcd,那么 dhcpcd 使用 resolvconf 更新 /etc/resolv.conf,编辑 /etc/resolvconf.conf

nameservers='127.0.0.1'

如果你使用其他网络管理器,如 systemd-networkd、NetworkManager 等,请查看它们的文档配置使用本机的 resolver。

BIND

因为 resolver 是个 recursive nameserver,会占用本机的 53 端口。如果你还想在本机运行一个 authoritative nameserver,那么就不能使用 dnsmasq(当然可以也可以建立多个 lo 然后绑定到另一个 lo 上,太折腾了这里不讨论)。你需要一个既可以进行 recursive 查询又可以进行 authoritative DNS 查询的 DNS 服务器软件,比如 BIND。

BIND 的每一个 zone 的配置中可以指定 forwarder 将这个 zone 的请求转发到指定的服务器,因此能作为 resolver 使用。/etc/named.conf

options {
  validate-except {
    "dn42";
    "neo";
    "20.172.in-addr.arpa";
    "21.172.in-addr.arpa";
    "22.172.in-addr.arpa";
    "23.172.in-addr.arpa";
    "10.in-addr.arpa";
    "d.f.ip6.arpa";
  };
};
zone "dn42" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "neo" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "20.172.in-addr.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "21.172.in-addr.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "22.172.in-addr.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "23.172.in-addr.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "10.in-addr.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};
zone "d.f.ip6.arpa" {
  type forward;
  forwarders { 172.20.0.53; fd42:d42:d42:54::1; };
};

同样地,配置 resolv.conf 查询 127.0.0.1

Authoritative DNS

首先需要申请一个 .dn42 域名,在 dn42-registry 注册一个 dns 对象。这是我的 data/dns/chuang.dn42 可供参考:

domain:             chuang.dn42
admin-c:            CHUANGZHU-DN42
tech-c:             CHUANGZHU-DN42
mnt-by:             CHUANGZHU-MNT
nserver:            ilama.chuang.dn42 172.23.36.33
nserver:            ilama.chuang.dn42 fd76:bcbe:1a52::1
nserver:            chokeberry.chuang.dn42 172.23.36.35
nserver:            chokeberry.chuang.dn42 fd76:bcbe:1a52::3
source:             DN42

这样在 x.recursive-servers.dn42 查询 chuang.dn42NS 记录就会返回你填的服务器了,并且会通过 AAAAA 胶水记录附上 ilama.chuang.dn42 的 IP 地址。然后就可以着手搭建 authoritative nameserver,这里用上面提到的 BIND,配置跟在上面的配置的后面。

zone "chuang.dn42" {
  type master;
  file "/var/lib/named/chuang.dn42.zone";
  allow-query { any; };
};

/var/lib/named/chuang.dn42.zone 为 DNS zone 文件,保存了记录本身,这是它的格式:

$ORIGIN chuang.dn42.
$TTL 3600
                            # 联系邮箱,将 @ 替换为 .
@ IN SOA ilama.chuang.dn42. chuang.melty.land. (
         1 ; Serial
         7200 ; Refresh
         1800 ; Retry
         604800 ; Expire
         3600 ) ; TTL
# 服务器 A AAAA 记录
ilama IN A 172.23.36.33
ilama IN AAAA fd76:bcbe:1a52::1
chokeberry IN A 172.23.36.35
chokeberry IN AAAA fd76:bcbe:1a52::3
# NS 记录
@ IN NS ilama.chuang.dn42.
@ IN NS chokeberry.chuang.dn42.
# 根 A AAAA 记录
@ IN A 172.23.36.33
@ IN AAAA fd76:bcbe:1a52::1
# CNAME 记录
www IN CNAME chuang.dn42.
blog IN CNAME ilama.chuang.dn42.

反向 DNS

需要在 dn42-registry 中为你的 inetnuminet6num 对象加入 nserver 项。

nserver:            ilama.chuang.dn42
nserver:            chokeberry.chuang.dn42

这样查询这个块下的反向 DNS 就会返回你的服务器的 NS 记录了。

zone "32/27.36.23.172.in-addr.arpa" {
  type master;
  file "/var/lib/named/172.23.36.32_27.zone";
  allow-query { any; };
};
zone "2.5.a.1.e.b.c.b.6.7.d.f.ip6.arpa" {
  type master;
  file "/var/lib/named/fd76:bcbe:1a52::_48.zone";
  allow-query { any; };
};

/var/lib/named/172.23.36.32_27.zone:

$ORIGIN 32/27.36.23.172.in-addr.arpa.
$TTL 3600
@ IN SOA ilama.chuang.dn42. chuang.melty.land. (
         1 ; Serial
         7200 ; Refresh
         1800 ; Retry
         604800 ; Expire
         3600 ) ; TTL
33 IN PTR ilama.chuang.dn42.
35 IN PTR chokeberry.chuang.dn42.
@ IN NS ilama.chuang.dn42.
@ IN NS chokeberry.chuang.dn42.

/var/lib/named/fd76:bcbe:1a52::_48.zone:

$ORIGIN 2.5.a.1.e.b.c.b.6.7.d.f.ip6.arpa.
$TTL 3600
@ IN SOA ilama.chuang.dn42. chuang.melty.land. (
         1 ; Serial
         7200 ; Refresh
         1800 ; Retry
         604800 ; Expire
         3600 ) ; TTL
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR ilama.chuang.dn42.
3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR chokeberry.chuang.dn42.
@ IN NS ilama.chuang.dn42.
@ IN NS chokeberry.chuang.dn42.

SSL

DN42 有一个中心化的 CA,它的根证书可以在 wiki 上找到。导入系统和浏览器就可以访问 DN42 内的 HTTPS 站点了。

签发证书有两套 API,我个人喜欢用 ACME 兼容的 API。只需要将 LEGO 的 --server 替换为 https://acme-v2.acme.dn42/directory 就可以自动签发证书了。

About Me