Chion82 / netfilter-full-cone-nat

A kernel module to turn MASQUERADE into full cone SNAT
GNU General Public License v2.0
433 stars 124 forks source link

关于padavan固件上对ConeNAT实现的讨论 #1

Closed xd5520026 closed 6 years ago

xd5520026 commented 6 years ago

padavan的老毛子固件通过netfilter里patch的方式实现了full cone,可以参考一下;

想请教一下你,NAPT在进行端口转换的时候,如何保证源ip、port和nat后的port的唯一性,而不是依据目的ip、port和nat后的port的唯一性进行nat转换,即源三元组而不是目的三原组的冲突

Chion82 commented 6 years ago

padavan的Cone NAT实现,直接修改了nf_conntrack_core的内部代码resolve_normal_ct(),对于ConeNAT,将原本的“tuple全匹配”修改为“只匹配tuple的单边三元组”。如果这个修改有patch到openwrt或者更泛的linux内核树中,我们就没必要这么折腾了。

回答你的问题: 我认为你的问题可以转化为“如何保证nat后的源port的唯一性”,因为只要保证分配的映射端口号唯一,你所指的三元组就一定唯一的。

linux在进行SNAT时分配端口有如下原则(不使用--random端口随机化参数),尽量保证分配的端口和原来的源端口一致,如果这样映射后的五元组有冲突(注意这里是五元组而不是你所说的单边三元组,在端口分配期间是需要使用双边的地址信息的),或者目的三元组存在冲突,或者本机占用了这个端口(如果有外网远程主机向本机的这个端口发送了数据而没有DROP掉的话,也算是占用了),则重新分配一个唯一的端口,经过我的抓包观察,这个重新分配的端口一般是从1024开始往上累加的。比如内网主机使用源地址 192.168.1.100:5000,映射后的地址会尽量变成 120.200.X.X:5000 (假设是外网IP),如果这样存在冲突,则可能会映射为 120.200.X.X:1024。

Chion82 commented 6 years ago

我认为这个仓库的优势在于实现一个可插拔的第三方内核模块,而不需要修改linux内核树中的代码,避免编译整个内核或替换关键的内核模块(xt_conntrack),也对内核版本向上兼容而无需频繁维护,同时能够兼容更复杂的case。

但是本仓库的实现由于毕竟没有修改conntrack的核心代码,所使用的“维护自定义端口映射表”的方法是有一些hack。

如果各位在各种开源的固件中发现了类似的实现,也欢迎提出供大家参考,以便移植到其他的Linux发行版中。

xd5520026 commented 6 years ago

感谢你的回复,同时也感谢你分享的技术文章,解答不少最近一直困惑我的nat端口转换问题。 我现在遇到的问题是的在同一nat网关下(可能是多级nat,最终同一个nat网关出口),

  1. 内网主机A到外网B,建立NAT映射到5000端口(A:5000->NAT:5000->B:100);
  2. 内网主机C到公网D,建立NAT映射到5000端口(C:5000->NAT:5000->D:100); 1和2两条映射并不冲突,但A与C利用映射后的5000端口互相打洞时,A->C与C->A 产生冲突导致C:5000到A的连接映射到一个新的NAT端口导致打洞失败,目前想到是修改netfilter里映射端口的检查规则,使1和2在映射时就产生冲突映射到不同端口(使用--random端口随机化参数应该每个新连接映射的端口都不一样),不知道您有没什么其他好的方法
Chion82 commented 6 years ago

@xd5520026 如果你已经使用了padavan的修改版netfilter或者是本仓库的模块,使用 --random--random-fully 应该是没有问题的,这不会影响NAT类型。但是如果你是用的是原版的netfilter,使用 --random 会使NAT类型从port restricted cone变为symmetric。

我有个方法不知符不符合你的场景:客户端应用程序打洞的第一步通常会使用stun协议进行外部ip:port和nat类型的探测,如果通过配置应用程序使得A和C使用同一个stun服务器,即 B:100==D:100 ,那么A和C在SNAT的时候便会产生不同的映射端口,从而避免你所说的冲突。

xd5520026 commented 6 years ago

@Chion82 感谢你的答复,你提出的这个方案确实能解决这个问题我们之前也考虑过,但是在我们的场景里不太完美。

今天我仔细看了一下--random 或 --random-fully的实现。如果用未修改的netfilter,使用--random参数又不能保证与服务器通信的端口和打洞的端口映射一致,所以之前想的还是修改netfilter的端口映射冲突检测机制;还没仔细看你实现的模块在使用--random参数下是如何保证映射的端口不变的。下来我会尝试一下使用你的模块加--random参数看看是什么结果。 再次感谢你的回复!

Chion82 commented 6 years ago

@xd5520026 我抽空在手上的极路由上刷了padavan固件,从它ConeNAT实现的源码分析上看,以及我的实际尝试,发现padavan的ConeNAT不支持 --random 或者 --random-fully ,表现为入站流量可穿透,但出站流量会映射到另一个端口。

后来我跟本repo的模块做了一下对比,之前我的实现也不完全兼容端口随机化(有一定条件性),昨天我修改了一下代码 https://github.com/Chion82/netfilter-full-cone-nat/commit/a3c4615cddecee002542613e2d56990e7f4f20d7 ,现在已经可以完全兼容--random或--random-fully了。后续我会专门开一个issue详细说明这个问题。现在可以先简单的说,无论是padavan的实现还是本repo之前的实现,都只考虑了入站方向的转发而忽略了出站方向的端口映射规则。

另外,不是很建议您去修改netfilter现有的代码,因为netfilter这个内核子系统的更新算是比较频繁,对日后的内核升级和模块兼容这些都不是特别友好。

xd5520026 commented 6 years ago

@Chion82 我后面又仔细重读一遍代码发现find_appropriate_src函数在一定条件下(不加--random参数)是可以保证映射的端口不发生变化的,你修改后代码在加了--random参数的情况下确实解决了二次映射的端口一致性问题

另外关于模块里mapping_table表老化问题,在is_mapping_active里面调用了nf_conntrack_find_get查找相应的跟踪信息,是否maping表里存在该条映射而跟踪连接里找不到相关信息则认为conntrack失效(超时)而从maping映射表里删除该记录,解决映射表不能删除的问题。

发现另外一个问题该模块新建连接依赖netfilter的nf_nat_used_tuple五元组冲突检查机制,如果先有一条映射A:100->nat:100->B:x; 后一条映射C:100->nat:100->D:x ; 在netfilter的端口映射中是不冲突的,但是在get_mapping的时候第二条映射会覆盖掉第一的maping信息,这样fullcone应该会产生问题

Chion82 commented 6 years ago

@xd5520026 mappings表的老化问题后面我还要想办法优化一下。

你举的这个冲突用例我之前也想到过,不过我觉得出现概率不大,而且可以通过--random解决就没有修复。改成了锥形NAT之后,nat:100 就只能用一次了。如果需要修复,我认为可以这样解决:在nat之前由于我们维护了mappings表,可以预先检测到冲突,如果发现冲突我们可以自动添加 --random flag来 针对本次nat 重新分配映射端口,虽然也可能随机到相同的端口但是概率很小。另一个方案是通过遍历mappings表来手动指定一个新的空闲的映射端口,但是这样在遍历mappings的过程中需要调用 nf_conntrack_find_get(),复杂度就达到了n*n,就比较慢了。

xd5520026 commented 6 years ago

@Chion82 这种冲突的几率针对同一种业务类型的数据冲突概率还是非常大的,比如内网两台主机同时访问不同网站(不同ip但业务端口相同),这种端口固定的业务就一定会产生冲突(当然该模块主要是用于udp连接,这儿只是举个列子,我就现实遇到这种业务)。当然用--random参数能很大概率避免这种问题。

我觉得解决这个问题可以将端口替换分配和冲突检查拿到模块里来做,(参nf_nat_l4proto_unique_tuple函数分配端口,通过遍历mapping表检查冲突)只需要遍历mapping表检查,但前提是mapping表得有老化机制。

这里分两种情况: 一、全部udp都走该模块,则只需要遍历mapping表检查冲突 二、部分udp端口走该模块,则设置端口rang范围,通过iptables规则将该模块nat端口替换范围和默认udp的nat端口替换范围错开。同时也只需要遍历maping表检查冲突

Chion82 commented 6 years ago

总结了一下冲突case和最近的更新,请到 https://github.com/Chion82/netfilter-full-cone-nat/issues/2 继续讨论端口冲突问题。 @xd5520026

LGA1150 commented 6 years ago

我找到了另一个 Cone NAT 的实现 https://github.com/Ty-Chen/NAT ,他似乎修改了部分 netfilter 的代码