部分网络内核参数说明

下面是我的理解,可能有误,仅供参考。

要调优,三次/四次握手必须烂熟于心。

client                  server
(SYN_SENT)      —>  (SYN_RECV)
(ESTABLISHED)   <—     
                —>  (ESTABLISHED)

client(主动)            server
(FIN_WAIT_1)    —>    (CLOSE_WAIT)
(FIN_WAIT_2)    <—    
(TIME_WAIT)     <—    (LAST_ACK)
                —>    (CLOSED)

大家熟知的 SYN flooding/SYN spoofing 就是在 SYN_RECV 的状态下发起的进攻。这种由于 TCP/IP 协议引起的缺陷只能防治而不好根治,除非换了 TCP/IP。通过下面的方式,可以在一定程度上缓解 DDOS 攻击。

  • 增大半连接的队列,即 backlog queue
  • 人工干预以减少 SYS_RECV 的时间,可以降低第一个重传包的时间或者减少重传的次数

检测 SYN 攻击,可以使用 netstat 命令查看当前的连接类型以及连接数目,如果发现有大量的 SYN_RECV,就值得怀疑了:
$ netstat -tuna | grep :80 | awk '{print $6}' | sort | uniq -c

或者可以通过 wget/curl 从远端实际来测试一下访问的速度:
$ time wget -O /dev/null www.example.com
正常情况下,其 real time 在个位数(s)左右,如果出现长达数十秒乃至几百秒的情况,有可能是此类情况。

最简单的方式是通过 syncookie 实现,Linux 还实现了一种称作 SYN cookie 的机制,开启:
# echo 1 > /proc/sys/net/ipv4/tcp_syncookies

该机制会在服务器收到 SYN 请求后,构造一个带有 ISN(initial sequence number)的 SYN/ACK 包,该 ISN 称为 cookie,其实就是一个哈希。通过此就可以验证客户端的真实性了
注意:SYN cookie 机制不会使用到 backlog queue,因此不必担心 queue 被填满然后服务器主动放弃连接。

使用了 SYN cookie 之后,在 /var/log/kern.log 会发现不少如下的 log,起作用了 ;-)
possible SYN flooding on port 80. Sending cookies

除了使用 syncookie,还可以修改 backlog queue 来达到目的。backlog queue 是一个用来处理在三次握手过程中带有 SYN 标志的包的数据结构,可以用来控制系统同时处理的最大连接,当达到该阈值后,接下来的请求会被系统丢弃。这需要系统开辟额外的内存来处理进来的包。如果处理的不好会导致系统内存耗尽,导致严重的性能问题。

tcp_max_syn_backlog 定义了 backlog queue 的半连接数量:
# echo 90000 > /proc/sys/net/ipv4/tcp_max_syn_backlog

当客户端发起 SYN 请求后,服务端会立刻发送 SYN+ACK 的回应,该次半连接会到 backlog queue 中,服务器会等待客户返回 ACK,如果在一段时间内没有应答,服务器会重新发送刚刚的 SYN+ACK,经历了几次还是没有回应后,服务器会主动断开此次半连接。
我们就可以修改重发的次数来减少整个半连接的时间:
# echo 3 > /proc/sys/net/ipv4/tcp_synack_retries

——————————————————————————-
|Value|    Time of retransmission      |         Total time         |
——————————————————————————-
|1       | in 3rd second                          |            9 seconds      |
——————————————————————————-
|2       | in 3rd and 9th second            |            21 seconds   |
——————————————————————————-
|3       | in 3rd, 9th and 21st second  |            45 seconds    |
——————————————————————————-
这张表格显示了不同重传次数消耗的总时间

上面属于 passive 连接,也就是客户端连接服务端,还有个相反的 active TCP connection 参数:
# echo 3 > /proc/sys/net/ipv4/tcp_syn_retries

tcp_fin_timeout 参数会通知 kernel 在 FIN_WAIT_2 状态 sockets 的存活时间,根据理解应该是 server 主动终止,像下面这样。

server                        client
(FIN_WAIT_1)    —>    (CLOSE_WAIT)
(FIN_WAIT_2)    <—    
(TIME_WAIT)     <—    (LAST_ACK)
                —>    (CLOSED)

当处于 CLOST_WAIT 的 client 有意(攻击)/无意(client 突然崩溃等)不发 fin 来继续时,server 会一直停留在 FIN_WAIT_2 状态,造成资源的浪费。
可以适当的减小该时间:
# echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout

跟 tcp_fin_timeout 相关的有 tcp_max_orphans 参数,表示没有跟任何用户文件相关联的 socket 最大个数,超出的将被内核丢弃。
建议该参数只增加不减小,但增加也意味着内存的消耗增加:
# echo 327680 > /proc/sys/net/ipv4/tcp_max_orphans

相关的 tcp_orphans_retries 关闭本端 TCP 连接前的重试次数,默认 7,高负载的 webserver 建议可以减小。这里解释了设置为 0 的情况。

下面这三个参数一起解释:
tcp_tw_recycle
tcp_tw_reuse
tcp_timestamps

其中 tcp_tw_recycle/tcp_tw_reuse 这两个官方的建议保持默认为 0,而 tcp_timestamps 这个参数在特定的情况开启会引起很严重的问题(via 1, 2)。

基本的情况就是,你的客户或者你的服务器在一个 NAT 后面,如果开启这个参数,会导致服务器能收到三次握手的 SYN 但是不会返回任何的 SYN+ACK,其结果是客户无法访问你的网站。可以通过 tcpdump 或者下面的这个查看:
# netstat -s | grep timestamp

tcp_timestamps 是 tcp 协议中的一个扩展项,通过时间戳的方式来检测过来的包以防止 PAWS(Protect Against Wrapped  Sequence numbers),可以提高 tcp 的性能,2.6 的内核默认是打开的。只要 client/server/nat/loadbalancer 不同时打开该选项就不会出现上面的问题。与之相关的包括 tcp_tw_recycle,如果 tcp_timestamps 和 tcp_tw_recycle 同时开启,就会开启时间戳选项,导致上面的问题。如果有上述的网络结构,比较合理的方式是禁用 tcp_tw_recyle 而开启 tcp_timestamps。禁用了 tcp_tw_recycle 其 TIME_OUT sockets 回收功能就没了,可以配合 tcp_tw_reuse 让 TIME_WAIT 降下来。

netdev_max_backlog 这个参数跟 TCP 的传输队列有关,发送队列长度是 txqueuelen ,netdev_backlog 则决定接收队列的长度。
前者通过 ifconfig 命令改变:
# ifconfig eth0 txqueuelen 10000
对于高吞吐的网络而言,默认的 100 肯定是不够的,一个 rrt 为 120ms 的千兆以太网络,可以设置成 10000 以上的值。

对于接受端而言,需要修改的话就涉及到了 /proc/sys/net/core/netdev_max_backlog 了,如果接收包的速度大于内核能处理的速度,则需要队列来维持,此数值表示最大队列值。默认为 1000,如果超过该数值,会引起丢包,根据实际情况增大。

对于网络不是很好的情况,可以开启 tcp_sack 参数。该实现是 TCP 的一个选项,称为 Selective Acknowlegement(SACK),默认开启,对于千兆网络可以关闭,能提高一定的性能。

keepalive 的情况,Linux 内置支持,只需要开启相应的内核参数就可以了,主要是下面三个:
tcp_keepalive_time 表示 TCP 发出第一个 keepalive 信息之前等待的时间,默认为 7200
tcp_keepalive_intvl keepalive 的时间间隔,默认是 75
tcp_keepalive_probes 触发的次数,默认是 9

ip_local_port_range 128M 内存以上的机器默认是 32768 61000,可以进一步扩大 10240 65535,尽量不要使用 1024 周围的,避免冲突。

以上只是网络内核参数的一小小部分,有待继续补充。

ref:

http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html
http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
http://www.symantec.com/connect/articles/hardening-tcpip-stack-syn-attacks
http://www.saview.net/archives/201