关于PREROUTING链处理自发自收包的新认识

关于PREROUTING链处理自发自收包的新认识 shixudong163.com在《组播优化、multicast_query_use_ifaddr和multicast_router》一文中由于当时对Linux源码不是很熟以及受网上资料误导一直以为nat表PREROUTING链不能处理本机自发自收包是因为conntrack机制判断从loopback_dev收包不做跟踪处理而已。实际上根本不是这么回事自发自收包除了单播外组播和广播虽然也通过netif_rx收包但其关联网卡却是物理网卡。针对该文第四部分bridge的multicast_querier发出的igmp查询报文能被nat表PREROUTING链处理正确解读如下igmp querier专用软件在三层发组播包给自己时必须通过ip_local_out,自然也经由nf_conntrack_in在此处留了痕并在POSTROUTING链经由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进行必要的清理操作由veth_xmit调用skb_scrub_packet实现。自发自收包在接收环节再次进入nf_conntrack_in时其conntrack状态并没有重置仍然保留着发送环节处理后的状态故被识别为loopback or untracked导致不再去查找conntrack表更不会去匹配PREROUTING链规则产生新的conntrack记录实际上也相当于untracked后续直接进入本机接收环节。虽然nat表PREROUTING链无法对自发自收包进行DNAT但nat表OUTPUT链同样可以实现本机外出包的DNAT两者组合完美实现了本机接收包和发送包的DNAT其逆转换SNAT则分别在其返回包的POSTROUTING链和INPUT链进行。此外PREROUTING链针对自发自收包的上述限制仅限于nat表mangle表则不存在上述限制。本机使用TPROXY功能就是利用了mangle表可以自发自收的特性先对本机外出包打mark并重新路由通过策略路由实现本机自发自收最终进入本机mangle表PREROUTING链进行TPROXY处理不象DNAT/SNATTPROXY只能在mangle表PREROUTING链处理。由于nat表PREROUTING链处理自发自收包的限制Linux主机在使用127.0.0.1访问Bridge模式Docker时只能通过OUTPUT链实现DNAT。同时由于发包前路由寻址时目标IP为127.0.0.1导致源IP也为127.0.0.1DNAT后重新路由时并不会改变源IP因此需要启用网桥docker0的route_localnet使得数据包可以顺利发送出去此外还需要针对源IP做MASQUERADE否则1、无法解析目标IP的MAC地址2、即使数据包能够到达Docker但由于源IP为127.0.0.1Docker默认关闭route_localnet内核同样无法接收3、即使Docker也启用了route_localnet数据包能被Docker接收也无法返回Linux主机。在WSL2mirrored和Docker配合使用时情形稍有不同。WSL2在使用127.0.0.1访问Docker时该127.0.0.1实际上对应Win11主机的127.0.0.1而非WSL2自身。在没有相应的OUTPUT链DNAT规则时数据包将先发往Win11主机然后重新转发到WSL2因此理论上也可以通过PREROUTING链实现DNAT。但实际情况是WSL2添加PREROUTING链DNAT规则后数据包在WSL2的接收环节再次进入nf_conntrack_in时事实上已经是新数据包其conntrack状态已经重置因而不会发生像Linux主机那样被识别为loopback or untracked的情形。然而后续查找conntrack表时能匹配到先前的外出记录故适用conntrack快速匹配机制。结果是不会产生新的conntrack记录并跳过了PREROUTING链DNAT规则直接进入本机接收环节通过代理模式访问Docker。针对WSL2mirrored的这种特殊情形可在WSL2的raw表PREROUTING链增加一条规则匹配条件和nat表PREROUTING链DNAT规则完全一致并将-j DNAT改为-j CT --zone-orig 1。由于增加了zone在数据包再次进入WSL2的接收环节查找conntrack表项时新数据包和原有外出记录将不再匹配而是根据DNAT规则产生新的conntrack记录此时WSL2就能顺利通过PREROUTING链DNAT而非OUTPUT链DNAT使用127.0.0.1访问Docker了如两种DNAT同时存在则只有OUTPUT链DNAT规则起作用此时数据包将不再发往Win11主机。至于Win11主机使用127.0.0.1访问WSL2mirrored里的Docker数据包在WSL2接收环节属于首次进入nf_conntrack_in因为无conntrack记录所以无需额外的raw表规则就能根据PREROUTING链DNAT规则产生新的conntrack记录。在WSL2的v2.3.11版本解决了Windows主机和WSL2的Docker之间路由互通问题后返回包就能原路返回Win11主机也能通过DNAT模式使用127.0.0.1访问WSL2的Docker。至于v2.3.11之前版本如果WSL2添加了PREROUTING链DNAT规则由于mirrored模式自带的策略路由未考虑到WSL2内部Docker等情形Docker的返回包无法原路返回Win11主机只能到达WSL2导致握手失败。该情形下为了使Win11主机仍能通过127.0.0.1访问WSL2的Docker只能使用代理模式并且WSL2不能添加PREROUTING链DNAT规则否则DNAT规则将优先于代理模式生效代理模式完全起不到作用。WSL2自身则不受v2.3.11之前版本影响仍可通过OUTPUT链的DNAT规则使用127.0.0.1访问Docker。最后顺便提一下WSL2在NAT或Bridged模式下默认启用了localhost转发功能Win11主机能和Mirrored模式一样使用127.0.0.1来访问WSL2资源在Win11主机看来两者似乎没有区别但在WSL2看来两者在网络层面表现完全不同。NAT或Bridged模式下Win11主机使用127.0.0.1访问WSL2里的Docker完全等价于WSL2自身使用127.0.0.1访问起作用的是WSL2的OUTPUT链DNAT规则。Mirrored模式下Win11主机使用127.0.0.1访问WSL2里的Docker则是Win11主机实实在在地通过网络访问起作用的是WSL2的PREROUTING链DNAT规则。事实上如关闭localhost转发功能WSL2mirrored将明确提示使用镜像网络模式时wsl2.localhostForwarding 设置无效。在NAT或Bridged模式下Win11主机此时无法使用127.0.0.1访问WSL2资源但Mirrored模式下的Win11主机则不受影响。另外无论是NAT或Bridged模式还是Mirrored模式要让Win11主机顺利使用127.0.0.1访问WSL2里的DockerWSL2里都必须在目标端口上开启哑侦听服务其目的是为了让该目标端口暴露给Win11主机使得Win11主机发出的数据包能够顺利传递给WSL2一旦数据包到达了WSL2就直接去匹配DNAT规则并发往Docker数据包压根不可能到达WSL2的哑侦听服务。