文章背景图

HomeKit 跨站点发现:用 VXLAN 与 Avahi 打通 mDNS,而不桥接整个局域网

2026-09-28
0
- 字
- 分钟
|

HomeKit 跨站点发现:用 VXLAN 与 Avahi 打通 mDNS,而不桥接整个局域网

两地已有 SD-WAN、IPsec 或其他三层互联,Home Assistant 的 HomeKit Bridge 放在站点 B,而站点 A 的 Apple 设备无法发现它?问题往往不是 TCP 不通,而是 Bonjour/mDNS 默认只在本地链路传播。本文用两台 Linux 中继机建立点对点 VXLAN,让 Avahi 仅在各自 LAN 与 VXLAN 之间反射 mDNS。HomeKit 的实际控制连接仍经已有站点路由直达 Home Assistant,不借道中继机,也不把两个 LAN 接入同一网桥。

这是从一套实际运行并通过两端重启恢复验证的方案抽象出的通用教程。全文没有真实住宅名称、网段、主机 IP、设备名和专有网关配置;所有尖括号项均需自行替换。

一、先看清两条流量路径

  • 发现:HomeKit Bridge 公告 _hap._tcp,通过 mDNS(IPv4 组播 224.0.0.251:5353/UDP)传播。站点 B LAN → 中继 B 的 Avahi → VXLAN(默认 UDP 4789)→ 中继 A 的 Avahi → 站点 A LAN。查询和回复也可以反方向经过同一路径。
  • 连接:Apple 设备解析到 Bridge 的地址与实际 HAP 端口后,使用原有的站点间三层路由建立 TCP 连接。仅发现成功不等于能配对或控制;客户端必须能路由到 Home Assistant 公告的地址及端口。后续重新发现、地址更新仍可能用到 mDNS,不能说“配对后完全不需要 mDNS”。

拓扑中两台中继机只处理服务发现;它们不做 LAN 二层桥接、不承载 HAP TCP 转发,通常也不必打开 net.ipv4.ip_forward。这可避免把 DHCP、广播、环路风险一起带过站点边界。锐捷 SD-WAN 只是可用的底层互联示例,方案不依赖某一厂商。

二、准备条件与参数表

两端 LAN 地址空间不能冲突;两台中继机固定地址且能通过现有互联相互访问;站点 A 的客户端能访问站点 B 的 Home Assistant 地址与 HomeKit Bridge 实际 HAP TCP 端口。Home Assistant 端先确认 Bridge 正常发布;别凭猜测扫描一串端口。两端 Linux 安装 iproute2、avahi-daemon、avahi-utils、tcpdump,且内核支持 VXLAN。

变量 含义
LAN_IF 本机接入本地 LAN 的接口;若承载两种用途,也可以与 UNDERLAY_IF 相同
UNDERLAY_IF 本机到对端中继机的外层出口接口
LOCAL_IP / PEER_IP 两端中继机在已有互联中可达的固定地址
OVERLAY_CIDR / PEER_OVERLAY_IP 新建 VXLAN 的本端地址/掩码与对端地址,使用一段未占用的点对点地址
HA_IP / HAP_PORT Home Assistant 对客户端可路由的地址,以及 HomeKit Bridge 实际监听端口

两端使用同一个 VNI(下文用示例 42),但每端的 LOCAL_IP 与 PEER_IP、Overlay 地址应对调。占位符不代表任何实际家庭网络。 文中只把 UDP 4789 作为常用 VXLAN 端口;设备若使用其他端口,两端及防火墙需一致。

在站点 A 中继机上先检查:

PEER_IP='填写对端中继机地址'
HA_IP='填写 Home Assistant 的可路由地址'
HAP_PORT='填写实际监听端口'
ip route get "$PEER_IP"
ping -c 3 "$PEER_IP"
# 在站点 A 可路由的设备上,按 Bridge 实际端口验证控制链路:
nc -vz "$HA_IP" "$HAP_PORT"

若底层单播或 HAP TCP 本身不通,应先修站点路由、ACL 和目的主机防火墙,不要先部署 VXLAN。只能从中继机连通 HA 不代表 Apple 客户端一定能连通,最终仍以客户端所在网段策略为准。

三、在两端建立临时 VXLAN

分别在两台中继机执行同一组命令,只填写各自的变量。OVERLAY_CIDR 为本端的 地址/掩码;PEER_OVERLAY_IP 是对端不带掩码的 Overlay 地址。下面不增设到对端中继机的主机路由:底层路由必须在执行前已正确,避免教程误改读者现有路由。

LAN_IF='<LAN_IF>'
UNDERLAY_IF='<UNDERLAY_IF>'
LOCAL_IP='<LOCAL_IP>'
PEER_IP='<PEER_IP>'
OVERLAY_CIDR='<OVERLAY_CIDR>'
PEER_OVERLAY_IP='<PEER_OVERLAY_IP>'
VX='vxlan-hk'
VNI=42

# 将所有尖括号占位符替换后,再执行下面各行;不可直接复制带占位符的命令。
sudo modprobe vxlan
sudo ip link add "$VX" type vxlan id "$VNI" \\
  local "$LOCAL_IP" remote "$PEER_IP" dev "$UNDERLAY_IF" dstport 4789
sudo ip addr add "$OVERLAY_CIDR" dev "$VX"
sudo ip link set dev "$VX" multicast on
sudo ip link set dev "$VX" up
ip -d link show "$VX"
ping -c 3 "$PEER_OVERLAY_IP"

两端先确认 Overlay Ping,再继续配置 Avahi。如果曾建立同名接口,ip link add 会报“已存在”;先确认该接口确属本教程创建,不能随意删除现有业务接口。底层若有 MTU 开销(VXLAN 大致增加数十字节,具体受 IPv4/IPv6 与封装影响),按实测调整隧道 MTU;不要笼统认定底层必须支持 1550。mDNS 报文通常较小,但连接与封装也应按环境验证。

四、让 Avahi 只反射指定接口

在两端备份 /etc/avahi/avahi-daemon.conf,检查原文件后只修改对应节;不要整文件覆盖,避免丢失本机已有发布策略。以下是需要出现的配置项,不是让你覆盖全文件的完整配置:

[server]
allow-interfaces=<LAN_IF>,vxlan-hk
use-ipv4=yes

[reflector]
enable-reflector=yes
reflect-ipv=no
sudo cp -a /etc/avahi/avahi-daemon.conf \\
  "/etc/avahi/avahi-daemon.conf.bak.$(date +%Y%m%d-%H%M%S)"
# 按上面的节与实际 LAN_IF 编辑原文件,并核对没有重复键或放错节。
sudo systemctl restart avahi-daemon
sudo systemctl status avahi-daemon --no-pager

仅在确认你的 HomeKit 环境是 IPv4-only,且本机没有其他 IPv6 mDNS 需求时,才考虑在 [server] 设置 use-ipv6=no。Avahi 反射的是 mDNS,不会代替真实地址的跨站路由。如果接口属于其他组播域,需同时检查交换机/AP 的组播隔离、无线客户端隔离等策略。

五、确认每一层都通

  1. 底层:两台中继机的 LOCAL_IP 与 PEER_IP 互通;外层 UDP 4789 只允许对端中继机访问,不对公网开放。
  2. VXLAN:两端分别 Ping 对端 Overlay 地址。抓包可用 sudo tcpdump -ni <UNDERLAY_IF> 'host <PEER_IP> and udp port 4789';若失败,检查路由、ACL、端口、接口与 MTU。不要用“清空全部防火墙规则”来排障。
  3. mDNS:分别观察 LAN 接口和 vxlan-hk 的 UDP 5353:sudo tcpdump -ni vxlan-hk udp port 5353。在站点 A LAN 执行 avahi-browse -rt _hap._tcp,核对服务解析地址与实际 HAP 端口。它应是从站点 A 可路由的 HA 地址,而不是 Overlay 地址。
  4. 控制:从站点 A 的客户端网段验证 <HA_IP>:<HAP_PORT> 可达;然后在家庭 App 中核对发现、配对、状态刷新与控制。已有配对不要为排障而先删掉。

能发现但不能控制,优先检查 HAP TCP、Home Assistant 的公告地址、跨站路由和 ACL;Overlay 通但发现不到,优先检查 Avahi 两端接口白名单、服务是否已在源 LAN 发布以及 Wi-Fi 组播隔离。

六、验证成功后再设置开机恢复

先用临时命令跑通,再部署持久化。下面是一份完整的通用启动脚本:两端文件结构相同,各自填写头部变量,不需要生成两套散碎命令。它只创建名为 vxlan-hk 的接口,不改默认网关或现有主机路由。部署前核实该接口名未被其他业务占用。

sudo tee /usr/local/sbin/homekit-mdns-vxlan-up >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

VX='vxlan-hk'
VNI=42
UNDERLAY_IF='<UNDERLAY_IF>'
LOCAL_IP='<LOCAL_IP>'
PEER_IP='<PEER_IP>'
OVERLAY_CIDR='<OVERLAY_CIDR>'

for value in "$UNDERLAY_IF" "$LOCAL_IP" "$PEER_IP" "$OVERLAY_CIDR"; do
  [[ "$value" != *'<'* && "$value" != *'>'* ]] || {
    echo '请先替换全部占位符' >&2; exit 1;
  }
done

for _ in {1..60}; do
  ip -4 addr show dev "$UNDERLAY_IF" | grep -Fq "$LOCAL_IP/" && break
  sleep 1
done
ip -4 addr show dev "$UNDERLAY_IF" | grep -Fq "$LOCAL_IP/" || {
  echo "本端接口尚未获得地址: $LOCAL_IP" >&2; exit 1;
}

modprobe vxlan
# 不删除同名现存接口;报错提示管理员核对归属。
if ip link show dev "$VX" >/dev/null 2>&1; then
  echo "接口 $VX 已存在:请先确认来源,避免覆盖其他业务" >&2
  exit 1
fi
ip link add "$VX" type vxlan id "$VNI" \\
  local "$LOCAL_IP" remote "$PEER_IP" dev "$UNDERLAY_IF" dstport 4789
ip addr add "$OVERLAY_CIDR" dev "$VX"
ip link set dev "$VX" multicast on
ip link set dev "$VX" up
EOF
sudo chmod 755 /usr/local/sbin/homekit-mdns-vxlan-up

如果你已经用临时命令创建接口,先验证其确为本教程的 vxlan-hk 再删除它,然后启用服务。否则脚本会主动拒绝接管同名接口。

sudo tee /etc/systemd/system/homekit-mdns-vxlan.service >/dev/null <<'EOF'
[Unit]
Description=Point-to-point VXLAN for HomeKit mDNS
Wants=network-online.target
After=network-online.target
Before=avahi-daemon.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/homekit-mdns-vxlan-up
ExecStop=/usr/sbin/ip link del vxlan-hk

[Install]
WantedBy=multi-user.target
EOF

sudo mkdir -p /etc/systemd/system/avahi-daemon.service.d
sudo tee /etc/systemd/system/avahi-daemon.service.d/vxlan.conf >/dev/null <<'EOF'
[Unit]
Requires=homekit-mdns-vxlan.service
After=homekit-mdns-vxlan.service
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now homekit-mdns-vxlan.service
sudo systemctl enable avahi-daemon.service
sudo systemctl restart avahi-daemon.service
systemctl status homekit-mdns-vxlan.service --no-pager
ip -br addr show vxlan-hk

注意:使用 NetworkManager、systemd-networkd、ifupdown 的主机无需为了本文强行切换网络管理器;network-online.target 是否真正等到地址就绪取决于当前网络管理服务。脚本自身也会等待本端 IP 最多约一分钟。验证部署后分别重启两端(不要同时重启),再检查 ip -br addr show vxlan-hk、systemctl is-active avahi-daemon、Overlay Ping、avahi-browse -rt _hap._tcp 与实际控制。oneshot + RemainAfterExit 显示 active (exited) 属正常,但仍须确认接口存在。

七、安全边界与回滚

VXLAN 不提供加密;底层 SD-WAN/专线是否加密须单独确认。仅允许两台中继机在可信互联上互访 UDP 4789;不要把 UDP 4789 或 mDNS 暴露到公网,不要把生产 LAN 接口和 VXLAN 接入同一 Linux Bridge。mDNS 可带出服务名、地址、端口与 TXT 元数据;Avahi 会反射允许接口上的其他 mDNS 服务,不是只转发 _hap._tcp 的协议级过滤器。若只能暴露 HomeKit 服务,需额外设计服务级过滤或更严格的隔离,不能仅依赖 allow-interfaces。

回滚时先停止并禁用 homekit-mdns-vxlan.service,再恢复部署前的 Avahi 备份、移除专门创建的 Avahi drop-in 并 daemon-reload、重启 Avahi。不要顺手清理未知规则或删除名称相似的接口。回滚仅影响两台中继机,不应修改 Home Assistant 本身。

排障顺序:站点单播 → VXLAN → Avahi/mDNS → HAP TCP → Apple 家庭 App。逐层核实,比一上来重建 HomeKit Bridge 更可靠。

原创

HomeKit 跨站点发现:用 VXLAN 与 Avahi 打通 mDNS,而不桥接整个局域网

本文链接: HomeKit 跨站点发现:用 VXLAN 与 Avahi 打通 mDNS,而不桥接整个局域网

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录