几乎每个小型 OpenVPN 网关都会遇到两个名称问题。你无法用证书上的名称访问一台已连接的笔记本,而隧道另一侧的服务根本没有名称。它们看起来像同一个问题,却需要相反的处理方式,因为一个地址会随每次会话移动,另一个不会。动手修改之前,先确定你添加的是哪一类名称。

按名称决定,而不是按网关决定

判断很简单:客户端重新连接时,这个地址会变吗?会变,名称就必须从 VPN 会话生成。不会变,它就属于一条可以阅读和审查的静态条目。

Name kindSource of truthWhere it lives
Connected clientThe address OpenVPN just assignedGenerated hosts file, rewritten per session
Service behind the tunnelA fixed address you choseStatic address= drop-in
Anything with a certificate nameThe certificate, not the poolEither, but the name must match the certificate

把两者混在一起是常见的失败。固定在生成文件里的服务,会在脚本第一次重写该文件时消失;写进静态条目的客户端,则指向最后持有该地址的那台机器。这两个错误都要等到某个名称解析到错误的机器时才暴露出来。到那时,排查的人会以为是 DNS 坏了,而不是文件有问题。

动态路径:learn-address 与地址池

OpenVPN 知道分配结果,那就让它告诉你。下面这些服务器选项让分配既持久又可用脚本处理:

client-config-dir /etc/openvpn/ccd
ifconfig-pool-persist /etc/openvpn/ip-pool 60
script-security 2
learn-address /etc/openvpn/learn-address.sh

learn-address 会在地址被添加、更新或删除时运行你的脚本,并把操作、地址和通用名称传进去。一个最小脚本为每个客户端追加一行,删除时再把那一行去掉:

#!/bin/sh
HOSTS=/etc/openvpn/hosts.lan
case "$1" in
  add|update) echo "$2 $3" >> "$HOSTS" ;;
  delete)     sed -i "/^$2 /d" "$HOSTS" ;;
esac
kill -HUP $(cat /var/run/dnsmasq.pid)

在 tun 设备上,第二个参数是客户端的隧道地址,所以那一行可以直接使用。在 tap 设备上,OpenVPN 传的是客户端的 MAC 地址,因为 tap 网段可以承载 IPv4 以外的协议;这时你必须先按通用名称在池文件里查到地址,再写入任何内容。

要清楚这个生成文件到底是什么:它是当前谁在线的快照。名称会在断开时消失,这对在不同网络之间移动的笔记本正是你想要的,而对整夜开着的服务器正是你不想要的。

静态路径:address= 与域名扩展

让 dnsmasq 指向生成文件而不是系统的 hosts 文件,并给它一个域名,让短名称扩展为完全限定名称:

domain=corp.example
no-hosts
addn-hosts=/etc/openvpn/hosts.lan
listen-address=127.0.0.1,10.8.0.1
bind-interfaces

设置 domain 之后,像 10.8.0.6 alice 这样的一行会同时应答 alice 和 alice.corp.example。把 listen-address 限制在隧道网关和 loopback 上,因为不小心发布到 LAN 接口上的解析器,是一项你并不打算提供的服务。

从不来自地址池的服务有自己的条目。把它们放进发行版配置目录下单独的 drop-in 文件,这样软件包升级就不会覆盖它们:

address=/files.corp.example/10.8.0.20
address=/db.corp.example/10.9.0.5

把解析器交出去,否则什么都不会发生

没人查询的解析器什么也改变不了。服务器必须把它通告出去:

push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"

Windows 客户端会自己应用这两个选项。大多数 Linux 客户端不会:它们需要受支持的解析器集成,例如发行版的 update-resolv-conf 钩子或 NetworkManager 的 DNS 处理,或者手动设置搜索域和名称服务器。要检查客户端真实的解析器,而不是假定 push 已被遵守,因为被忽略的 push 看起来和不响应的服务器一模一样。

分三层验证

先直接问服务器,再问客户端,最后问应用:

dig +short @10.8.0.1 alice.corp.example
getent hosts files.corp.example

如果直接查询有应答而应用没有,问题出在向客户端的交接,而不是区域本身。如果名称能解析而连接仍然失败,名称就是好的,接下来该看的是路由或防火墙。

把这两类名称写在配置旁边:哪些是生成的、哪些是固定的,以及每一个必须匹配哪张证书。下一个添加服务器的人,会照抄他最先找到的那种模式。

相关文章