Linux的Netfilter NAT实现中,为什么会有一个nf_nat_alloc_null_binding(在低版本内核比如2.6,它叫alloc_null_binding)调用?

该函数是在一条流没有命中任何NAT规则的时候调用了,其内部实现和对待命中了NAT规则的流的方式几乎一样,唯一的约束是,它只能修改一条流的源端口。

问题是,既然没有命中任何规则,为什么要修改流的源端口呢?

我早就想好好解释一下这个问题了,但我一直觉得有人已经解释过了,所以就没有写任何东西。周二下午家里有事正好休假,等待期间我用手机搜了一下这个话题,几乎没有发现正确的答案,很令人失望。正好最近也碰到一个问题,那我就写点东西吧。

碰到一个问题,一条流没有命中任何NAT规则,结果它的源端口却被改变了,这是谁做的呢?答案当然是, 这是nf_nat_alloc_null_binding做的! 细节就是:

  • 如果有一条流已经占据了未命中流的tuple,那么未命中流的tuple就会被修改,具体来讲就是修改它的源端口。

如果读过nf_conntrack的实现,可能有人不信,怼曰:

  • 如果要修改一条流的源端口,那必然是和既有流的tuple冲突了啊。
  • 如果和既有的tuple冲突,那必然在该流进入conntrack的时候就会匹配到啊。
  • 匹配到的tuple肯定已经被confirm了啊。
  • confirm的流肯定已经经过了NAT HOOK了啊。
  • 经过了NAT HOOK就不会再check tuple了啊。

很有道理,然而,事实上数据包是无锁经过conntrack逻辑的,期间任何事情都会发生。

多说无益,设计一个实验是必要的,如果我能展示出这个场景,非要抬杠还有什么意义呢。

我在实验中构造两条流,其中一条命中NAT规则,然后占据一个tuple,然后第二条流虽然未命中任何NAT规则,却不得不修改其源端口以适应tuple的唯一性。两条流被打上了不同的mark,因此很容易识别。

首先,我们看发起这两条流的代码:

#!/usr/bin/python3# client.py 正常的流,它绑定192.168.56.101:12345去连接192.168.56.102:80import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 标识正常流
sock.setsockopt(socket.SOL_SOCKET, socket.SO_MARK, 200)
sock.bind(('192.168.56.101', 12345))server_address = ('192.168.56.102', 80)
sock.connect(server_address)
sock.close();

我们来看一下抢占client.py生成的流tuple的小偷流:

#!/usr/bin/python3# thief.py 该流发起后通过NAT规则抢占了client.py发起的正常流的tupleimport socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 用于NAT规则识别该流
sock.setsockopt(socket.SOL_SOCKET, socket.SO_MARK, 100)
sock.bind(('192.168.56.101', 54321))server_address = ('192.168.56.102', 80)
sock.connect(server_address)
sock.close();

为了抢占tuple得手,我们配置下面的NAT规则:

iptables -t nat -A POSTROUTING -p tcp -m mark --mark 0x64 -j SNAT --to-source 192.168.56.101:12345

并且save mark以区别不同的流:

iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark

好了,现在,先运行thief.py再运行client.py,你就会发现client.py发起的流的源端口已经被改变:

root@zhaoya-VirtualBox:~/test# ./thief.py && ./client.py && conntrack -L
tcp      6 119 TIME_WAIT src=192.168.56.101 dst=192.168.56.102 sport=54321 dport=80 src=192.168.56.102 dst=192.168.56.101 sport=80 dport=12345 [ASSURED] mark=100 use=1
tcp      6 119 TIME_WAIT src=192.168.56.101 dst=192.168.56.102 sport=12345 dport=80 src=192.168.56.102 dst=192.168.56.101 sport=80 dport=50487 [ASSURED] mark=200 use=1
conntrack v1.4.5 (conntrack-tools): 2 flow entries have been shown.

可以看到,mark 200的流源端口已经被修改为50487了。

不过遗憾的是,这并不能让人满意,毕竟你先运行了thief.py,根据NAT规则,thief.py发起的流已经根据规则被强行转换了源端口,后来的client.py发起的流不得不重新选择一个端口,不然五元组就不唯一了,这并没有问题,在默默修改源端口和直接不通之间,显然NAT的实现选择了前者,无论如何,这都是nf_nat_alloc_null_binding的 功劳。

如果先运行client.py后运行thief.py,将会造成后者不通,因为当它的源端口被NAT规则强制修改成了12345时,将会和已经创建的client.py流tuple冲突。

我们需要另一种场景,先让client.py发包创建conntrack,但在其conntrack尚未confirm的时候,让thief.py发包通过NAT规则,抢占client.py流即将使用的tuple,迫使它不得不重新选择一个:

  • 我们就让client.py流进入nf_nat_alloc_null_binding的时候让thief.py流抢占其tuple。

client.py流显然不会匹配到NAT规则,它显然会执行nf_nat_alloc_null_binding,我们就在这个函数将要执行的时候做点动作,下面是stap脚本:

#!/usr/bin/stap -gglobal launchprobe begin
{launch = 1
}probe module("nf_nat").function("__nf_nat_alloc_null_binding")
{if (launch == 1 && $manip == 1) {launch = 0;system("/root/test/thief.py");mdelay(50);}
}probe module("nf_conntrack").function("nf_conntrack_tuple_taken").return
{printf("nf_conntrack_tuple_taken   ret:%d\n", $return);
}

将这个thief.stp脚本跑起来,然后执行client.py:

root@zhaoya-VirtualBox:~/test# ./client.py && conntrack -L
tcp      6 119 TIME_WAIT src=192.168.56.101 dst=192.168.56.102 sport=54321 dport=80 src=192.168.56.102 dst=192.168.56.101 sport=80 dport=12345 [ASSURED] mark=100 use=1
tcp      6 119 TIME_WAIT src=192.168.56.101 dst=192.168.56.102 sport=12345 dport=80 src=192.168.56.102 dst=192.168.56.101 sport=80 dport=30049 [ASSURED] mark=200 use=1
conntrack v1.4.5 (conntrack-tools): 2 flow entries have been shown.

一样的结果。

我想说的是,在一个流的conntrack被创建,经过nf_nat_alloc_null_binding,最终被confirm,这条路径非常长,其中很容易发生任意conntrack的插入和删除,如下的脚本展示了这一切,即便我们撤掉thief.py脚本,在clilent.py流到达nf_nat_alloc_null_binding的时候,插入一条抢占其tuple的conntrack项,也可以完成这个实验:

#!/usr/bin/stap -g%{#include <net/netfilter/nf_conntrack.h>
%}global launchprobe begin
{launch = 1
}probe module("nf_nat").function("__nf_nat_alloc_null_binding")
{if (launch == 1 && $manip == 1) {launch = 0;// 直接插入一条reply tuple和client.py流冲突的conntrack项,天知道这是怎么来的。system("conntrack -I --protonum 6 --timeout 100 --reply-src 192.168.56.102 --reply-dst 192.168.56.101 --state SYN_SENT --reply-port-dst 12345 --reply-port-src 80 --src 1.1.1.1 --dst 192.168.56.102");mdelay(50);}
}probe module("nf_conntrack").function("nf_conntrack_tuple_taken").return
{printf("nf_conntrack_tuple_taken   ret:%d\n", $return);
}%{struct nf_conn *thief = NULL;
%}function alertit(stp_ct:long)
%{struct nf_conn *ct = (struct nf_conn *)STAP_ARG_stp_ct;struct nf_conntrack_tuple *tuple;unsigned short port;tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;port = ntohs((unsigned short)tuple->dst.u.all);if (port == 80 && thief == NULL) {STAP_PRINTF("The thief coming!\n");thief = ct;}
%}probe module("nf_conntrack").function("nf_conntrack_hash_check_insert")
{alertit($ct);
}function run_away(stp_tuple:long, stp_ct:long)
%{if (thief) {thief = NULL;STAP_PRINTF("The thief ran away...\n");}
%}probe module("nf_conntrack").function("nf_conntrack_alter_reply")
{run_away($newreply, $ct);
}

执行该脚本,然后只需要执行client.py即可:

root@zhaoya-VirtualBox:~/test# ./client.py && conntrack -L
tcp      6 119 TIME_WAIT src=192.168.56.101 dst=192.168.56.102 sport=12345 dport=80 src=192.168.56.102 dst=192.168.56.101 sport=80 dport=55424 [ASSURED] mark=200 use=1
tcp      6 99 SYN_SENT src=1.1.1.1 dst=192.168.56.102 sport=0 dport=0 [UNREPLIED] src=192.168.56.102 dst=192.168.56.101 sport=80 dport=12345 mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 2 flow entries have been shown.

顺便放一个服务端的抓包:

17:28:15.522879 IP 192.168.56.101.55424 > 192.168.56.102.80: Flags [S], seq 1953656515, win 64240, options [mss 1460,sackOK,TS val 846034088 ecr 0,nop,wscale 7], length 0

显然,nf_nat_alloc_null_binding更改了client.py流的源端口。

要表达的观点是, NAT逻辑在主机粒度(or 容器?netns?)是全局管理的,即便没有命中NAT规则的流,为了确保tuple唯一性,该流的tuple也可能会被改变,这就是为什么对每一条流,nf_nat_alloc_null_binding都必须被调用的原因。

这种问题在很多人看来根本就不是什么问题,当我因为去剖析某种细节而感到愉快的时候,在他们看来却是不过如此,然而,这种不过如此的问题却偶尔真的成了那些解决常规问题不屑于这种细枝末节的人们路上的障碍,他们不知道问题发生的根因,也不屑于去深究这种实现上细节,于是很多人还是最终找到了我,同时我也感受到了愉快。

对,这是手艺人的自嗨,和工程无关。


浙江温州皮鞋湿,下雨进水不会胖。

谁动了你的五元组-Linux Netfilter NAT之nf_nat_alloc_null_binding相关推荐

  1. 谁动了你的五元组-nf_conntrack与NAT的性能

    在互联网上一个五元组标识一个应用程序到远端的另一个应用程序的连接.要保证端到端的可达性,显然在全局范围内,五元组必须是唯一的. 保证五元组的全局唯一性看起来是个重体力劳动,以IPv4网络为例,仅仅考虑 ...

  2. Linux的NAT如何处理ICMP这类带外信息

    清远鸡,不好吃.         关于Linux的nf_conntrack的详细细节,我不再赘述,由于我之前写的文章几乎没有编排目录,我也只能通过谷歌baidu搜索几篇相关列如下,以后我也会注意自己文 ...

  3. linux centos 7 系统性能查询、DHCP租期信息查询、网络五元组

    linux centos 7 系统性能查询 top CPU进程情况 killall 最后一列进程名 中止进程信息. killall -9 进程名 强制中断. sar -n DEV 1 每秒显示所有网卡 ...

  4. Linux:网络五元组tcp、udp特性

    标题 网络五元组信息 UDP简单的特性 TCP简单特性 网络字节序 IP地址+端口号就叫套接字,用于定位主机中的一个进程 我们在系统编程部分学过管道,管道是用于同一主机下不同进程间的通信 套接字则用于 ...

  5. linux五元组结构体,一种基于分类优先级的五元组查询方法与流程

    本发明涉及网络传输技术,特别涉及一种基于分类优先级的五元组查询方法. 背景技术: 近年来,随着网络的快速发展,网络数据包的转发效率显的尤为重要:目前,信息技术快速发展,其对应的数据量也迅速增长,在对大 ...

  6. java解析五元组,解析报文 解析出packet的五元组(源地址、目标地址、源端口、目标端口、协议号)信息和当前包的流量大...

    我们重点关注五元组+状态+inode号分别在第2.3.4.11列 进程文件描述符 网络状态文件/proc/net/tcp"0B": "CLOSING" 在lin ...

  7. 【计算机网络】网络通信基础(IP地址,端口号,五元组,OSI七层模型,TCP/IP五层模型,封装和分用)

    目录 初识网络 网络通信基础 IP地址 端口号 认识网络协议 五元组 协议分层 封装和分用 初识网络 网络互连 随着时代发展,需要计算机之间相互通信,共享软件和数据,即多台计算机相互协同工作来完成某个 ...

  8. java解析五元组_pcap文件解析,并且按照五元组分类

    [实例简介] pcap文件解析,并按照五元组分包,全部用java语言实现. [实例截图] [核心代码] PcapTestZZ ├── PcapTestZ │   ├── 111.206.37.1930 ...

  9. TCP/IP的四元组 五元组 七元组

    四元组是: 源IP地址.目的IP地址.源端口.目的端口 五元组是:       源IP地址.目的IP地址.协议号.源端口.目的端口 七元组是: 源IP地址.目的IP地址.协议号.源端口.目的端口,服务 ...

最新文章

  1. 笑岔气!一个程序员的水平能差到什么程度?
  2. java.lang.reflect.InaccessibleObjectException: Unable to make
  3. mx:button加skin光晕点击时,大小不一样
  4. oem718d 基准站设置_RTK基站设置、7参数、测点、放线等操作教程,文末有视频
  5. 计算机硬件:内存条的基础知识笔记
  6. oracle+mybatis查询遇到CHAR类型字段
  7. 智能家居火了这么久 何时到我家?
  8. Beyond Compare “许可证密钥已被撤销”解决
  9. 最新版《神经网络和深度学习》中文版.pdf
  10. iPhone 二手手机到底去哪了
  11. 肩外展固定支架的规格参数介绍和使用方法
  12. 企业中爬虫问题(来自网易公开课)
  13. 计算机名校远程在职硕士信息汇总Online Master
  14. com.android.phone已停止无限重启,Android Q没法用,大批用户反馈手机无限重启
  15. 【题解】昂贵的聘礼 POJ - 1062 (最短路 经典)⭐⭐⭐⭐
  16. 安卓学习笔记—渐变色背景
  17. 微软认证一览表(附图)
  18. linux基础篇,数据流重定向
  19. matlab 画图添加图例时,改变图例中字体大小
  20. 浅谈绝对定位与相对定位

热门文章

  1. mysql项目练习_mysql练习项目 - osc_wy5qpqnh的个人空间 - OSCHINA - 中文开源技术交流社区...
  2. VB 中自定义弹出提示框的位置
  3. 如何规划和选择数据库服务器:CPU、内存、磁盘、网络(转)
  4. 2021-12-01 工作记录--Wechat applet-邂逅
  5. Android Studio 的ListView 的用法
  6. 阿汤的疑惑(大数取余+质因数分解)
  7. Deepfake版阿汤哥,在海外版抖音上收获了32万粉丝
  8. FTP文件传输协议(英文:File Transfer Protocol,缩写:FTP)
  9. 常见行内元素、块级元素、行内块元素
  10. linux命令之打包和解压