在 Docker 容器中发现 Apple TV: mDNS、多播与 Avahi

3 天前
 rapiz

在 Docker 容器中发现 Apple TV:mDNS 、多播与 Avahi

Disclaimer: 本文由 GPT 协助完成,人类内容高于 80%。

在智能家居中,Home Assistant (HA) 可以自动发现 Apple TV ( ATV )设备。但是,当 Home Assistant 运行在 Docker 容器中,且使用 bridge 网络模式时,发现 Apple TV 往往遇到一些奇怪的问题:

本文将以解决 ATV 发现为引子,介绍 mDNS 、UDP 多播、Avahi 并提供解决方案。


初步探索和假设

HA 的推荐配置会将 Avahi socket 挂载到容器内:

  volumes:
    - /var/run/avahi-daemon:/var/run/avahi-daemon
    - /run/dbus:/run/dbus

进行配置后,在容器内 avahi-browse -a 可以看到 ATV 设备,但是 HA 依然无法发现。

经过查阅资料,得知 avahi 是一个 zeroconf 实现,用于给其他进程提供 zeroconf 发布和发现的服务。而 zeroconf 是一些 mdns 、多播…… 总之是一些不太懂有点绕的话。

此时初步怀疑 HA 可能不通过 avahi socket 查询宿主机 上的 avahi 服务,而是尝试自己直接在网络上。这些流量可能无法通过 Docker network bridge 。而宿主机上的 avahi 服务在容器外,可以直接访问本地网络,因此有正确的服务发现信息。avahi-browse 虽然在容器内运行,但通过 socket 连接到宿主机的 avahi 服务,因此也有正确的信息。

验证

通过 GPT 得知发现服务应该跑在 UDP 5353 上,目标 IP 为形如 224.0.0.251 的多播 IP 。通过多台网络中的非容器设备运行 wireshark 和 tcpdump 验证了这一事实,并观察到了 ATV 的广播流量。

tcpdump -i any udp port 5353

同时,在容器中

  1. 容器中 tcpdump 没有观察到任何 mDNS( 5353 ) 流量进入。
  2. 在 容器中运行 atvremote scan ,同时观察容器内和宿主机的 tcpdump ,发现 mdns 查询请求被发出,同时宿主机的有线连接没有 mdns 流量,说明容器内的查询请求没有发出。

这两个问题可能是症结所在。

HA 如何发现 ATV

查看 HA 源码,我们发现它使用了 pyatv 这个库,继续查看 pyatv 源码,发现它使用了 zeroconf 这个库,并且并没有使用 avahi 。 这可能就是为什么 avahi-browse 可以看到设备,而 HA 不能。

zeroconf 看起来是一个网络库了,自己发送网络流量,而非像 avahi socket 这样连接到 avahi 服务的 client-server 模式。

此时就必须了解多播和 mDNS 了

多播( Multicast )与 mDNS

mDNS 是一个跑在 UDP 多播上的 DNS 服务,使用端口 5353 。与一般的 DNS 服务是服务器配置域名到 IP 的映射不同,mDNS 由各个设备自己在 UDP 5353 上积极的向多播地址 (224.0.0.251) 发送自己的名称。客户端收到这些公告后就发现了对应的服务和 IP 。

家用网络中的多播

正确的多播包需要满足两个条件

  1. IP Header 中目标 IP 地址属于多播地址 224.0.0.0/4
  2. Ethernet Header 中目标 MAC 属于多播 MAC (由多播 IP 编码而成)
  3. UDP 端口 5353

(1)保证了路由器对多播包的正确处理,(2)保证了交换机对多播包的正确处理。

对于简单的家用网络,要么是只有一个路由器,要么是一个路由器加一个交换机,不会过于复杂,不会含有多个 LAN ,因此我们不需要考虑多播包的跨 LAN 路由,家用路由器也多半不支持。简单来理解,几乎就是对家用网络中所有设备的广播。

有了这些知识,在 PC 上打开 wireshark ,选择互联网对应的网卡,设置过滤器为 udp.port == 5353,就可以看到各种各样的 mDNS 流量。

和 ATV 相关的流量大概长这样

mDNS 扫描

我没有深入研究 mDNS 的协议格式,GPT 告诉我 mDNS 扫描有两种形式

这和我的 wireshark 观察相符,也符合这种协议的一般模式( ARP 也类似这种行为)。

多播和 Docker

根据以往经验和网络查询,docker network bridge 是不支持发送多播的。 但接受多播呢?此时观察发现是收不到的,但 avahi 有一个很有意思的选项

Avahi 与 reflector

Avahi socket 和 reflector

此时 reflector 听起来可以帮助解决我们的问题

https://manpages.debian.org/unstable/avahi-daemon/avahi-daemon.conf.5.en.html#enable_reflector=

enable-reflector= Takes a boolean value ("yes" or "no"). If set to "yes" avahi-daemon will reflect incoming mDNS requests to all local network interfaces, effectively allowing clients to browse mDNS/DNS-SD services on all networks connected to the gateway. The gateway is somewhat intelligent and should work with all kinds of mDNS traffic, though some functionality is lost (specifically the unicast reply bit, which is used rarely anyway). Make sure to not run multiple reflectors between the same networks, this might cause them to play Ping Pong with mDNS packets. Defaults to "no".

宿主机配置 /etc/avahi/avahi-daemon.conf

[reflector]
enable-reflector=yes

重启 Avahi:

sudo systemctl restart avahi-daemon

此时再次根据 tcpdump 观察,发现在容器内部可以接收到 mDNS 流量了。好像问题解决了!

再次验证

在网络层已经验证过看到流量了,此时在应用层再尝试验证一次

atvremote scan

遗憾的,发现仍然没有任何设备。这是怎么回事呢。

通过同时在容器内、宿主机、另外一台机器上 tcpdump 发现,atvremote scan 会主动发起 mDNS 查询,但这个多播流量并没有走出容器。宿主机的物理网卡、另外一台机器上都没有这个包的踪迹。看来是 Docket network bridge 无法发送多播流量了。听起来似乎可以通过某种代理软件或转发规则实现,经过一些查找,并没有发现。

在我灰心的时候,我发现 HA 能够发现 ATV 了!此时再次运行 atvremote scan,仍然没有设备。但 HA 确实能够正确添加。或许是 HA 持续的扫描,在公告到达前的几秒恰巧发起了查询,从而能够正确的缓存信息。或许是因为 HA 发起扫描时有更长的 timeout 设置,总之,它现在能工作了,我也不用去寻找转发多播流量的方法了,即使目前只有宿主机到容器内的单向多播是通的。

4. 总结

原文链接: https://blog.yqiao.me/2025/09/docker-apple-tvmdns-avahi.html

915 次点击
所在节点    分享发现
3 条回复
bkmi
3 天前
不错不错,很有探索精神
nctllnty
2 天前
给你点赞
julyclyde
6 小时 19 分钟前
有没有方法从 host 的网络参数方面解决这个问题?
按说 mDNS 消息应该是可以被转发的啊

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1160871

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX