组播优化、multicast_query_use_ifaddr、multicast_router以及PREROUTING处理自发自收包的新认识

本文详细探讨了树莓派在疫情期间针对组播功能的优化过程,涉及Linux Bridge和Hostapd的multicast_to_unicast参数区别、桥接隔离参数的影响以及multicast_querier的源IP问题。作者通过源码分析和实际测试,揭示了Bridge的multicast_to_unicast功能的工作原理,以及如何解决组播路由器和组播查询报文自收自发的问题。此外,还讨论了如何避免使用accept_local参数,通过设置Bridge的multicast_router参数来实现相同效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shixudong@163.com

疫情期间,对树莓派的组播功能进行了一些优化,优化过程中碰到了一些问题,通过网上搜索资料,这些问题均通过Linux的相关参数调整得到了妥善解决,原本一些模糊的概念也得以厘清,特此记录,以免遗忘。

一、Bridge multicast_to_unicast与hostapd multicast_to_unicast参数的区别
无线组播具有先天缺陷,常规优化方法就是启用multicast_to_unicast功能,Bridge和hostapd都有multicast_to_unicast参数,然而他们之间究竟有什么区别呢?
根据man bridge资料,并结合源码分析和实际验证,要启用bridge物理端口的multicast_to_unicast功能(默认关闭),必须同时启用multicast_snooping功能(默认启用)和multicast_querier功能(默认关闭),才能使bridge物理端口的multicast_to_unicast功能真正生效。
Bridge的multicast_to_unicast功能不仅对eth和wlan之间的组播有效,对不同eth之间的组播依然有效,然而对于同一AP下station之间的组播则无效。虽然station之间相互通信仍然必须通过AP,但这些通信全部在无线网卡驱动层处理,其行为更像Hub而非Switch,压根到不了bridge层面。为使station之间组播也能受益于bridge的multicast_to_unicast功能,需要额外再启用两个参数,首先启用hostapd的ap_isolate功能,使得station之间通信交由bridge处理;其次启用bridge下wlan端口的hairpin_mode功能,使得从同一wlan口进来的组播包经过multicast_snooping处理后允许原路返回。
综上,Bridge的multicast_to_unicast功能在bridge发送环节实现,通用性强,适用于所有无线网卡和有线网卡。不足是由于bridge的内在机制(RFC规范要求),multicast_to_unicast对224.0.0.X多播网段不起作用。此外无线station之间的multicast_to_unicast功能还取决于bridge是否采用SoftMAC无线网卡,大部分FullMAC无线网卡还没有实现ap_isolate功能。
Hostapd的multicast_to_unicast功能,在无线网卡发送环节实现,其优势在于不依赖bridge,无需配置multicast_snooping,可作用于任何多播网段;ap_isolate=0时,无线station之间通信也能实现multicast_to_unicast功能。其不足仍然是大部分FullMAC无线网卡还没有实现multicast_to_unicast功能。
Hostapd和Bridge配套使用时,两者的multicast_to_unicast功能可以同时启用,按照网络层对包发送的处理顺序,Bridge的multicast_to_unicast先发挥作用,漏网的组播包,由hostapd的multicast_to_unicast再次处理,理论上可实现multicast_to_unicast的全覆盖。然而对于FullMAC无线网卡来说,依然美中不足,譬如树莓派4的板载无线网卡既不支持multicast_to_unicast,也不支持ap_isolate,所以树莓派4用作无线AP时,其下连station之间组播要想充分受益于multicast_to_unicast功能,只能通过外置无线网卡实现(外置无线网卡采用SoftMAC驱动,使用mac80211框架,multicast_to_unicast和ap_isolate在mac80211实现,不依赖SoftMAC驱动)。
二、Bridge isolated与hostapd ap_isolate参数的区别
根据源码分析,前者隔离bridge物理端口之间的通信(必须进出端口都启用方生效),后者隔离AP station之间的通信。Hostapd不启用ap_isolate时,station之间的通信不经过桥,此时bridge isolated对station之间通信起不到隔离作用。
Hostapd启用ap_isolate后,station之间无法通信,此时启用bridge下wlan端口的hairpin_mode功能,station之间通信就可通过桥转发得以恢复,如再启用bridge下wlan端口的isolated功能,便再次隔离station之间通过桥转发的通信(虽然进出都是同一个wlan口,但仍然符合进出端口都启用isolated这一约束)。
当Hostapd参数ap_isolate=1主要用于控制引导station之间通信经由桥处理时,不宜再通过改变ap_isolate参数来控制station之间通信隔离与否,此时可联合使用bridge无线端口的isolated和hairpin_mode参数实现控制:(1)isolated=1,始终开启AP隔离;isolated=0,通过hairpin_mode控制AP隔离开启与否。(2)hairpin_mode=0,始终开启AP隔离;hairpin_mode=1,再通过isolated控制AP隔离开启与否。就源码分析来看,在上述场景下,isolated=1(开启隔离)和hairpin_mode=0(不允许同一端口之间转发,相当于开启隔离)所起的作用是完全等效的。
三、multicast_querier的源IP问题
如上文所述,要使bridge的multicast_to_unicast功能生效,网络上必须存在igmp querier定期维护组播转发信息。家庭网络可启用linux bridge自带的multicast_querier,定期发送igmp查询报文,默认使用0.0.0.0作为查询报文的源IP。根据资料,源IP为0.0.0.0的igmp querier不参与querier选举,为此可启用multicast_query_use_ifaddr参数使用桥IP作为查询报文的源IP。
在树莓派上启用multicast_querier后,一切运作正常。但一旦开启multicast_query_use_ifaddr功能,其他设备就无法找到树莓派本身提供的MiniDLNA服务(239.255.255.250)或其他multicast接收端服务(非224.0.0.X网段,对于224.0.0.X网段,Bridge不做任何限制)。在其他设备上抓包分析,开启multicast_query_use_ifaddr前后igmp查询报文唯一的区别就是源IP不同。设法在树莓派上运行igmp querier专用软件(采用桥IP作为查询报文的源IP),其他设备能正常找到树莓派的MiniDLNA服务,此时在其他设备抓包,专用软件发出的查询报文包和启用multicast_query_use_ifaddr后树莓派bridge的multicast_querier发出的包格式完全一致,都采用树莓派的桥IP作为源IP。
经在树莓派bridge上抓包,分析树莓派开启multicast_query_use_ifaddr前后的区别,开启前能收到本机发出的igmp report包,而开启后则无法收到本机发出的igmp report包,后者估计是树莓派没有收到自身bridge发出的查询报文包,从而无法发出后续igmp report包。结合源码分析和实际验证,可启用accept_local参数,允许从非loopback网卡进来数据包的源IP可以是本机IP,树莓派就能收到自身bridge发出的查询报文包,并向bridge的multicast_querier发回igmp report包,报告MiniDLNA服务所加入的多播地址,此时其他设备就能正常找到树莓派的MiniDLNA服务。
四、Linux本机组播包自发自收问题
鉴于从其他设备看到树莓派上igmp querier专用软件和bridge的multicast_querier(启用multicast_query_use_ifaddr后)发出的igmp查询报文完全一致,但树莓派本机只能收到前者报文,收不到后者报文(启用accept_local参数后可正常接收)。于是对这两者(都是组播包,理论上都能自发自收)进行了对比分析,意外发现,在本机接收multicast_querier发出的组播包时竟然能被iptables nat表的PREROUTING处理,而根据网上资料,接收自己发送的组播(含广播)和单播都不应被本机iptables nat表PREROUTING跟踪和处理。
经对源代码进行分析,通常三层软件发送组播包时,如启用IP_MULTICAST_LOOP选项(默认启用),将会发给自己一份(广播包无此选项,必须发给自己一份)。在三层发组播包给自己时,直接在三层调用dev_loopback_xmit,在此函数中通过skb_dst_force强制设置路由后直接调用netif_rx收包,本机接收时,调用skb_valid_dst,发现路由有效,从而跳过了需要进行源IP验证的环节,所以压根无需启用accept_local参数便能收到组播包。而multicast_querier在二层发组播包给自己时,直接调用netif_rx收包,本机接收时,调用skb_valid_dst,发现路由无效,后续进入源IP验证环节,如不启用accept_local参数,就DROP掉源IP为本机IP的数据包。
在三层发组播包给自己时,因为是通过netif_rx收包,不涉及物理网卡,在调用dev_loopback_xmit前通过rt_dst_clone将发送设备修改为loopback_dev,而multicast_querier在二层发组播包给自己时,没有修改发送设备这一环节。本机接收时,nat表的conntrack机制判断如是从loopback_dev收到的包(包括单播),不做跟踪处理,所以正常情况下,igmp querier专用软件发给自己的组播包不被nat表的PREROUTING跟踪,而multicast_querier发给自己的组播包能被nat表的PREROUTING跟踪(如果只启用multicast_query_use_ifaddr而不启用accept_local,因为组播包后续无法通过源IP验证而被DROP,能频繁看到LOG)。通过对raw和mangle表的PREROUTING链进行LOG,证明igmp querier专用软件发给自己的组播包确实经过这两个表的PREROUTING链,后续自然也会经过nat表的PREROUTING链,只是不进行跟踪处理而已。
五、Bridge的multicast_router
根据资料,Bridge物理端口上的multicast_router默认为1,表示该端口如持续收到igmp query包的话,该端口将成为临时router ports;如multicast_router设置为2,表示该端口将成为永久router ports。一旦物理端口成为router ports,该端口总是接收所有multicast traffic而不受multicast_snooping限制,使用bridge -s -d mdb命令可以查看物理端口是否为router ports。近期一系列测试过程中,发现不仅bridge物理端口有multicast_router参数,Bridge本身也有multicast_router参数,Bridge自己的multicast_router又在什么情况下起作用呢?
在前面igmp querier专用软件和bridge的multicast_querier(启用multicast_query_use_ifaddr后)发送igmp查询报文的对比测试中,已经得到结论,如后者不启用accept_local参数,就无法接收发给自己的igmp查询报文,导致收不到本机后续发出的igmp report包,其他设备就找不到树莓派本身提供的MiniDLNA服务。测试过程中,偶尔通过INPUT规则DROP掉前者发给自己的igmp查询报文,此时在bridge上抓包,确实发现也无法收到本机后续发出的igmp report包,但其他设备仍然能找到树莓派本身提供的MiniDLNA服务,根据前面的测试和推论,完全无法给出合理解释。
再次验证分析,通常情况下,Bridge对igmp查询报文的处理既可在bridge发出该报文时处理,也可在bridge接收该报文时处理。由第四部分分析可知,igmp querier专用软件发给自己的igmp查询报文在三层直接调用dev_loopback_xmit,不用经过bridge。dev_loopback_xmit调用netif_rx收包时,关联设备为bridge,故组播包将直接进入上层IP协议栈,也不会经过bridge。因此发给自己的igmp查询报文无论是发送还是接收,都不会触发bridge对igmp查询报文的处理。但igmp querier专用软件发往外部的igmp查询报文从三层发出,需要经过bridge,此时会调用bridge的发送函数br_dev_xmit对igmp查询报文进行处理。简单来说,就是bridge持续收到上层发来的igmp查询报文后,Bridge本身也将成为临时router ports,与bridge物理端口不同的是,该临时router ports仅仅面向上层协议,意味着bridge收到的multicast traffic将不受multicast_snooping限制,全部发往上层协议,因此虽然bridge收不到本机后续发出的igmp report,但丝毫不影响其他设备找到树莓派本身提供的MiniDLNA服务。
Bridge的multicast_querier(启用multicast_query_use_ifaddr后)的igmp查询报文从二层发出,无论是发给自己(直接调用netif_rx发包)还是发往外部,都无需经过br_dev_xmit处理(该函数仅面向上层提供发送服务),接收自己发送的查询报文则是通过netif_rx收包,关联设备也是bridge,组播包直接进入上层IP协议栈,也不会经过bridge。此时bridge的multicast_router(默认为1)因为收不到上层发出的igmp query包,永远不可能成为面向上层的临时router ports。
根据上述分析,为本文第三部分存在的问题提供了另外一种解决思路,即无需启用accept_local参数,直接将bridge的multicast_router设置为2即可。经测试,虽然此时bridge仍然收不到本机后续发出的igmp report,但也同样不影响其他设备顺利找到树莓派本身提供的MiniDLNA服务。
值得一提的是,无论bridge本身成为临时router ports,还是将bridge自己的multicast_router设置为2,都无法通过bridge -s -d mdb命令验证这一事实。
六、关于PREROUTING处理自发自收包的新认识
在本文第四部分,由于当时对Linux源码不是很熟,以及受网上资料误导,一直以为nat表PREROUTING链不能处理本机自收包,是因为conntrack机制判断从loopback_dev收包,不做跟踪处理而已。实际上根本不是这么回事,自发自收包的单播、组播和广播都是从物理网卡收包的。针对bridge的multicast_querier能被nat表PREROUTING链处理,正确解读如下:
igmp querier专用软件在三层发组播包给自己时,必须通过ip_local_out,自然也经由nf_conntrack_in,在此处留了痕,并在POST_ROUTING处经由nf_conntrack_confirm加以确认。该包被本机自收并到达nat表PREROUTING链,再次经由nf_conntrack_in时,被识别为loopback or untracked,故不再去匹配PREROUTING链LOG规则,导致永远无LOG产生,表面上看起来貌似没有被nat表PREROUTING链处理。
multicast_querier在二层发包,无需经由ip_local_out的nf_conntrack_in,也就是说没有经过连接跟踪处理,所以后续到达nat表PREROUTING链时,属于首次进入nf_conntrack_in,因为无conntrack记录,故必然去匹配PREROUTING链LOG规则,首次匹配产生LOG,后续适用快速匹配机制,不产生LOG。
其实严格说来,并没有nat表PREROUTING链不能处理本机自收包一说,只是自发自收包比较特殊,发送和接收各调用一次nf_conntrack_in,并且loopback_xmit(单播)和dev_loopback_xmit(多播/广播)不像veth驱动那样对skb进行必要的清理操作。Skb在接收环节再次进入nf_conntrack_in时,其conntrack状态并没有重置,仍然保留着发送环节处理后的状态,故被识别为loopback or untracked,导致不再去查找conntrack表,也不会去匹配PREROUTING链规则产生新的conntrack记录(相当于untracked),后续直接进入filter表INPUT链。
由于PREROUTING处理自发自收包的机制,Linux主机在使用127.0.0.1访问Bridge模式Docker时,无法通过iptables实现DNAT,只能通过userland proxy访问Docker。在WSL2(mirrored)和Docker配合使用时,情形略有不同。WSL2在使用127.0.0.1访问Docker时,该127.0.0.1实际上为Win11主机的127.0.0.1,数据包在WSL2的接收环节再次进入nf_conntrack_in时,事实上已经是新数据包,其conntrack状态已经重置,故不再被识别为loopback or untracked。但在查找conntrack表项时能匹配到先前的外出记录,故适用conntrack快速匹配机制,不会产生新的conntrack记录,导致后续也只能通过userland proxy访问Docker。
针对WSL2(mirrored)的这种特殊情形,可在WSL2的raw表PREROUTING链增加一条规则,匹配条件和nat表PREROUTING链DNAT规则完全一致,并将-j DNAT改为-j CT --zone-orig 1。由于增加了zone,在接收环节查找conntrack表项时,新数据包和原有外出记录将不再匹配,而是根据DNAT规则产生新的conntrack记录,后续WSL2就能顺利通过DNAT而非userland proxy使用127.0.0.1访问Docker了(实际上WSL2上还需要启用hairpin NAT以及docker0的route_localnet,userland proxy:false将自动启用这两个功能)。

至于Win11主机使用127.0.0.1访问WSL2(mirrored)里的Docker,数据包在WSL2接收环节属于首次进入nf_conntrack_in,因为无conntrack记录,所以无需额外的raw表规则,也能根据DNAT规则产生新的conntrack记录。在WSL2的v2.3.11版本解决了Windows主机和WSL2的Docker之间路由互通问题后,返回包就能原路返回(在v2.3.11之前版本,WSL2启用hairpin NAT和docker0的route_localnet后,返回包就能到达WSL2,但无法原路返回Win11主机,所以前例中WSL2本身不受版本号影响),Win11主机也能顺利通过DNAT而非userland proxy使用127.0.0.1访问Docker了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值