在Bird 1.6.8中,我成功宣布了 IPv4/24
子网,并且我主要使用根据RFC 5735 的示例 IP 地址/子网:
root@tunnel0:~# birdc show proto all vultr
BIRD 1.6.8 ready.
name proto table state since info
vultr BGP master up 2024-01-18 Established
我现在想使用WireGuard 在我家的本地服务器上使用203.0.113.32/27
其中的一部分203.0.113.0/24
- 本地服务器是多个 KVM 来宾的主机(
libvirt
在 Debian 12 Bookworm 上)。 - 主机本身以及访客都需要一个公共 IPv4 地址。
- 如果可能的话,我想使用基于策略的路由而不是 NAT。
- 我确实使用了
nftables
而不是iptables
因为后者变得越来越不赞成。
我能够在两台服务器之间设置 WireGuard 连接,但我不知道如何配置公共 IPv4 子网,以便我可以使用它而不是私有子网:
- WireGuard 服务器:
[Interface] Address = 10.17.0.1/24 SaveConfig = true ListenPort = 51820 PrivateKey = ... [Peer] PublicKey = ... AllowedIPs = 10.17.0.0/24
- WireGuard 客户端:(= VM 的本地服务器/主机)
# Client [Interface] PrivateKey = ... Address = 10.17.0.128/24 # Server [Peer] PublicKey = ... AllowedIPs = 10.17.0.0/24 PersistentKeepalive = 25 Endpoint = 198.51.100.77:51820
- 鸟:
router id 198.51.100.77; protocol bgp vultr { # substitute with your AS or Vultr's private AS local as 123456; source address 198.51.100.77; import none; export all; graceful restart on; multihop 2; neighbor 169.254.169.254 as 64515; password "..."; } protocol static { route 203.0.113.0/24 via 198.51.100.77; } protocol device { scan time 5; }
在两台主机上,通过
Table = 2
在两个配置中进行设置来禁用 wg-quick 的自动策略路由。(我什至会用来Table = off
禁用它的自动路由添加并手动完成这一切,但我认为在你的情况下保留它很好;稍后它会很有用。)当WireGuard隧道重新启动时,查看
ip rule
并ip route show table 2
查看路由策略是什么。我希望没有非默认规则(只有下面显示的三个规则),并且表 2 中只有一个“dev wg0”路由。更新隧道的AllowedIPs以匹配将通过它的流量。
在服务器端,dst=203.0.113.32/27的数据包将被发送到客户端,而src=203.0.113.32/27的数据包将从客户端接收,因此客户端的服务器端[Peer]条目必须在客户端的AllowedIP 中包含203.0.113.32/27。
在客户端,来自任何源(即整个互联网)的数据包都会从隧道接收,而到任何目标的数据包都会被发送回隧道,因此客户端配置需要AllowedIPs允许0.0.0.0。 0.0.0/0 即一切。
我只需将两侧的AllowedIPs 设置为0.0.0.0/0,以便WireGuard 成为一个完全标准的隧道,可以承载它所告知的任何IP 流量。
重新启动隧道;用于
wg show
确保更改生效。还可以
ip route show table 2
再次使用,检查 wg-quick 将AllowedIPs 转换为系统路由是否受此影响。在服务器上,实际上通过 WireGuard wg0 接口路由地址。
让它粘起来:
由于是三层隧道接口,所以不需要指定网关
via
;所有路由都可以是justdev wg0
(虽然添加via 10.17.0.128
无害,但也无用)。WireGuard 使用AllowedIPs 来决定将数据包转发到哪个对等点;像 GRE 这样的纯点对点隧道只会将所有内容发送到“另一端”。但是,如果它是 L2TP 或 ZeroTier 等第 2 层隧道,则必须指定via 10.17.0.128
为网关。在这种情况下,wg-quick 已经向表 2 添加了路由,因此我们可以通过添加 代替
ip rule
上述路由来使用它 - 这实际上在客户端是必要的- 但在服务器上,从为了理解起见,一条简单的路线。从技术上讲,您可以使用 Bird 的“静态协议”来使路由永久化,而不是使用 wg-quick 的“PostUp”,但我现在建议使用后者,因为使用 Bird,您需要添加过滤器以防止 /27 路由通过 BGP 发送。(无论如何,您的对等方都不会接受 /27 路由,但最好不要通告除 /24 聚合路由以外的任何内容。)
使用 tcpdump 验证数据包是否发送至客户端。
ping 203.0.113.32
在 Vultr 服务器上运行。tcpdump -n -i wg0
以确保这些数据包通过隧道出去(即路由表正常)tcpdump -n -i wg0
以确保数据包到达隧道的另一端(即AllowedIP 正常)。还要验证来自 Internet 的数据包是否到达您的 VPS 并转发到客户端。
ping 203.0.113.32
(如果是 Windows,请使用该选项)。(如果您没有任何其他位置可以进行 ping 操作,请使用公共“Looking glass”站点 – 许多大型运营商允许您从其网络进行 ping/traceroute 操作。)-t
tcpdump -n -i eth0 "host 203.0.113.32"
,查看数据包是否到达服务器(即BGP公告正常)。tcpdump -n -i wg0
以确保这些数据包通过隧道转发出去。如果不是,请检查 iptables 规则。在客户端上,您现在正在接收“发往”203.0.113.32/27 的数据包。出于所有意图和目的,您可以将其视为客户端直接执行 BGP 公告并且隧道不存在(除了仍然需要策略路由在两个上行链路之间进行选择之外)。
我假设您的虚拟机主机有一个
vmbr0
所有虚拟机都连接到的网桥。我无法提供有关 libvirt 网络的任何说明,因为我几乎没有接触过 libvirt,但所需要的只是配置 libvirt 网络 IP 地址 - 其中不会有任何 WireGuard 特定或 BGP 特定的内容。
为主机本身分配一个地址。将其作为 /32 添加到其他接口(“lo”是传统接口,也可以使用“dummy0”或“wg0”),以便即使 vmbr0 由于某种原因关闭时它也能继续工作。
让我们使用 /27 的第一个地址:
(因为它是 /32,所以您甚至可以使用第 0 个地址,即
.32/32
;“保留”规则在这里不适用。同样,/32 没有广播,因此最后一个地址.63
也可用。)此时,如果仍然
ping
运行该程序,则主机应该开始发送回复(尽管仍然通过错误的接口),因此再次使用 tcpdump 来验证是否存在 ICMP 回显请求和响应。添加策略路由规则,根据源地址通过 wg0 引导流量。
通常情况下,您需要首先将路由添加到表 2,但 wg-quick 已经做到了这一点(来自AllowedIPs)。不过,您应该验证它是否正确(如果不正确则更正):
您可以使用它
ip route get
来检查内核对任何特定数据包的看法:策略规则到位后,客户端主机上的 tcpdump 现在应该显示 Echo Reply 数据包确实通过 wg0 而不是 eth0 – 并且现在应该
ping 203.0.113.32
实际上正在接收回复。如果没有,请在每一步(客户端 wg0、服务器 wg0、服务器 eth0)重复 tcpdump 以查找卡住的位置。剩下的就是将 /27 的其余部分分配给您的 VM 网桥,就像它是普通子网一样:
...并编辑 libvirt 网络 XML 以实际使其持久化(甚至可能使其为此网络执行 DHCP)。
其他注意事项:
您现在应该正在使用Bird 2 。虽然 1.6.x 系列不会停止工作,但它已经停止接受任何进一步的开发(自四年前的 1.6.8 版本以来仅完成了一次修复)。
您应该对导出到 eBGP 对等方的内容有明确的过滤器(即使对等方已经过滤了从您那里导入的内容,也是一种良好的做法)。例如:
export where net = 203.0.113.0/24;
export where net ~ [ 203.0.113.0/24 ];
export filter { if net ~ [ 203.0.113.0/24 ] then accept; reject; };
route 203.0.113.0/24 unreachable
可能是首选。无论如何,网关规范不会成为 eBGP 声明的一部分(eBGP 总是将自身通告为下一跳)——但它将被导出到本地内核路由,并且最好有一个覆盖所有部分的“不可达”路由。您的网络没有专门路由到其他地方。scan time
在许多版本之前就不再需要了——Bird 从内核获得即时的 Netlink 通知。有一个空protocol device {}
块就足够了。