Linux内核4.4版本带来的网络新特性

简介:

本文题目有点大,但其实我只想描述一些我个人一直比较关注的特性,并且不会太详细,跟往常一样,主要是帮忙理清思路的,不会分析源码。这主要是为了哪一天突然忘了的时候,一目十行扫一眼就能记忆当时的理解,不然写的太细节了,自己都看不懂了。

Lockless TCP listener

先 从TCP的syncookie说起,如果都能使用syncookie机制该有多好,但是不能,因为它会丢失很多选项协商信息,这些信息对TCP的性能至关 重要。TCP的syncookie主要是为了防止半连接的syn flood攻击,超级多的节点发送大量的syn包,然后就不管了,而被攻击的协议栈收到一个syn就会建立一个request,绑定在syn针对的 Listener的request队列上。这会消耗很大的内存。
       但是仔细想想,抛开选项协商不说,仅仅针对TCP的syn,synack而言,事实上TCP在3次握手过程,只需要查找一下Listener即可,只要它 存在,就可以直接根据syn包构造synack包了,根本就不用Listener了,要记住2次握手包的信息,有两个办法,第一个办法就是 syncookie机制给encode并echo回去,等第3次握手ack来了之后,TCP会decode这个ack的序列号信息,构造子socket, 插入Listener的accept队列,还有一种办法就是在本地分配内存,记录这个连接客户端的信息,等第3次握手包ack到来之后,找到这个 request,构造子socket,插入Listener的accept队列。
       在4.4之前,一个request是属于一个Listener的,也就是说一个Listener有一个request队列,每构造一个request,都 要操作这个Listner本身,但是4.4内核给出了突破性的方法,就是基于这个request构造一个新的socket!插入到全局的socket哈希 表中,这个socket仅仅记录一个它的Listener的轻引用即可。等到第3个握手包ack到来后,查询socket哈希表,找到的将不再是 Listnener本身,而是syn包到来时构造的那个新socket了,这样传统的下面的逻辑就可以将Listener解放出了:
传统的TCP协议栈接收

sk = lookup(skb);
lock_sk(sk);
if (sk is Listener); then
    process_handshake(sk, skb);
else
    process_data(skb);
endif
unlock_sk(sk);

可以看出,sk的lock期间,将是一个瓶颈,所有的握手逻辑将全部在lock期间处理。4.4内核改变了这一切,下面是新的逻辑:

sk = lookup_form_global(skb);
if (sk is Listener); then
    rv = process_syn(skb);
    new_sk = build_synack_sk(skb, rv);
    new_sk.listener = sk;
    new_sk.state = SYNRECV;
    insert_sk_into_global(sk);
    send_synack(skb);
    goto done;
else if (sk.state == SYNRECV); then
    listener = sk.lister;
    child_sk = build_child_sk(skb, sk);
    remove_sk_from_global(sk);
    add_sk_into_acceptq(listener, child_sk);
fi
lock_sk(sk);
process_data(skb);
unlock_sk(sk);
done:


这个逻辑中,只需要细粒度lock具体的队列就可以了,不需要lock整个socket了。对于syncookie逻辑更简单,根本连SYNRECV socket都不用构造,只要保证有Listener即可!
       这是周四早上蹲厕所的时候猛然看到的4.4新特性,当时就震惊了,这正是我在2014年偶然想到的,但是后来由于没有环境就没有跟进,如今已经并在 mainline了,不得不说这是一件好事。当时我的想法是依照一个syn包完全可以无视Listner而构造synack,需要协商的信息可以保存在别 的地方而不必非要和Listner绑定,这样可以解放Listener的职责。但是我没有想到再构造一个socket,与所有socket平行插入到同一 个socket哈希表中。
       我觉得,4.4之前的逻辑是简单明了的,不管是握手包和数据包,处理逻辑完全一致,但是4.4将代码复杂化了,分离了那么多的if-else...但是这 是不可避免的。事实上,syn构造的request本身就应该与Listener进行绑定,只是如果想到优化,代码会变得复杂,但是如果在代码本身下一番 功夫,代码也会很好看,只是,我没有那个能力,我代码写的不好。
       这个Lockless的思想跟nf_conntrack的思想类似,但是我觉得conntrack对于related conn逻辑也可以这么玩。

TCP listener的CPU亲和力与REUSEPORT

紧随着Lockless TCP Listener而来的accept队列的优化!众所周知,一个Listener只有一个accept队列,在多核环境下这个单一的队列绝对是个瓶颈,一个高性能服务器怎么可以忍受这样!
       其实这个问题早就被REUSEPORT解决了。REUSEPORT允许多个独立的socket同时侦听同一个IP/Port对,这对于当今的多队列网卡, 多CPU环境绝对是个福音。但是,虽然路宽了,车道多了,没有规则的话,性能反而下降,拥挤程度反而降级!
       4.4内核为socket引入了一个SO_INCOMING_CPU选项,如果一个socket的该选项设置为n,意味着只有在n号cpu上处理协议栈逻 辑的执行流才可以将数据包插入这个socket。体现在代码上,就是在compute_score上给与加分,也就是说,除了目标IP,目标端口,源 IP,源端口之外,cpu也成了一个匹配项目。
       正如patch说明说的,此特性与REUSEPORT,多队列网卡相结合,一定是一道美味佳肴!

新的基于流的多路径路由选路

以 前的时候,有路由cache,一个路由cache项就是一个带有源信息的n元组信息,每一个数据包在匹配到FIB条目后都会建立一条cache项,后续的 查找首先去查找cache,因此都是基于流的。然而在路由cache下课后,多路径选路变成了基于包的,这对于TCP这种协议而言肯定会造成乱序问题。为 此4.4内核在多路径选路的时候,hash计算中引入了源信息,避免了这个问题。只要计算方法不变,永远一个流的数据hash到一个dst。

携带version number的socket路由缓存

这 个不是4.4内核携带的特性,是我自己的一些想法。early_demux已经被引入了内核,旨在消除本机入流量的路由查找,毕竟路由查找后还要再 socket查找,为何不直接socket查找呢?查找到的结果缓存路由信息。对于本机提供服务的设备而言,开启这个选项吧。
       但是对于出流量,还是会有很大的开销浪费在路由查找上。虽然IP是无连接的,但是TCP socket或者一个connected UDP socket却是可以明确标示一个5元组的,如果把路由信息存储在socket中,是不是更好的。好吧!很多人会问,怎么解决同步问题,路由表改了怎么 办,要notify socket吗?如果你被此引导而去设计一个“高效的同步协议”,你就输了!办法很简单,就是引入两个计数器-缓存计数器和全局计数器,socket的路 由缓存如下:

sk_rt_cache {
    atomic_t version;
    dst_entry *dst;
};

全局计数器如下:

atomic_t gversion;

每当socket设置路由缓存的时候,读取全局gversion的值,设置进缓存version,每当路由发生任何改变的时候,全局gversion计数器递增。如果cache计数器的值与全局计数器值一致,就可用,否则不可用,当然,dst本身也要由引用计数保护。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1735581

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
9天前
|
网络协议 算法 Linux
【Linux】深入探索:Linux网络调试、追踪与优化
【Linux】深入探索:Linux网络调试、追踪与优化
|
20小时前
|
关系型数据库 MySQL Linux
Linux CentOs7 安装Mysql(5.7和8.0版本)密码修改 超详细教程
Linux CentOs7 安装Mysql(5.7和8.0版本)密码修改 超详细教程
|
20小时前
|
缓存 运维 算法
深入理解Linux内核的虚拟内存管理
【5月更文挑战第6天】 在现代操作系统中,尤其是类Unix系统如Linux中,虚拟内存管理是一项核心功能,它不仅支持了多任务环境,还提供了内存保护和抽象。本文将深入探讨Linux操作系统的虚拟内存子系统,包括分页机制、虚拟地址空间布局、页面置换算法以及内存分配策略。通过对这些概念的剖析,我们旨在为读者揭示Linux如何有效地管理和优化物理内存资源,并确保系统的稳定运行与高效性能。
|
20小时前
|
Linux 调度 开发者
探索Linux内核调度:公平与效率的平衡艺术
【5月更文挑战第6天】 随着多核处理器的普及,操作系统的进程调度策略对系统性能的影响愈加显著。Linux作为广泛应用的开源操作系统,其内核调度器的设计哲学和实现细节一直是系统研究领域的热点。本文将深入分析Linux内核调度器的工作原理,探讨如何在保证公平性和效率之间取得平衡,并考察最新的调度器CFS(Completely Fair Scheduler)如何适应现代硬件架构的需求。
|
3天前
|
Linux Shell 网络安全
网络安全中Dos和linux常用命令总结
本篇是对网安学习中,常用的命令做一个图文与命令示例,并对一些比较重要的dos和shell命令进行总结,方便自己后续学习进行查询,并希望能够给更多人有一个总结命令和了解命令的地方.
28 5
|
6天前
|
运维 监控 Linux
提升系统稳定性:Linux内核参数调优实战
【5月更文挑战第1天】 在运维领域,保障服务器的高效稳定运行是核心任务之一。Linux操作系统因其开源、可靠和灵活的特点被广泛应用于服务器中。本文将深入探讨通过调整Linux内核参数来优化系统性能,提升服务器的稳定性和响应能力。文章首先介绍了内核参数调优的必要性和基本原则,然后详细阐述了调优过程中的关键步骤,包括如何监控当前系统状态,确定性能瓶颈,选择合适的参数进行调优,以及调优后的测试与验证。最后,文中提供了一些常见问题的解决策略和调优的最佳实践。
28 5
|
6天前
|
Ubuntu 网络协议 Linux
|
6天前
|
JSON 网络协议 Linux
Linux ip命令:网络的瑞士军刀
【4月更文挑战第25天】
10 1
|
6天前
|
算法 大数据 Linux
深入理解Linux内核的进程调度机制
【4月更文挑战第30天】操作系统的核心职能之一是有效地管理和调度进程,确保系统资源的合理分配和高效利用。在众多操作系统中,Linux因其开源和高度可定制的特点,在进程调度机制上展现出独特优势。本文将深入探讨Linux内核中的进程调度器——完全公平调度器(CFS),分析其设计理念、实现原理及面临的挑战,并探索未来可能的改进方向。
|
6天前
|
算法 Linux 调度
探索Linux内核:进程调度的奥秘
【4月更文挑战第30天】 在多任务操作系统中,进程调度是核心功能之一,它决定了处理器资源的分配。本文深入分析了Linux操作系统的进程调度机制,从调度器的基本原理到复杂的调度策略,以及它们如何影响系统性能和用户体验。通过剖析进程优先级、时间片分配以及实时性要求等方面,揭示了Linux如何在众多运行着的进程中做出快速而公平的决策,确保系统的高效与稳定运行。