转 【转载】 SYN cookies 机制下连接的建立

简介:

      在正常情况下,服务器端接收到客户端发送的 SYN 包,会分配一个连接请求块(即 request_sock 结构),用于保存连接请求信息,并且发送 SYN+ACK 包给客户端,然后将连接请求块添加到半连接队列中。客户端接收到 SYN+ACK 包后,会发送 ACK包 对服务器端的包进行确认。服务器端收到客户端的确认后,根据保存的连接信息,构建一个新的连接,放到监听套接字的连接队列中,等待用户层 accept 连接。这是正常的情况,但是在并发过高或者遭受 SYN flood 攻击的情况下,半连接队列的槽位数量很快就会耗尽,会导致丢弃新的连接请求,SYN cookies 技术可以使服务器在半连接队列已满的情况下仍能处理新的 SYN 请求  
      如果开启了 SYN cookies 选项,在半连接队列满时,SYN cookies 并不丢弃 SYN 请求,而是将源目的 IP、源目的端口号、接收到的客户端初始序列号以及其他一些安全数值等信息进行 hash 运算,并加密后得到服务器端的初始序列号,称之为 cookie 。服务器端在发送初始序列号为 cookie 的 SYN+ACK 包后,会将分配的连接请求块释放。如果接收到客户端的 ACK 包,服务器端将客户端的 ACK 序列号减 1 得到的值,与上述要素 hash 运算得到的值比较,如果相等,直接完成三次握手,构建新的连接。SYN cookies 机制的核心就是避免攻击造成的大量构造无用的连接请求块,导致内存耗尽,而无法处理正常的连接请求  
      启用 SYN cookies 是通过在启动环境中设置以下命令完成:  
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
      注意,开启该机制并不意味着所有的连接都是使用 SYN cookies 机制来完成连接的建立只有在半连接队列已满情况下才会触发 SYN cookies 机制。由于 SYN cookies 机制严重违背 TCP 协议不允许使用TCP 扩展,可能对某些服务造成严重的性能影响(如 SMTP 转发),对于防御 SYN flood 攻击的确有效。对于没有受到攻击的高负载服务器,不要开启此选项,可以通过修改 tcp_max_syn_backlog 、tcp_synack_retries 和tcp_abort_on_overflow 系统参数来调节。  
      下面来看看内核中是怎么通过 SYN cookie 机制来完成连接的建立。  
      客户端的连接请求由 tcp_v4_conn_request() 函数处理。tcp_v4_conn_request() 中有一个局部变量 want_cookie,用来标识是否使用 SYN cookies 机制。want_cookie 的初始值为 0,如果半连接队列已满,并且开启了 tcp_syncookies 系统参数,则将其值设置为 1,如下所示:  
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
#ifdef CONFIG_SYN_COOKIES
    int want_cookie = 0;
#else
#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
#endif
 
...... 
 
    /* TW buckets are converted to open requests without
     * limitations, they conserve resources and peer is
     * evidently real one.
     */
      if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
             if (sysctl_tcp_syncookies) {
            want_cookie = 1;
        } else
#endif
       
        goto drop;
    }
 
......
 
drop:
    return 0;
}
      如果没有开启 SYN cookies 机制,在半连接队列满时,会跳转到 drop 处,返回 0 。在调用 tcp_v4_conn_request() 的 tcp_rcv_state_process() 中会直接释放 SKB 包。  
      我们前面提到过,造成半连接队列满有两种情况(不考虑半连接队列很小的情况),一种是负载过高,正常的连接数过多;另一种是 SYN flood 攻击。如果是第一种情况,此时是否继续构建连接,则要取决于连接队列的情况及半连接队列的重传情况,如下所示:  
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
        goto drop;
      sk_acceptq_is_full() 函数很好理解,根据字面意思就可以看出,该函数是检查连接队列是否已满。inet_csk_reqsk_queue_young() 函数返回的是半连接队列中未重传过 SYN+ACK 段的连接请求块数量。如果连接队列已满并且半连接队列中的连接请求块中未重传的数量大于 1,则会跳转到 drop 处,丢弃 SYN 包。如果半连接队列中未重传的请求块数量大于 1,则表示未来可能有 2 个完成的连接,这些新完成的连接要放到连接队列中,但此时连接队列已满。如果在接收到三次握手中最后的 ACK 后连接队列中没有空闲的位置,会忽略接收到的 ACK 包,连接建立会推迟,所以此时最好丢掉部分新的连接请求,空出资源以完成正在进行的连接建立过程。还要注意,这个判断并没有考虑半连接队列是否已满的问题。从这里可以看出,即使开启了 SYN cookies 机制并不意味着一定可以完成连接的建立  
      如果可以继续连接的建立,调用 inet_reqsk_alloc() 分配连接请求块,如下所示:  
req = inet_reqsk_alloc(&tcp_request_sock_ops);
    if (!req)
        goto drop;
      看到这里可能就有人疑惑,既然开启了 SYN cookies 机制,仍然分配连接请求块,那和正常的连接构建也没有什么区别了。这里之所以要分配连接请求块是用于发送 SYN+ACK 包给客户端,发送后会释放掉,并不会加入到半连接队列中。  
      接下来就是计算 cookie 的值,由 cookie_v4_init_sequence() 函数完成,如下所示:  
if (want_cookie) {
#ifdef CONFIG_SYN_COOKIES
        syn_flood_warning(skb);
        req->cookie_ts = tmp_opt.tstamp_ok;
#endif
        isn = cookie_v4_init_sequence(sk, skb, &req->mss);
    }
      计算得到的 cookie 值会保存在连接请求块 tcp_request_sock 结构的 snt_isn 成员中,接着会调用 __tcp_v4_send_synack() 函数发送 SYN+ACK 包,然后释放前面分配的连接请求块,如下所示:  
if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
        goto drop_and_free;
      在服务器端发送完 SYN+ACK 包后,我们看到在服务器端没有保存任何关于这个未完成连接的信息,所以在接收到客户端的 ACK 包后,只能根据前面发送的 SYN+ACK 包中的 cookie 值来决定是否继续构建连接。  
      我们接下来看接收到 ACK 包后的处理情况。ACK 包在 tcp_v4_do_rcv() 函数中调用的 tcp_v4_hnd_req() 中处理,如下所示:  
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
    ......
 
#ifdef CONFIG_SYN_COOKIES
    if (!th->rst && !th->syn && th->ack)
        sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
    return sk;
}
      由于在服务器端没有保存未完成连接的信息,所以在半连接队列或 ehash 散列表中都不会找到对应的 sock 结构。如果开启了 SYN cookies 机制,则会检查接收到的数据包是否是 ACK 包,如果是,在cookie_v4_check() 中会调用 cookie_check() 函数检查 ACK 包中的 cookie 值是否有效。如果有效,则会分配 request_sock 结构,并根据 ACK 包初始化相应的成员,开始构建描述连接的 sock 结构。创建过程和正常的连接创建过程一样。  

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
安全 搜索推荐 网络架构
什么是内网和外网?什么是内网IP和外网IP?本地连接和宽带连接又有什么区别?
何为内网外网迷糊?究竟什么是内网?什么是外网?他们又有和区别?还有什么是内网IP和外网IP?本地连接和宽带连接有什么区别?怂怂今天就来给大家科普一下吧:
15914 0
|
存储 网络协议 网络安全
Hyper-V Win10虚拟机配置常见问题
在配置Hyper-V Win10虚拟机时,用户常面临网络连接、虚拟交换机配置、资源分配及其他问题。例如,虚拟机无法获取IP地址可能源于DHCP服务异常,需检查并启动该服务;外部虚拟交换机配置错误则需确保物理网络适配器正确连接。此外,内存不足或虚拟硬盘性能瓶颈也会影响运行效果。通过合理调整资源配置、优化设置及遵循最佳实践,可有效解决这些问题。
1068 20
|
人工智能 IDE Devops
通义灵码编程智能体,上线!
近期,Qwen3正式发布并开源8款“混合推理模型”,参数量235B,激活仅需22B,性能超越多个国际顶尖模型。通义灵码全面支持Qwen3,上线编程智能体,具备工程级变更、自动感知、工具使用和终端命令执行四大能力。插件集成魔搭MCP广场2400+服务,大幅提升开发效率。目前,通义灵码插件下载超1500万,生成代码超30亿行,服务上万家企业。
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
1349 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
应用服务中间件 nginx 开发者
从 Docker Hub 拉取镜像受阻?这些解决方案帮你轻松应对
最近一段时间 Docker 镜像一直是 Pull 不下来的状态,感觉除了挂🪜,想直连 Docker Hub 是几乎不可能的。更糟糕的是,很多原本可靠的国内镜像站,例如一些大厂和高校运营的,也陆续关停了,这对我们这些个人开发者和中小企业来说是挺难受的。之前,通过这些镜像站,我们可以快速、方便地获取所需的 Docker 镜像,现在这条路也不行了。感觉这次动作不小,以后想直接访问 Docker Hub 是不可能了。所以我们得想办法搭建自己的私有镜像仓库。
从 Docker Hub 拉取镜像受阻?这些解决方案帮你轻松应对
|
监控 安全 测试技术
选择Postman免费版还是付费版,进行 API 测试呢?
深入了解 Postman 免费版和付费版的细节,看看哪一个更适合您的 API 需求。
|
存储 消息中间件 运维
高可用架构和系统设计思想
本文从研发规范层面、应用服务层面、存储层面、产品层面、运维部署层面、异常应急层面这六大层面去剖析一个高可用的系统需要有哪些关键的设计和考虑
|
安全 应用服务中间件 API
微服务架构下的API网关设计与实现
【6月更文挑战第16天】本文将深入探讨在微服务架构中,如何设计和实现一个高效的API网关。我们将从API网关的基本概念入手,然后详细解析其设计原则和实现方法,最后通过一个实例来具体展示API网关的实现过程。
|
数据采集 前端开发 开发者
Selenium中如何实现翻页功能
在使用Python的Selenium库进行网页爬虫开发时,翻页操作是常见需求。本文详细介绍如何通过Selenium实现翻页,包括定位翻页控件、执行翻页动作以及等待页面加载等关键步骤,并提供了基于“下一页”按钮和输入页码两种方式的具体示例代码。此外,还特别提醒开发者注意页面加载完全、动态内容加载及反爬机制等问题,确保爬虫稳定高效运行。
1838 3
|
存储 NoSQL 关系型数据库
面试官:别告诉我你管这个叫高可用
大家好。今天分享一篇写得很透彻的关于高可用的理解。以下是正文: 今天我们来聊一下互联网三高(高并发、高性能、高可用)中的高可用,看完本文相信能解开你关于高可用设计的大部分困惑

热门文章

最新文章