環境:centos7.4 內核版本3.10
內核參數net.ipv4.tcp_max_syn_backlog定義了處於SYN_RECV的TCP最大連接數,當處於SYN_RECV狀態的TCP連接數超過tcp_max_syn_backlog后,會丟棄后續的SYN報文。
為了測試上述結論,首先將tcp_syncookies設置為0,並將net.ipv4.tcp_max_syn_backlog設置為2,測試拓撲為:1.1.1.1(client)------1.1.1.2:19090(server),在client端添加如下iptables規則,在發送完SYN報文后,底層丟棄接收到的SYN/ACK報文
iptables -t filter -I INPUT -p tcp -m tcp --sport 19090 --tcp-flag SYN,ACK SYN,ACK -j DROP
但在實際測試中發現處於SYN_RECV狀態的連接數可以大於設置的值2,且如果此時觸發新的連接,該連接也能正常建鏈。難道tcp_max_syn_backlog沒有生效?通過查找文檔,發現在這篇文章中給出了原因。在內核net/core/request_sock.c中的實現如下,紅色字體代碼給出了計算tcp_max_syn_backlog的最小值。sysctl_max_syn_backlog的值對應手動設置的net.ipv4.tcp_max_syn_backlog的值。
int reqsk_queue_alloc(struct request_sock_queue *queue, unsigned int nr_table_entries) { size_t lopt_size = sizeof(struct listen_sock); struct listen_sock *lopt; nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog); nr_table_entries = max_t(u32, nr_table_entries, 8); nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); lopt_size += nr_table_entries * sizeof(struct request_sock *); if (lopt_size > PAGE_SIZE) lopt = vzalloc(lopt_size); else lopt = kzalloc(lopt_size, GFP_KERNEL); if (lopt == NULL) return -ENOMEM; for (lopt->max_qlen_log = 3; (1 << lopt->max_qlen_log) < nr_table_entries; lopt->max_qlen_log++); get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd)); rwlock_init(&queue->syn_wait_lock); queue->rskq_accept_head = NULL; lopt->nr_table_entries = nr_table_entries; write_lock_bh(&queue->syn_wait_lock); queue->listen_opt = lopt; write_unlock_bh(&queue->syn_wait_lock); return 0; }
可以看到當sysctl_max_syn_backlog=2時,計算過程如下:
-
- nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);nr_table_entries為listen backlog的值,即系統net.core.somaxconn的值,默認128。此處獲取nr_table_entries和sysctl_max_syn_backlog的最小值,得出nr_table_entries=2
- nr_table_entries = max_t(u32, nr_table_entries, 8);計算nr_table_entries和8的最大值,此時得出nr_table_entries=8
- nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);計算(1UL << (ilog2((9) - 1) + 1)),即1<<3=16。這就是net.ipv4.tcp_max_syn_backlog的最小值
使用如下腳本模擬syn flood攻擊,當 watch 'netstat -antp|grep SYN_RECV|wc -l' 等於16時,換一台機器連接server發現連接超時;設置tcp_syncookies=1,重復上面測試,當 watch 'netstat -antp|grep SYN_RECV|wc -l' 等於16時,換一台機器連接server發現此時連接成功。
#!/bin/sh initPort=10000 for ((i=1; i<=200; i ++)) do initPort=$[initPort+1] sendip -v -p ipv4 -is 1.1.1.1 -p tcp -ts $initPort -td 19090 -tfs -tots 1.1.1.2 sleep 0.5 done