惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
MyScale Blog
MyScale Blog
Microsoft Security Blog
Microsoft Security Blog
量子位
B
Blog
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog
T
The Exploit Database - CXSecurity.com
N
News | PayPal Newsroom
Cloudbric
Cloudbric
A
About on SuperTechFans
AI
AI
Hacker News: Ask HN
Hacker News: Ask HN
S
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
T
The Blog of Author Tim Ferriss
Simon Willison's Weblog
Simon Willison's Weblog
有赞技术团队
有赞技术团队
H
Heimdal Security Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
D
Docker
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
IT之家
IT之家
Know Your Adversary
Know Your Adversary
N
Netflix TechBlog - Medium
T
Tailwind CSS Blog
B
Blog RSS Feed
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
博客园 - 叶小钗
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
The Hacker News
The Hacker News
Y
Y Combinator Blog
I
Intezer
The Register - Security
The Register - Security
F
Full Disclosure
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler

鸟窝

鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 LLM 究竟是如何工作的? Go 实验特性详解 amd64 微架构级别对 Go 程序性能提升多少? Loop Engineering 实践:一次批量实现 8 个 issue,完成夔牛工具的开发 Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 傻瓜式RDMA高性能网络开发:从零跑到 400 Gb每秒 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 百度网络监控工具开源第三弹:lidar — 不只是 pingmesh 别只盯着gopacket了,看看这个强大的网络库 大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密 如何构建你自己的 Agent 运行时 寻找你代码中的臭味:一个让 AI 帮你嗅出架构腐化的开源 Skill 套壳不丢人!我用Go+AI搓了一个Agent统一编排框架,ClaudeCode-Codex-Pi全被我包了 告别死锁和陈旧语法、告别性能瓶颈:三个开源 Skill,新手Gopher 秒变 Go 语言大神 SPEC和PRD的区别 SPEC和方案设计有什么区别 从需求到上线,让 AI 管理你的整个研发流程! antigravity-cli 使用 CLIProxyAPI, 让最新的 Codex 能够支持国内的各大模型 Clawpatch + codex-review:AI 代码审查工具链的正确打开方式 拆解Manus:沙盒架构深度解析 40+ Claude Code Tips: From Basics to Advanced Ralph 实验:构建 SQLite UI 了解 Manus Sandbox - 您的云计算机 Notex:一个开源 NotebookLM 替代方案的实现 拆解Manus:真正有用的深度报告的生成 Claude Code 使用 我给每个模型服务商『捐』了10块钱,只为了... Go之禅 - 基于Rob Pike思想的Go语言哲学 Codex CLI vs Gemini CLI vs Claude Code:谁是最佳选择? goskills:Claude Skills 功能强大,为我所用 一行代码使用 Claude Skill 和 deepseek langchain + MCP:如虎添翼 godotenv 库介绍 Codex CLI vs Gemini CLI vs Claude Code: Which is the Best? 使用Linux 30年了,我都不知道 ping 8.8 还能这么用? 从 AI 哪里挣钱? Go sync 包近两年发展综述 deepseek-v3.2-exp的闪电索引器
Linux 中网络包的一生
by smallnest · 2025-11-01 · via 鸟窝

write()recv() 的实用导览。

你运行了 curl http://example.com,现在在终端里得到了一些 HTML,但实际上发生了什么?Linux 会让你的字节经过一套明确的步骤:选定一条路径,查找邻居的 MAC 地址,把包放在,请求网卡发送,然后在另一端执行反向的操作。

这篇文章尽量简单地解释这条路。如果你用过 Linux,运行过 curl,或者试过 ip addr,你完全有能够读懂这篇文章。不需要多么高深的背景。

注意:当我在这篇文章中说“内核”时,我实际上指的是“Linux 内核及其网络栈”,即内核中运行并移动数据包的部分。

我们要讲的内容

以下是我们将介绍的简化路径:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

your app

write()/send()

TCP (segments your bytes)

IP (chooses where to send them)

Neighbor/ARP (find the next-hop MAC)

qdisc (queueing, pacing)

driver/NIC (DMA to hardware)

wire / Wi‑Fi / fiber

NIC/driver (other host)

IP (checks, decides it's for us)

TCP (reassembles, ACKs)

server app

第一部分 传输:从 write() 到网线

步骤1:你的应用将字节传递给内核

你在 TCP 套接字上调用 send()write()。 内核接受你的缓冲区并顺序发送。

  • TCP 会将大的缓冲区拆分成大小适合路径的 数据段 (segments)。通信双方会在 TCP 握手 期间通告各自的 最大数据段大小 (MSS),发送方会将自身的数据段大小限制在对方通告的 MSS 内,同时还要受到当前 路径最大传输单元 (Path MTU) 以及任何 IP/TCP 选项(如:时间戳)的进一步约束。
  • 它会为每个数据段标记 序列号 (sequence numbers),以便接收方能够按正确的顺序进行重组。

[!info] 🔌 套接字
套接字 只是你程序的一个 通信端点。对于 TCP 而言,内核会为每个套接字维护状态信息,包括:序列号拥塞窗口 (congestion window)定时器 等。

[!info] 🤝 TCP 握手 (TCP Handshake)
TCP 握手 在任何write()到达对端之前,TCP 会进行快速三步设置:1)客户端-> 服务器:SYN,并带有选项(MSS、SACK 允许、窗口规模、时间戳、ECN)。2)服务器 -> 客户端:SYN-ACK 及其选项。3) 客户端 -> 服务器:ACK.双方就初始序列号和选项达成一致;州已成立。TLS 说明:对于 HTTPS 来说,TLS 握手是在 TCP 建立后运行的。

[!todo] 试试看
下载东西时运行 ss -tni。你会看到随着数据在线路上传输并被应用消耗,TCP 的发送和接收队列大小会波动。

步骤2:内核决定将数据发送到哪里(路由)

内核会查看目标 IP 并选择最匹配的路由。在典型的主机上,问题归结为:这个 IP 是在我的本地网络上,还是我应该交给网关?

  • 如果地址位于直接连接的网络上,则会通过该接口发送。
  • 否则,它会连接到你的默认网关(通常是路由器)。

[!todo] 试试看
ip route get 192.0.2.10
它会打印接口、下一跳(如果有)以及内核将使用的源 IP。

[!info] 策略路由
内核可以使用 ip rule查询多个路由表(例如按源地址或标记选择路由)。大多数笔记本和服务器使用主路由表。

步骤 3:内核学习下一跳 MAC(邻居/ARP)

IP 路由选择下一跳。为了实际发送以太网帧,内核需要该跳的 MAC 地址。

  • 如果内核已经知道下一跳(在邻居/ARP 缓存里),那很好。
  • 如果没有,它会发送广播 ARP 请求:“谁拥有 10.0.0.1?告诉我你的 MAC。”回复已被缓存。

[!todo] 试试看
ip neigh show
你会看到像 10.0.0.1 lladdr 00:11:22:33:44:55 REACHABLE 这样的条目。

[!info] ARP vs NDP
IPv4 使用 ARP(广播)。IPv6 使用NDP(组播)。原理相同:找到你网络中某个 IP 的链路层地址。

步骤 4:数据包等待其轮到(qdisc)

在 NIC 发送任何内容之前,数据包会进入队列领域(qdisc)。你可以把它看作是一个小的等待队伍加上一个交通警察,内核可以:

  • 平滑突发流量,避免链路泛滥和缓冲膨胀(大队列->高延迟),
  • 在不同流之间公平共享带宽,
  • 如果你已经配置了整形/速率限制规则,请强制执行。

[!todo] 试试看
tc qdisc show dev eth0
tc -s qdisc show dev eth0 # same, but with counters/stats
eth0 替换成你的实际接口名称(例如 enp3s0,wlp2s0)。

[!info] MTU 与 MSS
MTU 是链路能承载的最大 L2 负载(典型以太网为 1500 字节)。
MSS 是段内最大的 TCP 有效载荷,仅次于 IP + TCP 头部和选项。
在 TCP 握手过程中,双方都宣布自己可以接收的 MSS,发送方不会发送比对方通告 MSS 更大的段,并且也会遵守路径 MTU(PMTU)。
在 IPv4 常见的无选项情况下,MSS≈MTU−40 字节。选项进一步降低 MSS。

步骤 5:网卡驱动和 NIC 负责繁重工作

内核的网络驱动将你的数据包交给网卡(NIC),并将其放入一个小的传输队列,卡从中读取。NIC 随后:

  • 直接从内存(使用 DMA)提取字节,并将其转化为链路上的比特流、铜缆上的微小电压变化、光纤上的光脉冲,或者如果你用 Wi-Fi 时是无线电波。

那才是真正的“接线”时刻:内存中的数据变成了网络上的信号。

[!todo] 试试看
ip -s link show dev eth0
ethtool -S eth0 # NIC stats
ethtool -k eth0 # offloads enabled
eth0 替换成你实际的接口名称。

[!info] offloads 卸载
TSO/GSO:让网卡或栈将大型缓冲区拆分为 MTU 大小的帧。
校验和卸载:传输时,网卡在内核递交包后填写 IP/TCP 校验和,发送前,接收时网卡可以验证校验和并告知内核结果。GRO(接收时):将许多小数据包合并成更大的块以节省 CPU。

[!info] DMA
直接内存访问(DMA)允许网卡通过总线(例如 PCIe)直接读写你在 RAM 中的数据,而 CPU 无需复制字节。这就是网卡能高效地从transmit ring拉取帧(并放置接收帧)的原因。

步骤 6:上线

在以太网上,网卡发送一个帧,内容如下:

1

[ dst MAC | src MAC | EtherType (IPv4) | IP header | TCP header | payload | FCS ]

交换机关心以太网头部:它们查看目标 MAC 地址,并将帧转发到正确的端口。

路由器会查看 IP 头部,减少 TTL / Hop Limit,并在(IPv4)更新头部校验和后再将数据包转发到下一跳。

每台交换机和路由器逐跳重复此作,直到路由器最终获得直接到目的网络的路由,并将数据包传送到服务器的局域网。

[!info] frame vs packet 帧与包
数据包是 IP 级单元(IP 头部+TCP/UDP+有效载荷)。 帧是指该数据包在特定链路上(例如以太网)上通过 src/dst MAC 和校验和传输的方式。

第二部分 接收:从线路回传到你的应用

步骤 7:网卡将数据传递给内核(NAPI)

在服务器端,网卡将收到的帧写入receive rings(内存中的小队列)。Linux 内核随后使用 NAPI 高效拉取数据包:快速中断后切换为轮询,一次性处理一批数据包。

[!info] NAPI
如果每个数据包都触发了满中断,忙碌的网卡可能会让 CPU 不堪重负。NAPI 的诀窍是:

  • 发起一次中断,
  • 暂时切换到轮询以排空大量数据包,
  • 然后重新启用中断。

中断减少,吞吐量更好。

步骤 8:IP 检查数据包并决定下一步行动

内核会验证 IP 头部(版本、校验和、TTL 等),然后问:“这个包是给我的吗?”

  • 如果目标 IP 与服务器的某个地址匹配,则该 IP 是本地的,并在堆栈中向上移动。
  • 如果没有,且启用了 IP 转发,内核可能会将它转发, 类似 Linux 路由器的表现。
  • 否则,数据包会被丢弃。

如果你使用防火墙,这时像 PREROUTING 和 INPUT(nftables/iptables)这样的钩子可以过滤、记录或 DNAT 流量,然后才会被送到本地套接字。在 POSTROUTING 中会发生 SNAT/假面舞会。对于本地生成的数据包,DNAT 也可能出现在输出中。

[!todo] 试试看
sudo nft list ruleset
# or, with iptables:
sudo iptables -L -n -v
sudo iptables -t nat -L -n -v

步骤 9:TCP 重新组装、确认并唤醒应用

TCP 协议栈会将段排序,检查缺失部分,并发送 ACK。当数据准备好时,它会唤醒在 recv() 中等待的进程。

[!todo] 试试看
ss -tni 'sport = :80 or dport = :80'
随着应用读取,接收队列(Recv-Q)会随着增长和缩小。

简短实用笔记

回环很特别(而且速度快)

发送到 127.0.0.1 的数据包从未到达物理网卡。路由仍然会进行,但所有内容都保留在仅软件的 lo 接口内存中。

桥接与路由(同一盒子,不同角色)

如果盒子是桥接器(例如带有 br0),它会在第 2 层转发帧,不会改变 TTL。如果是路由,它会在第 3 层转发,TTL 下降一跳。

NAT hairpin(为什么内部客户端会访问外部 IP)

通过路由器的公共 IP 从同一局域网访问服务需要“发夹式 NAT”。如果在这种情况下连接重置,请检查预路由后路由 NAT 规则。

IPv6

把 ARP 换成新民主党。否则,路径是相同的:

1

2

ip -6 route

ip -6 neigh

UDP 是故意的不同

UDP 不做排序、重传或拥塞控制。发送路径使用 udp_sendmsg,接收路径传输完整的数据报。你的应用自己负责处理数据丢失。

亲自看看(10个快速指令)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

ip route get 192.0.2.10

ip route; ip rule

ip neigh show

sudo nft list ruleset

sudo iptables -L -n -v

sudo iptables -t nat -L -n -v

ss -tni

sudo tcpdump -ni eth0 -e -vvv 'host 192.0.2.10 and tcp port 80'

tc -s qdisc show dev eth0

ip -s link show dev eth0

ethtool -S eth0

nstat -a | grep -E 'InErrors|OutErrors|InNoRoutes|InOctets|OutOctets'

tracepath 192.0.2.10

ARP/邻居问题

IP neigh 显示失败或不断切换状态——> L2 可达性、VLAN 标记或交换机过滤问题。

MTU / PMTU 黑洞

小 ping 可以正常,大传输会卡顿——>MTU 不匹配或 ICMP 被阻。

允许 PMTU 信号通过防火墙(IPv4:ICMP 类型 3 代码 4“需要分段”,IPv6:ICMPv6 类型 2“数据包过大”)或修复 MTU。

反向路径滤波器的痛点

非对称路由 + rp_filter=1 会丢弃返回流量。使用 rp_filter=2(松散)或使路由对称。

NAT 的惊喜

SNAT/MASQUERADE 错误地重写了来源,所以回复无效。检查 NAT 规则和 conntrack -L。

Backlog/accept pressure

新连接在大负载下重置 -> 增加应用backlognet.core.somaxconn,确保应用能及时处理accept

爆发产生的缓冲膨胀

如果遇到队列过大严重延迟尖峰的问题,请选择 fq_codel(或 fq)作为 队列规则 (qdisc),并且如果应用程序支持,请启用 数据包限速 (pacing)

内核调用路径(如果你感兴趣的话)发送(典型的 TCP 路径):

1

2

3

4

5

6

7

8

9

10

tcp_sendmsg

-> tcp_push_pending_frames

-> __tcp_transmit_skb

-> ip_queue_xmit

-> ip_local_out / ip_output

-> ip_finish_output

-> neigh_output

-> dev_queue_xmit

-> qdisc / sch_direct_xmit

-> ndo_start_xmit (driver)

接收(典型的 IPv4 TCP 路径):

1

2

3

4

5

6

7

8

9

napi_gro_receive / netif_receive_skb

-> __netif_receive_skb_core

-> ip_rcv

-> ip_rcv_finish

-> ip_local_deliver

-> ip_local_deliver_finish

-> tcp_v4_rcv

-> tcp_v4_do_rcv

-> tcp_data_queue (wake reader)

一份小清单,随时备着

  • Socket - Your program’s handle for network I/O.
  • MTU / MSS - Max link payload / max TCP payload.
  • ARP / NDP - Find the link-layer address (IPv4 / IPv6).
  • qdisc - Per-device queueing policy (fairness, shaping).
  • NAPI - Efficient receive: interrupt, then poll a batch.
  • TSO/GSO/GRO - Offloads to split/merge packets and save CPU.
  • Conntrack - Kernel’s flow table (used by NAT and filtering).
  • PREROUTING/INPUT/OUTPUT/POSTROUTING - Firewall hook points.
  • DMA (Direct Memory Access) - Hardware reads/writes RAM without CPU copies, NICs use this for TX/RX rings.
  • TTL / Hop Limit - Per‑packet counter decremented by each router (TTL in IPv4, Hop Limit in IPv6). When it hits zero, the packet is dropped.
  • FCS (Frame Check Sequence) - Link‑layer CRC at the end of an Ethernet frame, used to detect bit errors on the wire.