背景,很长一段时间以来,我们的防火墙一直存在问题,有时会使 HTTP 请求处于部分加载状态,直到 TCP 超时。
在跟踪防火墙上的流量后,我注意到它只在某些时间条件下发生,例如,当 Web 服务器在客户端发送有效负载上的第二个 ACK 之前发送整个响应时。[SYN、SYN/ACK、ACK] 已交换,REQUEST 已发送并确认,第一个 RESPONSE 数据包已收到并确认,然后 Web 服务器一次性发送其余响应主体(8 个数据包,包括最后一个 FIN、PSH),并且在客户端确认其中任何一个之前,防火墙会使用 RST 向 Web 服务器发送 REJECTS,并使客户端无限期挂起。
这是来自防火墙两侧的数据包的整个 wireshark 跟踪。192.168.126.161 是客户端的私有 NAT'et IP 地址。172.16.1.2 是 Web 服务器 IP(未显示真实的公共 IP),10.1.1.1 是防火墙外部 IP(未显示真实的公共 IP)
2105 0.086275 192.168.126.161 172.16.1.2 TCP 37854 > http [SYN] Seq=0 Win=5840 Len=0 MSS=1460 SACK_PERM=1 TSV=89375083 TSER=0
2106 0.000066 10.1.1.1 172.16.1.2 TCP 37854 > http [SYN] Seq=0 Win=5840 Len=0 MSS=1460 SACK_PERM=1 TSV=89375083 TSER=0
2107 0.002643 172.16.1.2 10.1.1.1 TCP http > 37854 [SYN, ACK] Seq=0 Ack=1 Win=32768 Len=0 MSS=1460
2108 0.007705 172.16.1.2 192.168.126.161 TCP http > 37854 [SYN, ACK] Seq=0 Ack=1 Win=32768 Len=0 MSS=1460
2109 0.006301 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=1 Ack=1 Win=5840 Len=0
2110 0.000025 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=1 Ack=1 Win=5840 Len=0
2111 0.000007 192.168.126.161 172.16.1.2 HTTP GET /test/style.css HTTP/1.1
2112 0.000015 10.1.1.1 172.16.1.2 HTTP GET /test/style.css HTTP/1.1
2113 0.001536 172.16.1.2 10.1.1.1 TCP http > 37854 [ACK] Seq=1 Ack=111 Win=32658 Len=0
2114 0.000014 172.16.1.2 192.168.126.161 TCP http > 37854 [ACK] Seq=1 Ack=111 Win=32658 Len=0
2115 0.002274 172.16.1.2 10.1.1.1 HTTP HTTP/1.1 200 OK (text/css)
2116 0.000025 172.16.1.2 192.168.126.161 HTTP HTTP/1.1 200 OK (text/css)
2117 0.005689 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=1461 Win=8760 Len=0
2118 0.000024 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=1461 Win=8760 Len=0
2119 0.001536 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2120 0.000026 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2121 0.000007 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2122 0.000023 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2123 0.000313 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2124 0.000030 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2125 0.000007 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2126 0.000023 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2127 0.000009 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2128 0.000023 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2129 0.001108 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2130 0.000035 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2131 0.000008 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
2132 0.000022 172.16.1.2 192.168.126.161 HTTP Continuation or non-HTTP traffic
2133 0.000007 172.16.1.2 10.1.1.1 HTTP Continuation or non-HTTP traffic
REJECT-->
2134 0.000089 10.1.1.1 172.16.1.2 TCP 37854 > http [RST] Seq=111 Win=0 Len=0
CLIENT FIRST ACK-->
2135 0.002421 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=2921 Win=11680 Len=0
2136 0.000033 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=2921 Win=11680 Len=0
2137 0.000007 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=4381 Win=14600 Len=0
2138 0.000014 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=4381 Win=14600 Len=0
2139 0.000008 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=5841 Win=17520 Len=0
2140 0.000014 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=5841 Win=17520 Len=0
2141 0.000007 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=7301 Win=20440 Len=0
2142 0.000013 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=7301 Win=20440 Len=0
2143 0.000007 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=8761 Win=23360 Len=0
2144 0.000015 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=8761 Win=23360 Len=0
2145 0.000007 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=10221 Win=26280 Len=0
2146 0.000013 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=10221 Win=26280 Len=0
2147 0.001059 192.168.126.161 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=11681 Win=29200 Len=0
2148 0.000018 10.1.1.1 172.16.1.2 TCP 37854 > http [ACK] Seq=111 Ack=11681 Win=29200 Len=0
我一直在挖掘和记录数据包遍历图表看起来最后一个传入数据包 2133 经过了 raw-PREROUTING、conntrack、mangle-PREROUTING,但随后丢失了。我的 iptables 中没有 REJECT 规则,我记录了所有 DROP 规则,但没有一个显示数据包 2133 在哪里丢失。
我很想在传入过滤器上使用 TRACE 目标,但不幸的是,ubuntu 8.04 不附带对 TRACE 目标的支持。
因此,我相信某些内部隐式路由/conntrack/mangling 规则适用,出于某种原因会重置连接。也许流量会触发一些 DOS 保护,但我不知道在哪里配置/分析它。最令人沮丧的是数据包被拒绝,什么都没有记录...
另外,从 Windows 主机请求此文件 100% 有效,但在某些 Linux 主机上会失败,并且 99.9% 的请求都会通过,但有时数据包的时间会在我们的防火墙中触发这种行为。
编辑 好的,现在我已经在 iptables 中添加了大量日志,但似乎发生了以下情况(仍然不知道为什么!)
对于成功穿越防火墙的数据包,将采取以下步骤,表格/步骤引用自这里
Table 3-3 step
2 raw-pre
conntrack
3 mangle-pre
4 [nat-pre]
5 routing-decision -> destination forward
6 mangle-fwd
7 filter-fwd
8 mangle-post
9 [nat-post]
被拒绝的数据包 2133 经过以下步骤:
Table 3-1 steps for the incoming FIN,ACK packet 2133
2 raw-pre
conntrack
3 mangle-pre
4 [nat-pre]
5 routing-decision -> destination local
6 mangle-input
7 filter-input
8 local process emits RST -> webserver
Table 3-2 steps for the outgoing RST packet 2134 in response to 2133
1 raw-out
2 routing decision
conntrack
3 mangle-out
reroute-check
4 [nat-out]
5 filter-out
6 mangle-post
7 nat-post
奇怪的是,步骤 5 中数据包 2133 的路由决策现在与其他数据包的路由决策不同。分析有效的请求(例如未卡住的请求)时,甚至最后一个 FIN 也会正确路由。这似乎是内核中的一个错误,或者路由决策在某种程度上是有状态的。
编辑
可能导致这些问题的一个原因是,流量在防火墙和本地 LAN 之间路由,因此客户端 LAN 不通过 L2 直接连接到防火墙。
+---------------------------+ +------------------+ +------------------------+
| | | Router | ( Lab network ) | |
( Internet ) -- + eth1 eth0 +-------+ +-- ( ) -+ Client 192.168.126.161 |
| 10.1.1.1 192.168.60.254 | | | ( 192.168.126.0/24 ) | |
+---------------------------+ +------------------+ +------------------------+
图中10.1.1.1代表防火墙的外部IP地址,其他地址均为实际使用的IP地址。
这是防火墙上的路由表:
Destination Gateway Genmask Flags Metric Ref Use Iface
10.1.1.0 0.0.0.0 255.255.255.240 U 0 0 0 eth1
192.168.126.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.60.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
0.0.0.0 10.1.1.15 0.0.0.0 UG 0 0 0 eth1
请注意,10.1.1.0 和默认网关 10.1.1.15 是虚构的,其余部分与使用的完全相同。我必须手动添加 192.168.126.0/24 路由才能从 eth0(192.168.60.254)到达实验室网络。
以下是有关最后一个数据包 2133 的数据包遍历的详尽日志,该数据包由于被路由到本地主机(例如防火墙)而被拒绝。
[16406874.374588] raw pre IN=eth1 OUT= MAC=00:02:b3:b9:ff:b5:00:90:1a:10:06:88:08:00 SRC=172.16.1.2 DST=10.1.1.1 LEN=1004 TOS=0x00 PREC=0x00 TTL=55 ID=13739 DF PROTO=TCP SPT=80 DPT=53497 WINDOW=5840 RES=0x00 ACK PSH FIN URGP=0
[16406874.374625] mangle pre IN=eth1 OUT= MAC=00:02:b3:b9:ff:b5:00:90:1a:10:06:88:08:00 SRC=172.16.1.2 DST=10.1.1.1 LEN=1004 TOS=0x00 PREC=0x00 TTL=55 ID=13739 DF PROTO=TCP SPT=80 DPT=53497 WINDOW=5840 RES=0x00 ACK PSH FIN URGP=0
[16406874.374667] mangle in IN=eth1 OUT= MAC=00:02:b3:b9:ff:b5:00:90:1a:10:06:88:08:00 SRC=172.16.1.2 DST=10.1.1.1 LEN=1004 TOS=0x00 PREC=0x00 TTL=55 ID=13739 DF PROTO=TCP SPT=80 DPT=53497 WINDOW=5840 RES=0x00 ACK PSH FIN URGP=0
[16406874.374699] filter in IN=eth1 OUT= MAC=00:02:b3:b9:ff:b5:00:90:1a:10:06:88:08:00 SRC=172.16.1.2 DST=10.1.1.1 LEN=1004 TOS=0x00 PREC=0x00 TTL=55 ID=13739 DF PROTO=TCP SPT=80 DPT=53497 WINDOW=5840 RES=0x00 ACK PSH FIN URGP=0
[16406874.374780] mangle out IN= OUT=eth1 SRC=10.1.1.1 DST=172.16.1.2 LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=53497 DPT=80 WINDOW=0 RES=0x00 RST URGP=0
[16406874.374807] mangle post IN= OUT=eth1 SRC=10.1.1.1 DST=172.16.1.2 LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=53497 DPT=80 WINDOW=0 RES=0x00 RST URGP=0
[16406874.378813] mangle pre IN=eth0 OUT= MAC=00:02:b3:b9:ff:b4:00:90:1a:10:0c:dd:08:00 SRC=192.168.126.161 DST=172.16.1.2 LEN=40 TOS=0x00 PREC=0x00 TTL=63 ID=35424 DF PROTO=TCP SPT=53497 DPT=80 WINDOW=11680 RES=0x00 ACK URGP=0
[16406874.378863] mangle fwd IN=eth0 OUT=eth1 SRC=192.168.126.161 DST=172.16.1.2 LEN=40 TOS=0x00 PREC=0x00 TTL=62 ID=35424 DF PROTO=TCP SPT=53497 DPT=80 WINDOW=11680 RES=0x00 ACK URGP=0
再次,我们的 fw 外部 IP 已被替换为 10.1.1.1,NAT 网络之外的 Web 服务器的 IP 已被替换为 172.16.1.2
编辑突发新闻!
好的,最后一次尝试是丢弃 RST 数据包,非常有趣,我添加了一条 iptables 规则,丢弃所有发往我们在请求文件时遇到问题的 Web 服务器的 RST 数据包。然后它工作例如,上面日志中的最后一个 FIN、ACK、PSH 数据包 2133 被丢弃,但是由于 RST 被丢弃,因此 Web 服务器有时间获取所有 ACK,然后决定再次重新传输最后一个数据包(数据包 2133),现在它会穿过防火墙,因为 contrack 模块现在已经看到从客户端返回的 ACK,并允许最后一个 ACK、FIN 数据包和最终有效载荷。
所以这肯定是一个时间/窗口问题,这个特定的文件,与来自客户端的 ACK 的时间一致,触发了 conntrack 中的某些操作,从而拒绝了来自 Web 服务器的最终数据包。
到目前为止,通过谷歌搜索和阅读内核文档没有发现任何可能导致这种行为的原因,下一步将是阅读路由/连接跟踪模块的内核源代码。
问题解决了
好吧,至少现在我们确切地知道发生了什么,并且有解决问题的方法。
谢尔盖 (Sergey) 指出了非常有价值的 -m state --state INVALID 匹配规则,这对调试有很大帮助,我现在意识到,没有针对 INVALID 数据包的明确规则的 iptables 设置是不完整的,因此有时似乎会出现奇怪的行为。
当在 conntrack 模块中启用日志记录以查找导致无效数据包的原因时,发生的情况非常明显,我对此产生了怀疑。
[16659529.322465] nf_ct_tcp: SEQ is over the upper bound (over the window of the receiver) IN= OUT= SRC=172.16.1.2 DST=10.1.1.1 LEN=1004 TOS=0x00 PREC=0x00 TTL=55 ID=40874 DF PROTO=TCP SPT=80 DPT=55498 SEQ=658735108 ACK=1194081763 WINDOW=5840 RES=0x00 ACK PSH FIN URGP=0
再次,172.16.1.2 是外部网络服务器(行为不正确),10.1.1.1 是防火墙的外部地址。
网络服务器通过网络推送的数据比客户端在接收窗口中公布的数据要多(conntrack 是状态满的并验证了这一点),似乎是在 FIN 数据包到达时 conntrack 退出,因为接收窗口实际上早就超出了。
我认为这可能是由于 Web 服务器上的网卡中不正确的 TCP 卸载造成的。当我开始分析这个问题时,我在 Web 服务器上进行了捕获,根据 tcpdump/wireshark 跟踪,内核中的 TCP 层写入了巨型帧,然后由网卡将其分割成 MTU=1500 的较小帧。因此,显然需要在 Web 服务器中解决这个问题,因为发送的数据量超过接收方在其接收窗口中所通告的数据量是不正确的 TCP 行为。
Polynomial 和 Sergey 都提供了宝贵的意见,但是 Sergey 向我指出了 conntrack/NAT 模块关于数据包遍历的确切行为。
答案1
类似情况描述如下http://www.spinics.net/lists/netfilter/msg51408.html:一些本应由 NAT 处理的数据包不知何故被标记为 INVALID 而不是 ESTABLISHED,并进入 INPUT 链。您应该添加一些规则来-m state --state INVALID
检查这种情况,答案位于http://www.spinics.net/lists/netfilter/msg51409.html建议始终丢弃此类无效数据包,因为 NAT 没有正确执行,因此其中的地址可能是错误的。
如果你的问题数据包确实被标记为无效,那么添加iptables -I INPUT -m state --state INVALID -j DROP
likely 可以解决问题(损坏的数据包不会到达本地进程,也不会导致 RST 响应,然后 TCP 将在超时后从丢失的数据包中恢复)。然后你可以尝试进一步调试问题,如中所述http://www.spinics.net/lists/netfilter/msg51411.html:
echo 255 >/proc/sys/net/netfilter/nf_conntrack_log_invalid
(在这种特殊情况下,问题是由路径上的某些网络硬件损坏引起的,可能还与某些 TCP 校验和卸载故障有关。)
答案2
我在其他类型的防火墙上也看到过这种行为,而且它们的行为如此相似,所以我想把它扔掉。
我遇到的问题是防火墙正在通过 NAT 进入与盒子上的临时端口相同的空间。如果两者发生冲突,这将导致这种确切的行为,因为内核现在假设连接是用于本地计算机的。为此,您可以检查几件事。首先,您是否在 iptables 中指定了出站端口配置(使用 --to-ports)?或者您是否修改了计算机上的临时端口范围:
$ cat /proc/sys/net/ipv4/ip_local_port_range
为了进行诊断,您可以设置捕获并查看是否在 RST 之前的 3*MSL 时间内看到使用相同外部 fw ip、端口组合的任何其他请求(我认为约为 180 秒)。
虽然我还不确定这是否就是答案,但如果我处于这种情况,我会首先排除这个可能性,然后再考虑其他一些事情。
这是否容易重现?是否可以从防火墙框捕获更多诊断信息并查看问题是否发生?我会尝试捕获:
$ netstat -anp
$ cat /proc/net/ip_conntrack
每秒左右尝试重现一次,查看是否有某些东西本地绑定到端口,以及问题发生时伪装表是什么样的。
如果您使用防火墙阻止出站 RST,那么来自内部客户端的最终 ACK 是否会导致连接成功?
最后一件事,你看到了所有的日志吗?你已经检查过 dmesg 了吗?你是否在 syslog 配置中的防火墙框上将 *.* 设置为文件以确保安全?
请告诉我你发现了什么!我非常感谢你在问题中提供的大量信息,谢谢。