Chion82/netfilter-full-cone-nat

Nftables fullcone implementation

wongsyrone opened this issue · 113 comments

Based on code from @Chion82 and @llccd , I implement fullcone support for nftables.

OpenWrt kernel module Package: Compile and run tested on Linux 5.15.32 OpenWrt master branch, not run tested thoroughly.
https://github.com/wongsyrone/nft-fullcone

OpenWrt firewall4, a.k.a. the new nftables-based firewall, patched repo listed below:
https://github.com/wongsyrone/openwrt-firewall4-with-fullcone

You have to patch libnftnl and nftables for the new 'fullcone' statement, patched repo listed below:
https://github.com/wongsyrone/libnftnl-1.2.1-with-fullcone
https://github.com/wongsyrone/nftables-1.0.2-with-fullcone

Sometimes you have to call autoreconf to regenerate configure script.

BONUS: luci-app-turboacc change listed below, I only use its shortcut-fe and fullcone NAT functionality, thus takes what you want.

wongsyrone/lede-1@a15fece

Hi, I get the error
root@OpenWrt:~# /etc/init.d/firewall restart
nft_try_fullcone: cmd /usr/sbin/nft -c 'add table inet fw4-fullcone-test; add chain inet fw4-fullcone-test dstnat { type nat hook prerouting priority -100; policy accept; fullcone; }; add chain inet fw4-fullcone-test srcnat { type nat hook postrouting priority -100; policy accept; fullcone; }; ' 2>/dev/null
nft_try_fullcone failed, disable fullcone globally
Section @zone[0] (lan) fullcone is not enabled in default settings, ignore fullcone settings in zone
Section @zone[1] (wan) fullcone is not enabled by default, ignore zone fullcone setting

@fraybyl You need to patch nftables and libnftnl as well

@wongsyrone

I spent quite a bit of time with this, but if I am not mistaken, I was able to change the repository for nftables and libnftnl.
Is there any way to check if nftables and libnftnl are built correctly?

@fraybyl

nft_try_fullcone failed disappears if everything is okay.

@wongsyrone
I take openwrt/package/libs/libnftnl/Makefile and change the PKG_SOURCE_URL to yours, what am I doing wrong?

@fraybyl Please note the patches folder in libnftnl and nftables directory.

@wongsyrone
Apparently I am very stupid!) thank you so much for your help and your efforts.

can I patch it into 5.10.113?

can I patch it into 5.10.113?

Yes.

can I patch it into 5.10.113?

Yes.

https://github.com/Chion82/netfilter-full-cone-nat#kernel-patch-optional
I want to build it into kernel like this link. would you please guide me to get that? thanks

How to configure it on Linux ?

Will this work ?

table inet my_nat {
     chain my_masquerade {
     type nat hook postrouting priority srcnat;
     oifname "pppoe0" masquerade fullcone
  }
}

Example from OpenWrt:

table inet fw4 {
	chain dstnat {
		type nat hook prerouting priority dstnat; policy accept;
		iifname "pppoe-wan" jump dstnat_wan comment "!fw4: Handle wan IPv4/IPv6 dstnat traffic"
	}

	chain srcnat {
		type nat hook postrouting priority srcnat; policy accept;
		oifname "pppoe-wan" jump srcnat_wan comment "!fw4: Handle wan IPv4/IPv6 srcnat traffic"
	}

	chain srcnat_wan {
		fullcone comment "!fw4: Handle wan IPv4/IPv6 fullcone NAT traffic"
	}

	chain dstnat_wan {
		fullcone comment "!fw4: Handle wan IPv4/IPv6 fullcone NAT traffic"
	}
}

If you use my OpenWrt firewall4 patch, you get these rules automatically.

https://github.com/Chion82/netfilter-full-cone-nat#kernel-patch-optional I want to build it into kernel like this link. would you please guide me to get that? thanks

You may modify the kernel source based on the link you refer to, I do not maintain a kernel patch.

I understand it is implementation on openwrt. I want to get it work on Linux. I will keep trying, thanks

Based on code from @Chion82 and @llccd , I implement fullcone support for nftables.

OpenWrt kernel module Package: Compile and run tested on Linux 5.15.32 OpenWrt master branch, not run tested thoroughly. https://github.com/wongsyrone/nft-fullcone

OpenWrt firewall4, a.k.a. the new nftables-based firewall, patched repo listed below: https://github.com/wongsyrone/openwrt-firewall4-with-fullcone

You have to patch libnftnl and nftables for the new 'fullcone' statement, patched repo listed below: https://github.com/wongsyrone/libnftnl-1.2.1-with-fullcone https://github.com/wongsyrone/nftables-1.0.2-with-fullcone

Sometimes you have to call autoreconf to regenerate configure script.

BONUS: luci-app-turboacc change listed below, I only use it's shortcut-fe and fullcone NAT functionality, thus takes what you want.

wongsyrone/lede-1@a15fece

Thanks for provide the solutions.
Should I compile these project along? Or is there a way to apply it to the openwrt package?
I just compiled an openwrt 22.03.0 build with the kmod-nft-fullcone you gave, but I'm confused how to add the rest 3 project into my build

Hi,

@wongsyrone thank you very much for this, impressive work!

I was able to integrate all your repos etc. into a nice and clean solution for OpenWRT trees, including the libnftnl and nftables patches. autoreconf was needed, but PKG_FIXUP did the trick.

Please find attached a commit in one of my forks which basically patches everything etc. and you can generate ipkgs. Tested partially on an AX3600 build, no issues. There was an initialization error with existing rules from firewall3 though, which might or might not be related.

bitthief/openwrt@baa231c

There was an initialization error with existing rules from firewall3 though, which might or might not be related.

I should change that to info level instead of warning level.

autoreconf was needed, but PKG_FIXUP did the trick.

Indeed. PKG_FIXUP is OpenWrt-specific, I just want to notify the one using GNU/Linux distros.

能出个教程,怎么用openwrt-firewall4-with-fullcone 和 libnftnl-1.2.1-with-fullcone nftables-1.0.2-with-fullcone这三个补丁,我看不像是feeds。

能出个教程,怎么用openwrt-firewall4-with-fullcone 和 libnftnl-1.2.1-with-fullcone nftables-1.0.2-with-fullcone这三个补丁,我看不像是feeds。

不是feeds,需要把带有 'add fullcone' 字样的commit给 git format-patch 然后在openwrt里面就可以用了,打给对应的包即可,如果有不能打的补丁,需要适配新版本

链接里的
libnftnl nftables和fw4补丁要如何使用呢……看上去不是makefile类型的 应该也不是放在patch文件夹里

什么时候出类似这里的iptables补丁格式,打到内核的

@wongsyrone Is it possible to build this module without changing kernel config? https://github.com/wongsyrone/lede-1/blob/ae833ac9e14a4a874144807b15f01c5d922a54c0/package/external/nft-fullcone/Makefile#L23
I'm trying to build this module that is compatible with official OpenWrt release kernel.

从很早以前 netfilter 方面的建议就是不要折腾 NAT,而是直接注重配好 ipv6。

现在**大陆以外的网络基本上都很容易就能换到有 ipv6 地址的 ISP。在**大陆的绝大多数城市也是可以通过**电信等 ISP 的光猫获得 ipv6 地址的。进入网关配置关闭一下 ipv6 的防火墙即可。

感觉这里的开发没有什么必要了。

编辑:
怎么说呢,我今天看到 reddit 上面一个帖子 https://www.reddit.com/r/homelab/comments/z8o8n3/are_we_ipv6_ready/iycj2q3

我觉得我们真的应该想尽办法快速普及 ipv6,甚至于阻挠人们继续使用并停留在 ipv4 上了,以致于我甚至认为在合适的时机 OpenWRT 应该像主流浏览器废除 SSL 等过时的加密通信接口,OpenSSH 废除 RC4 等不安全的加密算法和密钥类型一样加快往 ipv6 的转化。

ipv6 已经 22 年了,而且现在 ipv4 的地址段越来越贵。大公司甚至可以通过高价购买 ipv4 地址段进行垄断而阻挠初创公司购买 ipv4 地址段来运营自己的业务,这样的资本寡头垄断的时代真的不应该到来。

我建议还是不要在完全圆锥上下功夫了。

从很早以前 netfilter 方面的建议就是不要折腾 NAT,而是直接注重配好 ipv6。

现在**大陆以外的网络基本上都很容易就能换到有 ipv6 地址的 ISP。在**大陆的绝大多数城市也是可以通过**电信等 ISP 的光猫获得 ipv6 地址的。进入网关配置关闭一下 ipv6 的防火墙即可。

感觉这里的开发没有什么必要了。

编辑: 怎么说呢,我今天看到 reddit 上面一个帖子 https://www.reddit.com/r/homelab/comments/z8o8n3/are_we_ipv6_ready/iycj2q3

我觉得我们真的应该想尽办法快速普及 ipv6,甚至于阻挠人们继续使用并停留在 ipv4 上了,以致于我甚至认为在合适的时机 OpenWRT 应该像主流浏览器废除 SSL 等过时的加密通信接口,OpenSSH 废除 RC4 等不安全的加密算法和密钥类型一样加快往 ipv6 的转化。

ipv6 已经 22 年了,而且现在 ipv4 的地址段越来越贵。大公司甚至可以通过高价购买 ipv4 地址段进行垄断而阻挠初创公司购买 ipv4 地址段来运营自己的业务,这样的资本寡头垄断的时代真的不应该到来。

我建议还是不要在完全圆锥上下功夫了。

不同意,只是V6看起来普及很快,实际上,很多大厂的官网还是V4,只要是V4还是主流,就存在NAT问题。除非国家给政策XX年所有互联网必需支持双栈,否则这条路还长着

@escape0707 但问题是仍然有大量软件不支持 IPv6,尤其是 P2P 通信的游戏。而且直到现在 github.com 还不支持 IPv6。

@escape0707 但问题是仍然有大量软件不支持 IPv6,尤其是 P2P 通信的游戏。而且直到现在 github.com 还不支持 IPv6。

主要这是一个悖论,就好像现在**人越来越难摆脱在**人头上拉屎撒尿的微信一样。当你有越多的 workaround,你的ISP 和水平不够、贪心的游戏公司、软件公司就越喜欢让你继续用 ipv4 并买地址段垄断。另外如果你用的是开源软件为主,那些开源软件才有可能真正的与时俱进用上 ipv6,闭源软件和其公司才时刻想着垄断。

I wrote a script builds nft-fullcone kernel module suitable for OpenWrt official kernel.
It's kernel vermagic is same as OpenWrt official kernel.
So this module is compatible and can be installed directly in OpenWrt official image.

The build process references https://hamy.io/post/0015/how-to-compile-openwrt-and-still-use-the-official-repository/
The patch is derived from https://github.com/wongsyrone/lede-1

It uses GitHub Actions to automatically build binaries https://github.com/ysc3839/openwrt-official-builds-fullcone/actions

我编写了个编译 nft-fullcone 内核模块的脚本,可编译适用于 OpenWrt 官方内核的模块。
编译出的模块的内核 vermagic 和 OpenWrt 官方内核是一致的,因此可以直接在 OpenWrt 官方镜像中安装。

编译流程参考了 https://hamy.io/post/0015/how-to-compile-openwrt-and-still-use-the-official-repository/
patch 文件修改自 https://github.com/wongsyrone/lede-1

使用 GitHub Actions 自动编译 https://github.com/ysc3839/openwrt-official-builds-fullcone/actions

https://github.com/ysc3839/openwrt-official-builds-fullcone

来个标准linux内核补丁

@wongsyrone的repo 没法在debian编译成功 有空给看看?

vbzc commented

@fraybyl

If you really don't know how to compile these binaries correctly, just copy these folders and replace yours.

https://github.com/wongsyrone/lede-1/tree/master/package/network/utils/nftables https://github.com/wongsyrone/lede-1/tree/master/package/libs/libnftnl https://github.com/wongsyrone/lede-1/tree/master/package/network/config/firewall4

大佬你好,请问 拷贝好文件以后,后续是按照一般情况 来编译吗?其他还需要做什么吗?谢谢!

我最近看到一篇tailscale写得非常好非常通俗易懂的(但是是英语)解释udp打洞的文章。

https://tailscale.com/blog/how-nat-traversal-works/

据我所知linux、包括openwrt用的都是endpoint independent mapping。所以从linux方面来说没有技术限制打洞。只是有的闭源应用软件开发做的不好。建议要么换ipv6要么换开源软件。

Ovear commented

所以从linux方面来说没有技术限制打洞

主要问题是Endpoint-Dependent firewall (dest. IP+port),这样打洞的难度会提高很多。

不过这点我是有点疑问的,用NatTypeTester测下来是 Endpoint-Dependent firewall + Endpoint-Dependent NAT mapping,也就是Symmetric NAT

根据Conntrack的工作原理来看,这应该是预期行为。

晚点我再翻一翻之前openwrt吵架的那一篇issue和tailscale新旧两篇文章看看是不是误解了什么,感谢推荐。

IPv6的话实际是用下来感觉不太算银弹,就算大陆很普及了,但是实际上其他地区的普及率还是很低,软件兼容性也是一个大问题。问题最大的还是路由和网关(尤其是移动网络)上配置的防火墙行为,如果是NetfilterWindows defender的话,又要和Endpoint-Dependent firewall (dest. IP+port)缠斗了。

另外插播一则喜报,RouterOS v7.10beta5喜提endpoint-independent-nat,也就是fullcone。现在主流的软路由都能获得fullcone能力了。

最后再提一嘴IPv6吧。。如果在多线接入又没有BGP的支持下,还是得和NAT这永远绕不开的议题打交道(虽然NPT简单很多,但是最终还是引入了这个问题)。

Ovear commented

其实我更多还是比较好奇为什么Openwrt官方会对fullcone如此抗拒;但是反而市面上的商业产品(不管有没有基于openwrt,不管是家用还是商用,甚至运营商级)都不约而同的内置了fullcone

很有趣的一个现象。

主要问题是Endpoint-Dependent firewall (dest. IP+port),这样打洞的难度会提高很多。

文章中破除的迷信就是这个错误的观念,希望您能仔细阅读一下。

不过这点我是有点疑问的,用NatTypeTester测下来是 Endpoint-Dependent firewall + Endpoint-Independent NAT mapping,也就是Symmetric NAT。

比如您说的这种测试结果,用老名词对应着说的话应该是port restricted,这点和我以前测试的各种openwrt都是对的上的。

但是反而市面上的商业产品(不管有没有基于openwrt,不管是家用还是商用,甚至运营商级)都不约而同的内置了fullcone。

因为他们用自定的系统而不是依赖于linux的防火墙功能。另外cgnat实际上是被另一篇rfc要求达到所谓fullcone的,具体的现在不方便搜(

Ovear commented

比如您说的这种测试结果,用老名词对应着说的话应该是port restricted,这点和我以前测试的各种openwrt都是对的上的。

这个是我打错了,是Endpoint-Dependent firewall + Endpoint-Dependent NAT mapping

文章中破除的迷信就是这个错误的观念,希望您能仔细阅读一下。

NAT traversal文章我还没仔细看,根据之前how tailscale works这篇文章来看,Symmetric NAT是非常难以进行打洞的。

您是在什么网络环境下测出来的?

Ovear commented

您是在什么网络环境下测出来的?

感谢提醒,之前测试的时候没注意测试的网络是否下发了公网IP;如果是内网IP的话,测试会受到出口路由的NAT/Filter行为影响。

刚才重新用下列环境确保在公网IP下复测的结果是EndpointIndependent NAT mapping + AddressAndPortDependent firewall

Openwrt 21.02 + Windows 10 with WDF

如果默认行为能达到这个级别,打洞确实不应该存在显著的困难。之前我只确认了netfilterCONNTRACK的关系带来的Endpoint-Dependent firewall;重复测试之后也通过复现解除了NAT mapping的误解。

即:netfilter提供的默认行为确实是Endpoint-Independent NAT mapping + Endpoint-Dependent firewall (dest. IP+port)

另外cgnat实际上是被另一篇rfc要求达到所谓fullcone的,具体的现在不方便搜(

这个RFC我挺好奇的,大佬有空了的时候,方便搜一下发出来吗?

呀,记错了

https://www.rfc-editor.org/rfc/rfc6888

REQ-7 但是是推荐。不过其实影响不大,RFC而已,**都有防火长城了也不会去兢兢业业遵守这个。就比如REQ-9还让CGNAT必须提供PCP呢

哇哦,这有个幸运儿他的cgnat还真支持 https://www.reddit.com/r/HomeNetworking/comments/cx1rl3/

Ovear commented

很神奇的事,刚刚想着用原生Linux复现一下,结果出现了不一致的情况。

测试环境

测试客户端(192.168.0.100)->Openwrt(192.168.0.1/10.0.0.2)->Debian 11(10.0.0.1)

其中Openwrt开启Fullcone

测试客户端采用pystun3(自定义测试用)和NatTypeTester

受限于时间限制,暂时使用了多层NAT,有空了想办法看看单层NAT能否复线。

测试结论

NatTypeTester

RFC5780: EndpointIndependent Mapping + AddressAndPortDependent Filtering
RFC3489: Symmetric

pystun3

RFC3489: Symmetric NAT

结果分析

NatTypeTesterRFC3489按顺序进行了三次测试

Test1: 向目标服务器发送Bind测试(获取到changed address/port
Test2: 向目标服务器发送ChangeIPAndPort请求(此时STUN服务器会用changed address/port回复)
Test3: 向changed address/port发送Bind测试

测试结果
Test1收到回复/Test2未收到回复/Test3收到回复
Test1回报的公网地址与Test3回报的公网地址相同,但是回报的端口不同。
程序显示为Symmetric NAT

NatTypeTesterRFC5780按顺序进行了三次测试

Test1: 向目标服务器发送Bind测试(获取到changed address/port
Test2: 向目标服务器发送ChangeIPAndPort请求(此时STUN服务器会用changed address/port回复)
Test3: 向目标服务器发送ChangePort请求(此时STUN服务器会用changed port回复)
Test4: 向changed address原始端口发送Bind测试

测试结果
Test1收到回复/Test2未收到回复/Test3未收到回复/Test4收到回复
Test1回报的公网地址和端口与Test4回报的相同
程序显示为EndpointIndependent Mapping + AddressAndPortDependent Filtering

pystun3追加测试

RFC3489

默认情况下,pystun3采用测试的流程与NatTypeTesterRFC3489中的测试一致,但是增加了重复发包来避免丢包的行为,

测试表现、结果与NatTypeTester一致,都存在Test3与Test1回报的公网端口不同问题。

修改版RFC3489测试

变动如下
Test3(修改版): 向changed address原始端口发送Bind测试 (即RFC5780的Test4)

测试结果
Test1收到回复/Test2未收到回复/Test3收到回复
Test1回报的公网地址和端口与Test3回报的相同
程序显示为Restric Port NAT

修改版RFC3489测试

变动如下
Test3(修改版): 向changed address原始端口发送Bind测试 (即RFC5780的Test4)
Test4(追加): 向changed address/port发送Bind测试 (即原本的Test3)

测试结果
Test1收到回复/Test2未收到回复/Test3收到回复/Test4收到回复
Test1回报的公网地址和端口与Test3回报的相同
Test1/Test3回报的公网地址与Test4回报的公网地址相同,但是端口不同

结论

本次测试中Debian 11的NAT MAPPING行为:

在目标端口一致时表现为Endpoint-Independent NAT mapping
目标端口不一致时表现为Endpoint-Dependent NAT mapping

也就是至少采用三元组匹配 (src_ip,src_port,dst_port)

PS:测试中端口变更均发生在Debian11进行nat的时候,Openwrt侧未发生端口变更;因为时间原因未测试Openwrt关闭Fullcone的情况,有空了看看能不能补充下。

PS2:突然想起来很久以前我测试是在原版上做的(时间有点久远既不太清了),所以才测出之前说的Symmetric NAT的问题;而前面回复的测试也是在打了Fullcone补丁但是关闭选项的Openwrt下测试的;复测时应测原版Openwrt更好,毕竟不能保证补丁关闭后完全无影响。等测完Debian 11看看能不能复测下。

说实话没看明白你说的测试环境和步骤,你先是一层修改过netfilter的openwrt做nat,然后又用设置了另一个局域网络然后用一个debian机器做默认网关和第二层nat?你的debian机器是公网IP?你10.0.0.0/24的网络用的什么方法组建的?物理直连?

另外如果你找到linux原生masquerade不能Endpoint Independent Mapping的话你应该去netfilter那边发bug了,这特性netfilter是明确支持的

Ovear commented

说实话没看明白你说的测试环境和步骤

请问哪里没看懂?openwrt和debian都有公网权限,openwrt到debian是走的隧道,目前是二层隧道softether。测试时采用的stun服务器是NatTypeTester提供的服务器。

另外如果你找到linux原生masquerade不能Endpoint Independent Mapping的话你应该去netfilter那边发bug了,这特性netfilter是明确支持的

请问这个有资料吗?

openwrt和debian都有公网权限,openwrt到debian是走的隧道,目前是二层隧道softether

都有公网权限你为啥还要要 Debian 那台机器,你openwrt用个原版的、直接在 192.168.0.100 上面测不就好了吗。

请问这个有资料吗?

这个你随便搜一下就有,以前邮件列表里主要维护者已经解答过了:https://marc.info/?l=netfilter&m=125491138128462

Ovear commented

刚刚在Vultr上做的快速测试,结论还是一样

测试环境如下

Node1正常购买+开启VPC,WAN IP为x.x.x.x
Node2购买IPv6 Only机型,或者手动关闭IPv4+开启VPC

Node1开启forwarding,同时iptables设定masquerade
Node2新增默认路由通过VPC(网卡2)

测试结果如下

默认测试

root@vultr:~/pystun3# python3 cli.py -d -H stun.syncthing.net
DEBUG:pystun3:Do Test1
DEBUG:pystun3:sendto: ('stun.syncthing.net', 3478)
DEBUG:pystun3:recvfrom: ('139.59.84.212', 3478)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': 'x.x.x.x', 'ExternalPort': 54320, 'SourceIP': '139.59.84.212', 'SourcePort': 3478, 'ChangedIP': '139.59.49.16', 'ChangedPort': 3479}
DEBUG:pystun3:Do Test2
DEBUG:pystun3:sendto: ('stun.syncthing.net', 3478)
DEBUG:pystun3:sendto: ('stun.syncthing.net', 3478)
DEBUG:pystun3:sendto: ('stun.syncthing.net', 3478)
DEBUG:pystun3:sendto: ('stun.syncthing.net', 3478)
DEBUG:pystun3:Result: {'Resp': False, 'ExternalIP': None, 'ExternalPort': None, 'SourceIP': None, 'SourcePort': None, 'ChangedIP': None, 'ChangedPort': None}
DEBUG:pystun3:Do Test1
DEBUG:pystun3:sendto: ('139.59.49.16', 3479)
DEBUG:pystun3:recvfrom: ('139.59.49.16', 3479)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': 'x.x.x.x', 'ExternalPort': 16172, 'SourceIP': '139.59.49.16', 'SourcePort': 3479, 'ChangedIP': '139.59.84.212', 'ChangedPort': 3478}
NAT Type: Symmetric NAT
External IP: x.x.x.x
External Port: 16172

修改版测试

测试描述
Test1:向服务器发起Bind
Test2:向服务器发起ChangeAddressAndPort
Test3(修改版): 向changed address和原始端口发送Bind
Test4(追加): 向服务器发送changePort
Test5(pystun3自带):向changed address/原始端口发送ChangePort
Test6(pystun3自带):向changed address/port发送Bind

DEBUG:pystun3:Do Test1
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:recvfrom: ('139.59.84.212', 3478)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': 'x.x.x.x', 'ExternalPort': 54320, 'SourceIP': '139.59.84.212', 'SourcePort': 3478, 'ChangedIP': '139.59.49.16', 'ChangedPort': 3479}
DEBUG:pystun3:Do Test2
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:Result: {'Resp': False, 'ExternalIP': None, 'ExternalPort': None, 'SourceIP': None, 'SourcePort': None, 'ChangedIP': None, 'ChangedPort': None}
DEBUG:pystun3:Do Test4
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:sendto: ('139.59.84.212', 3478)
DEBUG:pystun3:Result: {'Resp': False, 'ExternalIP': None, 'ExternalPort': None, 'SourceIP': None, 'SourcePort': None, 'ChangedIP': None, 'ChangedPort': None}
DEBUG:pystun3:Do Test3
DEBUG:pystun3:sendto: ('139.59.49.16', 3478)
DEBUG:pystun3:recvfrom: ('139.59.49.16', 3478)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': 'x.x.x.x', 'ExternalPort': 54320, 'SourceIP': '139.59.49.16', 'SourcePort': 3478, 'ChangedIP': '139.59.84.212', 'ChangedPort': 3479}
DEBUG:pystun3:Do Test5
DEBUG:pystun3:sendto: ('139.59.49.16', 3478)
DEBUG:pystun3:sendto: ('139.59.49.16', 3478)
DEBUG:pystun3:sendto: ('139.59.49.16', 3478)
DEBUG:pystun3:sendto: ('139.59.49.16', 3478)
DEBUG:pystun3:Result: {'Resp': False, 'ExternalIP': None, 'ExternalPort': None, 'SourceIP': None, 'SourcePort': None, 'ChangedIP': None, 'ChangedPort': None}
DEBUG:pystun3:Do Test6
DEBUG:pystun3:sendto: ('139.59.49.16', 3479)
DEBUG:pystun3:recvfrom: ('139.59.49.16', 3479)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': 'x.x.x.x', 'ExternalPort': 19882, 'SourceIP': '139.59.49.16', 'SourcePort': 3479, 'ChangedIP': '139.59.84.212', 'ChangedPort': 3478}
NAT Type: Restric Port NAT
External IP: x.x.x.x
External Port: 54320

配置一览

Node1

root@vultr:~# ip -4 ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet x.x.x.x/23 brd x.x.x.x scope global dynamic enp1s0
       valid_lft 84898sec preferred_lft 84898sec
3: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc mq state UP group default qlen 1000
    inet 10.1.96.4/20 brd 10.1.111.255 scope global enp6s0
       valid_lft forever preferred_lft forever

root@vultr:~# ip r
default via 66.135.18.1 dev enp1s0 
10.1.96.0/20 dev enp6s0 proto kernel scope link src 10.1.96.4 
x.x.x.x/23 dev enp1s0 proto kernel scope link src x.x.x.x 
169.254.169.254 via 66.135.18.1 dev enp1s0 
root@vultr:~# 
root@vultr:~# iptables-save 
# Generated by iptables-save v1.8.7 on Wed May 10 20:15:37 2023
*filter
:INPUT ACCEPT [674:37696]
:FORWARD ACCEPT [183:80318]
:OUTPUT ACCEPT [623:36135]
COMMIT
# Completed on Wed May 10 20:15:37 2023
# Generated by iptables-save v1.8.7 on Wed May 10 20:15:37 2023
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o enp1s0 -j MASQUERADE
COMMIT
# Completed on Wed May 10 20:15:37 2023

root@vultr:~# sysctl -p
net.ipv4.ip_forward = 1

root@vultr:~# iptables --version
iptables v1.8.7 (nf_tables)

root@vultr:~# uname -a
Linux vultr 5.10.0-21-amd64 #1 SMP Debian 5.10.162-1 (2023-01-21) x86_64 GNU/Linux

Node2

root@vultr:~/pystun3# ip -4 ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 100.68.124.159/18 brd 100.68.127.255 scope global dynamic enp1s0
       valid_lft 84949sec preferred_lft 84949sec
3: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc mq state UP group default qlen 1000
    inet 10.1.96.3/20 brd 10.1.111.255 scope global enp6s0
       valid_lft forever preferred_lft forever

root@vultr:~/pystun3# ip r
default via 10.1.96.4 dev enp6s0 
10.1.96.0/20 dev enp6s0 proto kernel scope link src 10.1.96.3 
100.68.64.0/18 dev enp1s0 proto kernel scope link src 100.68.124.159 
169.254.169.254 via 100.68.64.1 dev enp1s0 

root@vultr:~/pystun3# iptables-save 
# Generated by iptables-save v1.8.7 on Wed May 10 20:16:08 2023
*filter
:INPUT ACCEPT [83:73113]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [115:8289]
COMMIT
# Completed on Wed May 10 20:16:08 2023

root@vultr:~/pystun3# iptables --version
iptables v1.8.7 (nf_tables)

root@vultr:~/pystun3# uname -a
Linux vultr 5.10.0-21-amd64 #1 SMP Debian 5.10.162-1 (2023-01-21) x86_64 GNU/Linux

According to the terminology of RFC 3489, netfilter implements port
restricted cone NAT. If the --random flag is specified to the
SNAT/MASQUERADE/... targets, it's better described as a symmetric NAT

你得发一下你防火墙怎么配的。

Ovear commented

没有加这个参数的,已经附在上面了。

没有加这个参数的,已经附在上面了。

请尽可能详尽的描述你测试的环境和步骤。请把你配置路由的命令详尽写出,有隐私信息的地方请去除。

另外也发一下你用的内核版本和防火墙配置工具。

另外依据官方手册的说法 --random 是进行Endpoint-dependent-mapping的开关。根据官方邮件列表的说法,不用 random 确实应该达到Endpoint-independent-mapping的效果。如果没有达到的话,你应该去netfilter的邮件列表、IRC、issue tracker提交bug报告。在这里你得不到最好的解释,如果真有bug也得不到上游修复。不过如果你真的去发的话,可以在这边留个链接。

Ovear commented

已经加上版本信息了,其他的信息应该已经足够复现了。

这个测试结论是符合我的记忆的,所以我一开始说的是Netfilter自带测下来是Endpoint-Dependent NAT mapping/Symmetric NAT的。

今天才看到说netfilter的默认行为是Endpoint-Independent NAT mapping,和记忆中的测试结果冲突了。所以就想看看能不能获得更多信息,或者官方说明之类的。

复现结果记录在Issue内,也方便以后搜索到的同学。

如果按照目前的行为,fullcone补丁还是非常有必要的。

大佬如果有更多的信息的话也欢迎提供下,等研究的差不多了,确认有疑问再打扰官方邮件列表中的大佬比较好。

似乎这个叫 pystun3 的软件有这样那样的 bug https://github.com/jtriley/pystun/issues
我现在没有时间去看他的代码,但我觉得得换个别的工具去测。

或者你用 wireshark 抓包看 pystun3 是否正常。

另外如果真就只有你有问题就太奇怪了。以前我还在用 openwrt (现在我这个运营商不能改桥接我就用的运营商光猫路由) 的时候专门测过是EDF+EIM,这个和上面邮件列表的结论是一致的。另外你在网上稍微搜一下就能看到很多人在 SO 上问怎么从默认的 EDF+EIM 变成 EDF+EDM (答案当然是用 --random)
https://unix.stackexchange.com/questions/345753/
https://unix.stackexchange.com/questions/137611

Ovear commented

似乎这个叫 pystun3 的软件有这样那样的 bug https://github.com/jtriley/pystun/issues

直接通过抓包就可以很清晰的看到了,抓包比较难去隐私所以我没放出来。封包正常发送出去后,就已经可以开始观察了。

这个结果一开始也是从NatTypeTester中发现的,我也是今天做Debian 11测试的时候才开始抓包追根究底的。

另外如果真就只有你有问题就太奇怪了

所以我刚才直接用纯净环境测试了,直接在批量分发的云服务器中测试,也方便复线。

另外我用的版本是 https://github.com/talkiq/pystun3 ,封包我是确认过发出去的。

是,我正想说抓包……

另外我粘贴错了仓库了,我知道你用的 pystun3。。

talkiq/pystun3#3

除了抓包看以外没有办法可以确认 stun 客户端的工作/测试是否正常运行的。这个 fork 作者也不清楚 STUN 的运作原理。。

总而言之,在找 netfilter 的问题之前,你必须证明自己用的测试工具和测试方式是没有瑕疵的。(虽然十有八九不是 netfilter 有瑕疵

既然你在 Vultr 上,你能不能用 ubuntu 测一下,他们官方仓库里应该是有个 stun 测试客户端的:
https://zerotier.atlassian.net/wiki/spaces/SD/pages/7405571/Using+Linux+STUN+Tools+to+Characterize+NAT+Behavior
https://sourceforge.net/projects/stun/files/stun/0.97/

Ovear commented

我试试看,主要问题是在客户端使用相同的本地地址/端口发送数据到changed ip/port时,nat关系被改变了:

映射后的source port变化了,这点在抓包中看的很明显。而且这个变化是发生在没有补丁的Debian11 NAT时。

客户端使用相同的本地地址/端口发送数据到changed ip/port时,nat关系被改变了

你这句话我没太懂。你说的 NAT 关系被改变了是什么意思。我前面和你说了,你要得出你的 NAT 的性质,加上你现在手头的工具不一定靠谱的样子,你只能用更基础的抓包来确定了。
你要不就把抓包的结果去一下隐私发出来。

另外还有一个客户端叫 stunclient (STUNTMAN)的也可以一用,我刚刚在 arch 上编译了。不过既然我们也到了怀疑 stun 客户端工具的地步了,那还是 wireshark 抓包吧。

Ovear commented

下面的包是在Debian 11中抓的,网络结构如下

测试客户端(因NAT抓包中不可见)->Openwrt(10.9.9.2)->Debian 11(10.9.9.1/*.197)

10.9.9.2 是 openwrt 的隧道中的 LAN IP
*.197 是 Debian11 的 WAN IP
*.212 是 stun 服务器的第1个ip
*.16 是 stun 服务器的第2个ip (changed ip)

3478是原始port,3479是changed port

image

你这句话我没太懂。你说的 NAT 关系被改变了是什么意思。

问题出在圈中的两个封包,在这之前所有的source port原本一直是63359(包括测试客户端本地也是),在dst port改变后,被Debian 11修改成59858了。所以我说NAT关系改变

Ovear commented

image

下面这段抓包可以看见,在dst_ip发生改变,但是dst_port没发生改变时,NAT关系继续维持(即src_port未被Debian 11改变)

下面这段抓包可以看见,在dst_ip发生改变,但是dst_port没发生改变时,NAT关系继续维持(即src_port未被Debian 11改变)

这行为确实很怪。。等我一会儿也配一下试试。

image

比较遗憾,我用 Arch Linux + Linux LTS 6.1.27,没能复现你说的行为。
我这个配置还是比较别扭的呢,我现在身边只有笔记本,我用他当NAT,实验室的台式机当客户端,两者之间在 10.0.0.0/24 的wireguard 局域网里的隧道能够连接。实验室的是客户端,用的10.0.0.10,手头的这台笔记本上面和你之前配的一样,除了 wireguard 只有一条masquerade和一个ip_forward。台式机上还是用 fmmark + ip rule + 单独的路由表强制所有从 10.0.0.10 出发的数据包走 wg0 接口。然后笔记本用wifi连的家里光猫。

我用的那个 STUNTMAN 的 stunclient,他默认还是连接的changedIP的originalPort,也就是3478,我特意给他换成changedIP changedPort,去发起bind请求,结果也没变,都能顺利的完成 EIM。笔记本 ArchLinux 的 MASQUERADE 没有做任何 EDM 的映射。我觉得你换一下系统和内核试试吧。

Ovear commented

我刚刚试过了Ubuntu 22.04一样的问题。

你可以试试看用云平台,Arch内核比较新也有可能已经修了这个问题?

Arch内核比较新也有可能已经修了这个问题?

感觉不至于,你可以翻翻 nf 的 issue tracker,我现在不太有功夫看。不过如果是这样的话,只要用新内核就好了,至少行为上还是会按照 EIM 去维护的。

云平台

我现在没有买这些东西(

Edit

我现在不太有功夫看。

我没找到 https://bugzilla.netfilter.org/buglist.cgi?bug_status=__all__&content=endpoint+dependent+masquerade&no_redirect=1&order=bug_id+DESC&product=&query_based_on=&query_format=specific

fullcone补丁还是非常有必要的。

我刚刚出去的时候想了一下,最早关注 fullcone 这个概念的原因是老家网从 ADSL 改光纤,改完没公网v4了。当时又想用 BT 呀电驴什么的。然后 BT 的协议里就只有很简单的 PeX 节点信息交换,swarm 里面有公网 ip 的节点也不会给你当 rendezvous 服务器,所以这时候你给 tracker 一个 fullcone 的 udp 端点,别人就可以随便连你,那在 NAT 里面用的也基本上算公网级的开心。不过除去这种情况(现在基本上国内都有 ipv6 了),常见的游戏啊软件啊之类的真的不会搞不起一个rendezvous服务器,然后 zerotier wireguard tailscale 之类的点对点虚拟局域网也越来越好组,所以 fullcone 的必需应该是越来越少了。

Ovear commented

喜报:我可以在Arch中复现你的测试结果

但是悲报:问题还是存在,最新版内核也没修复这个问题,只是测试方法不同导致的,同样你也可以复现我的结果。

我的修改方法是注释下面这行代码,不知道你的方式一不一样

https://github.com/jselbie/stunserver/blob/d2d06a6d002d1e2fba312cf4993b08bb57a0346b/stuncore/stunclienttests.cpp#LL183C9-L183C67

就有了如下测试结果,如果单独执行behavior测试,结果没问题 Endpoint Independent Mapping

[root@vultr ~]# ./stunclient --verbosity 99999 --mode behavior 198.211.120.59
Resolved 198.211.120.59 to 198.211.120.59:0
config.fBehaviorTest = true
config.fFilteringTest = false
config.timeoutSeconds = 0
config.uMaxAttempts = 0
config.addrServer = 198.211.120.59:3478
socketconfig.addrLocal = 0.0.0.0:0
Sending message to 198.211.120.59:3478
Got response (68 bytes) from 198.211.120.59:3478 on interface 10.1.96.4:40012
Other address is 188.166.128.84:3479

Preparing message for behavior test #2 (destination=AP)
Sending message to 198.211.120.59:3479
Got response (68 bytes) from 198.211.120.59:3479 on interface 10.1.96.4:40012
Binding test: success
Local address: 10.1.96.4:40012
Mapped address: 66.135.24.11:40012
Behavior test: success
Nat behavior: Endpoint Independent Mapping

但是如果改用full来测试,也就是pystun3的行为,问题就出现了Address Dependent Mapping;此时抓包就会发现src_port变更了

[root@vultr ~]# ./stunclient --verbosity 99999 --mode full 198.211.120.59
Resolved 198.211.120.59 to 198.211.120.59:0
config.fBehaviorTest = true
config.fFilteringTest = true
config.timeoutSeconds = 0
config.uMaxAttempts = 0
config.addrServer = 198.211.120.59:3478
socketconfig.addrLocal = 0.0.0.0:0
Sending message to 198.211.120.59:3478
Got response (68 bytes) from 198.211.120.59:3478 on interface 10.1.96.4:45474
Other address is 188.166.128.84:3479

Preparing message for filtering test #2 (ChangeRequest=AA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #2 (ChangeRequest=AA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #3 (ChangeRequest=PA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #3 (ChangeRequest=PA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for behavior test #2 (destination=AP)
Sending message to 198.211.120.59:3479
Got response (68 bytes) from 198.211.120.59:3479 on interface 10.1.96.4:45474
Preparing message for behavior test #2 (destination=AA)
Sending message to 188.166.128.84:3479
Got response (68 bytes) from 188.166.128.84:3479 on interface 10.1.96.4:45474
Binding test: success
Local address: 10.1.96.4:45474
Mapped address: 66.135.24.11:45474
Behavior test: success
Nat behavior: Address Dependent Mapping
Filtering test: success
Nat filtering: Address and Port Dependent Filtering

STUNTMAN client中有一句下面的内容,不知道该怎么理解;我只能理解为一种Feature?

要解开这个谜可能还是要看代码了,我有一种预感:秘密就出在我们现在issue内的fullcone补丁内。

"full" mode is a deprecated mode. It performs both a filtering and a behavior test together. Users are encouraged to run these tests separately and to avoid using the same local port.

我还是很好奇,为什么src_port会发生变更?

"full" mode is a deprecated mode. It performs both a filtering and a behavior test together. Users are encouraged to run these tests separately and to avoid using the same local port.

这意思就是说,你不要用同一个端口短时间内重复发起测试,两次测试之间防火墙的状态会互相干扰混淆。

所以我的测试方法那样就可以了,测EIM的话,用 basic 模式手动发起bind,然后根据 Other address 手动改命令再 bind 看自己发包出去的 src_ip:src_port 变没变就好了。

Got response (68 bytes) from 198.211.120.59:3479 on interface 10.1.96.4:45474
Preparing message for behavior test #2 (destination=AA)
Sending message to 188.166.128.84:3479
Got response (68 bytes) from 188.166.128.84:3479 on interface 10.1.96.4:45474
Binding test: success
Local address: 10.1.96.4:45474
Mapped address: 66.135.24.11:45474

你这不全是 45474 端口吗?什么变了?

另外你下次不要只说一句“此时抓包就会发现”,我又看不到结果我不知道你那边观察到的行为。直接发出来。

Ovear commented

两次测试之间防火墙的状态会互相干扰混淆

没有看出原文有这个叙述

就算是抛开这个问题,如果触发条件是收到外部端口不同的封包导致的,按照之前的文章所说的,这种互相发送的行为是必须的。

一旦进入这种模式,打洞的成功率就会无限降低,实际上问题还是存在,并没有改变。

另外你下次不要只说一句“此时抓包就会发现”

把你抓包的结果每次发一下,不然没法深入讨论啊

Ovear commented

你这不全是 45474 端口吗?什么变了?

修改了代码本身就是未经测试的行为,出现意料外的问题很正常的。

我又看不到结果我不知道你那边观察到的行为

所以我给了复现方法,你测试下看看你不能复现也是很重要的。

Resolved 198.211.120.59 to 198.211.120.59:0
config.fBehaviorTest = true
config.fFilteringTest = true
config.timeoutSeconds = 0
config.uMaxAttempts = 0
config.addrServer = 198.211.120.59:3478
socketconfig.addrLocal = 0.0.0.0:0
Sending message to 198.211.120.59:3478
Got response (68 bytes) from 198.211.120.59:3478 on interface 10.1.96.4:36376
Other address is 188.166.128.84:3479

Preparing message for filtering test #2 (ChangeRequest=AA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #2 (ChangeRequest=AA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #3 (ChangeRequest=PA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for filtering test #3 (ChangeRequest=PA)
Sending message to 198.211.120.59:3478
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Continuing to wait for response...
Preparing message for behavior test #2 (destination=AP)
Sending message to 198.211.120.59:3479
Got response (68 bytes) from 198.211.120.59:3479 on interface 10.1.96.4:36376
Preparing message for behavior test #2 (destination=AA)
Sending message to 188.166.128.84:3479
Got response (68 bytes) from 188.166.128.84:3479 on interface 10.1.96.4:36376
Binding test: success
Local address: 10.1.96.4:36376
Mapped address: 111.111.111.111:36376
Behavior test: success
Nat behavior: Address Dependent Mapping
Filtering test: success
Nat filtering: Address and Port Dependent Filtering



20:43:25.752635 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3478: UDP, length 28
20:43:25.752799 enp1s0 Out IP 111.111.111.111.36376 > 198.211.120.59.3478: UDP, length 28
20:43:25.829457 enp1s0 In  IP 198.211.120.59.3478 > 111.111.111.111.36376: UDP, length 68
20:43:25.829534 enp6s0 Out IP 198.211.120.59.3478 > 10.1.96.4.36376: UDP, length 68
20:43:25.843137 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3478: UDP, length 28
20:43:25.843201 enp1s0 Out IP 111.111.111.111.36376 > 198.211.120.59.3478: UDP, length 28
20:43:25.919468 enp1s0 In  IP 188.166.128.84.3479 > 111.111.111.111.36376: UDP, length 68
20:43:28.860273 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3478: UDP, length 28
20:43:28.860613 enp1s0 Out IP 111.111.111.111.36376 > 198.211.120.59.3478: UDP, length 28
20:43:28.937163 enp1s0 In  IP 188.166.128.84.3479 > 111.111.111.111.36376: UDP, length 68
20:43:31.874027 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3478: UDP, length 28
20:43:31.874159 enp1s0 Out IP 111.111.111.111.36376 > 198.211.120.59.3478: UDP, length 28
20:43:31.950820 enp1s0 In  IP 198.211.120.59.3479 > 111.111.111.111.36376: UDP, length 68
20:43:34.885438 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3478: UDP, length 28
20:43:34.885556 enp1s0 Out IP 111.111.111.111.36376 > 198.211.120.59.3478: UDP, length 28
20:43:34.962122 enp1s0 In  IP 198.211.120.59.3479 > 111.111.111.111.36376: UDP, length 68

# 下面就变了
20:43:37.899823 enp6s0 In  IP 10.1.96.4.36376 > 198.211.120.59.3479: UDP, length 28
20:43:37.900039 enp1s0 Out IP 111.111.111.111.21373 > 198.211.120.59.3479: UDP, length 28
20:43:37.976518 enp1s0 In  IP 198.211.120.59.3479 > 111.111.111.111.21373: UDP, length 68
20:43:37.976596 enp6s0 Out IP 198.211.120.59.3479 > 10.1.96.4.36376: UDP, length 68
20:43:37.982118 enp6s0 In  IP 10.1.96.4.36376 > 188.166.128.84.3479: UDP, length 28
20:43:37.982189 enp1s0 Out IP 111.111.111.111.21373 > 188.166.128.84.3479: UDP, length 28
20:43:38.064146 enp1s0 In  IP 188.166.128.84.3479 > 111.111.111.111.21373: UDP, length 68
20:43:38.064216 enp6s0 Out IP 188.166.128.84.3479 > 10.1.96.4.36376: UDP, length 68



两次测试之间防火墙的状态会互相干扰混淆

这是说,比如你先进行过滤行为测试,你给stun服务器1 stun1 发bind请求,他成功回你了。然后你发changeIp的请求,这时stun2:port2 在给你发消息,但你可能收不到,于是你给 stun1 发 changePort请求,stun1 再回你但你也有可能收不到。

假设你两次都没收到,你认定自己是 EDF,然后又按照 full 模式没有换本地端口接着去给 stun2:port1或者stun2:port2 发bind请求,这个时候根据他回不回你你能判断自己是 EIM 还是 EDM。

但假如说你实际上是 Address Dependent Filtering + EIM,然后因为你给 stun2 发了请求,这时候如果 stun2 给你在第一遍 changeIp 的时候发的包实际上因为网络延迟发来的太晚了本来不应该能发来的情况,却因为你主动给 stun2 发了请求结果打洞了,那你得到的实验结果就不好判断了。

所以至少Filtering和Mapping的结果分两个不同的端口测试。

我还没有来得及看你为什么非要改程序。你能直接用语言叙述一下你为什么需要改stun客户端程序才能测试吗?不是给了参数可以手动填写stun服务器的地址和端口吗?

20:43:37.899823 enp6s0 In IP 10.1.96.4.36376 > 198.211.120.59.3479: UDP, length 28
20:43:37.900039 enp1s0 Out IP 111.111.111.111.21373 > 198.211.120.59.3479: UDP, length 28

你这两条肯定是不理想的行为,我知道。但是我这边复现不出来你这个结果。

你的 debian arch ubuntu 全是在 vultr 上测试的吗?包括你最早截图的 wireshark 也是?

Ovear commented

我还没有来得及看你为什么非要改程序

因为触发更换端口的逻辑是,本地要在同端口+同地址的情况下,请求另外一个端口,但是地址无关。原程序没有这个行为,但是最早观察到出现问题时(Symmetric NAT),NatTypeTester封包行为就是如此,这是触发条件。

你的 debian arch ubuntu 全是在 vultr 上测试的吗?包括你最早截图的 wireshark 也是?

wireshark截图是之前叙述的环境,是我本地;后续我提到vultr和tcpdump的都是在vultr上测的。

原程序没有这个行为

我们本来就是测的 EIM 还是 EDM,你只测这个行为的话手动根据 STUN 返回回来的 Other address 手动写 bind 命令的新 endpoint 就好了,不用去改代码啊。

stunclient stun.syncthing.net 3478 --localaddr 10.0.0.10 --localport 32677
stunclient 188.166.128.84 3479 --localaddr 10.0.0.10 --localport 32677
# 188.166.128.84 是上一个请求返回回来的 Other Address

就比如这样。

至于你那边为什么是 Nat behavior: Address Dependent Mapping,我还真不清楚。

Ovear commented

因为网络延迟发来的太晚了本来不应该能发来的情况

我观察到的RFC5780测试时采用的逻辑不是这样的,这也是为什么更改代码。

不用去改代码啊

上面说过了,是触发条件,而且实际上也更贴近现实行为。

NatTypeTester中,这个测试方法是被标记为RFC3489,我推测可能是RFC中的规定,同时pystun3也是这么做的,同样测出Symmetric NAT

https://datatracker.ietf.org/doc/html/rfc3489#section-10.1

这里描述的三种测试,刚好就是NatTypeTesterRFC3489测试中所做的,pystun3也是。

In the event that the IP address and port of the socket did not match
   the MAPPED-ADDRESS attribute in the response to test I, the client
   knows that it is behind a NAT.  It performs test II.  If a response
   is received, the client knows that it is behind a full-cone NAT.  If
   no response is received, it performs test I again, but this time,
   does so to the address and port from the CHANGED-ADDRESS attribute
   from the response to test I.  If the IP address and port returned in
   the MAPPED-ADDRESS attribute are not the same as the ones from the
   first test I, the client knows its behind a symmetric NAT. 

如果测试2失败,要对Test1中返回的changed address/port重新执行Test1,如果返回的地址不同,则symmetric NAT

这里就是NAT后的src_port改变,也是我们讨论这么久的问题。

测试客户端采用pystun3(自定义测试用)和NatTypeTester

你之前写的具体测试方法太长了我没有全仔细看,但你现在这么一说我才发现你做的测试和我看 RFC 理解的不一样。你是说你从最开始做测试的时候就没有用我那种直接的方法去给stun2发请求是吧。

首先我们排除 RFC 3489,以及所有涉及 fullcone 的说法的 RFC。因为他们都是老而不可靠的。

然后我们看 RFC 5780 的 4.3 https://www.rfc-editor.org/rfc/rfc5780.html#section-4.3

我的测试里直接 test II 就知道自己是 EIM 了。你为什么不按照这个顺序测试呢?

stunclient stun.syncthing.net 3478 --localaddr 10.0.0.10 --localport 32677
stunclient 188.166.128.84 3478 --localaddr 10.0.0.10 --localport 32677
# 188.166.128.84 是上一个请求返回回来的 Other Address

另外 RFC 5780 4.1 里明确说了为什么不用 full 的原因:

o Because the results of some diagnostic checks depend on previous
state in the NAT created by prior traffic, the tests should be
performed using a source port that has not generated recent
traffic. Therefore, the application should use a random source
port or ensure that no traffic has previously occurred on the
selected port prior to performing tests, generally by allocating a
port and holding it unused for at least 15 minutes prior to the
tests.

主要原因就是之前的流量对下次测试带来的防火墙状态的影响,以及可能的因为延迟带来的测试结果的不准确。

并且实际网络应用中用一个端口专门和 rendezvous 服务器通信,用一个约定的新端口(编辑:+先用这个新端口去联系 rendezvous 服务器,让服务器知道你的公网端点。这样能避免之前用旧端点 redezvous 服务器联系带来的防火墙状态的副作用)去连接另一个 peer 是很正常很常见的操作。

Ovear commented

首先我们排除 RFC 3489

这就排除了我们的讨论内容了。。。我们不就是在讨论为什么会发生端口变化这种事情吗?

不管是哪种RFC,最终落实到使用中就会遇到的情况就是会出现未连接过端口的流量到达,如果一旦出现这种情况,NAT退化为symmetric,那是很难接受的。

另外 RFC 5780 4.1 里明确说了为什么不用 full 的原因:

这个我认为是误解,不然就和这一段冲突了。

      Although the most reliable results are obtained when performing
      tests with the specific ports that the application will use, in
      many cases an application will need to allocate and use ports
      without being able to perform complete Behavior Discovery tests on
      those ports

用一个约定的新端口去连接另一个 peer

就如你所说,这是workaround,而且在很多情况下是做不到的,比如说双方Port and address restrict的情况下。

It performs test II. If a response is received, the client knows that it is behind a full-cone NAT.

比如这句话本身就是错的,因为即使你能用原本的端点受到另一个ip发来的数据包,也不代表你往它的ip发的数据包会走同一个NAT端点。之所以老STUN和NAT行为的RFC要被更新就是因为他们太旧了,那时候大家还都对 NAT 的分类拿不准然后自己内部之间概念定义的也不明确。

最终落实到使用中就会遇到的情况就是会出现未连接过端口的流量到达

这个观点是错误的,我建议你去看一下 tailscale 的文章。

现代化的打洞是要两个 peer 在 rendezvous 服务器的帮助下约定打洞的时间和互相的端点的,然后只要 EIM 就能打通,所以 Openwrt 和 Linux 不需要改动。

这个我认为是误解,不然就和这一段冲突了。

你没有理解你引用的那段话的意思,那段话的意思是 p2p 软件很可能没有机会测试自己想用的端点能获得什么样的 NAT 行为,实际上也不需要,因为如果有 EIM 你就能打通,没有你就打不通,不需要专门的测试。

而我说你不要用 full 的原因是为了防止一次 full 测试的两部分,或者两次测试之间对于你做 MASQUERADE 的那个防火墙的状态有影响,再加上网络中数据包传输的不稳定、延迟,导致产生错误的判断。

就如你所说,这是workaround,而且在很多情况下是做不到的,比如说双方Port and address restrict的情况下。

首先,这不是 workaround,我不知道你在这里提这个词是什么意思。NAT的存在导致打洞本身就是一种 workaround,解决方案不是 EIF + EIM 而是ipv6。另外,你说双方是 EDF + EIM (=Port and address restrict)这种情况下就是可以轻松打洞的,参见 tailscale 的博客,我没有办法比他们说的更轻松易懂了。

Ovear commented

因为即使你能用原本的端点受到另一个ip发来的数据包,也不代表你往它的ip发的数据包会走同一个NAT端点

现在的情况恰恰相反了,你能用一个端口发,但是别人发不过来。

这个观点是错误的,我建议你去看一下 tailscale 的文章。

你可以直接说的,tailscale也说需要扫描,我不知道你说的哪一部分。

实际上也不需要

这段话我的理解是,可以做,做了更好。但是现在就是做了不通。

 Although the most reliable results are obtained when performing tests with the specific ports that the application will use

而你引用的这一段话,我的理解是程序去怎么选择想要使用的端口,其中也有一句话说的很明白,可以做测试,测试完更推荐直接用,我没有看到这个端口要用完就丢掉,或者仅供测试类似的意思的。

Therefore, the application should use a random source
      port or ensure that no traffic has previously occurred on the
      selected port prior to performing tests.

用一个约定的新端口

Sorry,这个我说的时候没说全,必须用你和 rendezvous 服务器通信过并确认过公网上的映射端点的端口才行。我当时的是不一定要用和 redezvous 保持通信的那个端口。说的容易让人误解了。

不过大体上的方向是没有变得,我还要花点时间看看你具体改动后是在做一个怎样的测试。但与此同时,你不要再用 RFC 3489 的方法和术语测试了,它是NAT这一系列RFC中最老的一个,也是人们对 NAT 的性质和影响理解还不够的时候的一篇。我们接下来就参照 RFC 5780。

然后你就按照我 #42 (comment) 这里说的方法测试,不要用任何改动过的内核、防火墙、stun客户端,只要你抓包能看出来两次都是本地同一个端点、NAT外被两个STUN服务器观测到的映射的端点都是一致的,那你就是 EIM,你的 NAT 也就没有 Linux 和 Openwrt 带来的什么问题了。

Ovear commented

我们接下来就参照 RFC 5780

不是参照的问题问题,而是实际使用的问题?或者你构造一个双向这种行为的客户端,然后看看如何打洞?

我觉得如果这个环境评估布料,我们接下来的讨论都只是空中阁楼,还是解释不了src_port端口变更带来的影响。

@Ovear 你能用没修改的客户端、内核跑一下这个测试看看两次返回的NAT映射后你的公网端点吗?

stunclient stun.syncthing.net 3478 --localaddr 10.0.0.10 --localport 32677
stunclient 188.166.128.84 3478 --localaddr 10.0.0.10 --localport 32677
# 188.166.128.84 替换成第一个请求返回回来的 Other Address
# 32677 替换成最近没有用来测试过的本地端口

#42 (comment) 这里我编辑追加了一些。我之前说到用新端口是想和你说要避免你做的实验中,先做了 Filtering 行为的测试之后又复用了那个端口所带来的防火墙状态副作用的影响。

Ovear commented

你能用没修改的客户端、内核跑一下这个测试看看两次返回的终端吗?

我刚刚服务器已经销毁了,之前的抓包测试中有这个场景了

#42 (comment)

我们如果要讨论的话,还是讨论 #42 (comment) 吧。

现有的工具能测试出的结论是这样,但是实际使用中遇到这种情况也很是正常的。

这样能避免之前用旧端点 redezvous 服务器联系带来的防火墙状态的副作用

如果有测试结果确认能避免这个影响,成功打通的话是个好消息。

但是之前所说的行为也是存在的,我们讨论的如果不是这个异常行为的话,其实都达成一致了。

我得先休息会了,等我起来再看看吧。

我现在怀疑netfilter行为是和时间有关,如果能找到一个办法,测试你说的那个打洞方式,直接在云端开机器用脚本测试,就知道影响了。

具体来说,就是测试就是你提到的,开一个新的EndPoint,在默认情况下,同时互相发送数据,看看能否建立隧道。

PS:其实我刚才翻过那篇文章,才想起来我很早之前就看过了;但是这篇文章实际上重点也是和我上面说的一样,要模拟同时建立这个过程,所以如果有方法能够测试下就最好了。我感觉很可能有机会成功。

但是回报的端口不同。 这里是异常行为,我没能复现

NatTypeTester自带两种测试,有环境的话可以直接测试下,然后抓包分析,还是很方便的。

你截图里那个情况是比较奇怪,但我现在很想排除你自定义修改了测试流程所带来的影响。在我重新翻回去阅读你的自定义测试方法找哪里有问题的时间里,如果你也能重新按我说的进行一下简单的、排除副作用带来的可能影响的测试的话,我会觉得更清楚情况。

你说的第二个讨论点,是因为我之前写评论的时候比较着急,没有写全,我在刚刚上面那一条里也提到了,我补充了我原本的没写全的评论 #42 (comment) 。所以希望我们能以完整的理解为基础继续讨论。

如果你要先休息的话,我很建议你之后看一下 tailscale 关于只靠 EIM 打洞的原理的文章。当然,往另一个端点发数据结果被映射到别的端口了的话确实就不是 EIM 了。那肯定不能打洞,这个确实是要着重讨论的问题。不过我这边没有出现这个问题。

我的一些疑惑主要在于你对测试工具或者内核进行了修改,大晚上我也没有非常仔细的去看你的测试流程和工具与标准测试有什么区别,所以并不知道是不是什么地方有副作用,特别是同时还在打字讨论/检索资料。一会儿我慢慢过一下。

NatTypeTester中RFC5780按顺序进行了三次测试

Test1: 向目标服务器发送Bind测试(获取到changed address/port)
Test2: 向目标服务器发送ChangeIPAndPort请求(此时STUN服务器会用changed address/port回复)
Test3: 向目标服务器发送ChangePort请求(此时STUN服务器会用changed port回复)
Test4: 向changed address和原始端口发送Bind测试

测试结果
Test1收到回复/Test2未收到回复/Test3未收到回复/Test4收到回复
Test1回报的公网地址和端口与Test4回报的相同
程序显示为EndpointIndependent Mapping + AddressAndPortDependent Filtering

你这里最初用 NatTypeTester 测得的结果是符合预期的,也符合 RFC 5780。

NatTypeTester中RFC3489按顺序进行了三次测试

Test1: 向目标服务器发送Bind测试(获取到changed address/port)
Test2: 向目标服务器发送ChangeIPAndPort请求(此时STUN服务器会用changed address/port回复)
Test3: 向changed address/port发送Bind测试

测试结果
Test1收到回复/Test2未收到回复/Test3收到回复
Test1回报的公网地址与Test3回报的公网地址相同,但是回报的端口不同。
程序显示为Symmetric NAT

但是回报的端口不同。 这里是异常行为。按照 RFC 5780 的说法是,先测 EIM 再测 ADM,最后测APDM。但是如果这里的异常行为是仅在连接 ChangedIp和非原始端口的时候发生的话, 那就变成了PDM。我没能复现。

我的推测是这样的,原因是因为在你用客户端按照 RFC 3489 做测试的时候,STUN2 用changedIP/changedPort 给你发过会被拒绝的数据包,所以你本地映射的端点被 linux 的防火墙保护了,可能正好不想用来再收刚刚才拒绝过的数据包的端点。所以就分配了新的端口来映射并发送数据。也就是说,如果你按照我之前建议的,不要按照 RFC 3489,而是按照 RFC 5780,并且要么先进行最重要的映射行为测试,要么映射行为和过滤行为用不同的端口分别测试,这样你都不会遇到问题,直接就可以给changedIP上的任何端口用原本的本地映射端点发包。

在实际生产中,你也只需要这种行为就可以了。因为实际生产中不会总花时间测每一个端口的NAT行为,也没有意义(只需要EIM,是不是EIM直接以连接代测试了)。打洞流程就和 tailscale 的一样,先分配新的本地端口以去掉以前连接的副作用的影响,然后用新端口连会合服务器得到NAT的映射,会合服务器得到双方端点后帮双方交换对方端点信息和约定同步发起连接的绝对时刻(谁也别早,有一个早了一个晚了的话就可能被这种保护给影响了)。这样就能顺利建立打洞建立连接。

至于这个保护,确实客观上给打洞带来了困难,让时间同步变得很重要,不过正常的时间同步的误差肯定是小于7ms左右的延迟的,一般不是问题。

编辑:这个问题应该就是conntrack对stun2之前发来的包进行了连接状态记录带来的影响。

Ovear commented

所以希望我们能以完整的理解为基础继续讨论

我看了你更新后的内容,但是之前的回复中我也说了,这个很高概率就是对RFC的误解。

首先我们抛开RFC因素,来看看三个支援RFC5780测试的工具行为:
stun-client: 一次性测试mappingfilter,且使用同一端口
NatTypeTester: rfc5780测试模式,一次性测试mappingfilter,且使用同一端口
STUNTMAN: 推荐分开测试mappingfilter,也有一次性测试模式;一次性测试中,使用同一端口

其中stun-client目前还在Debian系等发行版内的Package中;
NatTypeTester则是Windows流行的测试发行版本;
STUNTMAN我能查到曾经是在包内的,后来不明原因移除。

在这种情况下,不能够支撑你回复中提到的需要分开端口进行测试,或测试完之后弃置端口

其次我们再来看RFC:

首先我们讨论你不愿意讨论的rfc3489,其他的我就不多说什么了。
但是这进行的测试是预期内的测试,发生预期外的行为变更本身就是奇怪的。

我们再回到rfc5780

其中你截取的一段来自于RFC中的源地址选择,后面的补充说明也提到了,对要使用的端口进行测试,是理想/支持的行为
在这种叙述之下,不能支持测试后的端口需要丢弃,实际上先前提到的rfc后续的内容则是完全反驳了这种理解。
所以你引用的内容应当理解为:对程序将要使用的端口的要求;因为推荐对端口进行测试后,再进行使用,所以在同一个**未被使用过**的端口进行测试是预期内的

综上所述,分开端口进行测试,或者测试完丢弃端口是协议中推荐,或者定义的内容这个观点并未得到支撑。

实际上的测试也说明了,如果严格按照rfc5780叙述的测试方法进行测试,无论是否使用同一端口进行测试,测试结果都是能够维持一致的。

=================================================

然后再仔细的阅读rfc5780 的 4.3,会发现:

其中进行的测试和我之前叙述的相同,一开始只针对地址变更进行测试,于是rfc中就只有了三种mapping分类:

Endpoint-Independent
Address-Dependent Mapping
Address and Port-Dependent Mapping

唯独少了我们目前测试、研究的异常情况:Port-Dependent Mapping

结合你刚才补充的conntrack和反向NAT相关叙述:https://serverfault.com/questions/1073400/how-to-prevent-netfilter-to-automatically-change-the-source-ports

我得出的结论是:

如果是rfc5780中明确定义的行为,即同端口但是不同地址,netfilter会做特殊处理:在conntrack创建跟踪时候,反向连接跟踪优先使用原端口。
而对于rfc5780中未定义的行为,为了方便我称之为Port-Dependent Mappingnetfilter则采用了conntrack默认行为,即通过至少四元组跟踪:即(src, src_port, dst, dst_port)。根据上面回答中(我还没仔细看)的所说创建反向连接跟踪(conntrack的跟踪是双向的)。
后续因为nat依赖了conntrack,所以按照快取(preserve)原则,就直接取了变更后的src_port

综合以上信息和猜测,倾向支撑了netfilter的默认nat mapping行为设计希望做到Endpoint-Independent mapping

即:netfilter很可能只支持了rfc5780所定义的内容,而对rfc3489中的行为则进行了一定程度的舍弃/没有做处理。

=================================================

所以实情回到了原点,在获得了这种行为背后的理论猜测后,我们需要做下测试来确认刚才的猜想:

通过实测方式,借助脚本和云服务的特性,确认情况下双方即使在**不同端口和地址**的情况下,打通连接。

通过这个测试加上上述推测,就可以坚固支撑netfilter默认的nat mapping设计和行为倾向于维持rfc5780中叙述的Endpoint-Independent,并能通过测试

(我在想有什么简单的方法能做到这个测试吗?)

其次如果能够查询到源码中的相关内容,就更好了;
根据之前我打了fullcone补丁,但是关闭了fullcone的测试结果,本issue中的补丁有高度可能性改变了这个“异常”行为(以此符合rfc3489中定义的行为);

我们应该有很大机会找到问题的根源。

==================================================

最后netfilter这个行为果然最大可能是一个feature而不是bug)小声

昨天大半夜就一直在怀疑conntrack,但是神志不清没想明白。
今天一边打着第一段内容,一边仔细看rfc的时候,刚好看到你刚补充的serverfault连接(感谢Github的自动刷新),突然就有思路了。

Ovear commented

喜报,打洞成功

netfilter确实是严格/精确维持了rfc5780中所述的Endpoint-Independent MappingAddress and Port-Dependent Filtering

关键就是上面说到的,要要做到同时/接近同时

测试方法和结果

刚刚突然突发奇想,我们所需要的测试可以通过stun服务器完成。

只需要在往原始服务器发送ChangePortAndIP请求之前,向Changed IP/Port发送任意数据,就可以打通隧道。

抓包结果

# stun bind
13:09:21.967064 enp6s0 In  IP 10.25.96.4.11111 > 139.59.84.212.3478: UDP, length 20
13:09:21.967115 enp1s0 Out IP 111.111.111.111.11111 > 139.59.84.212.3478: UDP, length 20

# stun bind reply
13:09:22.071982 enp1s0 In  IP 139.59.84.212.3478 > 111.111.111.111.11111: UDP, length 68
13:09:22.072025 enp6s0 Out IP 139.59.84.212.3478 > 10.25.96.4.11111: UDP, length 68

# stun change ip and port
13:09:22.073900 enp6s0 In  IP 10.25.96.4.11111 > 139.59.84.212.3478: UDP, length 28
13:09:22.073914 enp1s0 Out IP 111.111.111.111.11111 > 139.59.84.212.3478: UDP, length 28

# 抢先对 changed ip and port 发送任意数据
13:09:22.073926 enp6s0 In  IP 10.25.96.4.11111 > 139.59.49.16.3479: UDP, length 1
13:09:22.073945 enp1s0 Out IP 111.111.111.111.11111 > 139.59.49.16.3479: UDP, length 1

# 客户端成功接收到 changed ip and port 发送的数据
13:09:22.179487 enp1s0 In  IP 139.59.49.16.3479 > 111.111.111.111.11111: UDP, length 68
13:09:22.179518 enp6s0 Out IP 139.59.49.16.3479 > 10.25.96.4.11111: UDP, length 68

补充测试

为了确认双方向隧道都通,将上述测试中的发送任意包到Changed IP/Port修改为:

Changed IP/Port发送Bind Request,发送时变更transaction-id,以免程序混淆。

测试结果说明双向隧道均已打通。

抓包结果

# stun bind
13:30:45.455410 enp6s0 In  IP 10.25.96.4.25555 > 139.59.84.212.3478: UDP, length 20
13:30:45.455454 enp1s0 Out IP 111.111.111.111.25555 > 139.59.84.212.3478: UDP, length 20

# stun bind reply
13:30:45.565092 enp1s0 In  IP 139.59.84.212.3478 > 111.111.111.111.25555: UDP, length 68
13:30:45.565122 enp6s0 Out IP 139.59.84.212.3478 > 10.25.96.4.25555: UDP, length 68

# stun change ip and port
13:30:45.566864 enp6s0 In  IP 10.25.96.4.25555 > 139.59.84.212.3478: UDP, length 28
13:30:45.566875 enp1s0 Out IP 111.111.111.111.25555 > 139.59.84.212.3478: UDP, length 28

# 同时对 changed ip and port 发送 bind(更换trans_id,以免程序混淆)
13:30:45.667365 enp6s0 In  IP 10.25.96.4.25555 > 139.59.49.16.3479: UDP, length 20
13:30:45.667400 enp1s0 Out IP 111.111.111.111.25555 > 139.59.49.16.3479: UDP, length 20

# 回复 stun change ip and port
13:30:45.682708 enp1s0 In  IP 139.59.49.16.3479 > 111.111.111.111.25555: UDP, length 68
13:30:45.682726 enp6s0 Out IP 139.59.49.16.3479 > 10.25.96.4.25555: UDP, length 68

# 回复对 changed ip and port 发送的 bind
13:30:45.782210 enp1s0 In  IP 139.59.49.16.3479 > 111.111.111.111.25555: UDP, length 68
13:30:45.782234 enp6s0 Out IP 139.59.49.16.3479 > 10.25.96.4.25555: UDP, length 68
  • 来看看三个支援RFC5780测试的工具行为
  • 首先我们讨论你不愿意讨论的rfc3489

你不应该再提过时的 RFC 3489 了。我已经强调过原因了,这里总结一下:

  1. 它本身对 NAT 行为的分类就是模糊和有误导性的
  2. 它本身对 NAT 行为的检测建议是没有考虑 STUN2 发包但被拒绝后在 NAT 的防火墙上带来的副作用的,这是疏忽
    If no response is received, it performs test I again, but this time,
    does so to the address and port from the CHANGED-ADDRESS attribute
    from the response to test I.  If the IP address and port returned in
    the MAPPED-ADDRESS attribute are not the same as the ones from the
    first test I, the client knows its behind a symmetric NAT.
    
    而 RFC 5780 中建议的方法
    In test II, the client sends a Binding Request to the alternate
    address, but primary port.
    
    是可以避免这种副作用的
  3. 人思考问题需要依据的是依照优先级是理性、常识、还有历史。对于 RFC 3489 你在知道它的误导性和后续的 RFC 5780 提议的更准确的分类方法、以及 RFC 5128 指示的 udp 打洞常见实践以后,你应该可以独立得出为了设计可以 udp 打洞的 NAT 你需要什么 NAT 行为、NAT 行为测试,以及什么 udp 打洞算法。之所以我们先提这些 RFC、术语、工具,只是为了节省沟通时间、方便交换基于常识和历史的知识。但当我们的常识不一致的时候,我希望能够回归到理性去思考——比如 udp 打洞这个需求到底需要的什么。不要再过分拘泥于 RFC 和前人工具的实现了。

接下来我主要基于理性,辅以常识来解释我的意见:

  1. 根据我的思考、RFC 5128,当然还可以加上 tailscale 的讨论,容易的 udp 打洞最小只需要 EIM-NAT。所有 fullcone 之类的老名词、RFC 3489 的测试之类的统统不要再提了,要提的话也是要告诉后人这是以前经验不足的时候的错误经验。
  2. 要测试 EIM-NAT 与否,一个客户端只需要进行类似 RFC 5780 中的测试I和II能得出的结论即可。而实际 P2P 程序甚至不需要进行测试,只要按照 RFC 5128 的方法去打洞,你只要通了就是通了没通就是没通。
  3. 你所拘泥的类似所谓 Port-Dependent Mapping 的现象,其实已经不是你的 NAT 自己的现象了。你的 NAT 必须被 STUN2 主动连接过 NAT 上的端点并被影响过才会发生这种结果。而单纯并受控的 P2P 打洞中完全可以控制不让这种现象发生并顺利先发包完成打洞。所以这严格来说并不是 NAT 自己的行为,也不影响打洞。而是 NAT 对 STUN2 的提前主动交互的应对,对于 Linux 防火墙来说 NAT 内部客户端没有联系 STUN2 却从那边发来了包意味着 STUN2 很有可能是恶意第三方,所以触发什么保护措施都是合理的。不能非拘泥于你原来的自定义的模仿 RFC 3489 的测试流程。
  4. 根据我的思考、辅以 RFC 5780 4.5 的说法,你需要注意合并多次测试、合并 Mapping 和 Filtering 测试所带来的影响,特别是在防火墙状态中遗留的副作用。结合 2,你对于 NAT 类型的测试肯定只需要测出来 EIM-NAT 也就够了,并且你也已经测出来了。你只要按照我之前让你用 stun-server 测试的两条命令做就能得知你的 Linux 是 EIM-NAT。
  5. 对于 RFC 5780 提到的两个测试为什么也注意规避副作用换不同端口测试的论点,更易懂的,你要是先测 Mapping 后测 Filtering,再加上你之前自定义的往 STUN2 发 BIND 还是发和 Filtering 测试时想测的一样的端点的话,显然你会因为给 STUN2 发过包而一直测出来 EIF-NAT。(不知道为什么这种副作用的影响反过来你就不认可了

如果是rfc5780中明确定义的行为,即同端口但是不同地址,netfilter会做特殊处理

这里你的理解还是错误的,不是 NAT 映射行为在决定这个,而是你之前请求了 STUN1 从 STUN2:changedPort 给你发测试 Filtering 的包,在防火墙上带来了副作用才导致你连 STUN2:changedPort 的时候被分别了新的端口的。事实上你不要去连 STUN2:changedPort 而是去连除了 changedPort 以外的所有端口都不会被 NAT 改端口。实际上假设如果 STUN 服务器允许的话,如果你的 CHANGE-REQUEST 里不是设置 change-port and change-IP,而是只设置 change-IP 的话,那你测试的结果就正好反过来——连除了 STUN2:originalPort 以外的所有其他端点都不会被改端口,但连它会被改。

对于这个 STUN2 的提前到的数据包带来的副作用,才是我比较感兴趣的东西,我之前讨论的时候搜过一些关键词但是没有找到具体的文档或者源码指出是哪一部分设计带来的这种副作用、有没有什么开关可以控制。并且这个副作用对 P2P 打洞确实有一定的影响,需要双方约定好同时发包。这个是我前面也和你说过的,但这部分已经和 EIM-NAT 与否无关了,是 Linux 防火墙对于可能是恶意的发包的处理方式和对打洞的影响的问题。我们最好先得出 Netfilter 不用 random 确实是 EIM-EDF-NAT 的结论。

只需要在往原始服务器发送ChangePortAndIP请求之前,向Changed IP/Port发送任意数据,就可以打通隧道。

这不是理所当然的吗 🤦 人家 STUN2 服务器有公网IP,你先给有公网IP的机器发了,它又没设置屏蔽你的防火墙规则的话,当然能往回给你发啊这是 UDP 打洞中的基础。。我建议你看一下 RFC 5128 3.2 Connection Reversal

Ovear commented

我发现你的思考逻辑,有比较强烈的跳跃性;你提到的东西,很多都是额外附加的,讨论时并未列出来源,所以这块有差异很正常。

这不是理所当然的吗 人家 STUN2 服务器有公网IP

我们测试的本地路由的行为,和对端没有关系,我不知道你提对端时在这里的作用是什么。事实上,如果你仔细看过你之前发的内容,就知道nat映射是双向的。

是可以避免这种副作用的

但是这条也存在同样的疏忽,按照RFC的意见是没有Port-Dependent Mapping的,但是是理论上有可能存在的。

不要再过分拘泥于 RFC 和前人工具的实现了

如果没有实现规范中所说的内容,那规范的存在就没有意义;工具是对规范的实现,本质上对行为是实现快速测试,所以去复现也是很重要的一环。

容易的 udp 打洞最小只需要 EIM-NAT。所有 fullcone 之类的老名词、RFC 3489 的测试之类的统统不要再提了

我们应该思考EIM-Mapping究竟是什么,我认为本质是NAT匹配的元组数目,既然如此Port-Dependent Mapping是客观存在的。

所以这严格来说并不是 NAT 自己的行为,也不影响打洞

你没有考虑过丢包的情况,如果双方互相发送包的时候,一方丢包,对端就会退化进入Dependent Mapping,这是客观存在且会造成的影响。
也不能证明所以触发什么保护措施都是合理的,目前这种实现单纯只是netfilter严格按照rfc实现的结果罢了。

根据我的思考、辅以 RFC 5780 4.5 的说法,你需要注意合并多次测试、合并 Mapping 和 Filtering 测试所带来的影响,特别是在防火墙状态中遗留的副作用

这个说法我是同意的,所以我也一直在强调要严格按照RFC5780提供的方法测试,但是文章中很显然没说明一定不能,而且是推荐要做
除此之外RFC5780中有一句重要的话

This usage does not provide backward compatibility with RFC 3489 [RFC3489] for either clients or servers.

所以我在后面的回复,也说明了netfilter是没有考虑这个RFC3489规范的;这也是我们争论了这么久的一个内容。
但是这也不能证明RFC3489就是错误经验,这是一个规范而已,只有能不能符合而已。

对于 RFC 5780 提到的两个测试为什么也注意规避副作用换不同端口测试的论点

这个我也反复陈述过了,严格按照RFC5780进行测试是没问题的。你说的这些都不是规范中说明一定要这么做的。

实际上假设如果 STUN 服务器允许的话,如果你的 CHANGE-REQUEST 里不是设置 change-port and change-IP,而是只设置 change-IP 的话,那你测试的结果就正好反过来——连除了 STUN2:originalPort 以外的所有其他端点都不会被改端口,但连它会被改

这里你提醒我了,我确实漏了这个测试,测试后确实是如你所说的。也正因为这样,我发现了一个有趣的地方,也确认了映射关系被改变。

ipv4     2 udp      17 115 src=10.1.96.4 dst=198.211.120.59 sport=20002 dport=3478 src=198.211.120.59 dst=222.222.222.222 sport=3478 dport=20002 [ASSURED] mark=0 zone=0 use=2
ipv4     2 udp      17 23 src=111.111.111.111 dst=222.222.222.222 sport=3478 dport=20002 [UNREPLIED] src=222.222.222.222 dst=111.111.111.111 sport=20002 dport=3478 mark=0 zone=0 use=2
ipv4     2 udp      17 25 src=188.166.128.84 dst=222.222.222.222 sport=3478 dport=20002 [UNREPLIED] src=222.222.222.222 dst=188.166.128.84 sport=20002 dport=3478 mark=0 zone=0 use=2
ipv4     2 udp      17 27 src=10.1.96.4 dst=111.111.111.111 sport=20002 dport=3478 [UNREPLIED] src=111.111.111.111 dst=222.222.222.222 sport=3478 dport=41322 mark=0 zone=0 use=2

这里你的理解还是错误的,不是 NAT 映射行为在决定这个,而是你之前请求了 STUN1 从 STUN2:changedPort 给你发测试 Filtering 的包

没有看明白为什么是错误;我认为这不是副作用,是conntrack本身的作用。如果你注意conntrack本身行为,那就知道至少四元组匹配。
实际上nat功能就是严重依赖conntrack的,讨论conntrack的行为和nat映射本身行为的一部分。
如果要深入讨论这一行为,就必须去查看相关代码了。
不过,如果你能够理解conntracknat的关系的话,就会理解为什么src port会发生变化了。

我们最好先得出 Netfilter 不用 random 确实是 EIM-EDF-NAT 的结论

我得出的结论已经说过了,他是严格按照rfc5780实现的,符合rfc5780中对Endpoint-Independent mapping的定义,也能通过测试。

换句话说就是rfc5780明确说明之外的行为都不保证。

我的想法还是,有机会可以自己测试下,很多事情就会豁然开朗。学而不思则罔,思而不学则殆,最终还是要落到实践上的。

但你这里的命题是错误的,说明你对 UDP 打洞和 NAT 并没有理解。正确情况是只要打洞时双方都发包了,自己的 NAT 就会完成并记录这个映射

网络中任一段都有可能产生丢包的,发生这个命题的可能性有两段。

如果要深入讨论这一行为,就必须去查看相关代码了。

我现在就是在看这块儿的源码。直接个你指个路吧,按顺序一步步递进(原始博客看的是较老版本的仓库了,部分源文件已经重命名、合并):

https://www.hwchiu.com/iptables-masquerade.html
https://git.netfilter.org/iptables/tree/extensions/libxt_NAT.c?h=4c923250269f9ef4a7b4235f4dc127b04932a8eb#n286
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/xt_MASQUERADE.c#L35
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/nf_nat_masquerade.c#L28
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/nf_nat_core.c#L580
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/nf_nat_core.c#L504
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/nf_nat_core.c#L185
https://github.com/torvalds/linux/blob/105131df9c3b27673392a6b7ff356360188dc869/net/netfilter/nf_conntrack_core.c#L1299

你没有考虑过丢包的情况,如果双方互相发送包的时候,一方丢包,对端就会退化进入Dependent Mapping

你别的留言我还没有来得及看完,但你这里的命题是错误的,说明你对 UDP 打洞和 NAT 并没有理解。正确情况是只要打洞时双方都发包了,自己的 NAT 就会完成并记录这个映射,之后允许对方端点发到自己内部客户的端点的数据包顺利被 DNAT。对方的包只要没有早于自己发包就到达自己的 NAT 的话就不会影响自己的 EIM-NAT 行为。实际 P2P 应用开发中,这个环节也是从约定的时间开始同时向对方发包,并且发送多次以防止仅仅是丢包导致的打洞失败。并且如果只是为了防止自己发的包抢先与对方发包就到达了对方 NAT 破坏了打洞的条件,完全可以通过 TTL 来控制只让包发到自己的 NAT 或者 NAT 的下一跳为止。

我们测试的本地路由的行为,和对端没有关系,我不知道你提对端时在这里的作用是什么。

你原文用的是“打通隧道”,这是当然会“通”的,原因我解释了。既然你说的是打通隧道收到对方发进来的包,你的说法中就包含了“这次对方发进来的包并没有被 EDF-NAT 给过滤”这层含义。这个测试中当然是和这个特定对端有关的,因为你给这个对端发包了。另外你说你必须“抢先发包”,这就和我前面说的行为是一致的。你要是让对端先给你发包了,你再给他发包的时候映射端口就会被改变。一切和我说的是一致的。

如果没有实现规范中所说的内容,那规范的存在就没有意义

你这个就太想当然了,包括PCP和推荐的CGNAT的EIF+EIM都没有被广泛实现。要说规范应该被实现,人人都应该用上IPv6,不要再考虑这个所谓 FullCone 的问题才对。并且规范是会被修订的,5780就是修订的3489

既然如此Port-Dependent Mapping是客观存在的。

他不存在,如果你理解我给你留言说的副作用的事情,你就知道这不是因为你的 NAT 自己的性质,而是对端抢先发包干扰了你的 NAT。我之前之所以给你提这个词是在还没有搞明白你的NAT为什么会在你的自定义测试中改变你的端口的情况下,用 RFC 5780 的术语打了个比方。后面我已经说明了你的问题并不是 NAT 行为问题,所以这个比喻也完全不正确了。然后根据奥卡姆剃刀原理, RFC 5780 定义的 EIM 与否 和 EIF 与否的概念足以解决打洞所遇到的 NAT 类型判别问题了,所以不需要再琢磨创建一个新名词。

没有看明白为什么是错误;我认为这不是副作用,是conntrack本身的作用。
不过,如果你能够理解conntrack和nat的关系的话,就会理解为什么src port会发生变化了。

这不只是 conntrack 本身的作用,这更根本的原因是你逼着 STUN2 抢先给你这个端点发包。正常的 P2P 连接中就如我之前和刚刚所说,是可以也应该自己控制这种事情不发生。按照我刚刚看源码的过程的理解,你这个抢先发包的后果就是 conntrack 记录了来自 STUN2 的发包并创建了单独的 conntrack 来记录,之后你再给 STUN2 发包的时候 NAT 给你找端口的时候就觉得你这个连接和上面那个 conntrack 不是一个了,所以给你分配了新的。我认为这是很正常的行为。

但是文章中很显然没说明一定不能,而且是推荐要做。但是这也不能证明RFC3489就是错误经验
你说的这些都不是规范中说明一定要这么做的。

钻牛角尖就没有意义了。我们这整个讨论、issue、甚至仓库的中心都在于要不要给OpenWRT甚至是Linux加入 FullCone 来解决 打洞不便的问题。而这一点上现在和你最初回复我之前我的结论仍然一致,现在 Linux 的行为就已经足够了,并不需要改变。如果改变,也不可能是 Linux/Netfilter 的 bug。同时他们为了节约开源项目的人力物力也不会接受 Feature Request 和 Pull Request。但这并没有什么问题。

换句话说就是rfc5780明确说明之外的行为都不保证。

我前面也说明过了,正是因为除此以外的行为都不需要(除了 BT 这种没有会合服务器的情况),所以 Netfilter 才没有也不会去做多余的事情。如果你正确理解了 UDP 打洞的话,你也就能理解我这么说的原因了。包括我们这个话题开始的缘由——Linux 和 OpenWRT 不需要 “FullCone NAT“ 实现

你有一些言论说实话和讨论的问题关系也不大,我就先忽略了:

我发现你的思考逻辑,有比较强烈的跳跃性;你提到的东西,很多都是额外附加的,讨论时并未列出来源
我的想法还是,有机会可以自己测试下,很多事情就会豁然开朗。学而不思则罔,思而不学则殆,最终还是要落到实践上的。

Ovear commented

讨论到这里,我发现没有什么可以讨论的了;毕竟现在两种说法都说的通。

我不太熟悉内核,如果你能够看懂的话我期待一个分享。

另外我给个建议,尽量不要用缩写,用术语就好了。

但你这里的命题是错误的

这个我刚才编辑的时候回复过了,这是可能发生的,所以才要想方法解决。

你再给他发包的时候映射端口就会被改变

这个我就当我们达成一致了,一开始讨论的行为就是这个。

我们现在的讨论的应该是在原因,但是你下面的说法就需要源码支撑了。

他不存在,如果你理解我给你留言说的副作用的事情,你就知道这不是因为你的 NAT 自己的性质,而是对方抢先发包干扰了你的 NAT

钻牛角尖就没有意义了。

这个对你同样你也是的,你太过在意是不是通过一个端口去测试了,实际这些软件的实现也是一个端口的,但是不影响结果。

所以不需要再琢磨创建一个新名词。

实际上这个分类行为本身就是RFC5780做的。

按照我刚刚看源码的过程的理解来说,你这个抢先发包的后果就是 conntrack 记录了来自 STUN2 的发包并创建了单独的 conntrack 来记录,之后你再给 STUN2 发包的时候 NAT 给你找端口的时候就觉得你这个连接和上面那个 conntrack 不是一个了,所以给你分配了新的。我认为这是很正常的行为

你的描述有一点问题,我没太看懂。
但是后面conntrack方面这是看到的现象,也是我放出来的原因;在没有源代码的情况下,能观察的就这么多了,我这也是我强调conntrack的运作方式的原因。

正是因为除此以外的行为都不需要(除了 BT 这种没有会合服务器的情况)

这个就是我的看法了,现在的行为比我之前要预想的好;
但是还是没有能支持Linux 和 OpenWRT 不需要 “FullCone NAT“ 实现,因为你也说了是存在正常且无法覆盖的场景。
至于你后面发散的内容也并不是重点。
但是如果在社区底下要求社区无视这种需求的存在,跟他们说你们做的东西不需要,没有意义,这就不能接受了。

这不只是 conntrack 本身的作用,这是你逼着 STUN2 抢先给你这个端点发包导致的结果。并且正常的 P2P 连接中就如我之前和刚刚所说,是可以自己控制这种事情不发生,也应该控制的。

1.正常的P2P连接,是专指UDP打洞吗?其他的P2P连接方式,没有看见有这个限制。
2.如果你手动做了DNAT,就没有这个限制了。

在没有源代码支持的情况下,从目前给出的信息,只知道NAT映射被修改了,但是没办法知道原因。

现在看下来我的猜测也基本都得到验证了,如果能有源码方面的分析,期待分享。

这个我刚才编辑的时候回复过了,这是可能发生的,所以才要想方法解决。

我从来没说不可能发生丢包,但我要说的就是丢包恰恰不会导致你自己的 NAT 出现 EIM-NAT 的行为。如果你能理解就好了。

你太过在意是不是通过一个端口去测试了,实际这些软件的实现也是一个端口的,但是不影响结果。

我无语了。软件测出来你是 EIM 和 对称式NAT 的原因我前面都给你分析过了。也给你分析过了什么是能解决你的需求的,你要是不愿意去理解那就没办法了。不是我太过在意,是你没有理解发生了什么。

但是如果在社区底下要求社区无视这种需求的存在,跟他们说你们做的东西不需要,没有意义,这就不能接受了。

你可以继续做,本来要不是你引用了我的回复并提出了你发现的这个现象我这两天就会和别的订阅一样,是不会回复和测试的。

其他的P2P连接方式,没有看见有这个限制。

你不妨举一个非要抢先而不是同时来打洞的应用的例子。当然,除了BT这种没有会合服务器的。

手动做了DNAT

这不也是说了等于没说,DNAT就是端口转发了,你平时要是能给 CGNAT 做端口转发(PCP协议)那你也不用纠结了。。。

讨论到这里,我发现没有什么可以讨论的了;毕竟现在两种说法都说的通。

如果你哪天能理解我这条评论里第一句回复的话我们可以接着讨论,不然的话确实讨论下去也没有意义。

或许,我前面提的 tailscale 的那篇文章,你再仔细读一遍也许能帮助理解?

Ovear commented

网络中任一段都有可能产生丢包的,发生这个命题的可能性有两段。
这是可能发生的,所以才要想方法解决。

但我要说的就是丢包恰恰不会导致你自己的 NAT 出现 EIM-NAT 的行为

我说的很明白了,我再引用一下,我不知道你是没看到,还是发生什么了。

我无语了。软件测出来你是 EIM 和 对称式NAT 的原因我前面都给你分析过了。也给你分析过了什么是能解决你的需求的,你要是不愿意去理解那就没办法了。

没明白你的意思,在我的角度看来反倒是一直是我在和你解释,测试也是我做的,结果和结论(包括纠正)也是我发的。只是很可惜你提供的内容都是理论分析,而我更倾向于实际一些的东西。

并提出了你发现的这个现象我这两天就会和别的订阅一样,是不会回复和测试的

所以我们解决了这个疑问,至少是大部分,这是一件好事;你愿意参与我是很高兴的,没有的话我自己琢磨,或者等一个有缘人都是意料内的。刚好我也希望我们的讨论能帮到搜索过来的同学。

你不妨举一个非要抢先而不是同时来打洞的应用的例子。当然,除了BT这种没有会合服务器的。

这句话的意思让人看起来有种去除了正确答案,都是错误答案这种感觉。
排除BT这一常见应用也不能理解,没有会合服务器也很正常。
现实情况就是,这种需求无法得到满足,但是有方法可以获得,我想不到拒绝的理由。
更何况是在社区内有人愿意的情况下,我非常感谢他们的努力。

这不也是说了等于没说,DNAT就是端口转发了,你平时要是能给 CGNAT 做端口转发(PCP协议)那你也不用纠结了。。。

或许,我前面提的 tailscale 的那篇文章,你再仔细读一遍也许能帮助理解?

既然在现象上,我们达成一致了。
那么现在就差原因没有解决了,我提出这个问题也是希望后面有大佬能看到,刚好推进一下,那就最好了。

我当然理解理想情况;但是我们面对的是一个具体实现,我的疑问也是对这个具体实现来说的。当然也包括我之前说到的RFC中未提到的那种分类。
这个问题我们今天倒腾了这么久,还是没有解决是有点可惜。

祝晚安。

网络中任一段都有可能产生丢包的,发生这个命题的可能性有两段。这是可能发生的,所以才要想方法解决。

我说的很明白了,我再引用一下,我不知道你是没看到,还是发生什么了。

别的都不谈,我就是不明白你这里在说什么。当然是会有丢包,然后呢?你担心丢包导致什么不理想的后果?为什么你认为会有这样的结果?

排除BT这一常见应用也不能理解,没有会合服务器也很正常。
现实情况就是,这种需求无法得到满足,但是有方法可以获得,我想不到拒绝的理由。

如果你是 NetFilter 的维护者你确实可以去维护这个功能,但是你不能否认这一部分没有会合服务器的需求是小众的,并且是没有赞助商、商业公司等支持着开源社区去开发的。希望你之后能成为一个 Linux 内核代码、NetFilter 的维护者。

Ovear commented

别的都不谈,我就是不明白你再说什么。是会有丢包,然后呢?你担心丢包导致什么结果,为什么你认为会有这样的结果?

双方任意一方,包在到路由之前丢了,那这个端口的打洞就宣告失败了,因为行为改变了。

并不是你说的恰恰不会导致你自己的 NAT 出现 EIM-NAT 的行为

如果你还是没办法理解的话,那就再具体一点。

双方通过stun获取到WAN端的地址了,但是互相通信的时候就出现在路由丢包的问题了。

路由:指有这个行为,且有公网IP的路由。你可以假设是双方都用openwrt,都有公网ip,这是最简单的场景,希望能帮助你理解。

但是你不能否认这一部分没有会合服务器的需求是小众的,并且是没有赞助商等支持着开源社区去开发的。希望你之后能成为一个 Linux 内核代码、NetFilter 的维护者

我个人不认为BT是小众的;表达支持的方法也是多样的,这个社区中已经有人愿意站出来,并已经开发并维护了很长一段时间。

我很感激他们,我不认为他们做的事情是“无意义”的,“没有必要”的,这么去说他们是不妥的。

开源社区本来就是百花齐放的,有人愿意做,有人愿意用,有人愿意接手,有人愿意捐赠,这就是社区运作的模式。