前言
我们的程序,在实际的网络部署时,一般比较复杂,会经过很多的网络设备,防火墙就是其中的一种。做开发的同事,一般对这块了解不多,也很可能被防火墙坑到。比如,应用一般需要访问数据库,为了避免频繁建立连接,一般是会提前建立一个连接池,每次来一个请求,就从连接池取一个连接来用,用完再归还到池子里。
连接池中的连接是啥呢,其实就是和数据库之间的完成了三次握手后的socket,这个socket在白天时,一般经常有数据传输,而到了凌晨这种,可能就很少数据传输,等到了第二天,某个请求来了,从池子里取了某个socket,就直接发送数据,此时,很可能就会出现一个read timeout的情况。
为啥呢,是数据库不返回数据吗?不一定,如果应用服务器和db服务器之间,经过了防火墙的话,很可能,你这个socket发出去的包,直接就防火墙给丢弃了,根本没有到达数据库。如果想搜索这块相关的案例,可以搜索“防火墙 长连接”关键字。
状态防火墙(stateful firewall)
现在的防火墙,基本都是有状态的,什么叫做有状态呢?以tcp为例,当服务端收到第一次握手(syn)时,此时,会认为这是要新建立连接,当三次握手完成后,再收到客户端在该socket上发来的请求报文时,此时,就知道这个报文不再是要新建立连接了,socket的状态此时也是established。说白了,握手时候的报文和握手完成后的报文,是不一样的,我们是可以区别对待的,这就是通俗意义上的“有状态”。
而在此之前,都是无状态防火墙(stateless firewall),不管是什么报文,都是一视同仁,没法根据状态来区分处理。
接下来,我们可以以iptables为例,理解下状态防火墙(iptables就是典型的状态防火墙)。
iptables中的状态
该部分参考官网:https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#STATEMACHINE
iptables是用户态中的命令,在内核中由netfilter实现,netfilter中实现连接的状态跟踪的机制,叫做conntrack(connection track)。在这个机制中,一共定义了4种状态:NEW, ESTABLISHED, RELATED,INVALID,UNTRACKED。
注意这里的conntrack,中文就是连接追踪,既然是连接,那么,我们怎么区分一个连接呢,比如,在tcp中,就是依靠四元组,这个四元组就可以唯一标识一个连接。
我们可以定义一个hash表,在收到一个tcp报文时,检查下这个四元组在hash表中是否存在(key就是四元组),如果不存在,我们就可以认为这个报文此时是新来的,也就是状态是new;当我们收到服务端返回的报文时,我们又可以检测下四元组在本地hash表是否存在,发现已经存在了,此时就可以认为状态是established。
这里大家如果要看这个状态的详细解释,可以查看官网文档;另外,我发现一篇文章也不错:
https://mp.weixin.qq.com/s/kv32lyWak4dCaPjMI4_6jw
NEW:
新建连接请求的数据包,且该数据包没有和任何已有连接相关联。 判断的依据是conntrack当前“只看到一个方向数据包(UNREPLIED)”,没有回包。
ESTABLISHED:
该连接是某NEW状态连接的回包,也就是完成了连接的双向关联。
其他几种状态,RELATED和INVALID我感觉一般比较少用,至于UNTRACKED ,可以等后续再慢慢领悟。
实际感受状态变化
客户端首次握手(syn)
可以通过cat /proc/net/nf_conntrack
查看连接状态。
如收到首次握手请求后,会看到如下内容:
tcp 6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.35 sport=1031 \
dport=23 [UNREPLIED] src=192.168.1.35 dst=192.168.1.5 sport=23 \
dport=1031 use=1
各个字段的意思:
tcp,协议名
6,传输层协议代号,其中tcp是6,udp是17,
117,ttl,表示这个conntrack的过期时间,类似于redis里的key的ttl
SYN_SENT,socket此时的状态。官方:The value SYN_SENT tells us that we are looking at a connection that has only seen a TCP SYN packet in one direction
src=192.168.1.5 dst=192.168.1.35 sport=1031 dport=23部分:
the source IP address, destination IP address, source port and destination port
UNREPLIED(未回复)
we have seen no return traffic for this connection,表示我们还没看到回包,现在只有单方向的包
src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031
这是我们期待的回包的样子
服务端返回syn+ack
tcp 6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.35 sport=1031 \
dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 \
use=1
收到syn+ack后,此时,我们的状态由SYN_SENT变成SYN_RECV,此时,也移除了之前的UNREPLIED
关键字
客户端第三次握手 ack
tcp 6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.35 \
sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 \
sport=23 dport=1031 [ASSURED] use=1
状态变成ESTABLISHED,增加了关键字:ASSURED
其他
涉及到socket关闭的部分,导致的状态变化请参考官方文档:https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#STATEMACHINE
简单来说,就是两边都完成关闭,状态才变成closed。
状态标记发生的阶段
我们上面可以看到在一个connection上状态的变化,说白了,这部分就是给一个报文打上标记,关于状态的标记,后续我们才能基于这个状态label进行匹配。
所以,我们对于这个标记发生的时间,需要特别注意,可以看下,下图的左上角的绿色方块(connection tracking)就是状态标记发生的位置,基本上是最前面了(最上面是网卡,再下来是raw,再下来就是connection tracking了)。
在这个绿色方块执行完成后,状态就已经标记好了,我们就可以根据状态来匹配这些报文来进行accept或者drop了,具体看下文。
iptables模拟长连接超时后继续使用该连接的场景
部署图
我们的两台机器如下:
服务器:10.0.2.15:2222,监听2222端口
客户端:10.0.2.4
iptables配置
放行第一次握手请求
三次握手的报文都需要放行,第一次握手请求的特征是,此时,根据前面我们讲的状态,第一次握手到来时,本地是找不到该四元组的,所以state是new,再加上目的端口是2222,因此,我们的匹配条件是:
--dport 2222 -m state --state NEW
,因此,命令如下:
iptables -I INPUT 4 -p tcp --dport 2222 -m state --state NEW -j ACCEPT
放行第二次、第三次握手请求
此时,马上会收到2222端口返回的syn + ack,此时,状态就会变成established。
这种报文的特征是,state为established,这种怎么放心呢,这种其实默认就放心了,不需要我们再干啥。
第三次请求,也是同理,状态会被标记为established。
等待conntrack记录过期或手动删除、清空
我们这边就需要模拟连接建立后,等conntrack记录过期后,客户端再使用这个socket会发生什么。
那么,我们怎么来查看这些conntrack呢,需要安装工具:yum install conntrack-tools
查看hash表
[root@node1 ~]# conntrack -L
tcp 6 431997 ESTABLISHED src=10.0.2.2 dst=10.0.2.15 sport=9526 dport=22 src=10.0.2.15 dst=10.0.2.2 sport=22 dport=9526 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
我们可以看到过期时间为431997,单位是秒,差不多是5天。这个初始值是432000,来自于内核参数:net.netfilter.nf_conntrack_tcp_timeout_established :
[root@node1 ~]# sysctl -a |grep net.netfilter.nf_conntrack_tcp_timeout_established
net.netfilter.nf_conntrack_tcp_timeout_established = 432000
我们测试的话,这个时间就太长了,我们可以修改这个内核参数进行设置。我们可以改成60s过期:
[root@node1 ~]# vim /etc/sysctl.conf
net.netfilter.nf_conntrack_tcp_timeout_established = 60
执行sysctl -p
我们也可以选择手动删除、清空这些conntrack记录:
删除目标端口为2222的记录:
conntrack -D -p tcp --dport 2222
清空整个hash表
conntrack -F
实时监控hash表的变动(增删改)
conntrack -E
丢弃客户端在长时间空闲的长连接上发过来的包
由于我们上一步执行了conntrack记录删除,此时,客户端发的报文,在iptables看来,又是全新的四元组,也就是会把状态标记为new,但是,这种请求报文和握手报文明显不同,这种报文的tcp层一般会设置psh标记,握手请求里则会设置syn等,所以,我们按照这个进行区分。
匹配条件为:--dport 2222 -m state --state NEW -m tcp --tcp-flags PSH PSH
,动作为丢弃:drop
iptables -I INPUT 4 -p tcp --dport 2222 -m state --state NEW -m tcp --tcp-flags PSH PSH -j DROP
ok,接下来开始测试。
最终的iptables效果
实际测试
用到了该博客中的客户端、服务端程序:
https://blog.csdn.net/fengcai_ke/article/details/125717134
server.c,主要是监听2222端口;client.c,就是连接并发送请求。
服务端发起监听,客户端发起连接,完成三次握手:
[root@node1 ~]# ./server
[root@node2 ~]# ./client
please input:
此时,可以看到三次握手:
服务端的conntrack -E
,也可以看到hash表的变化:
[root@node1 ~]# conntrack -E
[NEW] tcp 6 120 SYN_SENT src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 [UNREPLIED] src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216
[UPDATE] tcp 6 60 SYN_RECV src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216
[UPDATE] tcp 6 432000 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED]
可以看到established的conntrack:
[root@node1 ~]# conntrack -L|grep 2222
conntrack v1.4.4 (conntrack-tools): 5 flow entries have been shown.
tcp 6 431857 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
接下来,删除conntrack记录:
删除记录:
[root@node1 ~]# conntrack -D -p tcp --dport 2222
tcp 6 431800 ESTABLISHED src=10.0.2.4 dst=10.0.2.15 sport=46216 dport=2222 src=10.0.2.15 dst=10.0.2.4 sport=2222 dport=46216 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
conntrack v1.4.4 (conntrack-tools): 1 flow entries have been deleted.
查询:
[root@node1 ~]# conntrack -L|grep 2222
conntrack v1.4.4 (conntrack-tools): 4 flow entries have been shown.
客户端使用该socket发起请求:
[root@node2 ~]# ./client
please input:11
send result
: Success
可以看到,服务端2222,没回复,客户端一直重传:
观察iptables,可以发现,确实匹配上了drop那条:
参考
https://blog.csdn.net/fengcai_ke/article/details/125717134
https://blog.csdn.net/weixin_46768610/article/details/109354433
https://www.cnblogs.com/saolv/p/13096965.html
https://mp.weixin.qq.com/s/kv32lyWak4dCaPjMI4_6jw