第 1 章 Linux 网络栈概览
本章共 3 节,点击下方链接阅读:
第 1 章 深入内核:网络协议栈的黑盒解剖
章节引子
.2 网络设备(The Network Device)
让我们把视线落到底层,也就是 Layer 2(L2)——链路层。
.3 Linux 内核网络开发模型
网络子系统很复杂,而且它的变化非常快——快到如果你眨一下眼,可能就错过了一个 API 的变更。
第 2 章 Netlink 套接字
本章共 7 节,点击下方链接阅读:
第 2 章 当用户空间遇见内核
想象一下,你正在编写一个需要监控网络流量变化的应用程序。每当内核的路由表发生变化,或者有新的网卡上线时,你的程序都需要第一时间知道。
.2 内核 Netlink 套接字
上一节我们聊了用户空间那边的工具——iproute2、net-tools,还有那些帮你省事的 libnl 和 libmnl。但在另一头,内核 是怎么看待这一切的?
.3 Netlink 消息头
上一节我们看完了内核是怎么把消息扔进发送队列的——函数调用链一路从 rtnetlink 走到通用的 netlink 模块。
.4 NETLINK_ROUTE 消息:不只是路由
上一节我们在 Generic Netlink 的机制里打转,把消息头、TLV、验证策略这些东西拆了个底朝天。现在地基打好了,是时候把视线收回到网络子系统本身,看看那些「老资格」的 Netlink 协议家族是怎么工作的。
.5 增删路由表项:在 FIB 里跳舞
上一节我们拆解了 Netlink 消息的头部和那些让人眼花缭乱的标志位。现在,是时候把这套理论扔进实战环境里了。
.6 通用 Netlink 协议
还记得上一节结尾抛出的那个问题吗?
ch02_7
2.7 快速参考手册
第 3 章 ICMP 协议
本章共 3 节,点击下方链接阅读:
.0 网络的神经系统:为何我们需要 ICMP
想象一下,你正在管理一个庞大的、分布在全球的快递系统。
.2 IPv6 的「瑞士军刀」:ICMPv6
上一节我们在 IPv4 的世界里打转,虽然 ICMPv4 也很重要,但在 IPv4 的架构里,它多少有点像个「打补丁」的角色——ARP 负责找地址,IGMP 负责组播,ICMPv4 主要负责报错和诊断。
.3 快速参考与实战补充
我们在这一章里拆解了 ICMPv4 和 ICMPv6 的内部运作机制,从初始化流程到报文的收发,看了大量的内核代码。现在,是时候把这些散落在代码里的细节收拢一下,做一个速查,顺便补充两个在工程实践中非常容易忽略的「角落」。
第 4 章 IPv4 协议实现
本章共 9 节,点击下方链接阅读:
.1 IPv4 头部与协议注册
有一类问题,表面上看是「配置不对」,实际上是「结构认知偏差」。
.2 接收 IPv4 数据包
上一节我们讲到了 IPv4 的“注册”过程——内核把 ippackettype 挂到了全局协议链表上,设定了 ip_rcv() 作为回调函数。当网卡把数据包交上来,以太网类型又是 0x0800 时,内核就会直奔这个函数。
.3 接收 IPv4 组播数据包
上一节我们最后提到,当 iprcvfinish() 完成了路由查找和选项处理后,数据包的命运主要分三条路:本地投递、转发,或者——组播。
.4 当 IP 选项在包里醒来
上一节我们看着组播包在内核里走完了它的一生,从 ip_rcv 进来,要么本机接收,要么继续转发。那条路径很清晰,就像高速公路上没有障碍物一样。
.5 发送 IPv4 数据包
现在,我们要把角色换过来。
.6 分片
上一节我们说到,数据包最终会离开本地,踏上 iplocalout() 的不归路。
.7 重组:把碎掉的镜子拼回来
上一节我们像切菜一样把大包切成了碎片,这很爽,但也留下了一个巨大的烂摊子。
.8 包转发
上一节我们拼好了被打碎的镜子,看着那些碎片在 ip_defrag() 里重圆,最终递交给传输层。
.9 快速参考
走完这趟旅程,现在让我们停下来整理一下背包。
第 5 章 IPv4 路由子系统
本章共 8 节,点击下方链接阅读:
.0 没有地图,就无法远行
Linux 网络栈的核心任务之一,是充当搬运工——把不属于本机的数据包,准确无误地扔给下一站。这听起来很简单,但当你面对的是互联网骨干网上的核心路由器时,这就是一个完全不同的量级的故事了。在这个世界里,网络拓扑是瞬息万变的,海量的路由信息在每一秒都在更新。
.2 在路由子系统中执行查找
在上一节里,我们搞清楚了 FIB 是什么——它不是一张简单的表,而是内核手里那张决定数据包生死去处的「藏宝图」。图是有了,但还没人去读它。
.3 FIB 信息:一张路由条目的「身份证」
上一节我们说到,fiblookup() 填好 fibresult 就算完成了它的历史使命。结果结构里最重要的那个指针——指向 fib_info——才是真正的大佬。
ch05_4
5.4 最后一公里:Nexthop (fib_nh)
.5 Policy Routing:地图之外的选择权
上一节我们讲到 fib_nh 就像是一个尽职的向导,手里拿着写明出接口和网关地址的便签,指引着数据包怎么离开内核。我们建立了一个非常直观的认知:目的地决定路由。只要知道你想去哪(目标 IP),路由表就能告诉你怎么走。
.6 FIB Alias:当同一个目的地有了多个分身
上一节我们聊了 FIB Tables 和 fibinfo 的通用结构。你可能觉得一切看起来都很完美——一条路由,一个 fibinfo,记录网关、设备和度量,清晰明了。
.7 路由器的「悄悄话」:ICMPv4 Redirect
上一节我们聊了 FIB 那精密的静态结构。你可以把它想象成一张挂在墙上的、画得一丝不苟的地图。
第 5 章 IPv4 路由子系统
5.8 快速参考面板(Quick Reference)
第 6 章 组播路由
本章共 9 节,点击下方链接阅读:
.0 引言:一群人的信,怎么发给每一个人?
这是网络世界里一个很反直觉的问题:如果你想同时给一百个人发同一封邮件,你会怎么做?
.2 组播转发缓存 (MFC)
上一节我们把 mr_table 比喻成了调度中心——手里有接口表,挂着未解析队列。但你可能已经发现一个细节:真正的「转发决策」逻辑还没露面。
.3 Multicast Router
上一节我们拆解了内核里的 mrtable 和 mfccache——那是组播转发的骨架。
ch06_4
6.4 IPv4 Multicast Rx Path
ch06_5
6.5 The ipmrforward() Method
.6 The ipmr_queue_xmit() Method
上一节我们看到,ipmrforward() 像个尽职的调度员,决定把包发往哪个虚拟接口(VIF)。但它并不真正负责「发货」。真正的发货工作——包括查找路由、处理隧道封装、把包交给网卡驱动——是由 ipmrqueuexmit() 接管的。
ch06_7
6.7 策略路由(Policy Routing)
.8 多路径路由
策略路由给了我们选择不同路线的自由,但它解决的是「根据目的地以外的条件(比如源地址)来选路」的问题。
.9 最后的备忘单
我们终于走到了这一章的结尾。
第 7 章 邻居子系统与 ARP
本章共 5 节,点击下方链接阅读:
第 7 章 Linux 邻居子系统
本章探讨 Linux 内核如何维护链路层地址的映射,以及 ARP/NDISC 协议在内核中的实现细节。
.2 用户空间与邻居子系统的交互
上一节我们聊了邻居子系统内部的那些「家务事」:内存怎么分、引用计数怎么管、条目什么时候销毁。如果你是内核开发者,这些就是你的砖瓦和水泥。
ch07_3
7.3 The ARP protocol (IPv4)
.4 NDISC 协议 (IPv6)
上一节我们聊完了 IPv4 的 ARP。平心而论,ARP 做到了它该做的:简单、直接、有效。但它也生来就带着缺陷——没有任何安全机制,谁都可以冒充网关(这就是我们常说的 ARP 欺骗)。
.5 快速参考
走到这一步,我们已经拆解了邻居子系统的骨架(核心结构)、血液(协议交互)和肌肉(状态机)。
第 8 章 IPv6 协议
本章共 9 节,点击下方链接阅读:
第 8 章 当地址不再是稀缺资源
章节引子:历史包袱与新起点
.2 IPv6 地址类型与特殊地址
上一节我们拆解了 IPv6 的头部结构,那个巨大的 128 位地址空间带来了前所未有的地址自由,但也引入了分类上的复杂性。在 IPv4 时代,我们习惯了区分单播、广播和组播,但在 IPv6 里,这个游戏规则变了。
ch08_3
8.3 IPv6 Header
.4 扩展头部——链接式的无限扩展
在上一节,我们盯着 IPv6 那个只有 40 字节的固定头部看了半天。它干净、纯粹,把所有不必要的负担都甩掉了。
.5 Autoconfiguration:无状态的魔法
上一节我们讲完 ipv6_rcv() 的时候,其实心里悬着一块石头。
.6 接收 IPv6 数据包
上一节我们聊了地址配置——也就是「怎么让这台机器在网络上有个身份」。现在身份有了,真正的好戏才刚刚开始。
.7 接收 IPv6 组播数据包
上一节我们追踪了单播包在内核里的漂流路线,不管是本地投递还是转发,最终归宿都很明确。但网络世界里还有一种「一人说话,万人聆听」的通信模式——组播。对于路由器来说,组播包的处理逻辑显然要更复杂一点:它不仅要决定自己是否要听,还要决定是不是要帮邻居们转发。
.8 Multicast Listener Discovery (MLD)
上一节我们在数据包的「迷宫」里转了一圈,最终把数据包送到了正确的 Socket。那个过程像是经过了一系列安检:先查目的地,再查会员资格,最后还要查具体是谁发来的货。
.9 速查表与内核碎片(Quick Reference)
我们在这本书里经常说:「别死记硬背,要去理解机制。」但在你真正深入内核代码之前,手里有一张准确的「地图」还是很有必要的。这一节就是那张地图。
第 9 章 Netfilter 与防火墙
本章共 10 节,点击下方链接阅读:
第 9 章 Netfilter Frameworks
章节引子:在高速公路上设卡
ch09_10
9.10 快速参考与工具链
.2 Netfilter Hooks
让我们把目光收回到内核网络协议栈的内部。
ch09_3
9.3 连接跟踪初始化:把“状态”注入网络栈
.4 连接跟踪条目(Connection Tracking Entries)
上一节我们聊了 nfconntracktuple——那张「单程机票」。现在问题来了:内核拿到了这张机票,实际上要去哪里找对应的「乘客记录」?那个用来存放连接状态、被称为 struct nf_conn 的结构体,到底长什么样?
.5 连接跟踪助手与预期连接
到目前为止,我们谈论的连接跟踪都是「单线程」的——一条连接进来,一条记录出去。但是,现实世界的网络协议有时候比这要复杂得多。
.6 IPTables:规则的前端实现
上一节我们聊了 Xtables 的扩展机制,知道了 Target 和 Match 是如何注册到内核的。这就像是在工厂里准备好了一套标准的模具。
.7 网络地址转换 (NAT)
现在我们来到了 Netfilter 世界里最著名的那个功能——NAT(Network Address Translation)。
.8 NAT 钩子回调与连接跟踪钩子回调的共舞
上一节我们把 nfnatipv4_ops 这个钩子数组注册进了内核,这就像在数据包必经的高速公路上设了卡。但如果你仔细看这些关卡,你会发现一件有意思的事:有些关卡上,既有连接跟踪(conntrack)的人在查身份证,也有 NAT 的人在改地址。他们挤在同一个 Hook 点上,按什么顺序出来工作,这是个甚至能决定生死的细节。
.9 NAT Hook 回调与连接跟踪扩展
上一节我们看了 manip_pkt 这个「外科医生」是如何拿着手术刀修改数据包 IP 和端口的。但问题来了:这个函数是谁调用的?它在什么时候进场?更重要的是,它怎么知道该把这个数据包改成什么样——比如,它是该改源地址(SNAT)还是改目的地址(DNAT)?
第 10 章 IPsec 与加密
本章共 8 节,点击下方链接阅读:
第 10 章 通往信任的隧道
有一类问题,表面上看是网络配置问题,实际上是信任问题。
0.2 IKE (Internet Key Exchange)
上一节我们提到,IPsec 不仅仅是一个内核模块,它是一场跨越用户空间和内核空间的联合行动。内核只负责「执行」——也就是拿到密钥后去加密、解密数据包。但在执行之前,得有人先把密钥商量好,把规则定下来。这个「外交谈判」的任务,就是在这一节要讲的 IKE (Internet Key Exchange)。
0.3 The XFRM Framework
上一节我们聊了 IPsec 用到的那些加密算法,看到了内核是如何通过 Crypto API 和 pcrypt 来榨干 CPU 性能的。算法固然是肌肉,但光有肌肉没法干活——你还需要骨架和神经系统来把这些算法组织起来,告诉内核什么时候该加密、该用哪个密钥、以及把数据包发到哪里去。
0.4 ESP 实现 (IPv4)
我们已经看到了 XFRM 框架是如何存放策略(SPD)和状态(SAD)的——这就像是盖好了房子、备好了账本。但房子里的人怎么进出,账本里的规则怎么执行,取决于具体的协议。
0.5 接收一个 IPsec 数据包(传输模式)
上一节我们在初始化的最后停在了 xfrm4_rcv() 上——这是 ESP 协议注册给内核的接收入口。管道铺好了,现在真的有数据包流进来了,它会经历一场什么样的旅程?
ch10_6
10.6 XFRM Lookup
0.7 NAT Traversal in IPsec
上一节我们还在享受 xfrm_lookup() 带来的「确定感」——策略匹配了,状态找到了,加密完成了,包发出去了。一切看起来都很美好。
0.8 快速参考
这章的内容确实有点烧脑——XFRM 框架的状态机、策略与状态的纠缠、还有 NAT-T 那种「为了生存而不得不做的妥协」。
第 11 章 传输层协议
本章共 7 节,点击下方链接阅读:
1.1 Sockets
有一个哲学问题贯穿了 Unix 的历史:"一切皆文件"。
ch11_2
11.2 创建套接字(Creating Sockets)
1.3 UDP (User Datagram Protocol)
还记得上一节我们在 msghdr 结构里看到的那些字段吗?msgiov 存数据,msgcontrol 存辅助信息。当时你可能觉得这只是一堆枯燥的数据结构定义。
1.4 TCP (Transmission Control Protocol)
如果上一节我们聊的 UDP 是一个「发完即忘」的乐天派,那这一节要面对的 TCP 就是网络协议世界里最严重的强迫症患者。
1.5 SCTP:工程权衡下的混血儿
上一节我们最后看到 TCP 那个复杂且精细的世界,为了可靠性和有序性,它不惜一切代价。但在工程师的现实世界里,并不是所有场景都能忍受 TCP 的死板,也不能全盘接受 UDP 的冷漠。你需要的是一种混合体——既要 TCP 的可靠和拥塞控制,又要 UDP 的消息边界和多宿主能力。
1.6 DCCP: 数据报拥塞控制协议
我们终于来到了 IPv4 传输层家族的最后一站。
1.7 快速参考手册
代码读完了,协议看过了,现在让我们把散落在各处的零件拼回一张图纸。
第 12 章 无线网络
本章共 9 节,点击下方链接阅读:
2.1 Mac80211 Subsystem
在深入 Linux 内核的无线实现之前,我们需要先面对一个现实:无线网络和有线网络虽然都在 /etc/network/interfaces 里长得差不多,但在内核眼里,它们完全是两个物种。
2.2 802.11 MAC 头部
---
2.3 Network Topologies(网络拓扑)
搞定了帧头部的字节排列之后,我们需要退后一步,看看大局。
2.4 节电模式 (Power Save Mode)
除了转发数据包,AP 还有一个重要的功能:充当那些「睡着」的客户端的保姆,帮它们缓存数据。
ch12_5
12.5 MAC 层管理实体 (MLME)
Mac80211 实现细节
现在,让我们把目光从空中的协议交互收回内核。
2.7 High Throughput (802.11n) —— 高速公路的入场券
上一节我们聊完 mac80211 的骨架和肌肉时,我提到了无线世界不仅需要「通」,更需要「快」。
ch12_8
12.8 Mesh Networking (802.11s)
ch12_9
12.9 快速参考(Quick Reference)
第 13 章 RDMA 与高性能网络
本章共 8 节,点击下方链接阅读:
第 13 章 绕过内核的代价
有一类问题,表面上是「网络性能」问题,实际上是「谁在为此买单」的问题。
ch13_2
13.2 RDMA Device —— 谁来接管这台机器?
3.3 Memory Region (MR)
上一节我们搞定了地址句柄(AH),就像是给 RDMA 网络里的数据包指好了路牌。但这还不够——路牌只是告诉你怎么走,车(数据)还得先有地方装。
ch13_4
13.4 Completion Queue (CQ) —— 任务完成的信箱
3.5 Shared Receive Queue (SRQ)
我们现在已经聊过了 QP、CQ,还有各种花里胡哨的域。你可能会觉得 RDMA 的对象模型有点像俄罗斯套娃,一层套一层。
ch13_6
13.6 Queue Pair (队列对)
3.7 RDMA 支持的操作
上一节我们花了很大力气去搞清楚 QP 这个「阿凡达」是怎么造出来的,以及它的生命周期有多脆弱。
3.8 速查表
到这里,我们已经把 RDMA 栈的大多数骨头都拆过了。现在手边应该有很多散落的零件:ib_client、PD、QP、CQ、MR……
第 14 章 网络命名空间与高级特性
本章共 15 节,点击下方链接阅读:
4. Namespaces Implementation
章节引子:看不见的墙
4.10 通知链
上一节我们讨论了 NFC 这种「握手协议」,内核在其中扮演了精巧的翻译官角色。但内核不仅要在硬件之间翻译,还得时刻监听整个系统的状态变化。
4.11 The PCI Subsystem
上一节我们聊完了内核的「通知链」——那是软件层面的解耦艺术。
4.12 PPPoE Header —— 把协议钉在以太网上
上一节我们聊完了 PPPoE 的两个阶段——发现和会话,就像看完了两个人先握手问好,再开始聊天的全过程。
4.13 Android
上一节我们还在 PPPoE 的世界里琢磨怎么把以太网包装成点对点通道,这一节,我们要把视角拉高,看看 Linux 内核 networking 在移动端最大的「租客」之一——Android。
4.14 方法速查表(工具箱里的扳手和螺丝刀)
前面的旅程就真的到头了。
4.15 宏定义与工具函数
---
4.2 UTS 命名空间的实现
上一节我们提到,内核并不关心命名空间的「名字」,它只靠 inode 号来区分不同的实例。这听起来很极简,但极简是设计的最高境界。
4.3 网络命名空间的实现
上一节我们聊完了 UTS 命名空间——那个只管主机名的「软柿子」。它帮我们热了身,让我们明白内核是如何把「全局变量」这种坏习惯改造成「命名空间私有数据」的。
4.4 管理 Network Namespace:从上帝视角到手动操作
上一节我们站在内核的角度,把 struct net 的前世今生走了一遍。现在,让我们回到用户空间。
4.5 Cgroups:当隔离遇上资源争夺
命名空间解决了一个问题:「眼不见为净」。
4.6 Busy Poll Sockets
上一节我们聊完了 Cgroups 和 Namespaces,这两个是容器的基石。现在我们把目光从「隔离」移开,聚焦到一个极致性能的话题上。
4.7 Linux Bluetooth 子系统
上一节我们还在为网络延迟焦虑,讨论了怎么用 Busy Poll 这种「硬核」手段压榨系统的最后一点性能。这是一种通过放弃 CPU 休息时间来换取速度的极端策略。
4.8 IEEE 802.15.4 和 6LoWPAN
上一节我们还在享受蓝牙带来的「个人区域网」便利,这一节我们要去一个更抠门、更严苛的世界。
4.9 近场通信 (NFC)
如果你觉得蓝牙的几米通信距离还不够近,那我们再靠近一点。