起因是租了一台物理机, 然后IDC那边没有提供PVE和Debian系统镜像, 只有Ubuntu、CentOS、Windows系列. 同时数据盘是通过多路iSCSI提供的. 我希望使用PVE来对不同使用场景进行隔离, 因此尝试重装并迁移上述配置. 备份配置 首先对系统做个大致的检查, 可发现: 系统存在两张网卡, 一张enp24s0f0接入了公网地址, 用于外部访问; 一张enp24s0f1接入了192.168.128.153的私网地址 数据盘被映射为了/dev/mapper/mpatha /etc/iscsi下存在两个iSCSI Node的配置, 分别为192.168.128.250:3260、192.168.128.252:3260, 但是二者都对应iqn.2024-12.com.ceph:iscsi. 不难推断出, 数据盘挂载是通过同时配置两个iSCSI Node, 再使用多路的方式合并成一个设备. 查看一下系统的网络配置: network: version: 2 renderer: networkd ethernets: enp24s0f0: addresses: [211.154.[数据删除]/24] routes: - to: default via: [数据删除] match: macaddress: ac:1f:6b:0b:e2:d4 set-name: enp24s0f0 nameservers: addresses: - 114.114.114.114 - 8.8.8.8 enp24s0f1: addresses: - 192.168.128.153/17 match: macaddress: ac:1f:6b:0b:e2:d5 set-name: enp24s0f1 发现就是非常简单的静态路由, 内网网卡甚至没有默认路由, 直接绑定IP即可. 然后将/etc/iscsi下iSCSI的配置文件保存一下, 其中包含了账户密码等信息 重装Debian 此次使用的是bin456789/reinstall重装脚本. 下载脚本: curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O ${_##*/} $_ 重装成Debian 13: bash reinstall.sh debian 13 然后根据提示输入你想要设置的密码即可 如果不出意外的话, 等待10分钟左右, 会自动完成并重装成一个纯净的Debian 13. 中途可以通过ssh配合设置的密码连接, 查看安装进度. 重装完成后按照惯例进行一个换源和apt upgrade, 即可得到一个纯净的Debian 13啦 换源直接参考USTC镜像站的教程即可 安装Proxmox VE 这一步主要参考Proxmox官方的教程即可 需要注意，上述脚本安装的Debian会将主机名设置为localhost，你如果想要修改的话请在配置Hostname前修改并将hosts中的主机名改成你修改的主机名而非localhost 配置Hostname Proxmox VE要求为当前的主机名配置一个指向非回环地址的Hosts: The hostname of your machine must be resolvable to an IP address. This IP address must not be a loopback one like 127.0.0.1 but one that you and other hosts can connect to. 比如此处我的服务器IP为211.154.[数据删除], 我需要在/etc/hosts中添加如下的一条记录: 127.0.0.1 localhost +211.154.[数据删除] localhost ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 保存后, 使用hostname --ip-address检查是否会输出设置的非回环地址: ::1 127.0.0.1 211.154.[数据删除] 添加Proxmox VE软件源 Debian 13使用了Deb822格式当然你想用sources.list也可以, 因此直接参考USTC的Proxmox镜像站即可: cat > /etc/apt/sources.list.d/pve-no-subscription.sources <<EOF Types: deb URIs: https://mirrors.ustc.edu.cn/proxmox/debian/pve Suites: trixie Components: pve-no-subscription Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg EOF 此处需要同步迁移一个keyring过来但是我上网找了一圈没找到, 因此我选择从我现有的一个Proxmox VE服务器上拉过来一份. 放在这里了: proxmox-keyrings.zip 将公钥文件解压放在/usr/share/keyrings/中, 然后运行 apt update apt upgrade -y 即可同步Proxmox VE软件源 安装Proxmox VE内核 使用如下命令安装PVE内核并重启以应用新内核: apt install proxmox-default-kernel reboot 之后通过uname -r应该能看到当前使用的是类似于6.17.2-2-pve这样以pve结尾的内核版本, 代表新内核应用成功. 安装Proxmox VE相关软件包 使用apt安装对应软件包: apt install proxmox-ve postfix open-iscsi chrony 配置过程中会需要设置postfix邮件服务器, 官方解释: 如果您的网络中有邮件服务器, 则应将postfix配置为Satellite system. 然后, 您现有的邮件服务器将成为中继主机, 将Proxmox VE发送的电子邮件路由到其最终收件人. 如果您不知道在此处输入什么, 请仅选择Local only, 并保持系统hostname不变. 之后应该能访问https://<你的服务器地址>:8006来打开Web控制台了, 账户为root, 密码为你的root密码, 即重装Debian时配置的密码. 删除旧的Debian内核和os-prober 使用以下命令: apt remove linux-image-amd64 'linux-image-6.1*' update-grub apt remove os-prober 来移除旧的Debian内核, 更新grub并移除os-prober. 移除os-prober不是必须的, 但是官方建议这么做, 因为它可能会误将虚拟机的引导文件认成多系统的引导文件, 导致将不该加的一些东西加到引导列表中. 至此, Proxmox VE的安装就完成了, 已经可以正常使用啦！ 配置内网网卡 因为IDC那边iSCSI网卡和公网走的不是同一张, 而重装的时候丢失了这部分的配置, 因此需要手动配置一下内网的网卡. 打开Proxmox VE的Web后台, 找到Datacenter-localhost（主机名）-Network, 编辑内网网卡, 如我这里的是ens6f1, 填入上面备份的IPv4的CIDR格式: 192.168.128.153/17并勾选Autostart, 接着保存即可. 接着使用命令来设置网卡状态为UP: ip link set ens6f1 up 现在能Ping通内网iSCSI服务器的IP了. 配置数据盘 iSCSI 在上一步中, 我们应该已经安装了iscsiadm所需的软件包open-iscsi, 我们只需要按照之前备份的配置重新设置node即可. 首先发现一下iSCSI存储: iscsiadm -m discovery -t st -p 192.168.128.250:3260 可以得到原先存在的两个LUN Target: 192.168.128.250:3260,1 iqn.2024-12.com.ceph:iscsi 192.168.128.252:3260,2 iqn.2024-12.com.ceph:iscsi 将备份的配置文件传到服务器上, 覆盖掉原先的/etc/iscsi中的配置, 同时, 在我备份的配置中可以找到验证方面的配置: # /etc/iscsi/nodes/iqn.2024-12.com.ceph:iscsi/192.168.128.250,3260,1/default # BEGIN RECORD 2.1.5 node.name = iqn.2024-12.com.ceph:iscsi ... # 略去一些不重要的配置 node.session.auth.authmethod = CHAP node.session.auth.username = [数据删除] node.session.auth.password = [数据删除] node.session.auth.chap_algs = MD5 ... # 略去一些不重要的配置 # /etc/iscsi/nodes/iqn.2024-12.com.ceph:iscsi/192.168.128.252,3260,2/default # BEGIN RECORD 2.1.5 node.name = iqn.2024-12.com.ceph:iscsi ... # 略去一些不重要的配置 node.session.auth.authmethod = CHAP node.session.auth.username = [数据删除] node.session.auth.password = [数据删除] node.session.auth.chap_algs = MD5 ... # 略去一些不重要的配置 按照这些配置文件写入新的系统: iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.session.auth.authmethod -v CHAP iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.session.auth.username -v [数据删除] iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.session.auth.password -v [数据删除] iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.session.auth.chap_algs -v MD5 iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.252:3260 -o update -n node.session.auth.authmethod -v CHAP iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.252:3260 -o update -n node.session.auth.username -v [数据删除] iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.252:3260 -o update -n node.session.auth.password -v [数据删除] iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.session.auth.chap_algs -v MD5 （我不知道为什么验证信息需要单独写入一次, 但是实测下来不重写它无法登录） 接着, 使用: iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 --login iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.252:3260 --login 登录Target, 接着使用: iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.250:3260 -o update -n node.startup -v automatic iscsiadm -m node -T iqn.2024-12.com.ceph:iscsi -p 192.168.128.252:3260 -o update -n node.startup -v automatic 开启开机自动挂载. 这个时候通过lsblk之类的工具查看磁盘应该就能发现多了两块硬盘, 我这里多出来了sdb和sdc. 配置multipath多路径 关于如何识别出是多路径设备, 我尝试通过: /usr/lib/udev/scsi_id --whitelisted --device=/dev/sdb /usr/lib/udev/scsi_id --whitelisted --device=/dev/sdc 查看两个磁盘设备的scsi_id, 发现二者是相同的, 因此可推断二者是同一块盘, 使用了多路来实现类似负载均衡和故障转移的效果 使用apt安装multipath: apt install multipath-tools 接着, 创建/etc/multipath.conf并填入: defaults { user_friendly_names yes find_multipaths yes } 配置multipathd开机自启: systemctl start multipathd systemctl enable multipathd 接着, 使用如下指令扫描并自动配置多路设备: multipath -ll 会输出: mpatha(360014056229953ef442476e85501bfd7)dm-0LIO-ORG,TCMU device size=500G features='1 queue_if_no_path' hwhandler='1 alua'wp=rw |-+- policy='service-time 0' prio=50 status=active | `- 14:0:0:152 sdb 8:16 active ready running `-+- policy='service-time 0' prio=50 status=active `- 14:0:0:152 sdc 8:16 active ready running 可看到已经将两块盘识别成同一个多路设备了. 此时, 可以在/dev/mapper/下找到多路设备的磁盘: root@localhost:/dev/mapper# ls control mpatha mpatha即为多路聚合的磁盘. 如果没扫描到，可尝试使用: rescan-scsi-bus.sh 重新扫描SCSI总线后再次尝试，若提示找不到这个指令可以用apt install sg3-utils安装. 实在不行咱重启一下 配置Proxmox VE使用数据盘 因为我们使用了多路, 因此不能直接选择添加iSCSI类型的存储. 使用如下指令创建PV和VG: pvcreate /dev/mapper/mpatha vgcreate <vg名称> /dev/mapper/mpatha 此处我将整块盘都配置成了PV, 你也可以单独划分出来一个分区来做这件事 完成后, 打开Proxmox VE的后台管理, 找到Datacenter-Storage, 点击Add-LVM, Volume group选择刚刚创建的VG的名称, ID自己给他命个名, 然后点击Add即可. 自此, 所有原系统的配置应该已经都迁移过来了 配置NAT NAT地址转换 因为只买了一个IPv4地址, 所以需要配置一下NAT来让所有虚拟机都能正常上网. 打开/etc/network/interfaces, 添加如下内容: auto vmbr0 iface vmbr0 inet static address 192.168.100.1 netmask 255.255.255.0 bridge_ports none bridge_stp off bridge_fd 0 post-up echo 1 > /proc/sys/net/ipv4/ip_forward post-up iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o ens6f0 -j MASQUERADE post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 post-up iptables -A FORWARD -i vmbr0 -j ACCEPT post-down iptables -t nat -D POSTROUTING -s 192.168.100.0/24 -o ens6f0 -j MASQUERADE post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 post-down iptables -D FORWARD -i vmbr0 -j ACCEPT 其中, vmbr0为NAT网桥, 网桥IP段为192.168.100.0/24 , 该网段流量会被转换为ens6f0外网网卡的IP发出, 并在收到回复时转换为原始IP, 实现共享外部IP. 接着, 使用: ifreload -a 重载配置. 到此, 虚拟机就已经能实现上网了, 只需要安装的时候配置静态地址为192.168.100.0/24内的地址, 默认网关设置为192.168.100.1, 并配置DNS地址即可. 端口转发 懒了, 直接拷打AI 让AI写了个配置脚本/usr/local/bin/natmgr: #!/bin/bash # =================配置区域================= # 公网网卡名称 (请根据实际情况修改) PUB_IF="ens6f0" # ========================================= ACTION=$1 ARG1=$2 ARG2=$3 ARG3=$4 ARG4=$5 # 检查是否为 root 用户 if [ "$EUID" -ne 0 ]; then echo "请使用 root 权限运行此脚本" exit 1 fi # 生成随机 ID (6位字符) generate_id() { # 引入纳秒和随机盐以确保即使脚本执行速度很快，ID也不会重复 echo "$RANDOM $(date +%s%N)" | md5sum | head -c 6 } # 显示帮助信息 usage() { echo "用法: $0 {add|del|list|save} [参数]" echo "" echo "命令:" echo " add <公网端口> <内网IP> <内网端口> [协议] 添加转发规则" echo " [协议] 可选: tcp, udp, both (默认: both)" echo " del <ID> 通过 ID 删除转发规则" echo " list 查看当前所有转发规则" echo " save 保存当前规则，使其在重启后仍然存在 (必须运行!)" echo "" echo "示例:" echo " $0 add 8080 192.168.100.101 80 both" echo " $0 save" echo "" } # 内部函数：添加单条协议规则 _add_single_rule() { local PROTO=$1 local L_PORT=$2 local T_IP=$3 local T_PORT=$4 local RULE_ID=$(generate_id) local COMMENT="NAT_ID:${RULE_ID}" # 1. 添加 DNAT 规则 (PREROUTING 链) iptables -t nat -A PREROUTING -i $PUB_IF -p $PROTO --dport $L_PORT -j DNAT --to-destination $T_IP:$T_PORT -m comment --comment "$COMMENT" # 2. 添加 FORWARD 规则 (允许数据包通过) iptables -A FORWARD -p $PROTO -d $T_IP --dport $T_PORT -m comment --comment "$COMMENT" -j ACCEPT # 输出结果 printf "%-10s %-10s %-10s %-20s %-10s\n" "$RULE_ID" "$PROTO" "$L_PORT" "$T_IP:$T_PORT" "成功" # 提醒用户保存 echo "请运行 '$0 save' 命令以确保规则在重启后仍然存在。" } # 主添加函数 add_rule() { local L_PORT=$1 local T_IP=$2 local T_PORT=$3 local PROTO_REQ=${4:-both} # 默认为 both if | -z "$T_IP" || -z "$T_PORT" ; then echo "错误: 参数缺失" usage exit 1 fi # 转换为小写 PROTO_REQ=$(echo "$PROTO_REQ" | tr '[:upper:]' '[:lower:]') echo "正在添加规则..." printf "%-10s %-10s %-10s %-20s %-10s\n" "ID" "协议" "公网端口" "目标地址" "状态" echo "------------------------------------------------------------------" if "$PROTO_REQ" == "tcp" ; then _add_single_rule "tcp" "$L_PORT" "$T_IP" "$T_PORT" elif "$PROTO_REQ" == "udp" ; then _add_single_rule "udp" "$L_PORT" "$T_IP" "$T_PORT" elif "$PROTO_REQ" == "both" ; then _add_single_rule "tcp" "$L_PORT" "$T_IP" "$T_PORT" _add_single_rule "udp" "$L_PORT" "$T_IP" "$T_PORT" else echo "错误: 不支持的协议 '$PROTO_REQ'。请使用 tcp, udp 或 both。" exit 1 fi echo "------------------------------------------------------------------" } # 删除规则 (基于行号倒序删除) del_rule() { local RULE_ID=$1 if -z "$RULE_ID" ; then echo "错误: 请提供规则 ID" usage exit 1 fi echo "正在搜索 ID 为 [${RULE_ID}] 的规则..." local FOUND=0 # --- 清理 NAT 表 (PREROUTING) --- LINES=$(iptables -t nat -nL PREROUTING --line-numbers | grep "NAT_ID:${RULE_ID}" | awk '{print $1}' | sort -rn) if ! -z "$LINES" ; then for line in $LINES; do iptables -t nat -D PREROUTING $line echo "已删除 NAT 表 PREROUTING 链第 $line 行" FOUND=1 done fi # --- 清理 Filter 表 (FORWARD) --- LINES=$(iptables -t filter -nL FORWARD --line-numbers | grep "NAT_ID:${RULE_ID}" | awk '{print $1}' | sort -rn) if ! -z "$LINES" ; then for line in $LINES; do iptables -t filter -D FORWARD $line echo "已删除 Filter 表 FORWARD 链第 $line 行" FOUND=1 done fi if $FOUND -eq 0 ; then echo "未找到 ID 为 $RULE_ID 的规则。" else echo "删除操作完成。" echo "请运行 '$0 save' 命令以更新持久化配置文件。" fi } # 保存规则到磁盘 (新增功能) save_rules() { echo "正在保存当前的 iptables 规则..." # netfilter-persistent 是 Debian/Proxmox 中管理 iptables-persistent 的服务 if command -v netfilter-persistent &> /dev/null; then netfilter-persistent save if [ $? -eq 0 ]; then echo "✅ 规则已成功保存到 /etc/iptables/rules.v4，将在系统重启后自动恢复。" else echo "❌ 规则保存失败。请检查 'netfilter-persistent' 服务状态。" fi else echo "警告: 未找到 'netfilter-persistent' 命令。" echo "请确保已安装 'iptables-persistent' 软件包。" echo "安装命令: apt update && apt install iptables-persistent" fi } # 列出规则 list_rules() { echo "当前端口转发规则列表:" printf "%-10s %-10s %-10s %-20s %-10s\n" "ID" "协议" "公网端口" "目标地址" "目标端口" echo "------------------------------------------------------------------" # 解析 iptables 输出 iptables -t nat -nL PREROUTING -v | grep "NAT_ID:" | while read line; do id=$(echo "$line" | grep -oP '(?<=NAT_ID:)[^ ]*') # 提取协议 if echo "$line" | grep -q "tcp"; then proto="tcp"; else proto="udp"; fi # 提取 dpt: 之后的端口 l_port=$(echo "$line" | grep -oP '(?<=dpt:)[0-9]+') # 提取 to: 之后的 IP:Port target=$(echo "$line" | grep -oP '(?<=to:).*') t_ip=${target%:*} t_port=${target#*:} printf "%-10s %-10s %-10s %-20s %-10s\n" "$id" "$proto" "$l_port" "$t_ip" "$t_port" done } # 主逻辑 case "$ACTION" in add) add_rule "$ARG1" "$ARG2" "$ARG3" "$ARG4" ;; del) del_rule "$ARG1" ;; list) list_rules exit 0 ;; save) save_rules ;; *) usage exit 1 ;; esac save_rules 让其自动添加/删除iptables规则实现端口转发. 记得chmod +x 通过iptables-persistent实现保存配置开机自动加载: apt install iptables-persistent 配置过程中会询问是否需要保存当前规则，Yes或者No都可以。 添加转发规则时使用natmgr add <主机监听地址> <虚拟机内网IP> <虚拟机端口> [tcp/udp/both]即可, 脚本会自动分配一个唯一ID, 删除时使用natmgr del <ID>即可. 也可使用natmgr list查看已有转发列表. 参考文章: bin456789/reinstall: 一键DD/重装脚本 (One-click reinstall OS on VPS) - GitHub Install Proxmox VE on Debian 12 Bookworm - Proxmox VE PVE连接 TrueNAS iSCSI存储实现本地无盘化_pve iscsi-CSDN博客 ProxmoxVE (PVE) NAT 网络配置方法 - Oskyla 烹茶室
DN42&OneManISP - 使用VRF实现公网BGP和DN42共用一台机器
背景 目前同一区域内公网BGP和DN42分别用了一台VPS，也就是说同一个区域需要两台机器。从群友那里得知了VRF，便想着通过VRF实现同一台机器同时处理公网BGP并加入DN42。 注意：VRF方案因为其隔离性，会导致DN42无法访问主机的服务。如果你需要在服务器上跑诸如DNS之类的服务给DN42用，你可能需要再单独配置端口转发或者veth，但是不在本文讨论范围内。（这也是我实际生产环境最终还是没有采用VRF的原因） VRF的优点 虽然说DN42使用的IP段是私有地址，并且它的ASN用的都是内部ASN，理论上不会和公网BGP相互干扰，但是如果共用同一张路由表，可能会造成路由污染、管理复杂等问题。 VRF（Virtual Routing and Forwarding，虚拟路由转发）可以实现在一台机器上创建多个路由表，也就是说我们可以通过它将DN42的路由单独放到一个路由表里，以实现将DN42路由表和公网路由表相隔离。这么做的优点有： 绝对的安全与策略隔离：DN42路由表和公网路由表相隔离，从根本上杜绝了路由泄露的可能性。 清晰的运维管理：可以使用birdc show route table t_dn42和birdc show route table t_inet来分别查看和调试两张完全独立的路由表，一目了然。 故障域隔离：若果DN42的某个对等体发生Flap，这些影响将被完全限制在dn42的路由表内，不会消耗公网实例的路由计算资源，也不会影响公网的转发性能。 更符合现代网络设计理念：在现代网络工程中，为不同的路由域（生产、测试、客户、合作伙伴）使用VRF是标准做法。它将你的设备逻辑上划分成了多个虚拟路由器。 配置 系统部分 创建VRF设备 使用以下指令创建一个名为dn42-vrf的VRF设备并关联到系统的1042号路由表： ip link add dn42-vrf type vrf table 1042 ip link set dev dn42-vrf up # 启用 路由表号可以按照你自己的喜好修改，但是请避开以下几个保留路由表编号： 名称 ID 说明 unspec 0 未指定，基本不用 main 254 主路由表，大多数普通路由都放在这里 default 253 一般不用，保留 local 255 本机路由表，存放127.0.0.1/8、本机 IP、广播地址等，不能改 将现有的相应网卡关联到VRF 按照我目前的DN42网络为例，有若干WireGuard网卡和一个dummy网卡是用于DN42的，因此将这几个网卡都关联到VRF中： ip link set dev <网卡名> master dn42-vrf 需要注意的是，网卡关联到VRF之后可能会丢失地址，因此需要重新为其添加一次地址，如： ip addr add 172.20.234.225 dev dn42 完成之后，通过ip a应该能看到对应网卡的master是dn42-vrf： 156: dn42: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue master dn42-vrf state UNKNOWN group default qlen 1000 link/ether b6:f5:28:ed:23:04 brd ff:ff:ff:ff:ff:ff inet 172.20.234.225/32 scope global dn42 valid_lft forever preferred_lft forever inet6 fd18:3e15:61d0::1/128 scope global valid_lft forever preferred_lft forever inet6 fe80::b4f5:28ff:feed:2304/64 scope link valid_lft forever preferred_lft forever 持久化 我使用了ifupdown2来实现开机自动加载dummy网卡和VRF设备。 auto dn42-vrf iface dn42-vrf inet manual vrf-table 1042 auto dn42 iface dn42 inet static pre-up ip link add $IFACE type dummy || true vrf dn42-vrf address <IPv4 Address>/32 address <IPv6 Address>/128 post-down ip link del $IFACE 我的dummy网卡名称为dn42，如果你的名称不一样请按需要修改。创建完后使用ifup dn42-vrf && ifup dn42即可启动dummy网卡和VRF。 WireGuard隧道 添加PostUp使其关联到vrf并重新为其绑定地址。举个例子： [Interface] PrivateKey = [数据删除] ListenPort = [数据删除] Table = off Address = fe80::2024/64 + PostUp = ip link set dev %i master dn42-vrf + PostUp = ip addr add fe80::2024/64 dev %i PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = [数据删除] Endpoint = [数据删除] AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 172.31.0.0/16, fd00::/8, fe00::/8 然后重新启动隧道即可。 Bird2部分 首先我们需要定义两张路由表，分别用于dn42的IPv4和IPv6： ipv4 table dn42_table_v4; ipv6 table dn42_table_v6 随后，在kernel protocol中指定VRF和系统路由表编号，并在IPv4、IPv6中指定前面创建的v4、v6路由表： protocol kernel dn42_kernel_v6{ + vrf "dn42-vrf"; + kernel table 1042; scan time 20; ipv6 { + table dn42_table_v6; import none; export filter { if source = RTS_STATIC then reject; krt_prefsrc = DN42_OWNIPv6; accept; }; }; }; protocol kernel dn42_kernel_v4{ + vrf "dn42-vrf"; + kernel table 1042; scan time 20; ipv4 { + table dn42_table_v4; import none; export filter { if source = RTS_STATIC then reject; krt_prefsrc = DN42_OWNIP; accept; }; }; } 除了kernel以外的protocol都加上VRF和IPv4、IPv6独立的table，但不需要指定系统路由表编号： protocol static dn42_static_v4{ + vrf "dn42-vrf"; route DN42_OWNNET reject; ipv4 { + table dn42_table_v4; import all; export none; }; } protocol static dn42_static_v6{ + vrf "dn42-vrf"; route DN42_OWNNETv6 reject; ipv6 { + table dn42_table_v6; import all; export none; }; } 总而言之就是： 一切和DN42有关的都给配置一个VRF和之前定义的路由表 只有kernel协议需要指定系统路由表编号，其他不需要 对于BGP、OSPF等也如法炮制，不过我选择将公网的RouterID和DN42的分开，因此还需要单独配置一个RouterID： # /etc/bird/dn42/ospf.conf protocol ospf v3 dn42_ospf_iyoroynet_v4 { + vrf "dn42-vrf"; + router id DN42_OWNIP; ipv4 { + table dn42_table_v4; import where is_self_dn42_net() && source != RTS_BGP; export where is_self_dn42_net() && source != RTS_BGP; }; include "ospf/*"; }; protocol ospf v3 dn42_ospf_iyoroynet_v6 { + vrf "dn42-vrf"; + router id DN42_OWNIP; ipv6 { + table dn42_table_v6; import where is_self_dn42_net_v6() && source != RTS_BGP; export where is_self_dn42_net_v6() && source != RTS_BGP; }; include "ospf/*"; }; # /etc/bird/dn42/ebgp.conf ... template bgp dnpeers { + vrf "dn42-vrf"; + router id DN42_OWNIP; local as DN42_OWNAS; path metric 1; ipv4 { + table dn42_table_v4; ... }; ipv6 { + table dn42_table_v6; ... }; } include "peers/*"; 完成后birdc c重载配置即可。 这时，我们可以通过ip route show vrf dn42-vrf来单独查看DN42的路由表： root@iYoRoyNetworkHKGBGP:~# ip route show vrf dn42-vrf 10.26.0.0/16 via inet6 fe80::ade0 dev dn42_4242423914 proto bird src 172.20.234.225 metric 32 10.29.0.0/16 via inet6 fe80::ade0 dev dn42_4242423914 proto bird src 172.20.234.225 metric 32 10.37.0.0/16 via inet6 fe80::ade0 dev dn42_4242423914 proto bird src 172.20.234.225 metric 32 ... 也可以在Ping的时候通过参数-I dn42-vrf来实现通过VRF Ping： root@iYoRoyNetworkHKGBGP:~# ping 172.20.0.53 -I dn42-vrf ping: Warning: source address might be selected on device other than: dn42-vrf PING 172.20.0.53 (172.20.0.53) from 172.20.234.225 dn42-vrf: 56(84) bytes of data. 64 bytes from 172.20.0.53: icmp_seq=1 ttl=64 time=3.18 ms 64 bytes from 172.20.0.53: icmp_seq=2 ttl=64 time=3.57 ms 64 bytes from 172.20.0.53: icmp_seq=3 ttl=64 time=3.74 ms 64 bytes from 172.20.0.53: icmp_seq=4 ttl=64 time=2.86 ms ^C --- 172.20.0.53 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3006ms rtt min/avg/max/mdev = 2.863/3.337/3.740/0.341 ms 注意事项 如果vrf设备重载了，所有原先和vrf相关联的设备都需要重载一次，否则无法正常工作 目前DN42是无法访问到配置了VRF的主机内的服务的，后续可能出一篇文章讲一下如何去让VRF内的流量可以访问到主机服务（挖坑ing） 参考文章： 用 BIRD 运行你的 MPLS 网络
水文警告 背景 从朋友那里弄到一台深港IEPL机器，有两个网卡eth0和eth1，但是默认都走eth0，eth1没有配置路由。 我打算通过metric实现粗略的分流再通过PBR实现按照规则的路由配置。 配置 机器默认由cloudinit配置了网卡： # This file is generated from information provided by the datasource. Changes # to it will not persist across an instance reboot. To disable cloud-init's # network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: version: 2 ethernets: eth0: addresses: - 10.10.1.31/16 gateway4: 10.10.0.1 match: macaddress: bc:24:11:f8:42:7a nameservers: addresses: - 223.5.5.5 - 119.29.29.29 search: - [数据删除] set-name: eth0 eth1: addresses: - 10.20.1.31/16 - [数据删除]/64 gateway4: 10.20.0.1 gateway6: fe80::be24:11ff:fe80:66bb match: macaddress: bc:24:11:50:96:0a nameservers: addresses: - 223.5.5.5 - 119.29.29.29 search: - [数据删除] set-name: eth1 备份一份配置之后加上metric： # This file is generated from information provided by the datasource. Changes # to it will not persist across an instance reboot. To disable cloud-init's # network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: version: 2 ethernets: eth0: addresses: - 10.10.1.31/16 gateway4: 10.10.0.1 match: macaddress: bc:24:11:f8:42:7a nameservers: addresses: - 223.5.5.5 - 119.29.29.29 search: - [数据删除] set-name: eth0 + routes: + - to: "default" + via: "10.10.0.1" + # 设置 metric=50，作为备选出口 + metric: 50 eth1: addresses: - 10.20.1.31/16 - [数据删除]/64 gateway4: 10.20.0.1 gateway6: fe80::be24:11ff:fe80:66bb match: macaddress: bc:24:11:50:96:0a nameservers: addresses: - 223.5.5.5 - 119.29.29.29 search: - [数据删除] set-name: eth1 + routes: + - to: "default" + via: "10.20.0.1" + # 设置 metric=25，作为优先出口 + metric: 25 编写PBR配置： # /etc/netplan/90-pbr.yaml network: version: 2 ethernets: eth0: routes: - to: default via: 10.10.0.1 table: 10 routing-policy: - from: 10.10.0.0/16 table: 10 - to: 202.46.[数据删除]/32 table: 10 eth1: routes: - to: default via: 10.20.0.1 table: 20 routing-policy: - from: 10.20.0.0/16 table: 20 - to: 38.47.[数据删除]/32 table: 20 - to: 23.149.[数据删除]/32 table: 20 对于需要指定出口访问的IP为其加上to类型的规则并绑定对应路由表即可。 完成后运行 netplan apply 更新配置
前言 上文我们已经成功注册了一个ASN并且拿到了一段IPv6地址，这次我们就来将这段地址广播给全世界。 在RIPE Database设置子网对象 需要注意的是，公网允许广播的最小IPv6前缀是/48，也就是说你如果只有一个/48地址你无法将其拆成更小的段。所以我后来又单独租用了一段/40，打算将其拆成多个/48来广播。 我获取到的IPv6为2a14:7583:f200::/40，打算拆出来2a14:7583:f203::/48用于给Vultr使用。如果你不需要拆段，请直接跳转到「创建路由信息」一节 拆段 首先打开Create "inet6num" object - RIPE Database，填入如下内容： inet6num: 打算拆出来的IP段，CIDR格式 netname: 网络名称 country: IP段所属国家，需要符合ISO 3166标准（RIPE DB里可以直接选择） admin-c: 上文创建的Role对象的主键值 tech-c: 上文创建的Role对象的主键值 status: ASSIGNED即可 此步骤将你获得的地址拆出来一个小的/48地址块。 创建路由信息 打开Create "route6" object - RIPE Database，填入如下内容： route6: 填写你打算广播播的IPv6地址块的CIDR格式 origin: 填写你申请到的ASN，包含AS前缀 此步骤声明允许你的ASN使用这段地址段来发BGP路由。 申请VPS的BGP Session接入 此次我使用的是Vultr家的机器，他们家的BGP Session算是很新手友好的了，有一套自己的验证系统；并且上游良好的过滤器保证了一般情况下即使发送了错误的路由表也不会影响到公网。 （此处我配置的时候忘记截图了，可以参考一下宝硕大佬的文章 年轻人的第一个 ASN 中的 申请 Vultr 的 BGP 广播功能 章节） 进入BGP - Vultr.com，选择Get Started之后按照要求填写你的ASN信息和IPv6地址块。LOA（Letter Of Authorization，授权信）可参考以下模板：LOA-template.docx（因为发现网上查到的都是以公司的名义写的，因此以个人名义重新写了一份） 完成后系统会自动创建一条工单，并且能看到我们的ASN和IP地址块处于待验证的状态： 点击Start，系统会向注册Role时填写的abuse-mailbox邮箱发送一封验证邮件： 收到的邮件如图所示： 其中，上面那个链接代表同意授权Vultr广播你的IP段，下面那个则是不同意。我们点击上面那个之后会进入Vultr的网页： 再点击Approve Announcement即可。ASN和IP段都需要验证一次。 接着，等待Vultr的工作人员审核完成后来到VPS的控制台，就能看到我们的BGP选项卡了，其中可以得到上游的信息： 此处不得不称赞一下Vultr的工单效率，我平均从创建工单申请授权到完成只花了10分钟左右。（反观之前在iFog GmbH，平均工作日工单回复时间1天左右实在是好太多了） 其他厂商的VPS大概都是这么个流程，你需要告诉工作人员你要播的ASN和IP段，在验证完所有权之后工作人员会为你配置对应的BGP Session。 广播！ 你应该已经从上游那里得到了以下信息： 上游的ASN 上游用于BGP Session的地址 （可选）密码 我用的操作系统是Debian12 Bookworm，使用Bird2作为路由软件，并且按照这篇文章中「更新Bird2至v2.16及以上」章节更新Bird2至最新版。Vultr那边给我的上游ASN是64515，上游用于BGP Session的地址是2001:19f0:ffff::1，VPS用于BGP Session的地址是2001:19f0:0006:0ff5:5400:05ff:fe96:881f。 我的Bird2配置文件修改自DN42中的配置文件： log syslog all; define OWNAS = 205369; # 自己的ASN define OWNIPv6 = 2a14:7583:f203::1; # 给机器绑定的单个IPv6地址 define OWNNETv6 = 2a14:7583:f203::/48; # 打算播的网段 define OWNNETSETv6 = [ 2a14:7583:f203::/48+ ]; # 打算播的网段集合 router id 45.77.x.x; # 路由器ID，这里使用VPS的公网IPv4 protocol device { scan time 10; } function is_self_net_v6() { return net ~ OWNNETSETv6; } protocol kernel { scan time 20; ipv6 { import none; export filter { if source = RTS_STATIC then reject; krt_prefsrc = OWNIPv6; accept; }; }; }; protocol static { route OWNNETv6 reject; ipv6 { import all; export none; }; } template bgp upstream { local as OWNAS; path metric 1; multihop; # 指定多跳 ipv6 { import filter { if net ~ [::/0] then reject; # 拒绝导入默认路由 accept; }; export filter { if is_self_net_v6() then accept; # 仅导出自己网段内的路由，防止劫持 reject; }; import limit 1000 action block; }; graceful restart; } protocol bgp 'Vultr_v6' from upstream{ local 2001:19f0:0006:0ff5:5400:05ff:fe96:881f as OWNAS; # local后面的地址即上游给的用于BGP Session的VPS上的地址 password "123456"; # 上游给你的BGP密码，若没密码就将这一行删除 neighbor 2001:19f0:ffff::1 as 64515; # 上游的BGP Session IP和ASN } 几个值得注意的点： 此处upstream模板的导入规则拒绝了 默认路由 ，这样写可以防止上游发来的路由表覆盖掉本地的默认网关等路由信息。如果我们有多个BGP邻居，则可能导致绕路甚至路由环路。 upstream中指定了多跳（multihop;），这是因为Vultr的BGP对端不能直达，若不设置多跳则会导致BGP会话卡在Idle状态。如果你的BGP上游是直连，可以不设置此行或者改为direct;。 填写完配置文件，运行birdc configure载入配置。 运行birdc show protocols查看状态，如果不出意外的话应该能看到BGP会话已经Established： 这个时候，你可以起身做点别的事情，等待全球路由收敛。大概半个小时之后，打开bgp.tools，查询自己的/48段，应该就能看到已经成功被全球互联网收敛，并且能看到我们的上游信息： 接着，我们在VPS上创建一个dummy网卡，并绑定我们为这个机器设置的段内的单个IPv6地址，如我给我这台机器分配了2a14:7583:f203::1： ip link add dummy0 type dummy ip addr add 2a14:7583:f203::1/128 dev dummy0 接着使用我们自己的PC ping这个地址就能通了，traceroute也能看到完整路由路径： 感谢米露大佬提供的技术支持！ 参考文章： 自己在家开运营商 Part.2 - 向世界宣告 IP 段 (BGP Session & BIRD) 年轻人的第一个 ASN - 宝硕博客 BGPlayer 从零开始速成指北 - 开通 Vultr 的 BGP 广播功能 - AceSheep BGP (2) 在 Vultr 和 HE 使用自己的 IPV6 地址 - 131's Blog
写在前面 本文记录了我在RIPE NCC申请ASN的完整过程，内容适合入门者参考，若有错误欢迎邮件联系我，我会在第一时间更正。 既然已经在DN42上学习了基本的BGP概念，不玩玩公网的有点说不过去吧（ 基本概念 目前公网ASN和IP资源分配由世界上5个 区域互联网注册管理机构（Regional Internet registry, RIR） 管理： ARIN: 负责管理北美地区 RIPE NCC: 负责管理欧洲地区 APNIC: 亚洲地区 LACNIC: 拉丁美洲地区 AfriNIC: 非洲地区 RIR不直接对个人用户提供服务，而是通过将资源授权给 本地互联网注册管理机构（Local Internet Registry, LIR） ，再由LIR授权给个人。当然，个人用户也可以注册成为LIR，但是这一般而言一般是不划算的。 如果你愿意每年交几千美元作为会费的话当我没说。 其中，对个人申请比较友好的是RIPE NCC，其次是ARIN和APNIC。相较于RIPE NCC，APNIC的费用普遍要高30%左右；并且RIPE NCC提供了一套在线管理系统，用户可以自己修改信息、查询进度，而在APNIC则需要联系LIR处理。综合而言，我选择在RIPE NCC申请ASN。 申请到的资源（ASN、IP都算）一般分为两种： PA（Provider Aggregatable）资源: 属于LIR，由LIR注册授权给你用的资源 PI（Provider Independent）资源: 属于你自己的资源，一般而言价格较高 准备阶段 选择一个LIR 上网查询LIR Service，可以找到很多提供这类服务的商家。目前RIPE NCC会向PI资源收取一年50EUR的管理费用，也就是说一般而言LIR注册ASN的费用不会低于50EUR（截至文章发布，大概60USD）一年。此处我选择的群友推荐的NoPKT LLC，价格算是比较合理的并且随ASN附赠一段/48 PA IPv6地址，实测下号速度也很快，从提交完材料到下号只用了半天。 准备材料 身份证明 个人：提供身份证或者护照都可以（我提供的身份证正反面照片） 企业：提供有效的营业执照 若申请人为未成年人，通常需要其法定监护人提供书面同意书并履行相应监护职责。所有提交的文件应确保真实有效，且必须是原件或经过公证的副本。 联系信息 通信地址，被用于登记在RIPE Database 技术人员邮箱 滥用举报邮箱 技术要求证明 欧洲范围内的BGP服务商开具的账单，可以选择Vultr、BuyVM、iFog、V.PS等。 注：Vultr是先用后付制，每月初出账单。如果你着急备好材料，建议选择其他家。 两个你打算接入的上游的ASN（实际上审核方不会严格验证你填写的上游ASN，因此可以随意填写，但建议填写一些常见的公开ASN以显得合理，不用过多纠结。 甚至填我的也行 ） 注册RIPE DB账户并创建条目 打开RIPE Database并注册一个账户。此处填写姓名时建议填写你自己的真实姓名的拼音，并且强制要求开启2FA，请提前在手机上安装TOTP软件。 创建Role对象和Maintainer对象 打开Create role and maintainer pair - RIPE Database，创建一个role对象。 此处role为一个抽象概念，用来描述一个团队、部门或者职能角色的联系方式，代指一个角色，比如 NOC (Network Operations Center)、Abuse Team、Hostmaster 等。 mntner: maintainer对象的标识符，可包含大小写字母、数字、-_。例如我填写的是IYOROY-MNT role: role对象的名称，可包含大小写字母、数字、][)(._"*@,&:!'+/-。例如我填写的是IYOROY-NETWORK-NOC address: 此role对象的办公地址 e-mail: 此role的电子邮件地址 填写完成后SUBMIT即可创建role对象和maintainer对象。请记录下返回的主键名称，一般以-RIPE结尾，后续修改信息及提交到LIR需要用到。 此处maintainer对象标识符和role对象有所区别，前者意为谁有权限维护数据库中的对象（即写入/修改/删除权限控制），是维护者实体。关于RIPE Database中各个概念之间的归属，可参考文章后部的示意图。 添加滥用举报邮箱 打开Query - RIPE Database，查询刚刚获得的role主键名称，可以查询到刚刚创建的条目。点击右侧Update Object。 点击e-mail栏右侧的加号，创建一个abuse-mailbox属性，并且填入你的滥用举报邮箱： SUBMIT保存。 注：RIPE会定时检查滥用举报邮箱是否可用，请务必填写真实的邮箱地址 创建Organization对象 此处Organization对象是法律实体或组织（公司、大学、ISP、个人用户等）的抽象，是 RIPE Database 里资源对象（比如aut-num、inetnum、inet6num）的上层归属信息，也就是说后续的ASN、IP资源都将授权给Organization对象。 打开Create Organization - RIPE Database，填写如下信息： organisation: 唯一ID，保留为默认的AUTO-1即代表由RIPE NCC分配。 org-name: 组织的名称。如果是个人用户注册则需要填写你自己的姓名拼音全称。 address: 通信地址。 country: 国家代码，参考ISO 3166。中国就填写CN e-mail: 组织的电子邮件。 admin-c / tech-c: 联系对象（引用 role 的 handle）。 abuse-c: 指定abuse联系方式（必须是一个role对象，链接到role对象中的abuse-mailbox）。 mnt-ref: 指定可以创建引用该organisation的对象的维护者。 mnt-by: 谁能维护这个organisation本身。 填写完SUBMIT，并记下返回的对象标识符，类似于ORG-XXXX-RIPE的格式。 如果你在提交后发现需要修改，去Query - RIPE Database查找刚刚记录下的Role主键或者组织的对象标识符即可找到修改入口。 支付LIR费用并且提交材料 将以下材料提交给你的LIR： 身份证明 姓名 地址，推荐证件地址 身份证正反面照片 RIPE Database信息 org: 组织对象标识符 as-name: AS名称 admin-c: 上面创建的role对象主键 tech-c: 上面创建的role对象主键 abuse-c: 上面创建的role对象主键 nic-handle: 上面创建的role对象主键 mnt-by: 上面创建的maintainer对象名称 技术要求证明 VPS账单 上游的ASN LIR会要求你在你的Organization里添加一条mnt-ref，指向LIR的维护者，这样LIR那边才能够将AS和IP等资源授权到你的Organization。LIR那边审核没问题之后会将请求提交到RIPE，接着就是等待。一般而言需要3-5个工作日即可下号。 自此，我们已经在公网上注册了属于我们自己的ASN。 补充：RIPE Database中各个概念之间的关系 graph LR %% ========== ORG 层 ========== subgraph Org["Organisation"] ORG["organisation\n(ORG-XXX-RIPE)"] end %% ========== 资源层 ========== subgraph Resource["资源"] INETNUM["inetnum\n(IPv4 段)"] INET6NUM["inet6num\n(IPv6 段)"] AUTNUM["aut-num\n(ASN)"] ASSET["as-set\n(ASN 集合)"] end %% ========== 路由层 ========== subgraph Routing["路由"] ROUTE["route\n(IPv4 前缀公告)"] ROUTE6["route6\n(IPv6 前缀公告)"] end %% ========== 联系人层 ========== subgraph Contact["联系人"] ROLE["role\n(团队/职能)\nnic-hdl"] PERSON["person\n(个人)\nnic-hdl"] end %% ========== 权限层 ========== subgraph Maintainer["权限"] MNT["mntner\n(维护者)"] end %% ========== 联系 ========== INETNUM --> ROLE INET6NUM --> ROLE AUTNUM --> ROLE ASSET --> ROLE ROUTE --> ROLE ROUTE6 --> ROLE ROLE --> PERSON %% ========== 组织归属 ========== ORG --> INETNUM ORG --> INET6NUM ORG --> AUTNUM ORG --> ASSET %% ========== 权限 ========== ORG --> MNT INETNUM --> MNT INET6NUM --> MNT AUTNUM --> MNT ASSET --> MNT ROUTE --> MNT ROUTE6 --> MNT ROLE --> MNT PERSON --> MNT %% ========== 路由绑定 ========== ROUTE -->|origin| AUTNUM ROUTE6 -->|origin| AUTNUM %% ========== 路由范围 ========== ROUTE -->|属于| INETNUM ROUTE6 -->|属于| INET6NUM 特别感谢米露大佬提供部分技术支持和答疑解惑！ 参考文章： 自己在家开运营商 Part.1 - 注册一个 ASN - LYC8503 从0开始注册一个ASN并广播IP | Pysio's Home 青年人的第一个运营商：注册一个 ASN | liuzhen932 的小窝
写在前面 本人是BGP小白，文章中可能会存在不严谨内容/小白理解/低级错误，请诸位大佬们手下留情。若发现存在问题，您愿意的话可以邮件联系我，我会在第一时间更正。如果您不能接受，建议现在就关闭此文章。 什么是BGP Communities TL;DR: BGP Communities给路由“打标签”，其他人可以通过这个标签实现路由优选 这个概念对于刚入坑的小白 （比如我） 来说可能比较陌生，可能不理解它是干吗用的 简单来说，BGP Communities是一种标记路由的机制，类似于给路由打标签。它允许网络管理员给通过BGP传播的路由附加一个或多个“标签”（即社区值）。这些标签本身并不改变路由的路径属性（如AS_PATH、LOCAL_PREF、MED等），但它们提供了一种信号机制，用于向本AS内或下游对等AS中的其他路由器指示应该对该路由执行何种策略或处理。BGP Communities可以用来： 简化策略配置: 网络内部或下游 AS 的路由器只需配置基于社区值的策略（如设置LOCAL_PREF、添加NO_EXPORT、应用路由图等），无需知道具体的前缀细节。这使得策略更集中、更易管理、更不易出错 传达策略意图给下游AS: 可以将社区值附加在它通告给下游客户或对等AS的路由上。这些社区值传达了对这些路由应如何处理的要求或建议，如根据不同地理位置、不同延迟和带宽来进行路由优选等 在AS内部协调策: 在大型AS内部，IBGP全互联或使用路由反射器时，可以在 AS 边缘路由器（接收 EBGP 路由或重分发路由的路由器）给路由打上社区值，AS内部的核心路由器或路由反射器可以识别这些社区值，并应用相应的内部策略（如设置LOCAL_PREF、MED、决定是否向某些IBGP对等体通告、打上其他社区等），而无需在每个内部路由器上配置复杂的基于前缀的策略 DN42中有一套自己的Communities规范，详情可参考：BGP-communities - DN42 Wiki 配置 本文大致思路和方案来源于Xe_iu大佬，仅针对地理位置信息添加BGP Communities并进行优选。一般来说这样就足够了。（还有个原因是其他的我还没太搞明白） 注意: 我们应该只对自己的AS添加地理位置信息相关的BGP Communities，不应当对邻居传递来的路由添加相关条目。若对邻居的路由加上自己的地区Communities则会造成伪造路由起源，引发路由劫持。下游可能会误判流量路径，将本应直连的流量绕道至你的网络，增加延迟的同时大量消耗你的网络的流量。（Large Communities除外，Large Community有一套验证机制可以防止此类事情发生，但是不在本文讨论范围内） 思路已经很明白了，在导出路由时我们需要先验证是否是我们自己的路由，如果是则为路由打上communities标签即可。DN42官方给的样例配置中已经写好了两个函数is_self_net()和is_self_net_v6()用于检查是否是自己的路由，因此编写配置文件部分就很简单了。 为路由添加Communities 首先我们需要在节点配置文件开头定义好当前节点的地理区域信息，详情请查询BGP-communities - DN42 Wiki： define DN42_REGION = 52; # 52代表亚洲东部地区 define DN42_COUNTRY= 1344; # 1344代表香港 请将上述数值按照你的节点地理位置情况修改 随后在dnpeers模板中修改导出的过滤器： }; export filter { - if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept; + if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then{ + if (is_self_net()) then { # 检查是否是自己的路由 + bgp_community.add((64511, DN42_REGION)); # 打上大洲级别的区域信息 + bgp_community.add((64511, DN42_COUNTRY)); # 打上国家/地区信息 + } + accept; + } reject; }; import limit 1000 action block; 在导出时检查，如果是自己的路由则按照上面设置的DN42_REGION和DN42_COUNTRY为该路由设置bgp_community。其中，64511是专门为地理标签（Region/Country）保留的公共AS号标识符，直接照抄即可。在IPv6的导出规则中也如法炮制，不过将is_self_net()换成is_self_net_v6()即可。 根据Communities优选 此处我们需要引入另一个概念：local_pref（Local Preference），他用于在AS内部标识路由的优先级。其默认值为100，值越大优先级越高。并且在BGP选路逻辑中，local_pref有着最高的优先级，甚至高于AS_PATH长度。也就是说，通过设置local_pref我们可以调整整个路由的优先级，以实现路由优选。 再结合上文的BGP Communities，我们可以根据Communities来为路由设置相应的local_pref来实现优选。同时，因为BGP.local_pref会在AS内部传递，所以需要同时更改eBGP和iBGP的路由导入和导出逻辑。 此处我处理BGP.local_pref的逻辑参考 （实际上是照抄） 了Xe_iu大佬的方案： 对于同一个大洲的路由，优先级+10 对于同一个国家，优先级再+5 对于直接和自己Peer的路由，优先级再+20 创建一个函数用于优先级计算： function ebgp_calculate_priority() { int priority = 100; # 基础优先级 # 同区域检测（+10） if bgp_community ~ [(64511, DN42_REGION)] then priority = priority + 10; # 同国家检测（+5） if bgp_community ~ [(64511, DN42_COUNTRY)] then priority = priority + 5; # eBGP直接邻居检测（+20） if bgp_path.len = 1 then priority = priority + 20; return priority; } 接着，在dnpeers模板中的导入过滤器中将bgp_local_pref设置为从函数计算出来的值： template bgp dnpeers { local as OWNAS; path metric 1; ipv4 { import filter { if is_valid_network() && !is_self_net() then { if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; reject; } + bgp_local_pref = ebgp_calculate_priority(); accept; } reject; }; IPv6也如法炮制即可。 运行birdc configure之后，我们应该就能在自己的邻居那里看到我们的路由已经被打上了Communities标签： （此处截图来自：https://lg.milu.moe/route_all/hk/172.20.234.224） 特别感谢Nuro Trace大佬和Xe_iu大佬，他们帮助我加深了对BGP Communities的理解并提供了很多帮助 参考文章： [DN42] bird2的配置文件 – Xe_iu's Blog | Xe_iu的杂物间 [DN42] 谈一谈如何配置 BGP community – Xe_iu's Blog | Xe_iu的杂物间 BGP-communities - DN42 Wiki
写在前面 本人是BGP小白，文章中可能会存在不严谨内容/小白理解/低级错误，请诸位大佬们手下留情。若发现存在问题，您愿意的话可以邮件联系我，我会在第一时间更正。如果您不能接受，建议现在就关闭此文章。 假设你已经加入了DN42，并且能够正常收发路由表并且访问到DN42内的IP 起因 在调试网络的时候发现Ping或traceroute别人的DN42 IP都能显示出来反向解析的域名，能知道路由经过了哪些节点而不是单纯的看IP（如下图），非常一目了然，让别人一眼就看出来你绕路了（逃）， 因此打算自己也注册一个DN42域名并搭建权威DNS服务。 查阅了蓝天大佬的这篇文章，他使用了PowerDNS+MySQL主从同步方案，但是我的服务器性能较差（只有1核心1GB内存），因此打算使用KnotDNS作为DNS服务器，用标准区域传输协议（AXFR/IXFR）实现主从同步。 准备工作 {alert type="warning"} 本章及后续章节中提到的域名和IP均为我自己的域名和IP，实际部署时请换成你自己的；文章中尖括号括起来的值需要按你的需求修改 {/alert} 挑选一个自己心仪的域名：yori.dn42，并且计划在三台机器上部署DNS服务器： 172.20.234.225, fd18:3e15:61d0::1, ns1.yori.dn42 172.20.234.227, fd18:3e15:61d0::3, ns2.yori.dn42 172.20.234.229, fd18:3e15:61d0::5, ns3.yori.dn42 其中，ns1.yori.dn42作为主服务器，ns2、ns3作为从服务器。 安装KnotDNS 如果系统的53端口被systemd-resolvd之类的进程占用了，就先将其禁用： systemctl stop systemd-resolved systemctl disable systemd-resolved unlink /etc/resolv.conf echo "nameserver 8.8.8.8" > /etc/resolv.conf 我使用的是Debian12系统，因此使用APT安装： apt install knot knot-dnsutils -y 设置KnotDNS自启动： systemctl enable knot 配置KnotDNS 创建key 首先创建一个key用于同步： keymgr -t key_knsupdate 将输出部分复制下来： # hmac-sha256:key_knsupdate:<your secret> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <your secret> 编辑配置文件 主服务器 编辑/etc/knot/knot.conf，填入如下内容： server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ <监听地址1>@53, <监听地址2>@53, ... ] log: - target: syslog any: info database: storage: "/var/lib/knot" ### 此处粘贴上一步生成的Key # hmac-sha256:key_knsupdate:<your secret> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <your secret> remote: - id: <1号DNS服务器ID> address: <1号DNS服务器IP>@53 - id: <2号DNS服务器ID> address: <2号DNS服务器IP>@53 - id: <3号DNS服务器ID> address: <3号DNS服务器IP>@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: <DN42 域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] - domain: <IPv4反向解析域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] - domain: <IPv6反向解析域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] 其中，监听地址需要填写本机的DN42 IPv4和DN42 IPv6，如果需要本地调试可再加上127.0.0.1和localhost等内网IP 从服务器ID即remote里设置的服务器的ID，你选定哪台（哪些）机器作为从服务器就填写哪台（哪些）服务器的ID remote中的address可以填写内网地址或者DN42 IPv4或者DN42 IPv6，仅用于同步主从服务器。如果使用内网地址请将地址加到监听列表中 template中设置了将Zone文件存储在/var/lib/knot下 IPv4反向解析域名按照你申请的IPv4段填写，遵循RFC 2317规定的格式，如我的IPv4段是172.20.234.224/28，我的IPv4反向解析域名应该为224/28.234.20.172.in-addr.arpa，即将IPv4的最后一段225/28看作一个整体，剩下的按照点来分隔，再将各个部分倒序拼接，最后加上.in-addr.arpa IPv6反向解析域名按照你申请的IPv6段填写，遵循RFC 3152规定的格式，如我的IPv6段是fd18:3e15:61d0::/48，我的IPv6反向解析域名应该为0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa，即将地址块中的各个字符倒序拼接，最后加上.in-addr.arpa。如果有0需要补全。 {collapse} {collapse-item label="示例"} server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ 172.20.234.225@53, fd18:3e15:61d0::1@53, localhost@53, 127.0.0.2@53 ] log: - target: syslog any: info database: storage: "/var/lib/knot" # hmac-sha256:key_knsupdate:<key> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <key> remote: - id: 225 # 主服务器 address: 172.20.234.225@53 - id: 227 # 从服务器 address: 172.20.234.227@53 - id: 229 # 从服务器 address: 172.20.234.229@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: yori.dn42 notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] - domain: 224/28.234.20.172.in-addr.arpa notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] - domain: 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] # # Secondary zone # - domain: example.net # master: primary {/collapse-item} {/collapse} 从服务器 从服务器的大致内容和主服务器相同，只需要将监听地址改成从服务器的地址，并且将zone部分配置修改一下： --- a/knot.conf +++ b/knot.conf zone: - domain: <DN42 域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master - domain: <IPv4反向解析域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master - domain: <IPv6反向解析域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master 主服务器ID即remote里设置的服务器的ID，你选定哪台机器作为主服务器就填写哪台服务器的ID {collapse} {collapse-item label="样例"} server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ 172.20.234.227@53, fd18:3e15:61d0::3@53, localhost@53, 127.0.0.1@53 ] log: - target: syslog any: info database: storage: "/var/lib/knot" # hmac-sha256:key_knsupdate:<key> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <key> remote: - id: 225 address: 172.20.234.225@53 - id: 227 address: 172.20.234.227@53 - id: 229 address: 172.20.234.229@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: yori.dn42 master: 225 zonefile-load: whole acl: acl_master - domain: 224/28.234.20.172.in-addr.arpa master: 225 zonefile-load: whole acl: acl_master - domain: 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa master: 225 zonefile-load: whole acl: acl_master {/collapse-item} {/collapse} 编写完配置文件后运行如下指令重启KnotDNS： systemctl restart knot 编辑Zone区域文件 此章节中的记录值（非主机名）若需要填写域名，除了特殊说明外，都请遵循RFC 1034规范填写FQDN格式。此章节中的所有配置均在主DNS服务器上完成 DN42域名 进入/var/lib/knot，创建文件<dn42域名>.zone SOA记录 域名的第一条记录必须为SOA记录，SOA记录是起始授权记录，记录了域名的一些基本信息如主要NS服务器地址。填入如下内容： @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> @表示是当前域名本身，不用修改 TTL: 当前SOA记录的TTL值 主要NS服务器地址: 当前域名的主要权威NS服务器地址，可以是域名内的解析值，如我的主要NS服务器是172.20.234.225，我打算使用ns1.yori.dn42.指向此地址，那么此处可填写ns1.yori.dn42. 联系人邮件: 邮箱地址，并且用.代替@，如我的邮箱是i@iyoroy.cn，那么此处可填写i.iyoroy.cn 记录编号: 一个10位数字，遵循RFC 1912，表示Zone文件的版本号。其他DNS服务器在获取SOA后发现序列号增加了就会重新拉取新的记录。一般使用日期+编号的方式编码，因此此项值应该在每次修改后递增。 AXFR刷新时间: AXFR从服务器两次拉取的间隔 AXFR重试时间: AXFR从服务器拉取失败后重试时间 AXFR过期时间: AXFR从服务器拉取失败后，最多用先前最后一次拉取成功的记录继续提供服务这么长时间，之后停止应答 最小TTL: 当前整个域名的最小TTL值，所有记录的最小刷新时间，至少过了这么长时间才会刷新 {collapse} {collapse-item label="样例"} ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072705 60 60 1800 60 {/collapse-item} {/collapse} NS记录 @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> 根据你的实际情况填写，有几台服务器就填写几条记录。 {collapse} {collapse-item label="样例"} ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. {/collapse-item} {/collapse} A、AAAA、CNAME等记录 按照如下格式填写即可： <主机名> <TTL> <类型> <记录值> 如果你的NS服务器值指向了你自己的DN42域名的主机，请务必为其添加A类型或者AAAA类型的解析记录 {collapse} {collapse-item label="样例"} ; A ns1 600 A 172.20.234.225 ns2 600 A 172.20.234.227 ns3 600 A 172.20.234.229 hkg-cn.node 600 A 172.20.234.225 nkg-cn.node 600 A 172.20.234.226 tyo-jp.node 600 A 172.20.234.227 hfe-cn.node 600 A 172.20.234.228 lax-us.node 600 A 172.20.234.229 ; AAAA ns1 600 AAAA fd18:3e15:61d0::1 ns2 600 AAAA fd18:3e15:61d0::3 ns3 600 AAAA fd18:3e15:61d0::5 hkg-cn.node 600 AAAA fd18:3e15:61d0::1 nkg-cn.node 600 AAAA fd18:3e15:61d0::2 tyo-jp.node 600 AAAA fd18:3e15:61d0::3 hfe-cn.node 600 AAAA fd18:3e15:61d0::4 lax-us.node 600 AAAA fd18:3e15:61d0::5 {/collapse-item} {collapse-item label="完整样例"} /var/lib/knot/yori.dn42.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072705 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; A ns1 600 A 172.20.234.225 ns2 600 A 172.20.234.227 ns3 600 A 172.20.234.229 hkg-cn.node 600 A 172.20.234.225 nkg-cn.node 600 A 172.20.234.226 tyo-jp.node 600 A 172.20.234.227 hfe-cn.node 600 A 172.20.234.228 lax-us.node 600 A 172.20.234.229 ; AAAA ns1 600 AAAA fd18:3e15:61d0::1 ns2 600 AAAA fd18:3e15:61d0::3 ns3 600 AAAA fd18:3e15:61d0::5 hkg-cn.node 600 AAAA fd18:3e15:61d0::1 nkg-cn.node 600 AAAA fd18:3e15:61d0::2 tyo-jp.node 600 AAAA fd18:3e15:61d0::3 hfe-cn.node 600 AAAA fd18:3e15:61d0::4 lax-us.node 600 AAAA fd18:3e15:61d0::5 {/collapse-item} {/collapse} IPv4反向解析域名 在/var/lib/knot下创建文件<IPv4反向解析域名>.in-addr.arpa，并用_代替/。如我的IPv4段是172.20.234.224/28，我的IPv4反向解析域名是224/28.234.20.172.in-addr.arpa，那么此处文件名为224_28.234.20.172.in-addr.arpa.zone。填入解析记录： ; SOA @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> ; NS @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> ; PTR <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> ... SOA和NS记录和上方相同 IPv4地址最后一位: 你给设备绑定的DN42 IPv4地址四段中的最后一段，如我的HK节点分配的172.20.234.225地址，那么此处就填写`225 {collapse} {collapse-item label="样例"} 224_28.234.20.172.in-addr.arpa.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072802 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; PTR 225 600 PTR hkg-cn.node.yori.dn42. 226 600 PTR nkg-cn.node.yori.dn42. 227 600 PTR tyo-jp.node.yori.dn42. 228 600 PTR hfe-cn.node.yori.dn42. 229 600 PTR lax-us.node.yori.dn42. {/collapse-item} {/collapse} 你可能会疑惑为何需要带上CIDR掩码，这与Clearnet中常见的、按八位组倒序的格式（如234.20.172.in-addr.arpa）不同；以及如果你尝试本地测试，你会发现直接测试你自己的IP地址的反解会失败。 原因在于DN42的分布式注册机制：单个区域文件无法覆盖你的地址段的所有反向查询入口点（即每个具体IP地址对应的.in-addr.arpa名称）。为了解决这个问题，DN42官方DNS会在你的PR被合并后，在其权威DNS服务器上为你的地址段添加CNAME重定向，将单个IP的PTR记录指向你的带CIDR的格式，如下： ~$ dig PTR 225.234.20.172.in-addr.arpa +short 225.224/28.234.20.172.in-addr.arpa. # <-- Registry 添加的 CNAME (重定向) hkg-cn.node.yori.dn42. # <-- DNS 返回的最终 PTR 记录 当外部解析器查询某个具体IP (如172.20.234.225) 的反向记录时（查询225.234.20.172.in-addr.arpa），官方DNS会返回一个CNAME记录，将其指向CIDR的区域名称下的具体记录（225.224/28.234.20.172.in-addr.arpa）。最终，PTR 记录由你配置的权威 DNS 服务器提供。 IPv6反向解析域名 在/var/lib/knot下创建文件<IPv6反向解析域名>.in-addr.arpa。如我的IPv6段是fd18:3e15:61d0::/48，我的IPv6反向解析域名是0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa，那么此处文件名为0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa.zone。填入解析记录： ; SOA @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> ; NS @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> ; PTR <最后20字符反向序列> <TTL> PTR <反向解析DNS值> <最后20字符反向序列> <TTL> PTR <反向解析DNS值> <最后20字符反向序列> <TTL> PTR <反向解析DNS值> ... SOA和NS记录处理方式同上 PTR的主机名，需要你将主机IPv6地址移除/48前缀后的80位部分展开为20个十六进制字符，并倒序排列这些字符并用点分隔。如我的香港节点IPv6是fd18:3e15:61d0::1，展开就是fd18:3e15:61d0:0000:0000:0000:0000:0001，此处主机名就填写1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 {collapse} {collapse-item label="样例"} 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072802 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR hkg-cn.node.yori.dn42. 2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR nkg-cn.node.yori.dn42. 3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR tyo-jp.node.yori.dn42. 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR hfe-cn.node.yori.dn42. 5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR lax-us.node.yori.dn42. {/collapse-item} {/collapse} 验证设置 全部保存后在每台DNS服务器上都运行一次knot reload重载，不出意外的话应该能看到从服务器同步了主服务器的zone文件，此时通过dig或者nslookup指定服务器查询应该能查到解析记录了 注册 域名 克隆下DN42 Registry，进入data/dns，新建文件<你打算注册的域名>，填入如下内容： domain: <你打算注册的域名> admin-c: <管理员NIC句柄> tech-c: <技术人员NIC句柄> mnt-by: <维护者> nserver: <NS1服务器域名> <NS1服务器IP> nserver: <NS2服务器域名> <NS2服务器IP> nserver: <NS3服务器域名> <NS3服务器IP> ... source: DN42 admin-c、tech-c、mnt-by请参考DN42探究日记 - Ep.1 加入DN42网络 {collapse} {collapse-item label="样例"} data/dns/yori.dn42 domain: yori.dn42 admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT nserver: ns1.yori.dn42 172.20.234.225 nserver: ns1.yori.dn42 fd18:3e15:61d0::1 nserver: ns2.yori.dn42 172.20.234.227 nserver: ns2.yori.dn42 fd18:3e15:61d0::3 nserver: ns3.yori.dn42 172.20.234.229 nserver: ns3.yori.dn42 fd18:3e15:61d0::5 source: DN42 {/collapse-item} {/collapse} IPv4反向解析域名 进入data/inetnum，找到你注册的地址块，加nserver字段，填写为你自己的DNS服务器： nserver: <你的DNS服务器地址> nserver: <你的DNS服务器地址> ... {collapse} {collapse-item label="样例"} diff --git a/data/inetnum/172.20.234.224_28 b/data/inetnum/172.20.234.224_28 index 50c800945..5ad60e23d 100644 --- a/data/inetnum/172.20.234.224_28 +++ b/data/inetnum/172.20.234.224_28 @@ -8,3 +8,6 @@ tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 +nserver: ns1.yori.dn42 +nserver: ns2.yori.dn42 +nserver: ns3.yori.dn42 {/collapse-item} {/collapse} IPv6反向解析域名 进入data/inet6num，找到你注册的地址块，加nserver字段，填写为你自己的DNS服务器： nserver: <你的DNS服务器地址> nserver: <你的DNS服务器地址> ... {collapse} {collapse-item label="样例"} diff --git a/data/inet6num/fd18:3e15:61d0::_48 b/data/inet6num/fd18:3e15:61d0::_48 index 53f0de06d..1ae067b00 100644 --- a/data/inet6num/fd18:3e15:61d0::_48 +++ b/data/inet6num/fd18:3e15:61d0::_48 @@ -8,3 +8,6 @@ tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 +nserver: ns1.yori.dn42 +nserver: ns2.yori.dn42 +nserver: ns3.yori.dn42 {/collapse-item} {/collapse} 提交PR，等待合并 填写完后推送并提交Pull Request。因为DN42中人人都可以建立递归DNS，DNS配置完全生效可能要一周左右。虽然我实测合并后半天以内公共DNS（172.20.0.53）就已经能查询到我的记录了 特别感谢たのしい大佬，让我明白了DN42中IPv4反向解析与公网的不同之处 参考文章： https://www.haiyun.me/archives/1398.html https://www.jianshu.com/p/7d69ec2976c7 https://www.potat0.cc/posts/20220726/Register_DN42_Domain/ https://bbs.csdn.net/topics/393775423 https://blog.snorlax.blue/knot-reverse-dns-kickstart/ http://www.kkdlabs.jp/dns/automatic-dnssec-signing-by-knot-dns/ https://lantian.pub/article/modify-website/register-own-domain-in-dn42.lantian/ https://datatracker.ietf.org/doc/html/rfc2317 https://datatracker.ietf.org/doc/html/rfc3152 https://datatracker.ietf.org/doc/html/rfc1912#section-2.2
写在前面 本人是BGP小白，文章中可能会存在不严谨内容/小白理解/低级错误，请诸位大佬们手下留情。若发现存在问题，您愿意的话可以邮件联系我，我会在第一时间更正。如果您不能接受，建议现在就关闭此文章。 本文更新日志 {timeline} {timeline-item color="#50BFFF"} 2025年7月22日：文章第一版发布，使用VXLAN over WireGuard隧道 {/timeline-item} {timeline-item color="#50BFFF"} 2025年7月25日：更新隧道方案，使用type ptp;以支持直接通过WireGuard传输OSPF流量（特别感谢Nuro Trance大佬指导！） {/timeline-item} {timeline-item color="#50BFFF"} 2025年8月8日：添加iBGP部分的解释和配置 {/timeline-item} {timeline-item color="#4F9E28"} 2025年8月27日：更新节点拓扑结构图 {/timeline-item} {/timeline} 为什么需要内部路由 当节点数量增多，我们需要一个合适的方式处理自己AS的内部路由。因为BGP路由只负责将数据包路由到AS，这就导致了一个问题：假如我有A、B两个节点都与别人peer，但是在路由器看来这两台设备同属于一个AS，从A节点发出的请求回包可能被回到B上。这个时候，如果没有做内部路由，则A会无法收到回包。因此，我们需要保证自家内网各个设备之间都能连通。常见的几种方式如下： 通过ZeroTier等组网工具：这种方式较为简单，只需要在每个节点上配置一个客户端即可实现各个节点之间的P2P连接。 通过WireGuard等P2P工具手动建立$\frac{n(n+1)}{2}$条隧道，效果同1，但是节点一多工作量呈指数级上升 通过WireGuard等P2P工具手动建立<$\frac{n(n+1)}{2}$条隧道，再通过OSPF、Babel等内网寻路协议建立内网路由，优点是较为灵活，易于后期添加新节点，缺点就是较为危险，容易配置错误引爆DN42。 因此我决定冒险一下 节点拓扑 graph LR A[HKG 172.20.234.225 fd18:3e15:61d0::1] B[NKG 172.20.234.226 fd18:3e15:61d0::2] C[TYO 172.20.234.227 fd18:3e15:61d0::3] D[FRA 172.20.234.228 fd18:3e15:61d0::4] E[LAX 172.20.234.229 fd18:3e15:61d0::5] B <--> A C <--> A A <--> E A <--> D C <--> D C <--> E D <--> E 更新Bird2至v2.16及以上 因为我希望使用IPv6 Link-Local地址传递IPv4的OSPF数据，而Bird在2.16及以后才支持这项功能，因此需要更新至v2.16。如果你不希望更新，可以先跳过此步骤。 使用以下指令安装最新版本的Bird2： sudo apt update && sudo apt -y install apt-transport-https ca-certificates wget lsb-release sudo wget -O /usr/share/keyrings/cznic-labs-pkg.gpg https://pkg.labs.nic.cz/gpg echo "deb [signed-by=/usr/share/keyrings/cznic-labs-pkg.gpg] https://pkg.labs.nic.cz/bird2 $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/cznic-labs-bird2.list sudo apt update && sudo apt install bird2 -y 配置隧道 [Interface] PrivateKey = <本地WireGuard私钥> ListenPort = <监听端口> Table = off Address = <IPv6 LLA>/64 PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对端公钥> Endpoint = <对端公网接入点> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 172.31.0.0/16, fd00::/8, fe00::/8, ff02::5 ff02::5是OSPFv3路由器专用的链路本地范围组播地址，需要添加进AllowedIPs。 如果你正在使用v2.16以前的Bird，请再为隧道配置一个IPv4地址，不一定非要是DN42 IPv4，其他私有地址也可以。请参考： {collapse} {collapse-item label="包含IPv4的WireGuard配置示例"} [Interface] PrivateKey = <本地WireGuard私钥> ListenPort = <监听端口> Table = off Address = <IPv6 LLA>/64 PostUp = ip addr add 100.64.0.225/32 peer 100.64.0.226/32 dev %i PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对端公钥> Endpoint = <对端公网接入点> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 100.64.0.0/16, 172.31.0.0/16, fd00::/8, fe00::/8, ff02::5 请将100.64.0.225、100.64.0.226替换为你本机的IPv4和对端的IPv4，并且记得加入AllowedIPs。 {/collapse-item} {/collapse} 启用OSPF 默认你已经配置好了如上一篇文章所写的bird基础配置 在/etc/bird下新建一个名为ospf.conf的文件，填入如下内容： protocol ospf v3 <name> { ipv4 { import where is_self_net() && source != RTS_BGP; export where is_self_net() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; protocol ospf v3 <name> { ipv6 { import where is_self_net_v6() && source != RTS_BGP; export where is_self_net_v6() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; 理论上来说应该使用OSPF v2处理IPv4，但是因为需要通过IPv6 LLA地址通信IPv4，因此此处IPv4也使用OSPF v3。 过滤规则保证只有本网段内的路由能够通过OSPF传递，并且过滤掉外部BGP协议的路由 千万不要随意使用import all;export all;，有可能会导致路由劫持并影响到整个DN42网络。OSPF只应该处理网段内部的路由。 {collapse} {collapse-item label="配置示例"} /etc/bird/ospf.conf protocol ospf v3 dn42_iyoroynet_ospf { ipv4 { import where is_self_net() && source != RTS_BGP; export where is_self_net() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; protocol ospf v3 dn42_iyoroynet_ospf6 { ipv6 { import where is_self_net_v6() && source != RTS_BGP; export where is_self_net_v6() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; {/collapse-item} {/collapse} 接着，新建/etc/bird/ospf文件夹，在其中创建area配置文件（如：/etc/bird/ospf/0.conf），填写区域信息： area 0.0.0.0 { interface "<DN42 dummy网卡>" { stub; }; interface "<wg0网卡名称>" { cost 80; # 按照你的网络情况修改 type ptp; }; interface "<wg1网卡名称>" { cost 100; # 按照你的网络情况修改 type ptp; }; # 以此类推 }; 0.0.0.0区域代表骨干网 此处dummy网卡指上一篇文章中所写的DN42虚拟网卡 cost值本应该是用于开销计算，但在DN42这种对带宽要求不大而对延迟较为敏感的场景下可以直接填写延迟，OSPF会自动走开销值之和最短的路由。 {collapse} {collapse-item label="配置示例"} /etc/bird/ospf/0.conf area 0.0.0.0 { interface "dn42" { stub; }; interface "dn42_hkg" { cost 80; type ptp; }; interface "dn42_hfe" { cost 150; type ptp; }; interface "dn42_lax"{ cost 100; type ptp; }; }; {/collapse-item} {/collapse} 最后，打开/etc/bird/bird.conf，在末尾引入OSPF的配置文件： include "ospf.conf"; 运行birdc configure，然后birdc show protocols应该就能看到OSPF的状态是Running了。如果不是，请检查配置步骤是否出错。 此时，在非直连的两台机器上互相ping应该就能通了： 配置iBGP 在从多个地点建立对等连接之前，您的各个节点必须首先完整掌握自身网络的拓扑结构。除了所有外部 BGP 连接外，这还需要配置另一个关键组件：内部 BGP（即 iBGP）。 必要性 iBGP可以保证AS内部所有运行BGP的路由器都能获知到达外部目的地的完整BGP路由信息，进而确保： 内部路由器可以选择最优的出口路径。 流量能够被正确地引导到负责连接特定外部网络的边界路由器。 即使存在多个边界路由器连接到同一个外部网络，内部路由器也能根据策略选择最佳出口。 相比于在AS内部使用默认路由指向边界路由器，iBGP提供了精确的外部路由信息，使得内部路由器能做出更智能的转发决策。 缺点与解决方案 为了防止路由信息在AS内部无控制地扩散导致环路，iBGP路由器不会将从某个iBGP邻居学到的路由再通告给其他iBGP邻居，这就要求传统的iBGP要求在同一个AS内所有运行iBGP的路由器之间必须建立全网状的iBGP邻居关系（Full Mesh）。（还是要建立$\frac{n(n+1)}{2}$条连接 ，没办法。不过OSPF起来之后iBGP配置还是比配置隧道简单的 ） 解决方案就是： 使用路由反射器（Route Reflector, RR）: 由RR路由器管理整个AS内部所有的路由信息，缺点就是RR路由器故障将会导致整个网络瘫痪（这很不Decentralized） 通过BGP联盟（BGP Confederation） 搭建内部网络: 将AS内的路由器虚拟成一个个子AS，再把各个路由器之间的连接当作BGP处理，最后向外传递的时候抹去内部AS的路由路径。 后面两种方案我没有尝试过，下面是一些可能有用的参考文章。本文着重讨论iBGP的配置。 DN42 实验网络介绍及注册教程（2022-12 更新） - Lan Tian @ Blog Bird 配置 BGP Confederation，及模拟 Confederation（2020-06-07 更新） - Lan Tian @ Blog 编写iBGP配置文件 在/etc/bird下新建文件ibgp.conf，填入如下内容： template bgp ibgpeers { local as OWNAS; ipv4 { import where source = RTS_BGP && is_valid_network() && !is_self_net(); export where source = RTS_BGP && is_valid_network() && !is_self_net(); next hop self; extended next hop; }; ipv6 { 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(); next hop self; }; }; include "ibgp/*"; 导入和导出规则确保iBGP仅处理BGP协议学到的路由，并且过滤掉IGP的路由防止环回 next hop self是必须的，指示 BIRD 在向 iBGP 邻居导出路由时，将下一跳重写为边界路由器自身的IP地址（而非原始的外部下一跳）。因为内部路由器无法直接访问外部邻居地址，若不重写则会被认定为地址不可达。重写后，内部路由器只需通过 IGP 路由将流量送至边界路由器，由边界路由器完成最终的外部转发。 因为我希望使用IPv6地址建立MP-BGP，通过IPv6路由IPv4，因此在IPv4中启用了extended next hop 接着创建/etc/bird/ibgp文件夹，在其中为每台节点都创建一个iBGP Peer配置文件: protocol bgp 'dn42_ibgp_<节点>' from ibgpeers{ neighbor <对应节点的IPv6 ULA地址> as OWNAS; }; {collapse} {collapse-item label="样例"} /etc/bird/ibgp/hkg.conf: protocol bgp 'dn42_ibgp_HKG' from ibgpeers{ neighbor fd18:3e15:61d0::1 as OWNAS; }; {/collapse-item} {/collapse} 注意：每个节点上都需要建立(n-1)个iBGP连接，保证和AS内其他所有机器都建立连接，这也是为什么需要使用ULA地址 使用ULA地址确保即使两个节点之间的WireGuard断开，iBGP仍然能通过OSPF建立的内部路由建立，否则将会导致整个内部网络的崩溃 最后，在/etc/bird/bird.conf中加入对ibgp.conf的引入: include "ibgp.conf"; 并运行birdc configure应用配置即可。 参考文章： BIRD 与 BGP 的新手开场 - 海上的宫殿 萌新入坑 DN42 之 —— 基于 tailscale + vxlan + OSPF 的组网 – 米露小窝 使用 Bird2 配置 WireGuard + OSPF 实现网络的高可用 | bs' realm DN42 实验网络介绍及注册教程（2022-12 更新） - Lan Tian @ Blog 如何引爆 DN42 网络（2023-05-12 更新） - Lan Tian @ Blog Bird 配置 BGP Confederation，及模拟 Confederation（2020-06-07 更新） - Lan Tian @ Blog 深入解析OSPF路径开销、优先级和计时器 - 51CTO New release 2.16 | BIRD Internet Routing Daemon 第一章·第二节 如何在 Linux 上安装最新版本的 BIRD？ | BIRD 中文文档 [DN42] 使用 OSPF ptp 搭建内网与IBGP配置 – Xe_iu's Blog | Xe_iu的杂物间 [译] dn42 多服务器环境中的 iBGP 与 IGP 配置 | liuzhen932 的小窝
写在前面 本人是BGP小白，文章中可能会存在不严谨内容/小白理解/低级错误，请诸位大佬们手下留情。若发现存在问题，您愿意的话可以邮件联系我，我会在第一时间更正。如果您不能接受，建议现在就关闭此文章。 欢迎与我建立对等连接！详情请查看：iYoRoy DN42 Network 想研究BGP，奈何租一个ASN和IP段实在是太贵了，又怕自己因为配置错误干掉半个互联网，因此决定研究一下DN42这个虚拟的网络。DN42是个大型去中心化网络，使用BGP协议建立路由，和当今互联网的结构很相像，因此适合用于BGP等网络技术学习。在DN42中，每个人都将扮演ISP（网络服务提供商）的角色，和其他用户Peer，以加入并参与建设整个DN42网络。DN42运行在172.20.0.0/14和fd00::/8上，这两个地址都是内网地址，因此不会影响到正常的互联网。 注册 你需要会使用基础的git指令、GPG、Linux，并且最好启用GPG签名Commit。 有Git使用基础的话可以参考我的commit：Add AS4242422024 · d1f9046ecb - registry - dn42 git Fork并克隆DN42注册表Git仓库 在dn42 git注册一个账号，并且Fork dn42/registry仓库。Clone下Fork出来的仓库并进入。 注册联系人 在data/person下创建文件<昵称>-DN42，填入如下内容： person: <姓名> e-mail: <邮箱> pgp-fingerprint: <GPG密钥指纹> nic-hdl: <NIC 句柄> mnt-by: <维护者> source: DN42 person: 名称 e-mail: 邮箱 pgp-fingerprint: GPG密钥指纹，会被用于一些认证服务 nic-hdl: NIC句柄，指向文件本身，直接填写当前文件名即可 mnt-by: 由谁维护，指向下文注册维护者一节中的维护者信息 source: 来源，保持DN42即可 www: 可选，可填网页地址 {collapse} {collapse-item label="示例"} data/person/IYOROY-DN42 @ dn42/registry@master person: Kagura iYoRoy e-mail: i@iyoroy.cn www: https://www.iyoroy.cn pgp-fingerprint: 3ECCFFDEC2CB4CB8DA8089BE9AF2F2E03CE8FD67 nic-hdl: IYOROY-DN42 mnt-by: IYOROY-MNT source: DN42 {/collapse-item} {/collapse} 注册维护者 创建文件data/mntner/<昵称>-MNT，并填入如下内容： mntner: <昵称>-MNT admin-c: <联系人> tech-c: <联系人> auth: <验证方式> mnt-by: <维护者> source: DN42 mntner: 维护者名称，一般与文件名相同即可 admin-c: 管理员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 tech-c: 技术人员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 auth: 验证方式，支持pgp或者ssh-key mnt-by: 由谁维护，一般指向文件名本身即可 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/mntner/IYOROY-MNT @ dn42/registry@master mntner: IYOROY-MNT admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 auth: pgp-fingerprint 3ECCFFDEC2CB4CB8DA8089BE9AF2F2E03CE8FD67 mnt-by: IYOROY-MNT source: DN42 {/collapse-item} {/collapse} 注册ASN 在公网上，4200000000 - 4294967294为ASN保留范围。DN42使用了4242420000 - 4242429999，而目前开放注册的是4242420000 - 4242423999。官方不建议自己寻找可用ASN，而是推荐使用Burble的DN42 Free ASN Explorer，从中选取可用的ASN注册。 找好心仪的ASN后在data/aut-num创建<ASN，包含AS字母>文件，填写如下内容： aut-num: <ASN> as-name: <自治系统名称> descr: <自治系统描述> admin-c: <管理员NIC句柄> tech-c: <技术人员NIC句柄> mnt-by: <维护者> source: DN42 aut-num: 你选择的ASN，包含AS前缀。应该是AS424242xxxx的样式 as-name: 自治系统名称 descr: 自治系统描述，可以有空格 admin-c: 管理员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 tech-c: 技术人员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 mnt-by: 由谁维护，指向上文创建维护者一节中的文件名 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/aut-num/AS4242422024 @ dn42/registry@master aut-num: AS4242422024 as-name: IYOROYNET-AS-DN42 descr: iYoRoy DN42 Network admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT source: DN42 {/collapse-item} {/collapse} 注册IPv4地址块并添加路由 如果你不想要注册IPv4，可以忽略本节 官方提供了一个用于检索可用地址块的工具：DN42 Free IPv4 Explorer 。同样不建议手动指定，而是建议用检索工具检索出来的可用地址块。 DN42同样面临IPv4地址短缺，所以请尽量按需注册。一般而言，一个/27地址块包含30个IPv4地址就够用了，如果是小型网络可以申请/28（14个可用地址）和/29（6个可用地址）。能直接申请的地址块最大是/26，也就是62个地址，若申请/25、/24则需要提交申请然后等待审核。详情可参考：DN42 实验网络介绍及注册教程（2022-12 更新） - Lan Tian @ Blog 选定你的IPv4地址块，然后在data/inetnum创建一个文件，命名为IPv4地址块的CIDR格式，并使用_代替/（如：我申请的IPv4地址块为172.20.234.224/28，则文件名为172.20.234.224_28），填入内容： inetnum: <IPv4地址块第一个地址> - <IPv4地址块最后一个地址> cidr: <IPv4地址块CIDR格式> netname: <IPv4地址块名称> descr: <IPv4地址块描述> country: <IPv4地址块所属国家代码> admin-c: <管理员NIC句柄> tech-c: <技术人员NIC句柄> mnt-by: <维护者> status: ASSIGNED source: DN42 inetnum: IPv4地址范围，从第一个地址到最后一个地址，中间用-连接 cidr: IPv4地址块CIDR格式，不需要用_代替/ netname: IPv4地址块名称，自行填写即可，无特殊要求 descr: IPv4地址块描述，中间可以有空格 country: ISO 3166中的两字符国家代码，中国就填CN admin-c: 管理员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 tech-c: 技术人员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 mnt-by: 由谁维护，指向上文创建维护者一节中的文件名 status: 状态，保存ASSIGNED即可 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/inetnum/172.20.234.224_28 @ dn42/registry@master inetnum: 172.20.234.224 - 172.20.234.239 cidr: 172.20.234.224/28 netname: IYOROYNET-DN42-V4 descr: iYoRoy DN42 Network IPv4 Block country: CN admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 {/collapse-item} {/collapse} 接着，在data/route下同样创建一个名为IPv4地址块的 CIDR 格式，并使用_代替/的文件，填入内容： route: <IPv4地址CIDR格式> origin: <ASN> max-length: <IPv4掩码> mnt-by: <维护者> source: DN42 route: IPv4地址块，填写CIDR格式，无需转换/ origin: ASN，需要带AS max-lenth: 前缀长度（Prefix Length），即CIDR格式中/后面的数字 mnt-by: 由谁维护，指向上文创建维护者一节中的文件名 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/route/172.20.234.224_28 @ dn42/registry@master route: 172.20.234.224/28 origin: AS4242422024 max-length: 28 mnt-by: IYOROY-MNT source: DN42 {/collapse-item} {/collapse} 注册IPv6地址块并添加路由 如果你不想要注册IPv6，可以忽略本节 同样使用官方工具检索可用IPv6：DN42 Free IPv6 Explorer 。 选定地址块后在data/inet6num下创建名为IPv6地址块的CIDR格式，并使用_代替/的文件（如：我的IPv6地址块为fd18:3e15:61d0::/48，则文件名fd18:3e15:61d0::_48），填入内容： inet6num: <IPv6地址块第一个地址> - <IPv6地址块最后一个地址> cidr: <IPv6地址块CIDR格式> netname: <IPv6地址块名称> descr: <IPv6地址块描述> country: <IPv6地址块所属国家代码> admin-c: <管理员NIC句柄> tech-c: <技术人员NIC句柄> mnt-by: <维护者> status: ASSIGNED source: DN42 inet6num: IPv6地址范围，从第一个地址到最后一个地址，不可使用::简写，中间用-连接。也就是说，比如我的地址块是fd18:3e15:61d0::/48，我需要填入fd18:3e15:61d0:0000:0000:0000:0000:0000 - fd18:3e15:61d0:ffff:ffff:ffff:ffff:ffff。 cidr: IPv6地址块CIDR格式，不需要用_代替/ netname: IPv6地址块名称，自行填写即可，无特殊要求 descr: IPv6地址块描述，中间可以有空格 country: ISO 3166中的两字符国家代码，中国就填CN admin-c: 管理员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 tech-c: 技术人员联系信息，指向person文件夹中的文件，这里填上文注册联系人一节里的文件名即可 mnt-by: 由谁维护，指向上文创建维护者一节中的文件名 status: 状态，保存ASSIGNED即可 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/inet6num/fd18:3e15:61d0::_48 @ dn42/registry@master inet6num: fd18:3e15:61d0:0000:0000:0000:0000:0000 - fd18:3e15:61d0:ffff:ffff:ffff:ffff:ffff cidr: fd18:3e15:61d0::/48 netname: IYOROYNET-DN42-V6 descr: iYoRoy DN42 Network IPv6 Block country: CN admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 {/collapse-item} {/collapse} 接着，在data/route6下同样创建一个名为IPv6地址块的 CIDR 格式，并使用_代替/的文件，填入内容： route6: <IPv6地址CIDR格式> origin: <ASN> max-length: <IPv6掩码> mnt-by: <维护者> source: DN42 route6: IPv6地址块，填写CIDR格式，无需转换/ origin: ASN，需要带AS max-lenth: 前缀长度（Prefix Length），即CIDR格式中/后面的数字 mnt-by: 由谁维护，指向上文创建维护者一节中的文件名 source: 来源，保持DN42即可 {collapse} {collapse-item label="示例"} data/route6/fd18:3e15:61d0::_48 @ dn42/registry@master route6: fd18:3e15:61d0::/48 origin: AS4242422024 max-length: 48 mnt-by: IYOROY-MNT source: DN42 {/collapse-item} {/collapse} 创建并上传commit，提交PR 最好使用GPG签名你的commit并且在dn42 git里上传你的公钥 填完上述信息，回到仓库根目录下，运行脚本./fmt-my-stuff <维护者>自动格式化配置文件。注意：此处<维护者>是上文注册维护者章节中的mntner的值。接着按照正常git commit流程git add data/、git commit -S -m "<commit-msg>"创建commit，git push origin master推送，接着，来到dn42 git网页端，打开你Fork的仓库并提交Pull Request即可。 如果配置有问题，自动审查机器人和管理员会告知你，请耐心按照要求修改并且合并到原来的commit中，不要新开commit，推荐add后使用git commit --amend指令。修改完直接强制推送（git push origin master --force） 即可。 DN42 Registry 的工作语言是英语。请使用英语完成全部流程，以免出现不必要的麻烦。 配置Bird 首先安装bird，以Ubuntu22.04为例： apt install bird2 -y 如果要一并安装后续使用的WireGuard，就再加上wireguard、wireguard-tools软件包： apt install bird2 wireguard wireguard-tools -y 编写配置文件 bird.conf 修改/etc/bird/bird.conf： define OWNAS = <AS 号>; define OWNIP = <DN42 IPv4 地址>; define OWNIPv6 = <DN42 IPv6 地址>; define OWNNET = <DN42 IPv4 地址块, CIDR 格式>; define OWNNETv6 = <DN42 IPv6 地址块, CIDR 格式>; define OWNNETSET = [ <DN42 IPv4 地址块, CIDR 格式>+ ]; define OWNNETSETv6 = [ <DN42 IPv6 地址块, CIDR 格式>+ ]; router id OWNIP; protocol device { scan time 10; } function is_self_net() { return net ~ OWNNETSET; } function is_self_net_v6() { return net ~ OWNNETSETv6; } function is_valid_network() { return net ~ [ 172.20.0.0/14{21,29}, # dn42 172.20.0.0/24{28,32}, # dn42 Anycast 172.21.0.0/24{28,32}, # dn42 Anycast 172.22.0.0/24{28,32}, # dn42 Anycast 172.23.0.0/24{28,32}, # dn42 Anycast 172.31.0.0/16+, # ChaosVPN 10.100.0.0/14+, # ChaosVPN 10.127.0.0/16{16,32}, # neonetwork 10.0.0.0/8{15,24} # Freifunk.net ]; } function is_valid_network_v6() { return net ~ [ fd00::/8{44,64} # ULA address space as per RFC 4193 ]; } protocol kernel { scan time 20; ipv6 { import none; export filter { if source = RTS_STATIC then reject; krt_prefsrc = OWNIPv6; accept; }; }; }; protocol kernel { scan time 20; ipv4 { import none; export filter { if source = RTS_STATIC then reject; krt_prefsrc = OWNIP; accept; }; }; } protocol static { route OWNNET reject; ipv4 { import all; export none; }; } protocol static { route OWNNETv6 reject; ipv6 { import all; export none; }; } include "rpki.conf"; include "ebgp.conf"; 此处OWNIP和OWNIPV6参数意为本机的DN42 IPv4和IPv6地址，不是地址块的地址，比如我申请到的地址段是172.20.234.224/28，我给A机器分配了172.20.234.225，则OWNIP为172.20.234.225，IPv6同理。 rpki.conf 你可能在其他教程里看到过使用定时任务+重载Bird的更新ROA方案，此处配置RPKI则是另一种配置ROA的方式。 创建/etc/bird/rpki.conf，填写如下内容： roa4 table dn42_roa; roa6 table dn42_roa_v6; protocol rpki dn42_rpki_akix { roa4 { table dn42_roa; }; roa6 { table dn42_roa_v6; }; remote "<rpki服务器地址>" port 8082; # change it refresh 30; retry 5; expire 600; } RPKI服务器可以使用我朋友的AkaereIX提供的服务：rpki.akae.re {collapse} {collapse-item label="样例"} roa4 table dn42_roa; roa6 table dn42_roa_v6; protocol rpki dn42_rpki_akix { roa4 { table dn42_roa; }; roa6 { table dn42_roa_v6; }; remote "rpki.akae.re" port 8082; refresh 30; retry 5; expire 600; } {/collapse-item} {/collapse} ebgp.conf 创建/etc/bird/ebgp.conf，填入如下内容： template bgp dnpeers { local as OWNAS; path metric 1; ipv4 { import filter { if is_valid_network() && !is_self_net() then { if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; reject; } accept; } reject; }; export filter { if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept; reject; }; import limit 1000 action block; }; ipv6 { import filter { if is_valid_network_v6() && !is_self_net_v6() then { if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then { print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; reject; } accept; } reject; }; export filter { if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then accept; reject; }; import limit 1000 action block; }; } include "peers/*"; 此配置项定义了DN42内部互相peer的模板，并且引用/etc/bird/peers下的所有配置文件，之后我们peer的时候就只需要在这里按照模板创建配置文件并编写即可。 创建/etc/bird/peers文件夹用作后续peer。 系统配置 内核 因为每个节点都可能会作为其他节点的路由器使用，因此需要打开Linux内核数据包转发；同时因为使用了WireGuard，并不是通过物理网卡转发，因此需要关闭rp_filter严格模式。 echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf echo "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.conf echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.conf echo "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf sysctl -p DN42虚拟网卡 因为MP-BGP通常使用LLA地址建立BGP，因此我们需要在系统里设置一个dummy网卡并绑定我们给这台机器分配的DN42 IP。运行以下指令： ip link add dn42 type dummy ip addr add <你给这这台机器分配的DN42 IPv4> dev dn42 ip addr add <你给这这台机器分配的DN42 IPv6> dev dn42 如果需要持久化配置，可以参考以下三种方式： {tabs} {tabs-pane label="systemd-networkd"} tee /etc/systemd/network/10-dn42.netdev > /dev/null <<EOF [NetDev] Name=dn42 Kind=dummy EOF tee /etc/systemd/network/20-dn42.network > /dev/null <<EOF [Match] Name=dn42 [Network] Address=<你给这台机器分配的DN42 IPv4>/32 Address=<你给这台机器分配的DN42 IPv6>/128 EOF systemctl enable systemd-networkd systemctl restart systemd-networkd {/tabs-pane} {tabs-pane label="netplan"} tee /etc/netplan/99-dn42.yaml > /dev/null <<EOF network: version: 2 renderer: networkd ethernets: dn42: match: name: dn42 addresses: - <你给这台机器分配的DN42 IPv4>/32 - "<你给这台机器分配的DN42 IPv6>/128" accept-ra: no EOF netplan apply {/tabs-pane} {tabs-pane label="/etc/network/interfaces"} tee -a /etc/network/interfaces > /dev/null <<EOF auto dn42 iface dn42 inet static address <你给这台机器分配的DN42 IPv4> netmask 255.255.255.255 iface dn42 inet6 static address <你给这台机器分配的DN42 IPv6>/128 EOF ifup dn42 {/tabs-pane} {/tabs} 与其他成员Peer 对新手较友好的应该就是Potat0大佬的自助Peer服务了，详情可参考他的DN42 Network页面。按照指引找到他的自动对等连接机器人，按照要求在机器人上注册你的账号，需要提供你的DN42 ASN、上文注册时Maintainer里记录的邮箱（这也是为什么在注册的时候最好需要填邮箱，很多验证服务都会使用），并且用该邮箱接收验证码，然后按照机器人的要求建立peer即可。以下是我通过Bot建立peer的过程，全程使用Ubuntu22.04系统的香港VPS操作。 安装WireGuard apt install wireguard wireguard-tools -y 注：现在的软件包管理一般会在安装wireguard的时候自动安装wireguard-tools以使用wg-quick指令。 生成密钥对 wg genkey | tee privatekey | wg pubkey > publickey 记下privatekey、publickey中的内容，代表私钥和公钥 建立Peer 一般而言有两种建立Peer的模式，即使用MP-BGP（Multiprotocol BGP，多协议BGP）和不使用MP-BGP。个人感觉MP-BGP更为常用，因为其配置较为简单。两种配置稍有不同，请根据你的需求参考。 使用MP-BGP 与Peer对方交换信息 你需要提供对方以下信息，同时你也需要知道对方的以下信息： 公钥 公网地址（非DN42地址） 公网WireGuard端口，一般是监听对方ASN的后5位 DN42 ASN LLA（Link-Local Address）地址，一般而言是fe80::<对方ASN后4位> 是否支持ENH(Extended Next Hop)，注意，若使用v6交换路由而不启用ENH则无法交换v4路由 也有时会将公网地址和公网wg端口放在一起称作Endpoint。 配置WireGuard 在/etc/wireguard下创建一个配置文件，名称随意，但是我的命名习惯是dn42-<对方ASN后4位>.conf，其中dn42-xxxx即为隧道名。往文件内填入如下内容： [Interface] PrivateKey = <上文「生成密钥对」章节生成的私钥> ListenPort = <公网WireGuard监听端口> Table = off Address = <你的LLA地址>/64 PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对方给你的公钥> Endpoint = <对方的Endpoint，即IP:端口> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 172.31.0.0/16, fd00::/8, fe00::/8 编辑完成后保存，运行： wg-quick up <隧道名> 即可启动wg隧道。使用wg show <隧道名>即可查看连接状态。直接运行wg可查看所有隧道状态 如果需要让隧道开机自启动，可以运行： systemctl enable wg-quick@<隧道名> 配置Bird2的Peer部分 在/etc/bird/peers下新建文件<名称>.conf，填入如下内容： protocol bgp <BGP会话名称> from dnpeers{ neighbor <对方LLA地址> % '<WireGuard隧道名>' as <对方ASN，不带AS前缀>; }; 如果使用使用了Extended next hop（如上图交换信息时协商的），则配置： protocol bgp <BGP会话名称> from dnpeers{ neighbor <对方LLA地址> % '<WireGuard隧道名>' as <对方ASN，不带AS前缀>; ipv4{ extended next hop; }; }; 应用配置 运行birdc configure（或者birdc c，等同）重载Bird配置，然后运行birdc show protocols（或者birdc s p，等同）即可查看当前建立的BGP连接。 root@hkg2-202501092021514df2f0:~# birdc show protocols BIRD 2.0.12 ready. Name Proto Table State Since Info device1 Device --- up 07:24:14.255 static1 Static dn42_roa up 07:24:14.255 static2 Static dn42_roa_v6 up 07:24:14.255 kernel1 Kernel master6 up 07:24:14.255 kernel2 Kernel master4 up 07:24:14.255 static3 Static master4 up 07:24:14.255 static4 Static master6 up 07:24:14.255 dn42-0298 BGP --- up 08:03:13.347 Established dn42-1816 BGP --- up 07:53:05.028 Established 不使用MP-BGP 与Peer对方交换信息 你需要提供对方以下信息，同时你也需要知道对方的以下信息： 公钥 公网地址（非DN42地址） 公网WireGuard端口，一般是监听对方ASN的后5位 DN42 ASN DN42 IP，如果交换IPv4路由则需要IPv4地址，如果交换IPv6路由则需要IPv6地址 也有时会将公网地址和公网wg端口放在一起称作Endpoint。 配置WireGuard 在/etc/wireguard下创建一个配置文件，名称随意，但是我的命名习惯是dn42-<对方ASN后4位>.conf，其中dn42-xxxx即为隧道名。往文件内填入如下内容： [Interface] PrivateKey = <上文「生成密钥对」章节生成的私钥> ListenPort = <公网WireGuard监听端口> Table = off PostUp = ip addr add <LLA>/64 dev %i PostUp = ip addr add <本地DN42 IPv6> dev %i PostUp = ip addr add <本地DN42 IPv4> peer <对方DN42 IPv4> dev %i PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对方给你的公钥> Endpoint = <对方的Endpoint，即IP:端口> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 172.31.0.0/16, fd00::/8, fe00::/8 编辑完成后保存，运行： wg-quick up <隧道名> 即可启动wg隧道。使用wg show <隧道名>即可查看连接状态。直接运行wg可查看所有隧道状态 如果需要让隧道开机自启动，可以运行： systemctl enable wg-quick@<隧道名> 配置Bird2的Peer部分 在/etc/bird/peers下新建文件<名称>.conf，填入如下内容： protocol bgp <v4BGP会话名称> from dnpeers{ neighbor <对方DN42 IPv4地址> as <对方ASN，不带AS前缀>; direct; ipv6{ import none; export none; }; }; protocol bgp <v6BGP会话名称> from dnpeers{ neighbor <对方DN42 IPv6地址> % '<WireGuard隧道名称>' as <对方ASN，不带AS前缀>; direct; ipv4{ import none; export none; }; }; 应用配置 运行birdc configure（或者birdc c，等同）重载Bird配置，然后运行birdc show protocols（或者birdc s p，等同）即可查看当前建立的BGP连接。 自此，我们成功加入了DN42网络。要想让网络更加稳定，我们可以与更多用户进行Peer，建立多条BGP线路以防止其中部分节点瘫痪导致失联。你可以加入DN42非官方tg交流群获取更多信息。 如果出现BGP Established但是无法Ping通DN42内部IP的情况，请检查bird.conf中设置的IP和dummy网卡分配的IP或WireGuard中的本地IP是否相同。 配置DNS DN42内有自己的公共DNS，泛播地址为172.20.0.53，并且也能解析正常互联网的域名。要想能够访问以dn42结尾的内部域名，需要将这个地址放到resolv.conf最上面： nameserver 172.20.0.53 nameserver 223.5.5.5 #下面是正常的DNS服务器配置 之后就可以解析DN42内部域名啦，同时ping、traceroute、mtr等命令查询内部IP时也能显示出来rDNS的解析结果。 欢迎与我建立对等连接！详情请查看：iYoRoy DN42 Network 参考文章： https://blog.baoshuo.ren/post/dn42-network/ https://dn42.dev/howto/Bird2#example-configuration https://dn42.eu/howto/wireguard https://blog.udon.eu.org/archives/dbf21067.html https://blog.byteloid.one/2025/06/02/babeld-over-wireguard/ https://blog.wcysite.com/2021/%E8%B8%A9%E5%9D%91DN42-p2-peer/ https://blog.chs.pub/p/23-14-joindn42/ https://www.cnblogs.com/FengZeng666/p/15583434.html