在 Docker 容器中发现 Apple TV:mDNS、多播与 Avahi
Disclaimer: 本文由 GPT 协助完成,人类内容高于 80%。
在智能家居中,Home Assistant (HA) 可以自动发现 Apple TV(ATV)设备。但是,当 Home Assistant 运行在 Docker 容器中,且使用 bridge 网络模式时,发现 Apple TV 往往遇到一些奇怪的问题:
HA 无法发现 ATV
容器中
atvremote scan
看不到 ATV,宿主机可以容器中和宿主机中
avahi-browse
都能看到 ATV
本文将以解决 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
同时,在容器中
容器中 tcpdump 没有观察到任何 mDNS( 5353 ) 流量进入。
在 容器中运行 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。
家用网络中的多播
正确的多播包需要满足两个条件
IP Header 中目标IP地址属于多播地址
224.0.0.0/4
Ethernet Header中目标 MAC 属于多播 MAC(由多播IP编码而成)
UDP 端口 5353
(1)保证了路由器对多播包的正确处理,(2)保证了交换机对多播包的正确处理。
对于简单的家用网络,要么是只有一个路由器,要么是一个路由器加一个交换机,不会过于复杂,不会含有多个LAN,因此我们不需要考虑多播包的跨LAN路由,家用路由器也多半不支持。简单来理解,几乎就是对家用网络中所有设备的广播。
有了这些知识,在PC上打开 wireshark,选择互联网对应的网卡,设置过滤器为 udp.port == 5353
,就可以看到各种各样的 mDNS 流量。
和 ATV 相关的流量大概长这样
mDNS 扫描
我没有深入研究 mDNS 的协议格式,GPT 告诉我 mDNS 扫描有两种形式
主动扫描 (Active):发送查询包,等待设备响应
atvremote scan
就是这种方式在 Docker bridge 网络中,容器的 multicast UDP 查询 通常无法到达局域网
被动扫描 (Passive):监听设备定期广播的 announce 包
即使容器无法发出查询,也能发现设备
这和我的 wireshark 观察相符,也符合这种协议的一般模式(ARP也类似这种行为)。
多播和Docker
根据以往经验和网络查询,docker network bridge是不支持发送多播的。 但接受多播呢?此时观察发现是收不到的,但 avahi 有一个很有意思的选项
Avahi 与 reflector
Avahi 是 Linux 下的 mDNS 实现
功能:
广播本机服务
被动监听局域网设备
reflector:把 mDNS 包从一个接口转发到另一个接口
Avahi socket 和 reflector
容器挂载了 Avahi socket,
avahi-browse
可以返回正确信息但是根据之前的依赖调查,HA 并不会走 avahi 发现 ATV
根据观察,在 Docker bridge 网络中,外部 Apple TV 的广播包到不了容器内
此时 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. 总结
问题原因:
Docker bridge 网络不转发 multicast →
atvremote scan
主动扫描失败挂载 Avahi socket 只能解决使用 Aavhi 客户端的软件的问题,不解决 mDNS 主动扫描和 HA 的问题
解决方案:
开启宿主机 Avahi reflector → 广播被转发到 Docker bridge,虽然 atvremote 主动扫描并不会到达宿主机网络,但是通过一段时间的等待,大概率可以使用被动扫描发现。
主要调试方法:
到处 tcpdump 和 wireshark
效果:
即使 bridge 网络下,Home Assistant 也能稳定发现 Apple TV
评论
发表评论