昨天还能匹配的规则今天失效了,因为客户端换了一个隧道地址回来。分配器其实并不随机,了解它的顺序,就能把这种意外变成可预期的事件。它优先选择同一客户端此前占用的地址,退而求其次选择空闲最久的地址,而且如果你不给它记住的地方,它会忘掉一切。依赖租约的规则会一直匹配,直到某天悄悄不再匹配。

分配器遵循的顺序

每个 common name 只允许一个会话时,分配器先找同一客户端此前用过、当前空闲的地址。如果那个地址没了,它就取空闲最久的地址。如果为同一个名字启用了并发会话,就不再优先此前地址,而是直接使用第一个空闲地址,因为这些会话被视为彼此独立的对等节点。

SituationAddress chosenPractical effect
Reconnect, old address still freeThe remembered addressRules appear stable for days
Old address taken by another clientLeast recently used free addressA working rule silently stops matching
Concurrent sessions, same nameFirst free address, per sessionOne identity can hold several leases

由此立刻产生两个后果。快速重连的客户端通常能保住自己的地址,所以没人会注意到这个机制,直到它改变。而在那个短暂窗口里接入的另一台设备可能趁地址短暂空闲把它拿走,之后第一个客户端拿到别的地址,所有基于旧编号的规则就都错了。这两种情况都不会留下看起来像故障的日志。

为什么租约看起来稳定,之后却会移动

这里的稳定只是一种涌现性质:重连恰好发生在别人占用该地址之前。它不是承诺,客户端断开期间也不受保障。要按地址会变来规划,而不是被它打个措手不及。一个只比设备数略大的池会让冲突变成常态,因为退回到空闲最久的地址几乎没有挑选余地。

峰值并发才是决定容量的数字,而不是用户数。有人同时用两台设备测试,或某个客户端在旧会话超时前重连,都会同时占住两个租约一段时间。只按人头配置的池,恰好会制造人们归咎于服务器的频繁变动。

持久化文件只负责记住,不负责预留

要在服务重启后保住的地址需要持久化文件。它在关闭时保存客户端名和地址,在服务启动时读回来,所以计划内重启不再把你写过的规则全部打乱。

ifconfig-pool-persist /etc/openvpn/ipp.txt
server 10.8.0.0 255.255.255.0

它不会做的,是在客户端不在时占住地址。这段空档里分配器可以把这个编号给别人,文件于是记录下一个下次连接无法兑现的旧偏好。请把它当作改善重启表现的提示,而不是静态地址。

在文件里顺便把地址池和每条已路由的子网核对一遍。重叠的网段会发放服务器无法路由的地址,症状是客户端连上了、拿到了地址,却什么都访问不到。

正确地固定一个地址

当防火墙规则或 DNS 记录确实需要固定地址时,去分配它,而不是指望它。按客户端的一条配置项可以直接推送地址和掩码,而这条配置以实际生效的客户端名为键。

# client-config-dir entry for "laptop-acme"
ifconfig-push 10.8.0.50 255.255.255.0

把固定地址放在动态池之外,分配器就绝不会把同一个编号交给别的会话。然后检查隧道拓扑,因为它会改变每个客户端消耗多少个池地址:较早的点对点布局每个客户端要消耗一小块地址段,而子网布局每个客户端只用一个地址。在一个拓扑下看起来很宽裕的池,在另一个拓扑下可能很快耗尽。

在客户端上确认结果,而不是相信服务器的记账。会话回来之后,看接口地址和被推送的路由,因为服务器日志可能报告的是请求过的地址,而接口自己拿到的是另一个。

远端地址是另一个问题

客户端 remote 行里的地址必须在隧道存在之前就可达。把池地址写在那里,比如服务器自己的隧道端点,意味着客户端试图通过一条还没建立的隧道去连端点,结果只会是回环或无声超时,而不是捷径。

使用运营商提供的公网端点,把池留给它原本的用途:隧道内部的地址。把池范围、固定分配和持久化路径记在一处,下一个人就不会把每个租约都当成随机的。

相关文章