为什么没有规划时组播无法穿越 VPN
可达性是单播的事。在添加网桥或中继之前,先沿着组、组的作用域、转发状态,以及从单一源到单一接收者的一条路径走一遍。
两个站点可以靠单播完美互达,却仍表现得像其中一方看不到打印机的发现通告、IPTV 串流或集群心跳。这不是隧道坏了,而是目的地与组之间的差别。组播投递需要成员模型、沿途的转发状态,以及愿意承载流量的接口,而这些都不来自单播路由。从应用的组出发,走到一个能工作的接收者,是通往答案的最短路径。
从组和应用入手
先问应用用的是哪个组地址,以及这段流量是做什么的。宣告服务的发现协议、流媒体直播、路由协议的问候报文,是三个不同的问题,对应三种不同的可接受答案。组地址本身就说明了作用域:本地网络控制块中的地址并不打算由普通路由转发到链路之外,而使用这些地址的应用通常假定只有一个广播域。全球作用域的组上的流媒体直播,与期望每台设备都听到每次查询的发现协议是两套设计,只有后者才关心广播域的大小。
要判断需求是服务本身还是发现机制,因为这两类答案的成本相差一个数量级。前者要的是一个不会变化的地址;后者要的是一个 跨越广域链路延伸的广播域,以及由此带来的一切。
链路本地发现本该止步
RFC 6762 中描述的组播 DNS 是围绕单条链路设计的:查询发往固定组,响应发往同一个组,协议依赖于每个参与者都能听到所有人。路由器默认不转发这类流量,这是设计决定而不是疏忽。边界的存在,是为了让一栋楼里的设备不能随意把自己宣告到另一栋楼。看似靠把组带到各处来修复发现的隧道,等于对所有应用都撤掉了这条边界,而不只是对那个需要帮助的应用。
中继器和反射器确实存在,它们是真实的服务,也带来真实的后果:在一个站点宣告的每个名字都会在另一个站点可见,包括能识别人员或设备的设备名。如果真实需求只是让某个特定服务可被找到,那么在 DNS 中发布一条服务记录通常是更小的改动,而且它只暴露一个名字,而不是一串名字。
路由组播需要的三样东西
路由组播需要三样东西协同工作:加入组的接收者、为组维护转发状态的路由器,以及把组地址当作待承载流量的隧道。策略里只列出单播子网的隧道,会在加密边界把组流量丢掉,所以最初的症状往往是安静而不是报错。像 GRE 或 VTI 隧道这样的基于路由的接口,会承载路由表送进去的一切,只要组播状态存在,组就变得可达。
源的可达性是另一半。像 PIM 这样的组播路由协议,通过检查回到源的路径是否符合预期来构建分发树,所以指向别处的单播路由即便组成员关系正确,也会破坏这棵树。为接收者装一条路由对这项检查毫无帮助,正是这个错误造成了能通的 ping 和不通的串流。先验证源的返回路径,再加上成员关系,最后才去看应用。
ip maddr show dev tun0
ip mroute show
ss -gnu | grep 239.255.0.10
ip route get 10.20.5.20
前两行显示主机加入了什么、内核在转发什么,第三行显示应用自己的成员关系,最后一行显示反向路径检查将使用的单播路径。把它们放在一起读,就能区分从未加入的应用和从未转发的网络。
用一个源和一个接收者来验证
在动配置之前,先确认应用在单个网段上能正常工作。如果本地就失败,组播路由不是问题,改隧道也不会有帮助。然后再用一个接收者跨隧道复现,并观察加密边界两侧的报文。
tcpdump -ni tun0 -c 10 'ip multicast'
tcpdump -ni eth0 -c 10 'ip multicast and host 239.255.0.10'
隧道内可见、隧道外缺失的报文,指向封装或策略;两侧都可见但应用没有进展的报文,指向接收者的成员关系或应用的绑定。抓包要短、要过滤,因为在繁忙网段上做大范围组播抓包,多半只是噪声。
只需要服务时,优先选择服务地址
发现是为了找到会移动的东西。名字稳定的服务器不需要发现,私有 DNS 中记录在案的地址就能回答用户真正的问题,而不必把广播域延伸到广域链路上。同样的道理适用于打印机、家电和内部 Web 服务。DNS 里的名字可以从每个需要它的站点访问,而不会向不需要的站点宣告任何东西,并且在设备换成另一个地址的设备后依然可用。
当协议确实需要组播时,把它限制在组和需要的站点上,记录原因,并在应用变化时重新审视这个决定。把发现扩展到所有地方,做一次很容易,而在一年之后、设备都悄悄依赖它时再收回就很难。