CDN加速

技术博客 > 正文

SYN Flood对TCP协议栈的影响分析

2022-01-24

一、 概述
上节对DDoS各种类型的攻击进行了阐述,后续我们将结合各种类型报文到达服务器后的源码处理过程,深入分析各种类型报文的Flood流量对服务器的危害。SYN/ACK/UDP/ICMP类的攻击,侧重分析其在TCP协议栈的行为;应用层类的攻击,则侧重其在对应服务器的行为,如CC攻击(HTTP),侧重分析其在Nginx等服务器的行为;DNS攻击,则分析其在bind等服务器的行为。让我们首先从SYN Flood开始。

二、 目标服务器环境

SYN Flood对TCP协议栈的影响分析

后续的TCP协议栈是基于linux5.4源码进行分析

三、 协议栈的SYN 报文处理

  1. 函数调用栈
    通过ftrace,得到SYN报文进入TCP协议栈的函数调用过程及处理时间如下
    | tcp_v4_rcv() {
    | __inet_lookup_established() {
    0.347 us | inet_ehashfn();
    1.067 us | }
    | __inet_lookup_listener() {
    0.733 us | inet_lhash2_lookup();
    | inet_lhash2_lookup() {
    0.265 us | inet_ehashfn();
    0.938 us | reuseport_select_sock();
    2.980 us | }
    4.515 us | }
    | tcp_v4_inbound_md5_hash() {
    0.349 us | tcp_parse_md5sig_option();
    0.950 us | }
    | sk_filter_trim_cap() {
    0.817 us | __cgroup_bpf_run_filter_skb();
    | security_sock_rcv_skb() {
    0.244 us | apparmor_socket_sock_rcv_skb();
    1.255 us | }
    3.253 us | }
    0.263 us | tcp_v4_fill_cb();
    | tcp_v4_do_rcv() {
    | tcp_rcv_state_process() {
    | tcp_v4_conn_request() {
    | tcp_conn_request() {
    | inet_reqsk_alloc() {
    | kmem_cache_alloc() {
    0.240 us | should_failslab();
    0.260 us | memcg_kmem_get_cache();
    0.491 us | memcg_kmem_put_cache();
    2.876 us | }
    3.489 us | }
    0.498 us | tcp_parse_options();
    0.263 us | tcp_v4_init_req();
    0.339 us | security_inet_conn_request();
    | tcp_v4_init_ts_off() {
    0.788 us | secure_tcp_ts_off();
    2.402 us | }
    | tcp_v4_route_req() {
    | inet_csk_route_req() {
    0.240 us | security_req_classify_flow();
    | ip_route_output_flow() {
    | ip_route_output_key_hash() {
    | ip_route_output_key_hash_rcu() {
    | __ip_dev_find() {
    0.484 us | inet_lookup_ifaddr_rcu();
    1.042 us | }
    1.016 us | fib_table_lookup();
    0.256 us | fib_select_path();
    0.247 us | find_exception();
    5.207 us | }
    5.724 us | }
    | xfrm_lookup_route() {
    0.417 us | xfrm_lookup_with_ifid();
    1.008 us | }
    7.700 us | }
    8.955 us | }
    9.535 us | }
    | tcp_v4_init_seq() {
    | secure_tcp_seq() {
    0.368 us | ktime_get_with_offset();
    1.107 us | }
    1.682 us | }
    | tcp_openreq_init_rwin() {
    | ipv4_default_advmss() {
    0.515 us | ipv4_mtu();
    1.141 us | }
    0.356 us | __cgroup_bpf_run_filter_sock_ops();
    0.287 us | tcp_select_initial_window();
    3.273 us | }
    0.323 us | tcp_reqsk_record_syn();
    0.371 us | tcp_try_fastopen();
    0.300 us | __cgroup_bpf_run_filter_sock_ops();
    | inet_csk_reqsk_queue_hash_add() {
    0.244 us | init_timer_key();
    | mod_timer() {
    | lock_timer_base() {
    0.343 us | _raw_spin_lock_irqsave();
    0.870 us | }
    0.235 us | detach_if_pending();
    | __internal_add_timer() {
    0.253 us | calc_wheel_index();
    0.314 us | enqueue_timer();
    1.376 us | }
    0.251 us | trigger_dyntick_cpu.isra.0();
    0.242 us | _raw_spin_unlock_irqrestore();
    4.688 us | }
    | inet_ehash_insert() {
    0.262 us | inet_ehashfn();
    0.455 us | _raw_spin_lock();
    1.611 us | }
    7.768 us | }
    | tcp_v4_send_synack() {
    | tcp_make_synack() {
    | __alloc_skb() {
    | kmem_cache_alloc_node() {
    0.242 us | should_failslab();
    0.368 us | memcg_kmem_put_cache();
    1.984 us | }
    | __kmalloc_reserve.isra.0() {
    | __kmalloc_node_track_caller() {
    0.258 us | kmalloc_slab();
    0.239 us | should_failslab();
    0.290 us | memcg_kmem_put_cache();
    2.707 us | }
    3.278 us | }
    | ksize() {
    0.760 us | __ksize();
    1.255 us | }
    7.658 us | }
    0.270 us | skb_set_owner_w();
    | ipv4_default_advmss() {
    0.339 us | ipv4_mtu();
    0.796 us | }
    0.219 us | ktime_get();
    0.196 us | tcp_v4_md5_lookup();
    0.590 us | skb_push();
    0.344 us | tcp_options_write();
    + 12.570 us | }
    0.229 us | __tcp_v4_send_check();
    | ip_build_and_send_pkt() {
    0.240 us | skb_push();
    | ip_local_out() {
    | __ip_local_out() {
    0.234 us | ip_send_check();
    0.824 us | }
    | ip_output() {
    | ip_finish_output() {
    0.289 us | __cgroup_bpf_run_filter_skb();
    | __ip_finish_output() {
    0.260 us | ipv4_mtu();
    | ip_finish_output2() {
    | dev_queue_xmit() {
    | __dev_queue_xmit() {
    0.334 us | dst_release();
    0.322 us | netdev_core_pick_tx();
    0.242 us | _raw_spin_lock();
    | sch_direct_xmit() {
    | validate_xmit_skb_list() {
    | validate_xmit_skb() {
    | netif_skb_features() {
    0.249 us | skb_network_protocol();
    1.077 us | }
    0.250 us | skb_csum_hwoffload_help();
    0.243 us | validate_xmit_xfrm();
    3.001 us | }
    3.554 us | }
    0.244 us | _raw_spin_lock();
    | dev_hard_start_xmit() {
    | e1000_xmit_frame e1000 {
    0.257 us | e1000_maybe_stop_tx e1000;
    | e1000_tx_map e1000 {
    0.365 us | dma_direct_map_page();
    1.060 us | }
    0.303 us | skb_clone_tx_timestamp();
    0.241 us | e1000_maybe_stop_tx e1000;
    + 25.036 us | }
    + 25.973 us | }
    0.257 us | _raw_spin_lock();
    + 31.443 us | }
    | __qdisc_run() {
    0.300 us | fq_codel_dequeue sch_fq_codel;
    1.535 us | }
    0.246 us | __local_bh_enable_ip();
    + 37.852 us | }
    + 38.431 us | }
    0.242 us | __local_bh_enable_ip();
    + 40.742 us | }
    + 41.934 us | }
    + 43.050 us | }
    + 43.668 us | }
    + 45.433 us | }
    + 46.662 us | }
    + 60.682 us | }
    + 96.262 us | }
    + 96.863 us | }
    0.302 us | __local_bh_enable_ip();
    | consume_skb() {
    | skb_release_all() {
    0.284 us | skb_release_head_state();
    | skb_release_data() {
    | skb_free_head() {
    0.512 us | kfree();
    1.178 us | }
    1.862 us | }
    3.113 us | }
    | kfree_skbmem() {
    1.015 us | kmem_cache_free();
    1.773 us | }
    5.743 us | }
    ! 104.297 us | }
    ! 105.184 us | }
    ! 118.677 us | }
    关键函数的分析,链接里的文章分析得比较清楚,感兴趣的读者可以参考,这里就不再赘述。
    根据图表可以看到,tcp_v4_rcv的处理时间大概在118微秒,那么内核1s可以处理的SYN报文为10^6 / 118 = 8474。仅从处理时间上来看,我们如果对目标服务器每秒发送超过8474个SYN报文,TCP协议栈就要开始丢包了。
  2. 资源分配
    SYN报文涉及到的资源分配主要包含
    a. 存储SYN报文信息的struct sk_buff
    b. 发送SYN/ACK报文信息的struct sk_buff
    c. 存储请求信息的struct request_sock
    d. 创建重传定时器
    其中存储SYN报文信息的sk_buff和存储SYN/ACK报文信息的sk_buff, 在tcp_v4_rcv处理完成后,就都释放了。存储请求信息的request_sock, 结局有两个:一是在完成三次握手后,挂到更大的结构体struct sock下,二是连接超时后释放(tcp连接超时重传时,需要该结构体)。而定时器则会在超时后,重新发送SYN/ACK报文,白白浪费设备性能。 struct request_sock结构体的大小为224字节,而申请该结构体时是从request_sock_TCP slab中申请,该slab中的元素结构体大小为304字节。1G内存就可以支持300万的结构体分配,所以对当前服务器配置来讲, 当SYN Flood到达服务器后,消耗的主要是CPU资源,而不是内存资源。 当然,SYN Flood最大可能是直接把带宽堵死,服务器连处理正常SYN请求的机会都不会有,由于这里主要探讨其对TCP协议栈的影响,所以没有展开。

四、 协议栈的SYN Flood防护
TCP协议栈本身有针对SYN Flood的SYN Cookie算法,启用SYN Cookie算法
后,多了以下函数调用生成序列号
0) | cookie_v4_init_sequence() {
0) | __cookie_v4_init_sequence() {
0) 0.226 us | cookie_hash();
0) 0.196 us | cookie_hash();
0) 0.898 us | }
0) 1.256 us | }

该算法的核心思想为:

  1. 设T是随时间增长的计数器(每分钟增加一次)
  2. 设M为客户端发送的SYN/ACK报文中的MSS选项值
  3. 设S是连接的四元组和T经过Hash的值,即S = Hash(sip, dip, sport, dport, T),这就是服务器回复的SYN/ACK的序列号(序列号里还存储有MSS信息,这里不再展开)
  4. 发送完成后,不再需要定时器和存储请求信息的struct request_sock
  5. 当ACK报文到达后,通过对ACK 序列号的校验,确认连接是否建立成功。
    这样,协议栈的防护方案,在资源层面,节约了struct request_sock和 timer资源,但整个报文的处理时间却无法提升。该方法也只能用于防护虚假源IP的SYN Flood防护,对真实连接的SYN Flood攻击,却无能为力。
    五、 网宿的SYN Flood防护
    可以看到,协议栈本身的SYN Flood防护较弱,当碰到大流量SYN Flood攻击时,协议栈早早的就开始丢包,甚至罢工了事。网宿的SYN Flood防护,将报文直接从网卡接收到用户态,单SYN报文的处理时间可以达到0.201微秒,且不需要存储任何临时信息,无论是处理性能,还是资源占用,都有质的飞跃。对真实连接的SYN Flood攻击,也有多种防护方案,如分布式防护,代理连接,源IP新建连接限制,协议分析等。
    六、 参考链接
    1.https://blog.csdn.net/wangquan1992/article/details/108903813
    2.https://blog.csdn.net/wangquan1992/article/details/108908194
    3.https://wangquan.blog.csdn.net/article/details/108914464
    4.https://segmentfault.com/a/1190000019292140