From bbea71f62bad068d06ff1946a057a40786a4180c Mon Sep 17 00:00:00 2001 From: hhs <386998068@qq.com> Date: Sun, 17 May 2026 22:27:07 +0800 Subject: [PATCH] vault backup: 2026-05-17 22:27:07 --- .../01-基础概念/01-OSI与TCP-IP模型对比.md | 140 ++++++++ .../01-基础概念/02-数据封装与解封装.md | 126 +++++++ .../01-基础概念/03-寻址体系MAC-IPTPort.md | 169 ++++++++++ .../01-基础概念/04-带宽延迟RTT与吞吐量.md | 165 +++++++++ hhs/NETWORK/02-链路层/01-Ethernet帧结构.md | 104 ++++++ .../02-链路层/02-CSMA-CD与以太网退避.md | 138 ++++++++ hhs/NETWORK/02-链路层/03-MAC地址与广播域.md | 152 +++++++++ hhs/NETWORK/02-链路层/04-Switch与路由器.md | 181 ++++++++++ hhs/NETWORK/02-链路层/05-VLAN与Trunk.md | 208 ++++++++++++ .../03-网络层/01-IPv4首部与分段重组.md | 187 ++++++++++ hhs/NETWORK/03-网络层/02-CIDR与子网划分.md | 182 ++++++++++ .../03-网络层/03-IPv6地址与扩展头部.md | 165 +++++++++ hhs/NETWORK/03-网络层/04-ARP协议完整流程.md | 175 ++++++++++ .../03-网络层/05-ICMP与Ping-Traceroute.md | 189 +++++++++++ .../03-网络层/06-静态路由与默认网关.md | 166 +++++++++ .../03-网络层/07-动态路由协议OSPF-BGP.md | 200 +++++++++++ hhs/NETWORK/03-网络层/08-NAT原理与应用.md | 210 ++++++++++++ hhs/NETWORK/04-传输层/01-TCP段结构与状态机.md | 155 +++++++++ .../04-传输层/02-TCP三次握手与四次挥手.md | 199 +++++++++++ hhs/NETWORK/04-传输层/03-TCP可靠传输机制.md | 168 +++++++++ .../04-传输层/04-TCP流量控制与拥塞控制.md | 189 +++++++++++ hhs/NETWORK/04-传输层/05-TCP粘包与拆包.md | 222 ++++++++++++ hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md | 232 +++++++++++++ .../05-应用层协议/01-HTTP-1-1完全指南.md | 166 +++++++++ .../05-应用层协议/02-HTTP-2多路复用.md | 214 ++++++++++++ hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC.md | 175 ++++++++++ .../05-应用层协议/04-HTTPS与TLS握手.md | 183 ++++++++++ .../05-应用层协议/05-DNS与DHCP与WebSocket.md | 270 +++++++++++++++ hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md | 205 +++++++++++ .../01-SocketAPI与backlog详解.md | 177 ++++++++++ .../06-Socket编程/02-epoll深度解析ETvsLT.md | 298 ++++++++++++++++ .../06-Socket编程/03-零拷贝与GoNetpoller.md | 245 ++++++++++++++ .../01-连通性与状态探测工具.md | 224 ++++++++++++ .../02-抓包HTTPDNS诊断工具.md | 249 ++++++++++++++ hhs/NETWORK/08-网络安全/01-DDoS与MITM防御.md | 154 +++++++++ hhs/NETWORK/08-网络安全/02-SSRF与DNS安全.md | 217 ++++++++++++ .../08-网络安全/03-CSRFxss与XXE防御.md | 208 ++++++++++++ .../08-网络安全/04-JWT认证与TLS安全.md | 319 ++++++++++++++++++ .../09-性能优化/01-TCP内核调优与连接复用.md | 198 +++++++++++ .../02-CDN负载均衡与GoServer调优.md | 261 ++++++++++++++ .../10-前沿与进阶/01-QUIC协议深度解析.md | 185 ++++++++++ .../10-前沿与进阶/02-eBPF与ServiceMesh.md | 296 ++++++++++++++++ .../03-WireGuardVPN原理与实践.md | 182 ++++++++++ .../04-SDNSRv6与网络编程最佳实践.md | 267 +++++++++++++++ hhs/NETWORK/README.md | 268 +++++++++++++++ 45 files changed, 8983 insertions(+) create mode 100644 hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比.md create mode 100644 hhs/NETWORK/01-基础概念/02-数据封装与解封装.md create mode 100644 hhs/NETWORK/01-基础概念/03-寻址体系MAC-IPTPort.md create mode 100644 hhs/NETWORK/01-基础概念/04-带宽延迟RTT与吞吐量.md create mode 100644 hhs/NETWORK/02-链路层/01-Ethernet帧结构.md create mode 100644 hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避.md create mode 100644 hhs/NETWORK/02-链路层/03-MAC地址与广播域.md create mode 100644 hhs/NETWORK/02-链路层/04-Switch与路由器.md create mode 100644 hhs/NETWORK/02-链路层/05-VLAN与Trunk.md create mode 100644 hhs/NETWORK/03-网络层/01-IPv4首部与分段重组.md create mode 100644 hhs/NETWORK/03-网络层/02-CIDR与子网划分.md create mode 100644 hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部.md create mode 100644 hhs/NETWORK/03-网络层/04-ARP协议完整流程.md create mode 100644 hhs/NETWORK/03-网络层/05-ICMP与Ping-Traceroute.md create mode 100644 hhs/NETWORK/03-网络层/06-静态路由与默认网关.md create mode 100644 hhs/NETWORK/03-网络层/07-动态路由协议OSPF-BGP.md create mode 100644 hhs/NETWORK/03-网络层/08-NAT原理与应用.md create mode 100644 hhs/NETWORK/04-传输层/01-TCP段结构与状态机.md create mode 100644 hhs/NETWORK/04-传输层/02-TCP三次握手与四次挥手.md create mode 100644 hhs/NETWORK/04-传输层/03-TCP可靠传输机制.md create mode 100644 hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制.md create mode 100644 hhs/NETWORK/04-传输层/05-TCP粘包与拆包.md create mode 100644 hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md create mode 100644 hhs/NETWORK/05-应用层协议/01-HTTP-1-1完全指南.md create mode 100644 hhs/NETWORK/05-应用层协议/02-HTTP-2多路复用.md create mode 100644 hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC.md create mode 100644 hhs/NETWORK/05-应用层协议/04-HTTPS与TLS握手.md create mode 100644 hhs/NETWORK/05-应用层协议/05-DNS与DHCP与WebSocket.md create mode 100644 hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md create mode 100644 hhs/NETWORK/06-Socket编程/01-SocketAPI与backlog详解.md create mode 100644 hhs/NETWORK/06-Socket编程/02-epoll深度解析ETvsLT.md create mode 100644 hhs/NETWORK/06-Socket编程/03-零拷贝与GoNetpoller.md create mode 100644 hhs/NETWORK/07-网络工具与诊断/01-连通性与状态探测工具.md create mode 100644 hhs/NETWORK/07-网络工具与诊断/02-抓包HTTPDNS诊断工具.md create mode 100644 hhs/NETWORK/08-网络安全/01-DDoS与MITM防御.md create mode 100644 hhs/NETWORK/08-网络安全/02-SSRF与DNS安全.md create mode 100644 hhs/NETWORK/08-网络安全/03-CSRFxss与XXE防御.md create mode 100644 hhs/NETWORK/08-网络安全/04-JWT认证与TLS安全.md create mode 100644 hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用.md create mode 100644 hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优.md create mode 100644 hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析.md create mode 100644 hhs/NETWORK/10-前沿与进阶/02-eBPF与ServiceMesh.md create mode 100644 hhs/NETWORK/10-前沿与进阶/03-WireGuardVPN原理与实践.md create mode 100644 hhs/NETWORK/10-前沿与进阶/04-SDNSRv6与网络编程最佳实践.md create mode 100644 hhs/NETWORK/README.md diff --git a/hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比.md b/hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比.md new file mode 100644 index 0000000..afcc516 --- /dev/null +++ b/hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比.md @@ -0,0 +1,140 @@ +--- +tags: [计算机网络, OSI模型, TCP/IP, 网络分层] +create time: 2026-05-17 22:10 +--- + +# OSI 七层 vs TCP/IP 四层模型 + +## 概述 + +OSI(Open Systems Interconnection)和 TCP/IP 是理解计算机网络的两种经典分层模型。OSI 提供了一套理论化的参考框架,而 TCP/IP 则是实际运行在因特网上的协议栈。理解两者的映射关系,有助于我们在设计系统和排查问题时建立正确的抽象层次。 + +> [!QUESTION] 为什么现实用五层模型而不是 OSI? +> OSI 的七层理论很完美,但实现起来复杂到几乎不可能。TCP/IP 只有四层,又太粗。于是教学上采用折中的「五层模型」:把 OSI 的应用/表示/会话三层合并为应用层,保留传输层、网络层、链路层、物理层——刚好够表达所有关键概念。 + +## OSI 七层模型 + +### 分层结构 + +| 层级 | 名称 | PDU(协议数据单元) | 核心职责 | +|------|------|-----|---------| +| 7 | 应用层 | 数据(Data) | 为用户提供网络服务接口(HTTP、FTP、SMTP) | +| 6 | 表示层 | 数据 | 数据格式转换、加密解密、压缩解压 | +| 5 | 会话层 | 数据 | 建立/维护/终止会话(会话令牌、检查点) | +| 4 | 传输层 | 段(Segment) | 端到端可靠传输、流量控制、差错控制 | +| 3 | 网络层 | 包(Packet) | 路由选择、逻辑寻址(IP 地址) | +| 2 | 数据链路层 | 帧(Frame) | 物理寻址(MAC)、相邻节点间的帧传输 | +| 1 | 物理层 | 比特(Bit) | 电气特性、机械接口、光电信号转换 | + +```mermaid +graph BT + subgraph "传输中封装" + D["应用层 Data"] --> L6["表示层: 加密+压缩"] + L6 --> L5["会话层: 建立/维护/结束会话"] + L5 --> S["传输层 Segment"] + S --> P["网络层 Packet"] + P --> F["链路层 Frame"] + F --> B["物理层 Bitstream"] + end + + style D fill:#DDA0DD + style S fill:#FFD700 + style F fill:#98FB98 + style B fill:#B0C4DE +``` + +### 每层经典协议对照表 + +| 层 | 协议 | 作用场景 | +|----|------|---------| +| 应用层 | HTTP, DNS, SMTP, FTP, SSH, WebSocket | 应用间通信 | +| 表示层 | TLS/SSL, JPEG, MPEG, ASN.1 | 数据格式化与加密 | +| 会话层 | NetBIOS, RTP, SIP | 多路复用与同步 | +| 传输层 | TCP, UDP, SCTP | 端到端数据传输 | +| 网络层 | IP, ICMP, IGMP, ARP* | 跨网段路由转发 | +| 链路层 | Ethernet, Wi-Fi (802.11), VLAN, PPP | 相邻设备帧传输 | +| 物理层 | USB, RS-232, 光纤, Cat6 网线 | 物理信号传输 | + +> [!note] ARP 到底在第几层? +> ARP 将 IP 地址解析为 MAC 地址,跨越了网络层和链路层的边界。OSI 将其放在**网络层下方、链路层上方**的"夹层"位置。现代观点常把它视为网络层的一部分。 + +## TCP/IP 四层模型 + +### 分层结构 + +| 层级 | 对应 OSI | 核心协议 | 核心职责 | +|------|----------|---------|---------| +| 应用层 | 7+6+5 层合并 | HTTP, DNS, SMTP, SSH | 面向应用的协议栈 | +| 传输层 | 4 层 | TCP, UDP | 端到端连接管理 | +| 网络层 | 3 层 | IP, ICMP, ARP | 分组路由与寻址 | +| 网络接口层 | 2+1 层合并 | Ethernet, Wi-Fi | 帧传输与物理介质 | + +### 为何 OSI 没赢下标准之战 + +```mermaid +flowchart LR + A["OSI 七层模型
ISO 标准"] --> B{谁先落地?} + B -->|"1970s 末"| C["TCP/IP 已部署在 ARPANET"] + B -->|"1980s 后"| D["OSI 标准太多太复杂"] + C --> E["事实标准 ✅"] + D --> E + style E fill:#98FB98,color:#000 +``` + +- **先发优势**:ARPANET / Internet 先用上了 TCP/IP,基础设施已经建成 +- **简单粗暴**:TCP/IP 只关心能不能跑通,不搞复杂的标准化流程 +- **开源免费**:Berkeley BSD Unix 内嵌 TCP/IP 协议栈,免费分发 +- **OSI 的反面教材**:层层审批导致协议膨胀(X.400 邮件、X.25 分组交换),开发者根本懒得用 + +## 五层教学模型 + +这是教材中最常用的模型,取两者精华: + +```mermaid +flowchart TD + A["应用层 — Application
HTTP DNS SMTP SSH FTP SMTP POP3 IMAP WebSocket"] --> T["传输层 — Transport
TCP UDP SCTP"] + T --> N["网络层 — Internet
IP IPv6 ICMP ARP NAT"] + N --> D["链路层 — Link
Ethernet Wi-Fi 802.11 VLAN HDLC PPP"] + D --> P["物理层 — Physical
双绞线 光纤 无线电 IEEE 802.15"] + style A fill:#DDA0DD + style T fill:#FFD700 + style N fill:#87CEEB + style D fill:#98FB98 + style P fill:#B0C4DE +``` + +## 数据包逐层穿越过程 + +以你发送一封电子邮件为例: + +```mermaid +sequenceDiagram + participant App as 你的邮件客户端 + participant T as TCP层 + participant I as IP层 + participant L as 以太网层 + participant Router as 路由器 + participant R as 接收方 + + App->>T: SMTP 邮件内容 (Data) + T->>T: 加 TCP 首部 (Header + Data → Segment) + T->>I: 传递 Segment + I->>I: 加 IP 首部 (Header + Segment → Packet) + I->>L: 传递 Packet + L->>L: 加以太网帧头帧尾 (Header + Packet + FCS → Frame) + L->>Router: 通过网线发送 Frame + Router->>Router: 拆帧 → 查路由表 → 重新封帧 + Router->>R: 转发给接收方 + R->>L: 解包 → IP层校验 → TCP重组 → 提取 SMTP + R->>App: 邮件到达收件箱 +``` + +> [!tip] 封装 = 套娃;解封装 = 剥壳 +> 每一层只知道自己那一层的 header 是什么格式,对其他层的内容一无所知。这就是**层间透明**原则。 + +## 关联笔记 + +- [[hhs/NETWORK/02-以太网帧结构]] — 链路层帧的详细结构 +- [[hhs/NETWORK/03-IPv4协议详解]] — 网络层 IP 数据包格式 +- [[hhs/NETWORK/04-TCP状态机详解]] — 传输层 TCP 连接管理 +- [[hhs/NETWORK/05-HTTP请求响应]] — 应用层协议细节 diff --git a/hhs/NETWORK/01-基础概念/02-数据封装与解封装.md b/hhs/NETWORK/01-基础概念/02-数据封装与解封装.md new file mode 100644 index 0000000..8d01523 --- /dev/null +++ b/hhs/NETWORK/01-基础概念/02-数据封装与解封装.md @@ -0,0 +1,126 @@ +--- +tags: [计算机网络, 数据封装, 网络分层] +create time: 2026-05-17 22:20 +--- + +# 数据封装与解封装 + +## 概述 + +数据在发送端逐层加上头部(Header),就像装快递盒子:最里层是商品,外面一层一层套纸箱、贴面单、打托盘。到达接收端后,每一层剥掉自己的那层包装,只把原始内容交给上层处理。这个"套娃+拆箱"的过程就是 **封装(Encapsulation)** 和 **解封装(Decapsulation)**。 + +> [!QUESTION] 为什么每层都要加头? +> 因为每层需要知道如何管理自己的事务——传输层关心连接状态,网络层关心路由寻址,链路层关心物理介质。这些元信息必须跟在数据前面,让每一层都能正确解读并转发。 + +## 数据包尺寸变化轨迹 + +以发送一个 HTTP GET 请求为例,追踪尺寸变化: + +| 层级 | 操作 | 数据结构 | 新增首部大小 | 累计大小 | +|------|------|---------|-------------|---------| +| 应用层 | HTTP GET 报文 | `GET / HTTP/1.1\r\nHost: example.com\r\n\r\n` | — | ~40 bytes | +| 传输层 (TCP) | + TCP Header | Segment | 20 bytes | ~60 bytes | +| 网络层 (IP) | + IP Header | Packet | 20 bytes (IPv4) | ~80 bytes | +| 链路层 (Ethernet) | + Eth Header + FCS | Frame | 14 + 4 = 18 bytes | ~98 bytes | + +```mermaid +flowchart LR + H["HTTP GET 40B"] --> T["+ TCP Header 20B
Segment 60B"] + T --> I["+ IP Header 20B
Packet 80B"] + I --> E["+ Ethernet HDR 14B + FCS 4B
Frame 98B"] + E --> B["98 个 Bit 在网线上传输"] + + style H fill:#DDA0DD + style T fill:#FFD700 + style I fill:#87CEEB + style E fill:#98FB98 +``` + +## 发送端:逐层封装 + +```mermaid +sequenceDiagram + participant App as HTTP 应用 + participant TCP as TCP Socket + participant IP as 网络层 IP + participant ETH as 以太网驱动 + + App->>TCP: write("GET / HTTP/1.1...") + Note over TCP: 分配 seq/ack, state=ESTABLISHED + TCP->>IP: 传递 Segment (daddr=x.x.x.x, sport=1234, dport=80) + Note over IP: 查路由表 → 下一跳网关 + IP->>ETH: 传递 Packet (dst_mac=?, src_ip=192.168.1.100, dst_ip=93.184.216.34) + Note over ETH: ARP 解析 → dst_mac=aabb.ccdd.eeff + ETH->>ETH: 拼接 源MAC+目的MAC+EtherType+Payload+FCS + ETH-->>App: sendto() 返回 → 帧已发出 ✅ +``` + +### Ethernet II 帧完整结构 + +``` +Offset Size Field 说明 +────── ──── ───────────────── ───────────────────────────── +0 6 Dest MAC Address 目标 MAC 地址 +6 6 Source MAC Address 源 MAC 地址 +12 2 EtherType 0x0800=IPv4 / 0x86DD=IPv6 / 0x0806=ARP +14 N Payload IP 包(最大 1500 字节) +14+N 4 FCS CRC-32 帧校验序列(接收端自动验证) +``` + +总长度范围:**64 ~ 1518 bytes**(不含 VLAN Tag;有 VLAN Tag 则上限 1522)。 + +> [!note] MTU = 1500 是什么意思? +> MTU(Maximum Transmission Unit)是链路层允许的最大 Payload 大小,即 IP 包的最大体积。超过会被分片(Fragmentation),但分片会降低性能且增加丢包风险——这也是现代应用层(如 TLS、gRPC)倾向于小包传输的原因。 + +## 接收端:逐层解封装 + +```mermaid +sequenceDiagram + participant ETH as 网卡收到 Frame + participant IP as 剥离以太网头 + participant TCP as 剥离 TCP 头 + participant App as 提取 HTTP 数据 + + ETH->>IP: 校验 FCS OK → 取 EtherType=0x0800 → 剥离以太网头 + IP->>TCP: 校验 IP Checksum → 取 Protocol=6(TCP) → 剥离 IP 头 + TCP->>App: 按 Sequence Number 重组 → 剥离 TCP 头 + App->>App: 解析 "GET / HTTP/1.1..." 🎉 +``` + +每层的动作可以概括为三步: + +1. **检查**:校验和是否正确?mac/ip/port 是否指向自己? +2. **剥离**:去掉本层的 Header(必要时 Tail 如 FCS) +3. **移交**:将剩余的 Payload 交给上层协议处理 + +## 各层 PDU 对照表 + +| OSI 名称 | PDU | TCP/IP 对应 | 关键标识字段 | +|----------|-----|-------------|-------------| +| 数据 (Data) | 报文段 | 应用层消息 | — | +| 段 (Segment) | TCP Segment | 传输层 PDU | 源端口 + 目的端口 | +| 包 (Packet) | IP Datagram | 网络层 PDU | 源 IP + 目的 IP + Protocol | +| 帧 (Frame) | Ethernet Frame | 链路层 PDU | 源 MAC + 目的 MAC + EtherType | +| 比特流 (Bits) | Bitstream | 物理层信号 | 电信号 / 光脉冲 | + +## NAT 场景下的封装变化 + +NAT 路由器修改了中间层的地址: + +``` +发送端内部主机 NAT 路由器 外部 Web 服务器 +┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐ +│ Src IP: 10.0.0.5:45678 │ │ SNAT: 改写 Src IP │ │ Dst IP: 203.0.113.1:80 │ +│ Dst IP: 203.0.113.1:80 │ → │ │ → │ │ +│ │ │ → Src IP: 8.8.8.8:45678 │ │ ← Src IP: 203.0.113.1:80 │ +│ │ │ → Dst IP: 203.0.113.1:80 │ │ → Src IP: 10.0.0.5:45678 │ +└──────────────────────┘ └──────────────────────┘ └──────────────────────┘ +``` + +注意:NAT 不触碰应用层载荷,只是在中途修改了 IP 和 TCP 端口。这也意味着 **传输层的 Checksum 需要重新计算**。 + +## 关联笔记 + +- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 分层模型的起源与对比 +- [[hhs/NETWORK/IPv4协议详解]] — IP 包的详细格式 +- [[hhs/NETWORK/TCP状态机详解]] — TCP 段的结构与状态管理 diff --git a/hhs/NETWORK/01-基础概念/03-寻址体系MAC-IPTPort.md b/hhs/NETWORK/01-基础概念/03-寻址体系MAC-IPTPort.md new file mode 100644 index 0000000..b7b50a3 --- /dev/null +++ b/hhs/NETWORK/01-基础概念/03-寻址体系MAC-IPTPort.md @@ -0,0 +1,169 @@ +--- +tags: [计算机网络, MAC地址, IP地址, 端口号] +create time: 2026-05-17 22:30 +--- + +# 寻址体系:MAC / IP / Port + +## 概述 + +网络中的每个端点需要三层地址才能精确定位到一个进程:**MAC 地址**(物理层/链路层)定位到哪台设备、**IP 地址**(网络层)定位到哪个子网、**端口号**(传输层)定位到哪个进程。三层地址组合起来,就是经典的 `ip:port` 定位公式。 + +> [!QUESTION] 为什么需要三种地址?一层不够吗? +> 每种地址解决不同尺度的问题。MAC 在同一个广播域内有效,但路由器不转发广播——跨网段必须用 IP 路由。IP 能到达目标主机,但不区分进程——同一台服务器可能跑着 Web、数据库、邮件等多个服务,所以需要端口来分发。 + +## 一、MAC 地址(48 位) + +### 基本结构 + +``` +前 3 字节 后 3 字节 +───────────── ───────────── +OUI (厂商编号) NIC 序列号 +由 IEEE 分配 由厂商自定 +``` + +示例:`aabb.ccdd.eeff` = `AA:BB:CC:DD:EE:FF` +- 前三个字节 `AA:BB:CC` → OUI,代表厂商 +- 后三个字节 `DD:EE:FF` → 设备唯一序列 + +### MAC 地址类型 + +| 类型 | 格式 | 含义 | +|------|------|------| +| 单播 | LSB=0 | 发给单个设备 | +| 广播 | `ff:ff:ff:ff:ff:ff` | 发给局域网内所有设备 | +| 组播 | LSB=1 | 发给一组设备 | + +### ARP 地址解析 + +当知道一个主机的 **IP 地址** 时,需要通过 ARP 协议找到它的 **MAC 地址**。 + +```mermaid +sequenceDiagram + participant Src as 源主机
192.168.1.10/?.mac + participant Bcast as 局域网广播 + participant Dst as 目标主机
192.168.1.20/aa:bb:cc:11:22:33 + participant Cache as 源主机 ARP 缓存 + + Src->>Bcast: ARP Request "192.168.1.20 是谁?→ ff:ff:ff:ff:ff:ff" + Dst-->>Src: ARP Reply "我是!MAC = aa:bb:cc:11:22:33" + Src->>Cache: 写入缓存条目 (TTL ≈ 15~30 分钟) + Note over Src,Dst: 之后的通信直接用此 MAC,不再发 ARP +``` + +### ARP 缓存查看与管理 + +```bash +# Linux +$ ip neigh show # 等价于 arp -n +192.168.1.20 dev eth0 lladdr aa:bb:cc:dd:ee:ff STALE + +$ ip neigh del 192.168.1.20 dev eth0 # 删除缓存条目 + +# macOS / Windows +$ arp -a +``` + +## 二、IP 地址(IPv4 32 位 / IPv6 128 位) + +### IPv4 地址结构 + +``` +版本(4 bits) IHL(4 bits) DSCP(8 bits) Total Length(16 bits) +... +Identification(16 bits) | DF/MF | Fragment Offset(13 bits) +TTL(8 bits) | Protocol(8 bits) | Header Checksum(16 bits) +Source Address(32 bits) +Destination Address(32 bits) +Options (可选)... +``` + +### IPv4 vs IPv6 + +| 特性 | IPv4 | IPv6 | +|------|------|------| +| 地址长度 | 32 bit (4 字节) | 128 bit (16 字节) | +| 地址数量 | ~43 亿 (2³²) | ~3.4×10³⁸ (2¹²⁸) | +| 表示法 | `192.168.1.1` | `2001:0db8::1` | +| 首部固定长度 | 20 bytes(可变选项更长) | 40 bytes(固定) | +| Checksum | ✅ 有 | ❌ 无(依赖上层校验) | +| NAT | 广泛使用(缓解地址耗尽) | 不需要(地址充足) | +| SLAAC | — | 支持无状态自动配置 | +| 组播 | 有限支持 | 原生支持 | + +### IP 地址分类(历史) + +| 类别 | 首字节范围 | 默认掩码 | 可用主机数 | 用途 | +|------|-----------|---------|-----------|------| +| A | 1–126 | /8 | 16,777,214 | 大型组织 | +| B | 128–191 | /16 | 65,534 | 中型组织 | +| C | 192–223 | /24 | 254 | 小型网络 | +| D | 224–239 | — | — | 组播 | +| E | 240–255 | — | — | 保留 | + +> [!warning] CIDR 已淘汰分类编址 +> 现代网络全部使用 **CIDR(Classless Inter-Domain Routing)** 无类编址,子网掩码可以是任意位数(如 /23、/27)。 + +## 三、端口号(16 位) + +### 端口范围 + +| 范围 | 名称 | 说明 | +|------|------|------| +| 0–1023 | 熟知端口 (Well-Known) | 预分配给标准服务(HTTP:80, HTTPS:443, SSH:22, DNS:53) | +| 1024–49151 | 注册端口 (Registered) | 申请注册的商业软件 | +| 49152–65535 | 动态端口 (Ephemeral) | 客户端临时分配,用完即释放 | + +### 服务端 vs 客户端端口 + +```mermaid +flowchart LR + Client["客户端 192.168.1.100:54321"] -->|"SYN"| Server["服务端 10.0.0.1:80"] + Server -->|"SYN-ACK"| Client + Client -->|"ACK"| Server + + style Client fill:#DDA0DD,color:#000 + style Server fill:#98FB98,color:#000 +``` + +- **服务端端口**:固定且知名(如 80),告诉客户端"我在哪" +- **客户端端口**:操作系统动态分配(49152–65535),保证连接的唯一标识 `(src_ip, src_port, dst_ip, dst_port)` + +### 常见端口速查 + +| 端口 | 协议 | 服务 | +|------|------|------| +| 21 | FTP | 文件传输控制 | +| 22 | SSH | 安全远程登录 | +| 25 | SMTP | 邮件发送 | +| 53 | DNS | 域名解析 | +| 80 | HTTP | Web 网站 | +| 443 | HTTPS | 加密 Web | +| 993 | IMAPS | 加密邮件收取 | +| 3306 | MySQL | 数据库 | +| 5432 | PostgreSQL | 数据库 | +| 6379 | Redis | 缓存 | +| 8080 | HTTP Alt | Web 备用端口 | + +## 四、完整寻址链:从请求到进程 + +```mermaid +flowchart TD + S["你在浏览器输入 example.com"] -->|"DNS 查询"| A["DNS 返回 93.184.216.34"] + A -->|"TCP 三次握手"| B["建立 TCP 连接
src:192.168.1.100:54321 → dst:93.184.216.34:80"] + B -->|"以太网帧头"| C["Dst MAC: router的出口MAC
Src MAC: 本机MAC"] + C -->|"ARP缓存"| D["如果没缓存,先发ARP请求"] + D -->|"发送 HTTP GET"| E["Web 服务器的 PID 1234
nginx worker 进程读取数据"] + + style S fill:#DDA0DD,color:#000 + style E fill:#98FB98,color:#000 +``` + +**结论:** 三层地址缺一不可——没有 IP 找不到机器,没有 MAC 连不上网线,没有端口不知道该交给哪个进程。 + +## 关联笔记 + +- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 三层地址对应 OSI 的不同层级 +- [[hhs/NETWORK/IPv4协议详解]] — IP 地址的详细编码与 CIDR 计算 +- [[hhs/NETWORK/TCP状态机详解]] — 四元组如何唯一标识 TCP 连接 diff --git a/hhs/NETWORK/01-基础概念/04-带宽延迟RTT与吞吐量.md b/hhs/NETWORK/01-基础概念/04-带宽延迟RTT与吞吐量.md new file mode 100644 index 0000000..817f3e7 --- /dev/null +++ b/hhs/NETWORK/01-基础概念/04-带宽延迟RTT与吞吐量.md @@ -0,0 +1,165 @@ +--- +tags: [计算机网络, 带宽, 延迟, RTT, 吞吐量, BDP] +create time: 2026-05-17 22:40 +--- + +# 带宽、延迟、RTT、吞吐量 + +## 概述 + +理解网络性能的核心指标,是优化应用的前提。带宽决定"管道有多粗",延迟决定"跑一程要多久",RTT 是往返一次的时间,而吞吐量是实际能搬多少数据。它们之间的关系经常影响系统架构设计。 + +## 核心定义 + +### 带宽(Bandwidth) + +单位时间内能传输的**最大比特数**,通常用 bps(bits per second)表示: + +``` +1 Gbps = 10⁹ bits/s = 125 MB/s(注意:bit vs Byte) +``` + +| 链路类型 | 理论带宽 | 实际可用 | +|----------|---------|---------| +| 千兆以太网 (GbE) | 1 Gbps | ~940 Mbps(协议开销) | +| Wi-Fi 6 (802.11ax) | 9.6 Gbps | ~1.2–2 Gbps(理论峰值) | +| SATA III | 6 Gbps | 600 MB/s 实际 | +| PCIe 4.0 x16 | 32 Gbps | ~3.9 GB/s | + +> [!tip] bit vs Byte:记住除 8 +> 运营商说的 "100M 宽带" 是 **100 Mbps**。换算成下载速度约 `100 ÷ 8 ≈ 12.5 MB/s`。 + +### 延迟(Latency / Propagation Delay) + +信号从发送端传播到接收端所需的时间,仅取决于**物理距离和介质光速**: + +$$\text{Prop. Delay} = \frac{\text{Distance}}{v}$$ + +其中 $v$ 是传播速度。光纤中光速约为 $2 \times 10^8$ m/s(真空中光速的 2/3)。 + +| 场景 | 典型延迟 | +|------|---------| +| 同机房内 | 0.1–1 ms | +| 同城(同一城市数据中心) | 1–5 ms | +| 跨省(国内) | 20–40 ms | +| 跨洋(中美) | 80–150 ms | +| 卫星轨道(LEO) | 20–40 ms | +| TCP 握手 + TLS 1.3 | 1~2 RTT(额外 RTT × 加密计算时间) | + +### RTT(Round-Trip Time) + +从发送方发出数据包到收到确认应答的总时间。**RTT = 2 × 单向延迟 + 排队处理时间 + 传输时间**。 + +```mermaid +flowchart LR + A["t=0ms: 发送 SYN"] -->|"传播+路由器处理"| B["t=5ms: 到达服务端"] + B -->|"处理+准备响应"| C["t=5.1ms: 服务端回发 SYN-ACK"] + C -->|"传播+路由器处理"| D["t=10.2ms: 客户端收到 → RTT = 10.2ms"] + + style A fill:#DDA0DD,color:#000 + style D fill:#98FB98,color:#000 +``` + +### 吞吐量(Throughput) + +单位时间内**实际成功传输的数据量**,受限于最窄环节: + +$$\text{Throughput} = \min(\text{带宽}, \text{拥塞窗口限制}, \text{应用处理能力})$$ + +实际吞吐量永远 ≤ 带宽。常见瓶颈: + +```mermaid +flowchart LR + A["磁盘IO
可能 < 网卡"] --> B["TCP收发缓冲区"] + B --> C["网络带宽"] + C --> D["对端带宽"] + D --> E["对端CPU/应用"] + + F["中间路由拥塞"] -.-> C + G["丢包重传"] -.-> C + + style C fill:#FFD700,color:#000 + style F fill:#FF6B6B,color:#fff + style G fill:#FF6B6B,color:#fff +``` + +> [!question] 为什么我的千兆网卡只跑到 100MB/s? +> 因为千兆以太网理论上限是 `1Gbps ÷ 8 = 125MB/s`,去掉以太网帧头、IP 头、TCP 头等协议开销后,纯 Payload 大约 **94 MB/s**。如果还用了 HTTPS/TLS,CPU 加解密还会进一步限制吞吐量。 + +## 带宽时延积(BDP) + +**BDP(Bandwidth-Delay Product)** 定义了链路上"在途"数据的最大量——这是维持满带宽所需的发送缓冲大小: + +$$\text{BDP} = \text{Bandwidth} \times \text{RTT}$$ + +### 实际意义 + +假设带宽 1 Gbps,RTT = 100 ms: + +$$\text{BDP} = 10^9 \text{ bps} \times 0.1s = 10^8 \text{ bits} = 12.5 \text{ MB}$$ + +这意味着: +- TCP 发送窗口至少要有 **12.5 MB**,才能填满这条管道 +- 如果窗口太小,TCP 会频繁停下来等 ACK,吞吐量上不去 +- 这就是 Linux 的 `net.core.rmem_max` / `wmem_max` 要调大的原因 + +```mermaid +flowchart TD + Pipe["管道: 1Gbps, RTT=100ms
BDP = 12.5MB"] + + Small["发送窗口 1MB ❌
只能填 8% 管道 → 吞吐量 ≈ 80Mbps"] + Big["发送窗口 25MB ✅
填满管道 → 吞吐量 ≈ 1Gbps"] + + Pipe --> Small + Pipe --> Big + + style Small fill:#FF6B6B,color:#fff + style Big fill:#98FB98,color:#000 +``` + +### 各场景 BDP 速查 + +| 链路 | 带宽 | RTT | BDP | +|------|------|-----|-----| +| 局域网 | 1 Gbps | 0.5 ms | 62.5 KB | +| 同机房 | 10 Gbps | 1 ms | 1.25 MB | +| 同省 | 1 Gbps | 20 ms | 2.5 MB | +| 跨洋 | 100 Mbps | 150 ms | 1.875 MB | + +## 延迟 vs 带宽的典型对比 + +| 场景 | 带宽瓶颈还是延迟瓶颈? | 说明 | +|------|---------------------|------| +| 大文件传输 | 带宽 | 传输时间长,RTT 占比小 | +| 小请求/高频交互(RPC) | 延迟 | 每个请求 2~3 RTT,带宽几乎不占 | +| 视频流 | 带宽 | 持续大量数据传输 | +| DNS 查询 | 延迟 | 单次查询仅几十字节 | +| Web 首屏加载 | 两者兼有 | TCP+TLS握手(延迟)+ HTML/CSS/JS 下载(带宽) | + +## Go 中的实践示例 + +```go +// 设置 TCP 连接保持活跃,定期检测死链接 +tcp := net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + return c.Control(func() { + // 开启 keepalive,每 30s 探测一次 + syscall.SetsockoptInt(int(c.Fd()), syscall.SOL_SOCKET, + syscall.SO_KEEPALIVE, 1) + }) + }, +} + +// HTTP Transport 的连接池配置 +transport := &http.Transport{ + MaxIdleConns: 100, // 最多空闲连接数 + MaxIdleConnsPerHost: 10, // 每个 host 最多 10 个空闲 + IdleConnTimeout: 90 * time.Second, // 空闲超时释放 +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 网络分层中各层的角色 +- [[hhs/NETWORK/TCP状态机详解]] — TCP 的拥塞控制直接影响吞吐量 +- [[hhs/NETWORK/TCP内核参数调优]] — BDP 对应内核参数 rmem/wmem diff --git a/hhs/NETWORK/02-链路层/01-Ethernet帧结构.md b/hhs/NETWORK/02-链路层/01-Ethernet帧结构.md new file mode 100644 index 0000000..0636994 --- /dev/null +++ b/hhs/NETWORK/02-链路层/01-Ethernet帧结构.md @@ -0,0 +1,104 @@ +--- +tags: [计算机网络, Ethernet, MAC, 帧结构, 数据链路层] +create time: 2026-05-17 22:50 +--- + +# Ethernet 帧结构 + +## 概述 + +Ethernet(以太网)是目前最主流的局域网技术,由 Xerox、DEC 和 Intel 在 1980 年联合制定(IEEE 802.3)。理解 Ethernet 帧结构是掌握网络底层通信的基础——每一帧都包含了足够的信息来让网卡判断"这是给我的吗?内容完整吗?上层协议是什么?" + +## Ethernet II 帧格式(最常见) + +``` +┌──────────┬──────────┬──────────┬─────────────┬──────────┐ +│ Dest MAC │ Src MAC │ EtherType│ Payload │ FCS │ +│ 6 bytes │ 6 bytes │ 2 bytes │ 46–1500 B │ 4 bytes │ +│ aabb.ccdd│ dd.eeff. │ 0x0800 │ IP Packet │ crc32() │ +│ ee.ff.00│ │ │ │ │ +└──────────┴──────────┴──────────┴─────────────┴──────────┘ + ←──────── 14 bytes Header ───────→ ← Tail → + ←──── Min Frame = 64B ────→ + ←──── Max Frame = 1518B ────→ +``` + +### 字段详解 + +| 字段 | 大小 | 说明 | +|------|------|------| +| Dest MAC | 6 bytes | 目标 MAC 地址,`ff:ff:ff:ff:ff:ff` 表示广播 | +| Src MAC | 6 bytes | 源 MAC 地址,发出方的物理地址 | +| EtherType | 2 bytes | 上一层协议类型:`0x0800`=IPv4, `0x86DD`=IPv6, `0x0806`=ARP | +| Payload | 46–1500 bytes | 承载的上层数据包。**最小 46 字节**,不足则填充(Padding) | +| FCS | 4 bytes | CRC-32 校验和,网卡硬件自动计算,接收端验证错误则直接丢弃 | + +### 为什么 Payload 最小 46 字节? + +最早的以太网最大电缆长度 2500m,信号传播延迟需要至少 64 字节才能保证碰撞检测(CSMA/CD)生效。所以规定**整个帧最小 64 字节**,减去 14 字节头部和 4 字节 FCS,Payload 最少 46 字节。短包会补零填满。 + +现代 Gigabit+ 网卡已不用 CSMA/CD,但为了兼容仍保留此限制。 + +## IEEE 802.3 / 802.3Q(VLAN Tagged)帧 + +当网络中使用 VLAN 时,帧中会插入一个 4 字节的 802.1Q Tag: + +``` +┌──────────┬──────────┬──────┬──────┬──────────┬─────────────┬──────────┐ +│ Dest MAC │ Src MAC │ Type │ TPID │ TCI/VID │ Payload │ FCS │ +│ 6 bytes │ 6 bytes │ 2B │2B │ 2B │ 46–1500 B │ 4 bytes │ +└──────────┴──────────┴──────┴──────┴──────────┴─────────────┴──────────┘ +``` + +新增字段: +- **TPID** (Tag Protocol Identifier): `0x8100` — 标识这是带 VLAN Tag 的帧 +- **TCI** (Tag Control Information): 包含 PCP(优先级,3 bit)、CFI(规范格式指示符,1 bit)、**VID**(VLAN ID,12 bit) + +VID 取值范围 1–4094,共 4094 个可用 VLAN。 + +## 常见 EtherType 对照表 + +| EtherType | 十六进制 | 协议 | +|-----------|---------|------| +| IPv4 | `0x0800` | IP 数据包 | +| IPv6 | `0x86DD` | IPv6 数据包 | +| ARP | `0x0806` | 地址解析协议 | +| RARP | `0x8035` | 反向 ARP(历史遗留) | +| PPPoE | `0x8864` | PPP over Ethernet | +| 802.1Q | `0x8100` | VLAN 标记 | +| LACP | `0x8809` | 链路聚合控制协议 | + +## MAC 地址空间与 OUI + +### OUI(Organizationally Unique Identifier) + +MAC 地址的前三个字节由 **IEEE** 统一分配给厂商,称为 OUI。查询 OUI 可以知道网卡的制造商: + +```bash +$ ip link show +link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff + ↑ 前三个字节 aa:bb:cc 就是 OUI + +# 在线查询:https://www.wireshark.org/tools/oui-lookup.html +# Linux 也可以用 ethtool -P eth0 查看出厂 MAC +``` + +### 单播/多播位(LSB of First Byte) + +第一个字节的最低比特位决定寻址类型: + +| LSB | 类型 | 示例 | +|-----|------|------| +| 0 | 单播 (Unicast) | `aa:bb:cc:dd:ee:ff` | +| 1 | 多播 (Multicast) | `01:00:5e:xx:xx:xx`(IPv4 组播) | +| 1 | 广播 (Broadcast) | `ff:ff:ff:ff:ff:ff` | + +> [!tip] 如何快速判断?看 MAC 第一个字节的十六进制最后一位 +> - `a` → 1010 → LSB=0 → 单播 +> - `3` → 0011 → LSB=1 → 多播/广播 + +## 关联笔记 + +- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 以太网属于链路层 +- [[hhs/NETWORK/MAC地址与广播域]] — MAC 地址的工作范围 +- [[hhs/NETWORK/Switch与路由器]] — Switch 如何使用 MAC 表转发帧 diff --git a/hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避.md b/hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避.md new file mode 100644 index 0000000..03bd84d --- /dev/null +++ b/hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避.md @@ -0,0 +1,138 @@ +--- +tags: [计算机网络, CSMA-CD, 以太网退避, 冲突检测] +create time: 2026-05-17 23:00 +--- + +# CSMA/CD 与以太网退避 + +## 概述 + +CSMA/CD(Carrier Sense Multiple Access / Collision Detection,载波监听多路访问/冲突检测)是早期共享式以太网的 MAC 子层协议。虽然现代交换式以太网已不再使用它,但理解其原理对掌握网络演进和碰撞域概念至关重要。 + +> [!QUESTION] 为什么需要 CSMA/CD? +> 在共享介质(如同轴电缆或 Hub 集线器)上,多台设备共用同一根网线。如果没有协调机制,两台同时发送会导致信号叠加破坏——就像两个人同时在同一频道说话,谁也听不清。 + +## CSMA/CD 工作流程 + +```mermaid +flowchart TD + Start["有帧要发送"] --> Wait["等待信道空闲?"] + Wait -->|"空闲"| Send["开始发送"] + Wait -->|"忙碌"| Wait + Send --> Check{"是否在 512bit
时间内检测到冲突?"} + Check -->|"否"| Done["发送完成 ✅"] + Check -->|"是"| Collide["发生冲突 → 发送 Jam Signal"] + Collide --> Backoff["指数退避算法计算等待时间"] + Backoff --> Retry{"重传次数 < 16?"} + Retry -->|"是"| Wait + Retry -->|"否"| Fail["丢弃帧,上报错误 ❌"] + + style Done fill:#98FB98,color:#000 + style Fail fill:#FF6B6B,color:#fff +``` + +### 四个步骤详解 + +| 步骤 | 英文名称 | 说明 | +|------|---------|------| +| 1 | **Carrier Sense** | 发送前先监听:信道忙就等,空闲才发 | +| 2 | **Multiple Access** | 多个设备共享同一介质 | +| 3 | **Collision Detection** | 发送过程中持续监测是否有冲突信号 | +| 4 | **Collision Handling** | 检测到冲突 → 发 Jam Signal → 执行退避 → 重试 | + +## 二进制指数退避算法 + +冲突后的等待时间通过随机退避来避免再次冲突: + +$$\text{等待时间} = \min(r, 2^{10}-1) \times 512 \text{ bit-time}$$ + +其中 $r$ 为重传次数,`512 bit-time` 是最小帧长对应的传输时间(即争用期)。 + +```mermaid +flowchart TD + k["重传次数 k"] --> r["r = random(0 ~ 2^k - 1)"] + r --> capped["capped at 2^10 - 1 = 1023 (k > 10)"] + capped --> backoff["backoff_time = r × 512 bit-time"] + + style k fill:#DDA0DD,color:#000 + style backoff fill:#98FB98,color:#000 +``` + +### 退避表 + +| 重传次数 k | 窗口大小 2^k - 1 | 可能等待的 slot 数范围 | +|-----------|------------------|---------------------| +| 1 | 1 | {0, 1} | +| 2 | 3 | {0, 1, 2, 3} | +| 3 | 7 | {0..7} | +| 4 | 15 | {0..15} | +| 5–10 | 最大值 1023 | {0..1023} | +| >10 | 仍为 1023 | 上限固定 | + +如果重传 16 次仍然失败,帧被丢弃并向上层报告错误。 + +### 示例 + +假设节点 A 和 B 同时发送,发生冲突: + +``` +时刻 0ms: A 和 B 同时发送 → 冲突! + 两者都发送 32bit Jam Signal +时刻 0.1ms: A 选 r=3 → 等 3×512bit-time = 3×51.2μs ≈ 154μs + B 选 r=1 → 等 1×51.2μs ≈ 51μs +时刻 0.05ms: B 先等完,先尝试发送 → A 还在等 +时刻 0.15ms: A 等完,发现信道已被 B 占用 → 退避重选 r +... +``` + +## 最小帧长与争用期 + +### 为什么最小帧是 64 字节? + +关键公式:**帧传输时间 ≥ 2 × 端到端传播延迟** + +如果帧太短,可能在冲突信号到达之前就已经发完了——发送方根本不知道发生过冲突。 + +```mermaid +sequenceDiagram + participant A as 节点 A + participant H as 最远节点 H + Note over A,H: 传播延迟 = τ + + A->>H: 开始发送一个超短帧 (长度 < 2τ) + H->>A: 冲突信号沿原路返回 + Note over A: 此时 A 已经发完了...
没检测到冲突!🔥 +``` + +所以规定: + +$$\text{最小帧长} \geq 2 \times \tau \times \text{带宽}$$ + +对于经典 10Mbps 以太网,最长电缆段 2500m,$\tau \approx 12.5\mu s$: + +$$\text{最小帧长} \geq 2 \times 12.5\mu s \times 10^7 \text{ bps} = 250,000 \text{ bits?}$$ + +等等,实际工程中使用了中继器和更短的网段组合,最终标准定为 **512 bit-time = 64 bytes**。 + +## 现代以太网中的命运 + +| 年代 | 拓扑 | 介质 | CSMA/CD? | +|------|------|------|---------| +| 1980s | 总线型 | 同轴电缆 | ✅ 必需 | +| 1990s | 星型 + Hub | 双绞线 | ✅ 存在但极少冲突 | +| 2000s+ | 全双工 Switch | 双绞线/光纤 | ❌ 已禁用 | + +全双工模式下,收发通道分离(两根线对),不存在碰撞的可能,CSMA/CD 自动关闭。从 Gigabit Ethernet (802.3ab) 起,交换机端口默认全双工运行。 + +> [!note] 如何检查当前网卡是否启用了 CSMA/CD? +> ```bash +> $ ethtool eth0 | grep -i coll +> Supports Collision Detection: yes +> Current Collision Detection: disabled (full-duplex) +> ``` + +## 关联笔记 + +- [[hhs/NETWORK/Ethernet帧结构]] — 以太网 II 帧的格式定义 +- [[hhs/NETWORK/Switch与路由器]] — Switch 如何通过全双工消除冲突 +- [[hhs/NETWORK/VLAN与Trunk]] — VLAN 隔离冲突域 diff --git a/hhs/NETWORK/02-链路层/03-MAC地址与广播域.md b/hhs/NETWORK/02-链路层/03-MAC地址与广播域.md new file mode 100644 index 0000000..262f6c6 --- /dev/null +++ b/hhs/NETWORK/02-链路层/03-MAC地址与广播域.md @@ -0,0 +1,152 @@ +--- +tags: [计算机网络, MAC地址, 广播域, 组播, OUI] +create time: 2026-05-17 23:10 +--- + +# MAC 地址与广播域 + +## 概述 + +MAC(Media Access Control)地址是数据链路层的物理地址,为全球每台联网设备的每个网络接口分配一个唯一标识。理解 MAC 地址的范围边界——**广播域**——是网络规划的基础。 + +## MAC 地址结构 + +### OUI + NIC Serial + +``` +┌───────────────┬───────────────┐ +│ OUI (3B) │ NIC Serial │ +│ IEEE 分配 │ 厂商自定 │ +│ │ │ +│ AA:BB:CC │ DD:EE:FF │ +└───────────────┴───────────────┘ +``` + +- **OUI** (Organizationally Unique Identifier): 前 3 字节由 IEEE 分配给厂商 +- **NIC Serial**: 后 3 字节由厂商自行分配给具体网卡 + +### 寻址类型位 + +第一个字节的最低比特位(LSB)决定类型: + +| 位模式 | 类型 | 示例 | +|--------|------|------| +| `xx:xx:xx:xx:xx:xx` — LSB=0 | 单播 (Unicast) | `a0:88:b4:12:34:56` | +| `01:00:5e:xx:xx:xx` — LSB=1 | IPv4 组播 | 特定多播组 | +| `ff:ff:ff:ff:ff:ff` | 广播 (Broadcast) | 全网段广播 | + +## 广播域(Broadcast Domain) + +### 定义 + +**广播域**是指广播帧能到达的所有设备的集合。在一个广播域内,一个设备发出的 ARP 请求或 DHCP Discover 能被该域内所有其他设备收到。 + +### 哪些网络设备处理/转发广播? + +| 设备 | 是否转发广播帧? | 说明 | +|------|----------------|------| +| Hub(集线器) | ✅ 复制转发到所有端口 | 相当于延长网线,扩大冲突域也扩大广播域 | +| Switch(交换机) | ✅ 转发到除接收口外的所有端口 | **同一 VLAN 内广播不隔离** | +| Router(路由器) | ❌ 不转发 | 天然隔离广播域,每个接口独立广播域 | +| Firewall(防火墙) | ⚙️ 可配置 | 默认通常丢弃 | + +```mermaid +flowchart LR + A["PC-A
IP:192.168.1.10"] -->|"ARP广播"| B[Switch
VLAN 10] + B -->|"ARP广播"| C["PC-B
192.168.1.20"] + B -->|"ARP广播"| D["PC-C
192.168.1.30"] + + B --"不转发"--> E[Router
VLAN 20] + E --> F["PC-D
192.168.2.10"] + + style A fill:#DDA0DD,color:#000 + style E fill:#FF6B6B,color:#fff +``` + +**结论:** Switch 内部不隔离广播域,只有 Router(或三层交换机)的 VLAN 接口才能隔离广播。这就是为什么大型网络需要划分 VLAN。 + +## 二层转发 vs 三层转发 + +### ARP 解析流程(跨网段的情况) + +```mermaid +sequenceDiagram + participant Src as PC-A
192.168.1.10 + participant GW as 网关 Router
eth0: 192.168.1.1
eth1: 192.168.2.1 + participant Dst as PC-D
192.168.2.10 + + Src->>Dst: IP包 → 但目的MAC设为谁的? + Note over Src: 发现 dst不在同一子网,
交给默认网关 + + Src->>GW: ARP "192.168.1.1是谁?"
(局域网内广播) + GW-->>Src: ARP回复 "我是! eth0 MAC=aa:bb:cc:dd:ee:01" + Note over Src,Dst: 源MAC=Src MAC
目的MAC=Gateway MAC
目标IP=Remote IP + + Src->>GW: Frame[EthHdr(Dst=GW_mac)|IP(src_ip,dst_ip)] + Note over GW: Strip Ethernet → 查路由表 → 重封帧 + GW->>Dst: Frame[EthHdr(Dst=Dst_mac)|IP(src_ip,dst_ip)] + Note over GW: src MAC 变成了 Gateway MAC!
MAC 逐跳改写,IP 不变 +``` + +### 关键规律总结 + +| 层级 | 地址变化 | 跨越范围 | +|------|---------|---------| +| IP 地址(第3层) | **保持不变**(除非 NAT) | 端到端,从源到终点 | +| MAC 地址(第2层) | **每跳改写** | 只服务于当前链路(两台相邻设备之间) | + +## MAC 地址表(Switch CAM 表) + +### Switch 如何学习 MAC? + +```mermaid +flowchart TD + S["Switch 启动"] --> L["清空 MAC 表"] + L --> R["收到帧 Port1"] + R --> Learn["提取 Src MAC → 记录: mac→port1"] + Learn --> Fwd{"已知目的 MAC?"} + Fwd -->|"是"| Forward["仅转发到对应端口"] + Fwd -->|"否"| Flood["泛洪到所有其他端口"] +``` + +### 查看 MAC 表 + +```bash +# Linux Bridge / 大多数交换机等价命令 +$ bridge fdb show +aa:bb:cc:dd:ee:01 dev eth0 master br0 permanent +aa:bb:cc:dd:ee:ff dev eth1 vlan 10 self permanent + +# 传统命令 +$ arp -a # 查看 ARP 缓存(IP → MAC 映射) +$ ip neigh # 等价于 arp,更现代 +``` + +### MAC 表老化 + +MAC 表条目有超时时间(通常 300 秒),超时后自动删除,等待下一帧到来重新学习。这防止了表中堆积无效条目。 + +## 广播风暴与防范 + +### 什么是广播风暴? + +当广播帧在网络中无限循环(如物理环路未启用 STP)时,每个交换机会不断泛洪,最终耗尽带宽。 + +### 防范措施 + +| 措施 | 说明 | +|------|------| +| **STP/RSTP/MSTP** | 生成树协议,自动阻断环路端口 | +| **VLAN 划分** | 缩小广播域规模 | +| **BPDU Guard** | 在接入端口禁用 STP 协议包 | +| **广播速率限制** | Switch 端口限速广播流量 | +| **IP 亚网划分** | 减少每个广播域内的主机数 | + +> [!tip] 最佳实践 +> 生产网络的广播域规模建议不超过 `/23`(510 台主机)。超过就应考虑划分 VLAN,用三层路由互联。 + +## 关联笔记 + +- [[hhs/NETWORK/Ethernet帧结构]] — 以太网帧的目的/源 MAC 字段 +- [[hhs/NETWORK/VLAN与Trunk]] — VLAN 如何进一步隔离广播域 +- [[hhs/NETWORK/ARP协议完整流程]] — MAC 地址的动态解析过程 diff --git a/hhs/NETWORK/02-链路层/04-Switch与路由器.md b/hhs/NETWORK/02-链路层/04-Switch与路由器.md new file mode 100644 index 0000000..ca34b40 --- /dev/null +++ b/hhs/NETWORK/02-链路层/04-Switch与路由器.md @@ -0,0 +1,181 @@ +--- +tags: [计算机网络, Switch, Hub, Router, VLAN] +create time: 2026-05-17 23:20 +--- + +# Switch vs Hub vs Router + +## 概述 + +这三种设备是构建局域网的核心组件,但它们工作在完全不同的 OSI 层,转发决策的依据也不同。理解它们的差异是设计网络拓扑的基础。 + +| 特性 | Hub(集线器) | Switch(交换机) | Router(路由器) | +|------|-------------|-----------------|-----------------| +| OSI 层级 | 物理层 L1 | 数据链路层 L2 | 网络层 L3 | +| 寻址依据 | 无(全量复制) | MAC 地址 | IP 地址 + 路由表 | +| 冲突域 | 全部共享 | 每个端口独立 | 每个接口独立 | +| 广播域 | 不隔离 | 不隔离(同 VLAN) | **天然隔离** | +| 性能 | 极低(半双工) | 高(全双工并行) | 依赖 CPU/ASIC | +| 现代使用 | ❌ 已淘汰 | ✅ 局域网核心 | ✅ 网关/边界 | + +## Hub(集线器)——已淘汰的共享介质 + +### 工作原理 + +Hub 本质上就是一个多端口的中继器(Repeater)。收到任何端口的电信号,放大后复制到所有其他端口。 + +```mermaid +flowchart LR + A["PC-A 发送"] -->|"信号"| H[Hub] + H -->|"原样复制"| B["PC-B (收到!)"] + H -->|"原样复制"| C["PC-C (碰撞! 🔥)"] + + style H fill:#FF6B6B,color:#fff +``` + +### 致命缺陷 + +1. **所有端口在同一冲突域**:两台同时发送 = 碰撞 → CSMA/CD 退避 +2. **只能半双工**:同一时刻要么收要么发 +3. **无法隔离故障**:一台 PC 中毒疯狂发包会影响所有人 +4. **安全性差**:所有流量对所有端口可见 + +## Switch(交换机)——现代 LAN 核心 + +### 工作原理 + +Switch 为每个端口维护一张独立的 MAC 表,只将帧转发到目标端口,实现"点对点"通信。 + +```mermaid +flowchart TD + S["Switch
MAC 表: mac_A→Port1, mac_B→Port2"] + + PC_A["PC-A → Port1"] -->|"发给 Mac_B"| S + S -->|"仅从 Port2 发出"| PC_B["PC-B"] + + PC_D["PC-D → Port4"] -->|"未知目标"| S + S -->|"泛洪到 Port1,2,3"| N["Port1~3 都收到
只有正确的会处理"] + + style S fill:#98FB98,color:#000 +``` + +### MAC 表学习流程 + +```mermaid +sequenceDiagram + participant SW as Switch + participant A as PC-A (mac_A) + participant B as PC-B (mac_B) + + A->>SW: Frame(mac_A→mac_B) from Port1 + SW->>SW: Learn: mac_A on Port1 + SW->>B: Flood (mac_B unknown) + + B->>SW: Reply Frame(mac_B→mac_A) from Port2 + SW->>SW: Learn: mac_B on Port2 + + A->>SW: Next Frame(mac_A→mac_B) from Port1 + SW->>SW: Forward directly → Port2 only ✅ +``` + +### Switch 的关键优势 + +| 特性 | 说明 | +|------|------| +| **每个端口一个冲突域** | 端口间互不影响 | +| **全双工通信** | 收发光纤分开,可同时对发对收 | +| **硬件转发** | ASIC 芯片查 MAC 表,延迟 < 1ms | +| **背板带宽** | 交换机内部总线,决定交换能力上限 | +| **VLAN 支持** | 802.1Q 实现逻辑分割 | + +### 常见 Switch 规格参数 + +| 参数 | 说明 | 典型值 | +|------|------|-------| +| 端口速率 | 千兆/万兆/25G/100G | 1/10 Gbps | +| 端口数 | 24/48 口最常见 | — | +| 背板带宽 | 内部交换容量 | 双向 ≥ 端口总数 × 速率 × 2 | +| 包转发率 | PPS (packets per second) | 10G 端口满速 ≈ 14.88M PPS | +| 缓存 | SRAM 缓冲丢包 | 几 MB ~ 几百 MB | + +## Router(路由器)——跨网段互联 + +### 工作原理 + +Router 运行在 OSI 第三层,根据 **IP 路由表**做出转发决策,不同接口属于不同的子网和广播域。 + +```mermaid +flowchart TD + subgraph "LAN 1 — 192.168.1.0/24" + A["PC-A 192.168.1.10"] + S["Local Switch"] + end + + subgraph "Router Gateway" + R["Router eth0: .1 / eth1: .1"] + end + + subgraph "LAN 2 — 192.168.2.0/24" + B["PC-B 192.168.2.10"] + S2["Local Switch"] + end + + A --> S --> R + R --> S2 --> B + + style A fill:#DDA0DD,color:#000 + style B fill:#DDA0DD,color:#000 + style R fill:#98FB98,color:#000 +``` + +### Router 转发动作详解 + +当 PC-A(192.168.1.10)要发数据给 PC-B(192.168.2.10)时: + +```mermaid +sequenceDiagram + participant A as PC-A + participant R as Router + participant B as PC-B + + Note over A: 判断:192.168.2.10 不在本地子网?
→ 走默认网关 + A->>R: Frame{SrcMac=A, DstMac=Gateway}
IP(Src=192.168.1.10,
Dst=192.168.2.10) + Note over R: Step 1: Strip Ethernet Header
Step 2: 校验 IP Checksum
Step 3: 查路由表 → 下一跳 via eth1
Step 4: ARP 找 B 的 MAC
Step 5: Rebuild Ethernet Header + R->>B: Frame{SrcMac=Gateway, DstMac=B}
IP(Src=192.168.1.10,
Dst=192.168.2.10) + Note over R: ← MAC 改了 ← IP 没变 +``` + +### 关键规则:IP 不变,MAC 逐跳改 + +```mermaid +flowchart LR + A["原始帧
SrcMAC=A DstMAC=R1-E0
SrcIP=A DstIP=B"] -->|"通过 Router1"| M1["帧改写
SrcMAC=R1-E1 DstMAC=R2-E0
SrcIP=A DstIP=B ← 不变!"] + M1 -->|"通过 Router2"| M2["帧改写
SrcMAC=R2-E1 DstMAC=B
SrcIP=A DstIP=B"] + + style A fill:#DDA0DD,color:#000 + style M2 fill:#98FB98,color:#000 +``` + +> [!tip] 一句话记住区别 +> **Switch 看 MAC 转发,Router 看 IP 转发。** Switch 是"同城快递",Router 是"跨省物流"。 + +## 三者的协作关系 + +现实网络中,三者通常协同工作: + +``` +PC ──┬── Hub(旧时代遗留) ← 冲突域大,已淘汰 + ├── Switch(局域网核心) ← MAC 转发表驱动,全双工 + │ │ + │ └── Router(网关) ← IP 路由表驱动,隔离广播域 + │ │ + │ └── ISP Router → Internet + │ + └── Wireless AP → Switch (AP 本身不转发) +``` + +## 关联笔记 + +- [[hhs/NETWORK/MAC地址与广播域]] — Switch 如何管理 MAC 表和广播 +- [[hhs/NETWORK/VLAN与Trunk]] — 三层交换机如何实现 VLAN 间路由 +- [[hhs/NETWORK/静态路由与默认网关]] — Router 的路由表配置 diff --git a/hhs/NETWORK/02-链路层/05-VLAN与Trunk.md b/hhs/NETWORK/02-链路层/05-VLAN与Trunk.md new file mode 100644 index 0000000..f5d53f0 --- /dev/null +++ b/hhs/NETWORK/02-链路层/05-VLAN与Trunk.md @@ -0,0 +1,208 @@ +--- +tags: [计算机网络, VLAN, 802.1Q, Trunk, Access端口] +create time: 2026-05-17 23:30 +--- + +# VLAN 与 Trunk + +## 概述 + +VLAN(Virtual Local Area Network,虚拟局域网)允许在一台物理交换机上创建多个逻辑隔离的广播域。它是现代数据中心和办公网络的基本组织单元。 + +> [!QUESTION] 为什么需要 VLAN? +> 一台万兆交换机的 MAC 表通常限制在几万条以内,加上广播泛洪带来的开销,单广播域超过 2000 台主机会显著降低性能。VLAN 将大网络划分为多个小广播域,就像在一栋大楼里用防火墙隔出多个独立办公室——同一楼层但互不干扰。 + +## IEEE 802.1Q Tag 结构 + +### 不带 VLAN 的 Ethernet II 帧 + +``` +┌──────────┬──────────┬──────┬─────────────┬──────┐ +│ Dest MAC │ Src MAC │Type │ Payload │ FCS │ +│ 6 bytes │ 6 bytes │ 2B │ 46-1500 B │ 4 B │ +│ │ │0x0800│ │ │ +└──────────┴──────────┴──────┴─────────────┴──────┘ + ←── 14 ──→ ←──MTU──→ ←── 4 ──→ + Header Payload Tail +``` + +### 带 802.1Q Tag 的帧 + +``` +┌──────────┬──────────┬──────┬───┬──────┬─────────────┬──────┐ +│ Dest MAC │ Src MAC │Type │TPID │ TCI │ Payload │ FCS │ +│ 6 bytes │ 6 bytes │ 2B │2B │ 2B │ 46-1500 B │ 4 B │ +│ │ │0x0800│8100 │ VID │ │ │ +└──────────┴──────────┴──────┴───┴──────┴─────────────┴──────┘ + ←── 14 ──→ │←──4──→│ ←──MTU──→ ←── 4 ──→ + ↑ 新增 4-byte Tag +``` + +### TCI (Tag Control Information) 位图 + +``` +Bit 15 Bit 13-12 Bit 11-0 +──────────────────────────────────── +CFI | PCP (优先级) | VID (VLAN ID) +(1bit) | (3 bits) | (12 bits) +``` + +| 字段 | 大小 | 取值范围 | 说明 | +|------|------|---------|------| +| CFI | 1 bit | 0 或 1 | Canonical Format Indicator,传统以太网固定为 0 | +| PCP | 3 bit | 0–7 | Priority Code Point,QoS 优先级(见下表) | +| VID | 12 bit | 0–4095 | VLAN Identifier,**实际可用 1–4094** | + +### PCP 优先级对照 + +| PCP | 用途 | 典型场景 | +|-----|------|---------| +| 0 | Best Effort | 普通数据流量 | +| 1 | Background | 后台低优先级的备份任务 | +| 2–3 | Excellent Effort | 文件传输等中等业务 | +| 4 | Critical Applications | 视频流会议 | +| 5 | Voice | 语音通话 VoIP(最高优先) | +| 6 | Network Control | 网络管理协议 | +| 7 | Reserved | 保留 | + +## VLAN 端口类型 + +| 端口类型 | Access | Trunk | Hybrid(Huawei/H3C) | +|----------|--------|-------|---------------------| +| 允许 VLAN | 1 个(PVID) | 多个 | 多个(可配置) | +| 入栈标签 | 接收不带标签 → 打上 PVID | 所有到达的帧保持标签 | 根据配置决定 | +| 出栈标签 | 发送时剥离标签 | 发送时保留标签(除 PVID) | 按每 VLAN 配置 | +| 使用场景 | 接 PC/打印机/AP | 交换机互联、接路由器 | 灵活混合 | + +### Access 端口示例 + +```mermaid +flowchart LR + PC["PC (无标签帧)"] -->|"发裸帧"| A["Access Port
PVID = 10"] + A -->|"打上 Tag 10"| S["Switch 内部 VLAN 10 域"] + S -->|"移除 Tag"| B["Access Port
PVID = 10"] + B -->|"发裸帧"| GW["网关 Router"] + + style A fill:#DDA0DD,color:#000 + style B fill:#DDA0DD,color:#000 +``` + +> Access 端口对主机透明——主机看到的仍是普通以太网帧,完全不知道 VLAN 的存在。 + +### Trunk 端口示例 + +```mermaid +flowchart LR + SW1["Switch A"] -->|"Trunk
携带多个 VID"| SW2["Switch B"] + + Note over SW1,SW2: VLAN 10 流量: EtherType=0x8100, VID=10
VLAN 20 流量: EtherType=0x8100, VID=20 + + style SW1 fill:#98FB98,color:#000 + style SW2 fill:#98FB98,color:#000 +``` + +## Router-on-a-Stick(单臂路由) + +当路由器只有一个物理接口时,可以通过子接口 + VLAN 实现多个 VLAN 间路由: + +``` + VLAN 10 VLAN 20 + ┌──────────┐ ┌──────────┐ + │ 192.168.10.x /24 │ │ 192.168.20.x /24 │ + └──────┬───┘ └──────┬───┘ + │ │ + Access Access + │ │ + ┌──────┴───────────────────┴───┐ + │ Switch │ + │ Trunk Port (eth0.10 & .20) │ + └──────┬───────────────────┬───┘ + │ 子接口 eth0.10 │ 子接口 eth0.20 + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ Router │ │ Router │ + │ eth0.10: 192.168.10.1/24 │ │ eth0.20: 192.168.20.1/24 │ + └─────────────────┘ └─────────────────┘ +``` + +> [!warning] 单臂路由的性能瓶颈 +> 所有 VLAN 间的流量都经过同一个物理接口进出(先入后出),相当于**双倍带宽占用**。对于万兆环境,建议使用三层交换机做硬件 VLAN 间路由。 + +## 配置示例 + +### Linux Bridge 上的 VLAN + +```bash +# 创建 vlan interface +ip link add link eth0 name eth0.10 type vlan id 10 +ip link add link eth0 name eth0.20 type vlan id 20 + +# 设置 IP 并启用 +ip addr add 192.168.10.1/24 dev eth0.10 +ip addr add 192.168.20.1/24 dev eth0.20 +ip link set eth0.10 up +ip link set eth0.20 up + +# 查看 +$ ip -d link show type vlan +4: eth0.10@eth0: mtu 1500 ... + link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff promiscuity 0 + vlan protocol 802.1Q id 10 reassemble 0 +``` + +### Docker 中利用 VLAN + +```yaml +# docker-compose.yml +services: + web: + networks: + frontend: # VLAN 10 + ipv4_address: 192.168.10.10 + backend: # VLAN 20 + ipv4_address: 192.168.20.10 + +networks: + frontend: + driver: bridge + ipam: + config: + - subnet: 192.168.10.0/24 + backend: + driver: bridge + ipam: + config: + - subnet: 192.168.20.0/24 +``` + +## VLAN 边界与跨 Switch + +```mermaid +sequenceDiagram + participant SW1 as Switch-A + participant Link as 交换机间链路 + participant SW2 as Switch-B + + SW1->>Link: Frame {EtherType:0x8100, VID:10} + Note over Link: Trunk 链路保持 802.1Q Tag + SW2->>SW2: 收到 → 识别 VID=10 → 在 VLAN 10 内转发 + SW2->>SW2: 查 MAC 表 → 仅从同 VLAN 端口发出 +``` + +**核心要点:** VLAN 信息随 802.1Q Tag 穿过 Trunk 链路传递到下一台交换机。每台交换机根据自己的 VLAN 数据库独立处理。 + +## 最佳实践 + +| 项目 | 建议 | +|------|------| +| VLAN ID 分配 | 管理 VLAN=1(不建议,安全风险),业务 VLAN ≥ 10 | +| Native VLAN | 修改默认 native VLAN(避免用 1),防 VLAN Hopping | +| VLAN 数量 | 单台交换机支持最大 4094 个 VLAN | +| Inter-VLAN 路由 | >10 个 VLAN 用三层交换机而非单臂路由 | +| 安全 | 未使用的端口划入"黑洞 VLAN"(无 IP 段),关闭 DTP 自动协商 | + +## 关联笔记 + +- [[hhs/NETWORK/Ethernet帧结构]] — 802.1Q Tag 插入在 Ethertype 位置之前 +- [[hhs/NETWORK/Switch与路由器]] — Switch 如何基于 VLAN 隔离广播域 +- [[hhs/NETWORK/MAC地址与广播域]] — VLAN 缩小了广播域的规模 diff --git a/hhs/NETWORK/03-网络层/01-IPv4首部与分段重组.md b/hhs/NETWORK/03-网络层/01-IPv4首部与分段重组.md new file mode 100644 index 0000000..5bef9fa --- /dev/null +++ b/hhs/NETWORK/03-网络层/01-IPv4首部与分段重组.md @@ -0,0 +1,187 @@ +--- +tags: [计算机网络, IPv4, IP首部, TTL, MTU, 分片] +create time: 2026-05-17 23:40 +--- + +# IPv4 首部与分段重组 + +## 概述 + +IPv4 数据包是互联网的基本传输单元。理解其首部结构,有助于排查 MTU 问题、分析抓包工具输出、以及理解 IP 级故障恢复机制。 + +## IPv4 首部格式(32-bit fields) + +``` +Version(4b) │ IHL(4b) │ DSCP/ECN(8b) │ Total Length(16b) │ +├─────────────────────────────────────────────────────────────────┤ +│ Identification(16b) │DF│ MF │Offset(13b)│ +├─────────────────────────────────────────────────────────────────┤ +│ TTL(8b) │ Protocol(8b) │ Header Checksum │ +├─────────────────────────────────────────────────────────────────┤ +│ Source Address (32b) │ +├─────────────────────────────────────────────────────────────────┤ +│ Destination Address (32b) │ +├─────────────────────────────────────────────────────────────────┤ +│ Options (variable) │ +│ ...padding... │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 各字段详解 + +| 字段 | 大小 | 说明 | +|------|------|------| +| **Version** | 4 bit | 版本号,IPv4 = `0100` | +| **IHL** (Internet Header Length) | 4 bit | 首部长度,单位 4 bytes。**最小值 5**(20 bytes),最大 15(60 bytes) | +| **DSCP** (Differentiated Services Code Point) | 6 bit | QoS 优先级标记;前 3 bit 为 ECN(Explicit Congestion Notification)| +| **Total Length** | 16 bit | 整个 IP 包总长(含首部),最大 65535 字节 | +| **Identification** | 16 bit | 唯一标识符。同一原始包的分片共享此 ID | +| **DF** (Don't Fragment) | 1 bit | 置 1 则禁止分片;路由器不能分片的包如果 DF=1,直接丢弃并返回 ICMP "Fragmentation Needed" | +| **MF** (More Fragments) | 1 bit | 置 1 表示后面还有分片;最后一个分片 MF=0 | +| **Fragment Offset** | 13 bit | 该分片在原始数据中的偏移量(以 8 字节为单位) | +| **TTL** (Time To Live) | 8 bit | 生存时间,每经过一个路由器减 1,归零时丢弃并发送 ICMP Timeout | +| **Protocol** | 8 bit | 上层协议编号:TCP=6, UDP=17, ICMP=1, IGMP=2 | +| **Header Checksum** | 16 bit | 仅校验首部的 CRC,**不校验 payload** | +| **Source/Dest Address** | 各 32 bit | 源和目的 IP 地址 | + +### 最小 vs 最大 IP 包 + +``` +最小 IP 包: 20B 首部 + 0B 数据 = 20 bytes +最小以太网帧承载: 20B 首部 + 8B ICMP Echo = 28 bytes + → 加上 14B EthHdr + 4B FCS = 46 bytes ≥ MTU 下限 ✅ + +最大 IP 包: 20B 首部 + 65515B 数据 = 65535 bytes +受限于链路 MTU: 通常 1500B Payload → 最大常用包 1520 bytes +``` + +## TTL 深度解析 + +### TTL 的常见误解 + +很多人以为 TTL 的单位是秒。其实在 IPv4 中它是**跳数计数器**——每经过一个路由器(三层转发节点)减 1,不是按时间递减。 + +```mermaid +flowchart LR + A["Source
TTL=64"] -->|"路由器减1"| B["TTL=63"] + B -->|"路由器减1"| C["TTL=62"] + C -->|"路由器减1"| D["TTL=61"] + D -->|"到目的地"| E["TTL=60
继续处理"] + + style A fill:#DDA0DD,color:#000 + style E fill:#98FB98,color:#000 +``` + +### 查看 TTL 的实践 + +```bash +# ping 默认从 64 或 128 开始,减去到达时的 TTL ≈ 经过了跳数 +$ ping -c 1 example.com +PING example.com (93.184.216.34): 56 data bytes +64 bytes from ... icmp_seq=0 ttl=55 time=23.1 ms +# 64 - 55 = 9 跳 + +# Windows 默认 TTL=128 +$ tracert example.com + 1 1 ms 1 ms 1 ms 192.168.1.1 + 2 2 ms 2 ms 2 ms 10.0.0.1 + ... + 9 23 ms 23 ms 23 ms 93.184.216.34 +``` + +### Linux 系统初始 TTL + +```bash +$ sysctl net.ipv4.ip_default_ttl +net.ipv4.ip_default_ttl = 64 + +# macOS / Windows 默认 128 +# 某些嵌入式设备可能用 32 或 60 +``` + +## MTU 与分片(Fragmentation) + +### 什么是 MTU? + +MTU(Maximum Transmission Unit)是链路层允许的最大 IP 包载荷大小。超出 MTU 的包需要被**分片**。 + +| 链路类型 | 典型 MTU | Jumbo Frame | +|----------|---------|-------------| +| Ethernet | 1500 bytes | 9000 bytes | +| Wi-Fi (802.11n/ac/ax) | 1500 | ❌ 不支持 | +| PPPoE (宽带拨号) | 1492 (预留 8B PPPoE头) | — | +| IPv6 | 1280 (硬性最小) | 可选 | +| GRE 隧道 | 1476 (预留 24B GRE头) | — | +| VXLAN | 1450 (预留 50B VxLAN头) | — | + +### 分片规则 + +当 IP 包 > MTU 且 DF=0 时,路由器将其切分为多个分片: + +``` +原始 IP 包 (Payload = 3000 bytes) ── MTU=1500 ──→ + +分片 1: Headers + [Bytes 0..1479] Offset=0 MF=1 +分片 2: Headers + [Bytes 1480..2959] Offset=185*8=1480 MF=1 +分片 3: Headers + [Bytes 2960..2999] Offset=370*8=2960 MF=0 (最后一片) + ↑ offset × 8 = 实际偏移 +``` + +**关键限制:** 除最后一个分片外,每个分片的 payload 必须是 **8 字节的整数倍**。这就是为什么 offset 字段以 8 字节为单位。 + +### 分片的优缺点 + +| 优点 | 缺点 | +|------|------| +| 允许大应用层数据穿越小 MTU 链路 | 增加路由器的 CPU 负担 | +| 透明(对应用层无感知) | 任一丢包导致整个原始包失效 | +| — | 攻击者可以利用分片做碎片攻击(Teardrop 等) | +| — | IPv6 已取消中间路由器分片,改由端到端 PMTUD | + +### PMTUD(Path MTU Discovery) + +现代网络推荐使用 **路径 MTU 发现**而非分片: + +```mermaid +sequenceDiagram + participant Src as 源主机 + participant R as 瓶颈路由器(MTU=1400) + participant Dst as 目标服务器 + + Src->>Dst: IP Packet (size=1500, DF=1) + Note over Src: DF(Do Not Fragment)=1 + R->>R: MTU=1400 < 1500, DF=1 → 无法分片! + R->>Src: ICMP Type 3 Code 4 "Fragmentation Needed" + Note over Src: Path MTU = 1400 - IP头(20) - Eth(14) = 1366 + Src->>Dst: IP Packet (size=1366, DF=1) ✅ + Dst-->>Src: ACK ✅ +``` + +```bash +# 检查当前 PMTUD 状态 +$ ip route show | grep mtu +default via 192.168.1.1 dev eth0 mtu 1500 + +# Linux 内核 PMTUD 控制 +$ sysctl net.ipv4.tcp_mtu_probing +net.ipv4.tcp_mtu_probing = 0 # 0=关闭, 1=低速率, 2=始终开启 +``` + +## 协议编号对照表 + +| Protocol 值 | 名称 | 用途 | +|------------|------|------| +| 1 | ICMP | 网络诊断(ping、traceroute) | +| 2 | IGMP | 组播管理 | +| 6 | TCP | 可靠面向连接传输 | +| 17 | UDP | 无连接不可靠传输 | +| 47 | GRE | 虚拟私有隧道 | +| 50 | ESP | IPsec 加密封装安全载荷 | +| 51 | AH | IPsec 认证头 | +| 89 | OSPF | 动态路由协议 | + +## 关联笔记 + +- [[hhs/NETWORK/CIDR与子网划分]] — IP 地址的子网分配方法 +- [[hhs/NETWORK/NAT原理与应用]] — NAT 如何处理 IP 包的 Checksum +- [[hhs/NETWORK/ICMP与Ping-Traceroute]] — ICMP 协议细节 diff --git a/hhs/NETWORK/03-网络层/02-CIDR与子网划分.md b/hhs/NETWORK/03-网络层/02-CIDR与子网划分.md new file mode 100644 index 0000000..2376889 --- /dev/null +++ b/hhs/NETWORK/03-网络层/02-CIDR与子网划分.md @@ -0,0 +1,182 @@ +--- +tags: [计算机网络, CIDR, 子网划分, IP计算] +create time: 2026-05-18 00:00 +--- + +# CIDR 与子网划分 + +## 概述 + +CIDR(Classless Inter-Domain Routing,无类别域间路由)是现代 IP 寻址的核心机制。它摒弃了 A/B/C 类地址的固定边界,允许任意长度的前缀——让网络管理员可以按需精确分配 IP 空间。 + +> [!QUESTION] 为什么需要子网划分? +> 想象一个有 5000 台主机的公司,如果全放在 /12 大网络里:每秒数千个 ARP 广播、巨大的 MAC 表、单点故障影响面极大。子网划分将大问题拆小,既缩小了广播域,又加强了安全性隔离。 + +## CIDR 表示法 + +``` +192.168.1.0/24 ← /24 表示前 24 位是网络号 +``` + +即 `192.168.1.0` + 子网掩码 `255.255.255.0` + +### 常见子网对照表 + +| CIDR | 子网掩码 | 主机数 ( usable) | 典型用途 | +|------|---------|-----------------|---------| +| /30 | 255.255.255.252 | 2 | 点对点链路 | +| /29 | 255.255.255.248 | 6 | 小型办公室 | +| /28 | 255.255.255.240 | 14 | VLAN 极小 | +| /27 | 255.255.255.224 | 30 | 小组 VLAN | +| /26 | 255.255.255.192 | 62 | 小型部门 | +| /25 | 255.255.255.128 | 126 | 中型部门 | +| /24 | 255.255.255.0 | 254 | **最常用**,标准办公室 VLAN | +| /23 | 255.255.255.254 | 510 | 大型部门 | +| /22 | 255.255.255.252 | 1022 | 数据中心租户 | +| /21 | 255.255.255.248 | 2046 | 大规模部署 | +| /16 | 255.255.255.0 | 65534 | 大型企业内网 | + +> [!tip] 快速计算可用主机数 +> $N_{usable} = 2^h - 2$,其中 $h$ 是主机位数量($h = 32 - \text{prefix}$) +> - `-2` 因为网络地址和广播地址不可用 +> - 对于 /31(点对点链路),RFC 3021 允许使用 2 个地址(无网络/广播位) + +## 子网划分的核心算法 + +### 示例:从 /24 划分为 4 个子网 + +原始:`192.168.1.0/24`(254 台主机) +目标:分成 4 个子网 → 需借 2 位主机位 → `/26` + +``` +原始 /24 的网络位: +192.168.1.0 = 11000000.10101000.00000001.00000000 + +借 2 位给子网: +192.168.1.XX = XX 是子网位(00, 01, 10, 11) + +四个子网: +Subnet 0: 192.168.1.0/26 — 范围: .0 ~ .63 — 可用: .1 ~ .62 — Bcast: .63 +Subnet 1: 192.168.1.64/26 — 范围: .64 ~ .127 — 可用: .65 ~ .126 — Bcast: .127 +Subnet 2: 192.168.1.128/26 — 范围: .128~.191 — 可用: .129~.190 — Bcast: .191 +Subnet 3: 192.168.1.192/26 — 范围: .192~.255 — 可用: .193~.254 — Bcast: .255 +``` + +```mermaid +flowchart LR + S0["192.168.1.0/26
.1-.62"] -->|"PCs / Servers"| SW0[Switch-A] + S1["192.168.1.64/26
.65-.126"] -->|"IP Phones"| SW1[Switch-B] + S2["192.168.1.128/26
.129-.190"] -->|"Guest WiFi"| AP1[AP-C] + S3["192.168.1.192/26
.193-.254"] -->|"IoT Devices"| GW1[Gateway .1] + + style S0 fill:#DDA0DD,color:#000 + style S1 fill:#FFD700,color:#000 + style S2 fill:#98FB98,color:#000 + style S3 fill:#B0C4DE,color:#000 +``` + +### VLSM(可变长子网掩码) + +不同子网可以用不同长度的掩码——这就是**可变长子掩码**。 + +场景:公司有 4 个部门,需要不同规模: +- 研发部:200 台 → 需要至少 202 → /24(254 台) +- 市场部:50 台 → 需要至少 52 → /26(62 台) +- 行政部:10 台 → 需要至少 12 → /28(14 台) +- 互联链:2 台 → 需要 2 → /30(2 台) + +总计借用:32 - 24 = 8 位主机位 +- 研发 /24: 借回 0 位子网位 → 用掉 1 个大块 +- 剩余给其他 3 个 /26, /28, /30... + +## 实用计算工具 + +### Linux ipcalc + +```bash +$ ipcalc 192.168.10.50/26 +Network: 192.168.10.0/26 +Netmask: 255.255.255.192 = 26 +Wildcard: 0.0.0.63 +Broadcast: 192.168.10.63 +HostMin: 192.168.10.1 +HostMax: 192.168.10.62 +Hosts/Net: 62 + +# Python 中内置 ipaddress 模块 +$ python3 -c "import ipaddress; print(list(ipaddress.ip_network('192.168.10.0/26').hosts()))" +[IPv4Address('192.168.10.1'), ..., IPv4Address('192.168.10.62')] +``` + +### 快速手算方法 + +| 步骤 | 操作 | +|------|------| +| 1 | 将最后一个 octet 转换为二进制 | +| 2 | 前 prefix%32 位为网络位,其余为主机位 | +| 3 | 网络地址 = 全置 0;广播地址 = 全置 1 | +| 4 | 可用范围 = 网络地址+1 ~ 广播地址-1 | + +## 私有地址空间(RFC 1918) + +以下地址段在公网不可路由,专供内部网络使用: + +| 地址段 | 数量 | 默认掩码 | 适用场景 | +|--------|------|---------|---------| +| `10.0.0.0 – 10.255.255.255` | 16,777,216 | /8 | 大型企业、运营商 | +| `172.16.0.0 – 172.31.255.255` | 1,048,576 | /12 | 中型企业 | +| `192.168.0.0 – 192.168.255.255` | 65,536 | /16 | 家庭/小型办公室 | + +> [!tip] 如何选择私有地址段? +> - 单机房、<1000 台主机 → `192.168.0.0/16` 够用 +> - 多机房、分布式 → `10.0.0.0/8` 留有余量 +> - Docker 默认使用 `172.17.0.0/16` + +## 超网聚合(Supernetting / CIDR Aggregation) + +将多个连续的小网合并为一个更大的网: + +``` +原始: 192.168.1.0/24, 192.168.2.0/24, 192.168.3.0/24 + = 11000000.10101000.00000001.0/24 + + 11000000.10101000.00000010.0/24 + + 11000000.10101000.00000011.0/24 + +共同前缀: 11000000.10101000.000000XX → /22 + +聚合后: 192.168.0.0/22 包含 1024 个 IP + (注意:实际只用了其中两个 /24 块,浪费了中间两个) +``` + +路由聚合减少了全球 BGP 路由表的大小。没有 CIDR 聚合,BGP 路由表会膨胀到百万级别。 + +## Go 中的子网判断 + +```go +import "net" + +ip := net.ParseIP("192.168.1.50") +_, cidr, _ := net.ParseCIDR("192.168.1.0/26") + +if cidr.Contains(ip) { + fmt.Println("IP 在子网范围内 ✅") +} + +// 遍历所有可用 IP +for ip := cidr.IP.Mask(cidr.Mask); cidr.Contains(ip); inc(ip) { + if ip.Equal(cidr.IP) || ip.Equal(lastIP) { + continue // 跳过网络地址和广播地址 + } + // 处理每个可用 IP +} +func inc(ip net.IP) { for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] > 0 { break } +}} +``` + +## 关联笔记 + +- [[hhs/NETWORK/IPv4首部与分段重组]] — IP 地址在 IPv4 包中的位置 +- [[hhs/NETWORK/IPv6地址与扩展头部]] — IPv6 同样使用 CIDR 表示法 +- [[hhs/NETWORK/NAT原理与应用]] — NAT 通常映射到私有地址空间 diff --git a/hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部.md b/hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部.md new file mode 100644 index 0000000..5acd9a9 --- /dev/null +++ b/hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部.md @@ -0,0 +1,165 @@ +--- +tags: [计算机网络, IPv6, SLAAC, 地址空间] +create time: 2026-05-18 00:20 +--- + +# IPv6 地址与扩展头部 + +## 概述 + +IPv6 是为解决 IPv4 地址枯竭而设计的下一代互联网协议。但它的改进远不止于地址空间——简化首部、内置安全支持、无分片(by design)、自动配置等特性使其更适合现代网络架构。 + +## IPv6 地址表示法 + +### 十六进制冒号分隔格式 + +``` +2001:0db8:0000:0000:0000:ff00:0042:8329 +``` + +### 缩写规则 + +| 规则 | 说明 | +|------|------| +| 前导零省略 | `0db8` → `db8`;`0042` → `42` | +| 连续全零段压缩为 `::` | 只能出现**一次**(否则歧义) | + +示例: +``` +完整: 2001:0db8:0000:0000:0000:ff00:0042:8329 +缩写: 2001:db8::ff00:42:8329 ← 最简形式 + +loopback: 0000:0000:0000:0000:0000:0000:0000:0001 + ::1 ← 仅一个字符 + +IPv4映射: 0000:0000:0000:0000:0000:ffff:c0a8:0101 + ::ffff:192.168.1.1 ← 兼容旧系统 +``` + +### 常见 IPv6 地址类型 + +| 前缀 | 类型 | 示例 | 范围 | +|------|------|------|------| +| `2000::/3` | 全球单播 (GUA) | `2001:db8::1` | 公网路由 | +| `::1/128` | 环回 (Loopback) | `::1` | 本机 | +| `fc00::/7` | ULA (唯一本地地址) | `fd00::1` | 私有地址,等价 RFC1918 | +| `fe80::/10` | 链路本地 (Link-Local) | `fe80::1` | 仅同链路可用,自动配置 | +| `ff00::/8` | 组播 (Multicast) | `ff02::1` | 多播组 | +| — | 未指定地址 | `::` | 类似 IPv4 的 0.0.0.0 | + +## IPv6 地址组成 + +### EUI-64(自动生成) + +从 MAC 地址生成接口标识符(正在被隐私扩展取代): + +``` +MAC: aa:bb:cc:dd:ee:ff +插入 ff:fe → aa:bb:cc:ff:fe:dd:ee:ff +翻转第七位(UD bit) → ab:bb:cc:ff:fe:dd:ee:ff + +结果: fe80::ab:bb:cc:ff:fe:dd:ee:ff (link-local) +``` + +### SLAAC( Stateless Address Autoconfiguration ) + +主机无需 DHCP 服务器,通过 Router Advertisement(RA)消息自行配置: + +```mermaid +sequenceDiagram + participant Host as 主机 + participant R as 路由器 + + R->>Host: Router Advertisement (每 200s~60min) + Note over Host: RA 中包含: + Note over Host: - 前缀 (如 2001:db8:abcd::/64) + Note over Host: - prefix length + Note over Host: - 默认网关 + Note over Host: - DNS (RFC 6106 / RDNSS) + + Host->>Host: 自构 IPv6 地址 + Host->>R: NDP Neighbor Solicitation (查重) + R-->>Host: (无冲突 → 无回复) + Host->>Host: ✅ 分配 2001:db8:abcd:: +``` + +### DUID-DHCPv6 + +DHCPv6 服务器分配地址时需要客户端标识: +- **DUID-LLT**: DUID + Link-Layer Time + Link-Layer Address +- **DUID-UUID**: DUID + UUID (持久不变) + +## IPv6 首部 vs IPv4 首部 + +| 字段 | IPv4 | IPv6 | +|------|------|------| +| 版本 | 4 bit | 4 bit | +| 优先级 | DSCP+ECN (8 bit) | Traffic Class + Flow Label (20 bit) | +| 长度 | Total Length (含首部) | Payload Length (**不含**首部) | +| Next Header | Protocol (替代) | Next Header (等同) | +| Hop Limit | TTL | Hop Limit (改名但不改功能) | +| 源/目的 IP | ✅ | ✅ | +| 首部 Checksum | ✅ | ❌ **已删除!** | +| 分片 | ID/DF/MF/Offset | 移到扩展头(由源端分片) | + +### IPv6 为什么去掉 Checksum? + +- TCP/UDP/ICMPv6 自带端到端校验 +- 链路层 Ethernet FCS 已做底层校验 +- 每跳计算 Checksum 增加路由器负担 → 提升转发性能 + +### Flow Label(流标签) + +新引入的 20-bit 字段,用于标识属于同一"流"的数据包序列。中间网络设备可据此做 QoS 或负载均衡——无需深度解析载荷。 + +## IPv6 扩展头部(Extension Headers) + +IPv6 将可选功能移出固定首部,放在扩展头链中,由 Next Header 字段串联: + +``` +Fixed Header → Hop-by-Hop Options (可选) → Destination Options → Routing → Fragment → Authentication (AH) → Encapsulating Security Payload (ESP) → Upper Layer (TCP/UDP/ICMPv6) +``` + +| 扩展头 | 类型值 | 用途 | +|--------|-------|------| +| Hop-by-Hop Options | 0 | 逐跳选项(极少使用) | +| Routing | 43 | 路由类型 0 (SRv6 前身)、Type 2 (Mobile IPv6) | +| Fragment | 44 | 分片信息(ID/offset/MF),仅出现在原始包中 | +| Authentication (AH) | 51 | IPsec 认证(较少独立使用) | +| Encapsulating Security Payload (ESP) | 50 | IPsec 加密封装 | +| Destination Options | 60 | 终点选项(MIO 移动 IPv6、Jumbo Payload) | +| Mobility Header | 135 | Mobile IPv6 | + +> [!warning] Path MTU Discovery in IPv6 +> IPv6 规定**路由器不允许对 IP 包分片**。如果包超过链路 MTU,路由器直接丢弃并返回 ICMPv6 "Packet Too Big"。这就是 PMTUD 在 IPv6 中成为强制要求的原因。 + +## IPv4 到 IPv6 的过渡机制 + +| 技术 | 原理 | 适用场景 | +|------|------|---------| +| Dual Stack | 同时运行 IPv4 和 IPv6 | 最常见,逐步迁移 | +| Tunneling (6in4/6to4) | IPv6 包封装在 IPv4 中传输 | 穿越纯 IPv4 骨干网 | +| NAT64/DNS64 | 客户端仅有 IPv6,NAT 翻译到 IPv4 | 运营商级部署 | +| SIIT | 状态less 翻译,地址嵌入 (IPv4-mapped) | ISP、企业边界 | + +## Go 中的 IPv6 支持 + +```go +import "net" + +// 创建双栈监听器(同时支持 v4/v6) +ln, err := net.Listen("tcp6", ":8080") +// 设置 IPv6Only = false 可同时接受 IPv4 连接 (DualStack) + +// 解析 IPv6 地址 +ip := net.ParseIP("2001:db8::1") // net.IP 天然支持 v4/v6 + +_, cidr, _ := net.ParseCIDR("2001:db8::/32") +fmt.Println(cidr.Contains(ip)) // true +``` + +## 关联笔记 + +- [[hhs/NETWORK/IPv4首部与分段重组]] — IPv4 和 IPv6 首部的差异对比 +- [[hhs/NETWORK/DNS原理与优化]] — AAAA 记录解析 IPv6 地址 +- [[hhs/NETWORK/CIDR与子网划分]] — CIDR 在 IPv6 中的应用方式相同 diff --git a/hhs/NETWORK/03-网络层/04-ARP协议完整流程.md b/hhs/NETWORK/03-网络层/04-ARP协议完整流程.md new file mode 100644 index 0000000..d406651 --- /dev/null +++ b/hhs/NETWORK/03-网络层/04-ARP协议完整流程.md @@ -0,0 +1,175 @@ +--- +tags: [计算机网络, ARP, gratuitous-ARP, ARP欺骗] +create time: 2026-05-18 00:40 +--- + +# ARP 协议完整流程 + +## 概述 + +ARP(Address Resolution Protocol,地址解析协议)负责将 IP 地址解析为同一局域网内的 MAC 地址。它是 IPv4 网络通信中不可或缺的"胶水协议"——每对首次通信的主机之间都必须经过一次 ARP。 + +## ARP 报文格式 + +ARP 直接封装在 Ethernet 帧中(EtherType = 0x0806),不依赖 IP: + +``` +┌───────────────────┬───────────────┬────────────────────┐ +│ Hardware Type │ Protocol Type │ Hardware Addr Len │ +│ 2 bytes │ 2 bytes │ 1 byte │ +│ 1 = Ethernet │ 0x0800=IP │ always 6 │ +├───────────────────┼───────────────┼────────────────────┤ +│ Protocol Addr Len │ Operation │ Sender MAC │ +│ 1 byte │ 2 bytes │ 6 bytes │ +│ always 4 │ 1=Request │ │ +│ │ 2=Reply │ │ +├───────────────────┴───────────────┴────────────────────┤ +│ Sender IP Address (4 bytes) │ +├────────────────────────────────────────────────────────┤ +│ Target IP Address (4 bytes) │ +├────────────────────────────────────────────────────────┤ +│ Target MAC Address (6 bytes) │ +│ (in Request, this is zeroed/empty) │ +└────────────────────────────────────────────────────────┘ +Total: 28 bytes payload (padded to ≥ 46 bytes by Ethernet) +``` + +### Operation 字段 + +| 值 | 含义 | +|----|------| +| 1 | **ARP Request** — "谁是 x.x.x.x?请告诉我的 MAC" | +| 2 | **ARP Reply** — "我是 x.x.x.x,我的 MAC 是 yy" | +| 3 | RARP (Reverse ARP) — 历史遗留,已废弃 | +| 8 | InARP (Inverse ARP) — ATM 网络用 | + +## ARP 请求与回复流程 + +```mermaid +sequenceDiagram + participant Src as PC-A
192.168.1.10/aa:bb:cc:dd:ee:01 + participant Bcast as 广播域内所有主机 + participant Dst as PC-B
192.168.1.20/?mac + + Note over Src: Application wants to send packet to .20
But needs MAC first! + + Src->>Bcast: ARP Request (Broadcast)
Who has 192.168.1.20? Tell 192.168.1.10 + Note over Src,Bcast: Src MAC=aa:bb:cc:dd:ee:01
Dst MAC=ff:ff:ff:ff:ff:ff
Target IP=.20, Target MAC=00:00:00:00:00:00 + + loop For each host on LAN + Bcast-->>Host-C: 收到但不匹配 → 忽略 + end + + Dst-->>Src: ARP Reply (Unicast)
192.168.1.20 is at aa:bb:cc:dd:ee:20 + + Src->>Src: Update ARP Cache: 192.168.1.20 → aa:bb:cc:dd:ee:20 +``` + +### ARP 缓存条目 + +```bash +$ ip neigh show +192.168.1.20 dev eth0 lladdr aa:bb:cc:dd:ee:20 STALE +192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:01 REACHABLE +fe80::1 dev eth0 lladdr aa:bb:cc:dd:ee:01 DELAY + +# 状态机: +# INCOMPLETE — 请求已发但未收到回复 +# REACHABLE — 确认可达(刚收到回复或收到 ACK) +# STALE — 过期但可用,等待下次验证 +# DELAY — 已标记为 STALE,等待 probe +# FAILED — Probe 全部失败 +``` + +### ARP 缓存超时时间 + +```bash +# Linux 默认超时(秒) +$ sysctl net.ipv4.neigh.default.base_reachable_time_ms +net.ipv4.neigh.default.base_reachable_time_ms = 30000 # 30s + +# 可调为毫秒精度 +$ echo 60000 > /proc/sys/net/ipv4/neigh/default/base_reachable_time_ms # 改为 60s +``` + +## Gratuitous ARP(免费 ARP / 主动 ARP) + +### 什么是 Gratuitous ARP? + +一台主机**主动发送 ARP 回复**,即使没有收到任何请求。这相当于在广播域中宣布:"我现在是 xx:xx:xx:xx:xx:xx,绑定的是 x.x.x.x"。 + +### 三种使用场景 + +| 场景 | 目的 | +|------|------| +| 网卡故障切换 (VRRP/Keepalived) | 通知交换机更新 MAC→端口映射 | +| IP 冲突检测 | 先发送 GARP,如果有人回应则发现冲突 | +| 虚拟机迁移 | 新宿主机的 MAC 替代旧主机的 IP | +| DHCP 续租 | 重新声明自己的地址绑定关系 | + +```mermaid +sequenceDiagram + participant V1 as VM-on-Host-A
IP: 10.0.0.10
MAC: aa:bb:cc:00:00:01 + participant SW as Switch + participant Migrate as VM migrates to Host-B + + Note over V1: Live Migration starts... + V1->>SW: 最后一帧 from Host-A
Switch learns: .10 → Port-X + + Note over Migrate: VM boots on Host-B
Same IP, New MAC! + Migrate->>SW: GARP "10.0.0.10 is now cc:dd:ee:00:00:01!" + SW->>SW: UPDATE MAC table:
.10 → Port-Y ✅ + + Note over V1,Migrate: Without GARP, all traffic would go to old port 😱 +``` + +## ARP 安全与攻击 + +### ARP Spoofing / Poisoning + +攻击者发送伪造的 ARP Reply,让受害者认为网关的 MAC 已被篡改: + +``` +Before: +PC: "Gateway is at aa:bb:cc:gg:hh:ii ✅" + Server: "I am at dd:ee:ff:jj:kk:ll ✅" + +After Attack (Attacker injects fake ARP): +PC: "Gateway is at mm:nn:oo:pp:qq:rr ❌ ← Attacker's MAC!" +``` + +### 防范措施 + +| 措施 | 说明 | 部署位置 | +|------|------|---------| +| **静态 ARP** | `arp -s` 手动绑定 | 小型网络,管理成本高 | +| **Dynamic ARP Inspection (DAI)** | Switch 拦截非合法 DHCP binding 的 ARP | 交换机配置 | +| **ARP Watch / Arpmonitor** | 监控 ARP 变动并告警 | 服务端 | +| **ndp-scan / arp-scan** | 定期扫描 ARP 表变化 | 运维工具 | + +```bash +# 手动添加静态 ARP 条目(重启失效) +$ sudo ip neigh add 192.168.1.1 lladdr aa:bb:cc:dd:ee:01 nud permanent dev eth0 + +# 永久生效需写入 NetworkManager 或 systemd-networkd 配置 +``` + +## IPv6 中的替代:NDP(Neighbor Discovery Protocol) + +IPv6 废除了 ARP,改用 NDP(ICMPv6 类型 133/134/135/136): + +| ARP 功能 | NDP 实现 | ICMPv6 Type | +|----------|---------|-------------| +| ARP Request | Neighbor Solicitation (NS) | 135 | +| ARP Reply | Neighbor Advertisement (NA) | 136 | +| gratuitous ARP | Unsolicited NA | 136 | +| 路由发现 | Router Solicitation / Advertisement | 133 / 134 | + +> [!tip] 为什么 NDP 更安全? +> NDP 支持 SEND(Secure Neighbor Discovery,RFC 3971),利用加密签名防止伪装。虽然部署不多,但架构上比明文 ARP 好得多。 + +## 关联笔记 + +- [[hhs/NETWORK/MAC地址与广播域]] — ARP 如何在广播域中工作 +- [[hhs/NETWORK/Switch与路由器]] — Switch 如何处理 ARP 包 +- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — ARP 跨越链路层和网络层的边界 diff --git a/hhs/NETWORK/03-网络层/05-ICMP与Ping-Traceroute.md b/hhs/NETWORK/03-网络层/05-ICMP与Ping-Traceroute.md new file mode 100644 index 0000000..311ea70 --- /dev/null +++ b/hhs/NETWORK/03-网络层/05-ICMP与Ping-Traceroute.md @@ -0,0 +1,189 @@ +--- +tags: [计算机网络, ICMP, Ping, Traceroute] +create time: 2026-05-18 00:50 +--- + +# ICMP 与 Ping / Traceroute + +## 概述 + +ICMP(Internet Control Message Protocol,互联网控制消息协议)是 IP 的"副手"——它不承载用户数据,而是用来报告错误、传递诊断信息。ping 和 traceroute 这两个最常用的网络工具,底层全靠 ICMP。 + +## ICMP 报文格式 + +``` +┌──────────┬───────────┬──────────┬───────────────┐ +│ Type(8b) │ Code(8b) │ Checksum │ Rest of Header │ +├──────────┼───────────┴──────────┴───────────────┤ +│ Data (variable) │ +└───────────────────────────────────────────┘ +``` + +### 常用 Type 对照表 + +| Type | Code | Name | 用途 | +|------|------|------|------| +| 0 | 0 | Echo Reply | ping 回复 | +| 3 | 0–3 | Destination Unreachable | 目的不可达 | +| 3/0 | Network Unreachable | 路由表中无目标网段 | +| 3/1 | Host Unreachable | 同一网段但主机不存在 | +| 3/2 | Protocol Unreachable | TCP/UDP 端口无监听 | +| 3/3 | Port Unreachable | UDP 端口不可达(最常见) | +| 8 | 0 | Echo Request | ping 请求 | +| 11 | 0 | Time Exceeded | TTL 归零(traceroute 利用此) | +| 12 | 0 | Parameter Problem | IP 首部字段错误 | +| 13 | 0 | Timestamp Request | 时间戳查询 | +| 14 | 0 | Timestamp Reply | — | +| 17 | 0 | Address Mask Request | 子网掩码查询(老旧) | +| 18 | 0 | Address Mask Reply | — | + +## Ping — 最基础的连通性测试 + +### Ping 工作原理 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Server as 服务器 + + Client->>Server: ICMP Echo Request
Type=8, ID=0x1234, Seq=0 + Note over Server: 内核自动回复,无需应用层处理 + Server-->>Client: ICMP Echo Reply
Type=0, ID=0x1234, Seq=0 + + loop 继续发第 2, 3... 个包 + Client->>Server: Echo Request Seq=1 + Server-->>Client: Echo Reply Seq=1 + end +``` + +### Linux ping 详解 + +```bash +$ ping -c 4 -s 1024 example.com +PING example.com (93.184.216.34): 1024 data bytes +1032 bytes from 93.184.216.34: icmp_seq=0 ttl=55 time=23.4 ms +1032 bytes from 93.184.216.34: icmp_seq=1 ttl=55 time=22.8 ms +1032 bytes from 93.184.216.34: icmp_seq=2 ttl=55 time=23.1 ms +1032 bytes from 93.184.216.34: icmp_seq=3 ttl=55 time=23.5 ms + +--- example.com ping statistics --- +4 packets transmitted, 4 received, 0% packet loss +rtt min/avg/max/stddev = 22.8/23.2/23.5/0.30 ms +``` + +参数速查: +| 参数 | 含义 | +|------|------| +| `-c N` | 发送 N 个包后停止 | +| `-i SEC` | 间隔秒数(默认 1s,root 可用 < 0.1s) | +| `-s SIZE` | 载荷大小(默认 56 bytes,即 64 byte ICMP 包) | +| `-t TTL` | 指定初始 TTL | +| `-W MSEC` | 等待超时毫秒数 | +| `-q` | 静默模式,仅显示统计摘要 | + +### macOS / Windows 的差异 + +| 特性 | Linux | macOS | Windows | +|------|-------|-------|---------| +| 默认行为 | 不停发送直到 Ctrl+C | 不停发送 | 发 4 次后停止 | +| 默认 TTL | 64 | 64 | 128 | +| 默认载荷 | 56 bytes | 56 bytes | 32 bytes | +| `-c` | ✅ 限制次数 | ❌ | ❌ | + +> [!tip] 判断丢包率的阈值 +> +> | 丢包率 | 评价 | +> |--------|------| +> | 0% | 正常 | +> | < 0.1% | 可接受(偶尔重传) | +> | 0.1%~1% | 注意观察 | +> | 1%~5% | 可能存在瓶颈或线路老化 | +> | > 5% | 严重问题,需排查物理链路或设备 | + +## Traceroute — 路由路径探测 + +### 原理:利用 ICMP Time Exceeded + +```mermaid +sequenceDiagram + participant T as 客户端 + participant R1 as Router 1 + participant R2 as Router 2 + participant D as Destination + + T->>R1: ICMP(TTL=1) → R1 TTL归零,返回 Time Exceeded + Note over T: 收到 R1 的 reply → 第一跳延迟记录完毕 + + T->>R1: ICMP(TTL=2) → R1转发 → R2 TTL归零→ Time Exceeded + Note over T: 收到 R2 的 reply → 第二跳记录完毕 + + T->>R1: ICMP(TTL=3) → ... → D 收到 → 返回 Echo Reply + Note over T: ✅ 到达目的地 +``` + +### Linux traceroute vs tracepath + +```bash +# traceroute 使用 UDP(默认高位端口)或 ICMP(-I 参数) +$ traceroute -I example.com # 使用 ICMP + 1 192.168.1.1 1ms 1ms 1ms + 2 10.0.0.1 10ms 10ms 10ms + 3 202.97.xx.xx 20ms 19ms 20ms + ... +12 93.184.216.34 45ms 44ms 44ms + +# tracepath 不需要 root 权限,默认 MTU 发现 +$ tracepath example.com + 1: 192.168.1.1 0.4ms + 2: 10.0.0.1 8.2ms + ... +12: 93.184.216.34 43.5ms + + No route to above host (mtu discovered: 1492) +``` + +### Windows 等价命令 + +```cmd +tracert example.com +``` + +> [!warning] 为什么有些 traceroute "星号 * * *"? +> 路由器可以配置为**不回复 ICMP Time Exceeded**(出于安全考虑)。此时你看不到中间节点,只能看到最后的目的地。 + +## ICMP Rate Limiting(限速) + +Linux 内核默认对 ICMP 进行限速,防止 ICMP flood 攻击: + +```bash +$ sysctl net.ipv4.icmp_ratelimit # 每秒最多发出多少个 ICMP +net.ipv4.icmp_ratelimit = 1000 # 默认 1000 msg/s + +$ sysctl net.ipv4.icmp_ratemask # 哪些 type 受限速 +net.ipv4.icmp_ratemask = 6168 # mask bits +``` + +> [!note] iptables 中也可以限速 +> ```bash +> iptables -A INPUT -p icmp --icmp-type echo-request -m limit \ +> --limit 1/s --limit-burst 4 -j ACCEPT +> ``` + +## IPv6 等价:ICMPv6 + +IPv6 中 ICMP 被增强为 NDP(Neighbor Discovery Protocol),功能更丰富: + +| IPv4 ICMP | IPv6 ICMPv6 对应 | +|-----------|-----------------| +| Echo Request/Reply | 类型 8/0(不变) | +| Destination Unreachable | 类型 1(相同语义) | +| Time Exceeded | 类型 3(traceroute 用) | +| Router Discovery | Router Solicitation / Advertisement(类型 133/134)| +| ARP | Neighbor Solicitation / Advertisement(类型 135/136)| +| PMTUD | Path MTU Discovery 成为强制要求 | + +## 关联笔记 + +- [[hhs/NETWORK/IPv4首部与分段重组]] — ICMP 作为 IP 上层协议(Protocol=1) +- [[hhs/NETWORK/CIDR与子网划分]] — 理解子网后才能正确使用 ping +- [[hhs/NETWORK/NAT原理与应用]] — NAT 会影响 ICMP 的回程路径 diff --git a/hhs/NETWORK/03-网络层/06-静态路由与默认网关.md b/hhs/NETWORK/03-网络层/06-静态路由与默认网关.md new file mode 100644 index 0000000..0ec4c0b --- /dev/null +++ b/hhs/NETWORK/03-网络层/06-静态路由与默认网关.md @@ -0,0 +1,166 @@ +--- +tags: [计算机网络, 静态路由, 默认网关, 策略路由] +create time: 2026-05-18 01:00 +--- + +# 静态路由与默认网关 + +## 概述 + +路由是数据包从源到目的地所经历的路径选择过程。Linux 内核维护着一张**路由表**,每收到一个包就查表决定下一跳。最基础也是最常用的就是静态路由和默认网关。 + +## Linux 路由表结构 + +```bash +$ ip route show +default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.100 metric 100 +192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.100 metric 100 +10.0.0.0/8 via 10.0.0.1 dev eth1 proto static metric 200 +172.16.0.0/12 dev eth2 scope link +``` + +### 关键字段含义 + +| 字段 | 说明 | +|------|------| +| `default` | 匹配所有目标(等价于 `0.0.0.0/0`),即**默认网关** | +| `via` | 下一跳的 IP 地址 | +| `dev` | 出口网卡 | +| `proto` | 路由来源:kernel/auto/static/dhcp | +| `scope` | 范围:link(直连)/ host(单主机)/ global(全局可路由)| +| `metric` | 优先级,值越小越优先 | + +## 最长前缀匹配原则 + +当多条路由能匹配目标 IP 时,选择**前缀最长**的那条: + +``` +目标 IP: 10.0.0.5 + +路由表: + default via 192.168.1.1 → /0 (0 bits) + 10.0.0.0/8 via 10.0.0.1 → /8 (8 bits) ✅ 选中! + 10.0.0.0/24 via 10.0.0.2 → /24 (24 bits) ← 实际上这条更长,应该选它! + 10.0.0.5/32 via 10.0.0.3 → /32 (32 bits) ← 最佳匹配! + +结论: 最长前缀优先 → 选 /32 那条 +``` + +```mermaid +flowchart TD + S["目标 IP: 10.0.0.5"] --> C{"/32 match?"} + C -->|"是"| R3["10.0.0.5/32
via 10.0.0.3 ✅"] + C -->|"否"--> C2{"/24 match?"} + C2 -->|"是"| R2["10.0.0.0/24
via 10.0.0.2 ✅"] + C2 -->|"否"| C8{"/8 match?"} + C8 -->|"是"| R1["10.0.0.0/8
via 10.0.0.1 ✅"] + C8 -->|"否"| D["default route
via 192.168.1.1 ✅"] + + style R3 fill:#98FB98,color:#000 + style R2 fill:#DDA0DD + style R1 fill:#FFD700 + style D fill:#B0C4DE +``` + +## 默认网关 + +默认网关是所有未知目的地的兜底路径: + +```bash +# 查看当前默认网关 +$ ip route show default +default via 192.168.1.1 dev eth0 proto dhcp + +# 添加默认网关 +$ ip route add default via 192.168.1.1 dev eth0 metric 100 + +# 删除默认网关 +$ ip route del default via 192.168.1.1 + +# 修改默认网关(先删后加) +$ ip route replace default via 192.168.1.254 dev eth0 metric 50 +``` + +### 多默认网关(负载均衡与故障转移) + +```bash +# ECMP(Equal-Cost Multi-Path)— 同开销的多条路径负载均衡 +$ ip route add default via 10.0.0.1 dev eth0 +$ ip route add default via 10.0.1.1 dev eth0 + +# 流量会自动在两条链路间负载均衡(基于哈希) +$ ip -d route show +default nexthop via 10.0.0.1 dev eth0 weight 1 + nexthop via 10.0.1.1 dev eth0 weight 1 +``` + +## 静态路由配置 + +### 基本语法 + +```bash +# 格式: ip route add <网络>/<掩码> via <下一跳IP> dev <网卡> metric <优先级> +$ sudo ip route add 172.16.0.0/12 via 192.168.1.1 dev eth0 metric 100 +$ sudo ip route add 10.100.0.0/16 via 10.0.0.1 dev eth1 onlink +# onlink: 强制使用,即使下一跳不在同一子网 +``` + +### 不同发行版的持久化方式 + +| 系统 | 配置文件 | 示例 | +|------|---------|------| +| Debian/Ubuntu | `/etc/network/interfaces` | `up ip route add ...` | +| RHEL/CentOS 7+ | `/etc/sysconfig/network-scripts/route-eth0` | `10.0.0.0/8 via 192.168.1.1` | +| Systemd-networkd | `.network` 文件 | `Route:` section | +| NetworkManager | `nmcli connection modify` | `ipv4.routes "..."` | +| Alpine | `/etc/network/interfaces` | `post-up ip route add ...` | + +### Netplan (Ubuntu 18.04+) + +```yaml +# /etc/netplan/01-netcfg.yaml +network: + version: 2 + ethernets: + eth0: + dhcp4: true + routes: + - to: 10.100.0.0/16 + via: 192.168.1.1 +``` + +## 策略路由(Policy-Based Routing) + +Linux 支持基于源 IP、端口、协议等条件选择不同路由表: + +```bash +# 创建自定义路由表 +echo "100 web-table" >> /etc/iproute2/rt_tables +echo "200 db-table" >> /etc/iproute2/rt_tables + +# 为 web-table 添加路由 +ip route add 10.0.0.0/8 via 10.0.0.1 dev eth1 table web-table + +# 策略:来自 192.168.10.0/24 的流量走 web-table +ip rule add from 192.168.10.0/24 table web-table + +# 效果:即使是同一个目的地,不同源 IP 走不同路径 +``` + +```mermaid +flowchart LR + SrcA["192.168.10.5"] -->|"Source IP match"| Rule["ip rule policy"] + SrcB["192.168.20.5"] -->|"Source IP match"| Rule + + Rule -->|"web-table → eth1"| WebRouter["Web Router
10.0.0.1"] + Rule -->|"db-table → eth2"| DBRouter["DB Router
10.1.0.1"] + + style WebRouter fill:#DDA0DD,color:#000 + style DBRouter fill:#98FB98,color:#000 +``` + +## 关联笔记 + +- [[hhs/NETWORK/CIDR与子网划分]] — CIDR 前缀长度决定路由表的精确度 +- [[hhs/NETWORK/NAT原理与应用]] — NAT 常作为默认网关的角色存在 +- [[hhs/NETWORK/动态路由协议]] — OSPF/BGP 自动生成路由项,无需手动配置 diff --git a/hhs/NETWORK/03-网络层/07-动态路由协议OSPF-BGP.md b/hhs/NETWORK/03-网络层/07-动态路由协议OSPF-BGP.md new file mode 100644 index 0000000..aa24faa --- /dev/null +++ b/hhs/NETWORK/03-网络层/07-动态路由协议OSPF-BGP.md @@ -0,0 +1,200 @@ +--- +tags: [计算机网络, OSPF, BGP, 动态路由协议] +create time: 2026-05-18 01:10 +--- + +# 动态路由协议:OSPF 与 BGP + +## 概述 + +在大型网络中手动维护静态路由不现实——网络拓扑变化时每条路由都要手动更新。动态路由协议让路由器自动"学习"彼此的网络,发现链路故障后自动切换路径。核心分类: + +| 类别 | IGP (内部网关协议) | EGP (外部网关协议) | +|------|-------------------|-------------------| +| 代表协议 | OSPF, IS-IS, RIP | **BGP** | +| 使用场景 | 同一个 AS(自治系统)内 | **不同 AS 之间**(即互联网 backbone)| +| 算法类型 | 链路状态 (LSA), 距离向量 | 路径向量 | + +## OSPF(Open Shortest Path First) + +### 基本概念 + +OSPF 是一种**链路状态**协议,每台路由器收集全网拓扑信息,独立计算最短路径树(SPF)。 + +```mermaid +flowchart TD + subgraph "AS 内部 — OSPF Area 0" + R1["Router A"] --- R2["Router B"] + R1 --- R3["Router C"] + R2 --- R3 + R1 --- SW1["Network 192.168.1.0/24"] + R2 --- SW2["Network 10.0.0.0/8"] + R3 --- R4["Router D"] + R4 --- SW3["Network 172.16.0.0/12"] + end + + style R1 fill:#DDA0DD,color:#000 + style R3 fill:#FFD700,color:#000 +``` + +### OSPF 工作流程 + +```mermaid +sequenceDiagram + participant R1 as Router A + participant R2 as Router B + participant R3 as Router C + + Note over R1,R3: Step 1: Neighbor Discovery + R1->>R2: Hello Packet (multicast 224.0.0.5) + R2-->>R1: Hello + DB Description + R1->>R2: Adjacency Established ✅ + + Note over R1,R3: Step 2: LSA Exchange + R1->>R2: LSUpdate (我的直连网络: 192.168.1.0/24, cost=10) + R1->>R3: LSUpdate (我的直连网络: 192.168.1.0/24, cost=10) + + Note over R1,R3: Step 3: SPF Calculation + R2->>R2: Dijkstra → 最优路径已构建 + R3->>R3: Dijkstra → 最优路径已构建 + + Note over R1,R3: Step 4: Routing Table Updated +``` + +### OSPF 区域(Area)概念 + +OSPF 用"区域"来水平扩展: + +| 区域类型 | 特点 | +|----------|------| +| **Backbone Area 0** | 所有其他 Area 必须连接到 Area 0 | +| Standard Area | 传播全部 LSA,路由表最大 | +| Stub Area | 不接受 Type 5 LSA(外部路由),注入一条 default route | +| Totally Stubby | Stub + 不接收 Type 3(区域间路由),只有一条默认路由 | +| NSSA (Not-So-Stubby Area) | Stub 的变种,允许引入外部路由(Type 7)| + +```mermaid +flowchart LR + R1["ABR: Router A
Area 0 ↔ Area 1"] -->|"全部LSA"| A0["Area 0 (Backbone)"] + A0 -->|"全部LSA"| R1 + R1 -->|"汇总路由 → Stub"| A1["Area 1 (Totally Stubby)
只有默认路由"] + + style R1 fill:#FFD700,color:#000 + style A0 fill:#DDA0DD,color:#000 + style A1 fill:#98FB98,color:#000 +``` + +- **ABR** (Area Border Router): 连接多个区域的边界路由器 +- **ASBR** (AS Boundary Router): 引入外部路由的路由器 + +### Cost 计算 + +``` +Cost = Reference_Bandwidth / Interface_Bandwidth + +参考带宽默认值: 100 Mbps (10^8) + +接口实际 Cost: +- 10 Mbps Ethernet: 100 / 10 = 10 +- 100 Mbps Ethernet: 100 / 100 = 1 +- 1 Gbps Ethernet: 100 / 1000 = 0 → 调为 1 +- 10 Gbps Ethernet: 100 / 10000 = 0 → 需调整参考带宽 +``` + +> [!warning] 参考带宽问题 +> 如果参考带宽是 100Mbps,那么 Gigabit (1G) 和 TenGig (10G) 的 cost 都是 1,无法区分优劣。正确做法: +> ```bash +> router ospf 1 +> distance ospf 10 +> auto-cost reference-bandwidth 10000 # 单位 Mbps → 支持到 100G +> ``` + +### 查看 OSPF 状态 + +```bash +$ ip route | grep O # 过滤 OSPF learned routes +O 10.0.0.0/8 [110/20] via 192.168.1.1, eth0 +OE1 172.16.0.0/12 [110/130] via 192.168.1.1, eth0 + +$ ip -d ospf show # FRR/quagga/Zebra +$ netstat -s | grep ospf # OSPF 统计信息 + +# Quagga/FRR 中的详细查看 +$ vtysh +show ip ospf neighbor # 邻居关系 +show ip ospf database # LSA 数据库 +show ip ospf route # 计算出的 OSPF 路由 +show ip ospf interface eth0 # 接口 OSPF 状态 +``` + +## BGP(Border Gateway Protocol) + +### BGP vs OSPF 对比 + +| 特性 | OSPF | BGP | +|------|------|-----| +| 范围 | AS 内部 (IGP) | **AS 间 (EGP)** | +| 算法 | 链路状态 (Dijkstra) | **路径向量** | +| 传输 | IP 协议号 89 | **TCP 179** | +| 收敛速度 | 秒级 | 分钟级(设计如此,稳定优先)| +| 可扩展性 | ~几千台路由器 | **全球互联网 10 万+ 前缀** | +| 策略控制 | 基于 cost | **丰富的 attribute 策略** | + +### BGP 两种会话类型 + +```mermaid +flowchart LR + iBGP["iBGP
同一 AS 内的路由器"] -->|"传递可达信息
不改变 next-hop"| R1["AS 65001
Peer A -- Peer B"] + + eBGP["eBGP
不同 AS 间"] -->|"交换路由并修改 next-hop"| R2["AS 65001 -- AS 65002
Peer A -- Peer B"] + + style iBGP fill:#DDA0DD,color:#000 + style eBGP fill:#98FB98,color:#000 +``` + +### BGP 属性(Attributes)选路顺序 + +当多条路径可选时,按以下优先级逐条比较: + +```mermaid +flowchart TD + Start["新路由到达"] --> Weight["① Cisco专有 Weight
本地有效"] + Weight --> LocPrf["② Local Preference
AS 内传递"] + LocPrf --> Originate["③ 本地起源 > 接收的路由"] + Originate --> AS_Path["④ AS Path 最短"] + AS_Path --> ORIGIN["⑤ Origin: IGD < EGP < INCOMPLETE"] + ORIGIN --> MED["⑥ MED: 越低越好"] + MED --> ebgp["⑦ eBGP > iBGP"] + ebgp --> IGP_Cost["⑧ IGP cost to next-hop"] + IGP_Cost --> Cluster["⑨ Cluster List 最短"] + Cluster --> RouterID["⑩ Router ID 最小"] + + style Start fill:#FFD700,color:#000 + style LocPrf fill:#DDA0DD,color:#000 + style AS_Path fill:#98FB98,color:#000 +``` + +### ASN(自治系统编号) + +| 范围 | 说明 | +|------|------| +| 0–64511 | 私有 ASN(类似 RFC1918 IP 地址)| +| 64512–65534 | 16-bit 扩展私有 ASN | +| 65535 | 保留 | +| 65536–4294967295 | 32-bit 全局 ASN(互联网注册)| + +IANA 分配给各大 ISPs 和大型企业(如 Google: AS15169, Cloudflare: AS13335, Alibaba: AS37963)。 + +## RIP(Routing Information Protocol)— 了解即可 + +RIP 是最简单的距离向量协议,已基本被淘汰: + +- 跳数上限 15(第 16 跳视为不可达) +- 每 30 秒广播整个路由表 +- `routing protocol name is rip` → 太慢了,现代网络不用 + +## 关联笔记 + +- [[hhs/NETWORK/静态路由与默认网关]] — 静态路由是动态协议的替代方案 +- [[hhs/NETWORK/NAT原理与应用]] — NAT 后方的路由器不需要运行 OSPF/BGP +- [[hhs/NETWORK/负载均衡]] — Load Balancer 通常依赖 L4/L7 而非完整路由协议 diff --git a/hhs/NETWORK/03-网络层/08-NAT原理与应用.md b/hhs/NETWORK/03-网络层/08-NAT原理与应用.md new file mode 100644 index 0000000..e717a35 --- /dev/null +++ b/hhs/NETWORK/03-网络层/08-NAT原理与应用.md @@ -0,0 +1,210 @@ +--- +tags: [计算机网络, NAT, SNAT, DNAT, PAT, STUN, TURN] +create time: 2026-05-18 01:20 +--- + +# NAT 原理与应用 + +## 概述 + +NAT(Network Address Translation,网络地址转换)是 IPv4 时代最重要的"续命"技术之一——它让大量私有 IP 主机通过少数几个公网 IP 访问互联网。理解 NAT 的工作原理对于排查连接问题、设计分布式系统和部署 VPN 至关重要。 + +> [!QUESTION] NAT 为什么能工作? +> NAT 的本质是一个中间人:它在修改经过的数据包的源或目的地址/端口。因为 TCP/IP 协议的端到端原则要求只有通信两端才应看到完整地址,而 NAT 在中间偷偷改了——这破坏了理论模型的"纯洁性",但解决了现实中的地址短缺问题。 + +## NAT 分类速览 + +| 类型 | 全称 | 方向 | 修改字段 | 用途 | +|------|------|------|---------|------| +| **SNAT** | Source NAT | 出向 | 源 IP | 内网 → 外网 | +| **DNAT** | Destination NAT | 入向 | 目的 IP+Port | 外网 → 内网服务器 | +| **PAT** | Port Address Translation | 双向 | 同时改 IP+Port | 多主机共享单公网 IP | +| **静态 NAT** | Static NAT | 双向一对一 | 固定映射 | 内网服务器暴露到公网 | + +## SNAT(源地址转换)— 内网上网的钥匙 + +### 工作流程 + +```mermaid +sequenceDiagram + participant PC as 内网主机
192.168.1.100:45678 + participant NAT as 路由器/NAT网关
eth0: 203.0.113.5
eth1: 192.168.1.1 + participant Server as 外部 Web 服务器
93.184.216.34:80 + + PC->>NAT: SYN src=192.168.1.100:45678 dst=93.184.216.34:80 + Note over NAT: NAT Table Entry:
192.168.1.100:45678 ↔ 203.0.113.5:50001 + NAT->>Server: SYN src=203.0.113.5:50001 dst=93.184.216.34:80 + Server-->>NAT: SYN-ACK dst=203.0.113.5:50001 + Note over NAT: 反向查找 NAT Table
203.0.113.5:50001 → 192.168.1.100:45678 + NAT-->>PC: SYN-ACK + PC->>NAT: ACK src=192.168.1.100:45678 + NAT->>Server: ACK src=203.0.113.5:50001 + + Note over PC,Server: ← 双方都不知道 NAT 的存在 → +``` + +### iptables 实现 SNAT + +```bash +# 内网接口 eth1, 外网接口 eth0 +sudo iptables -t nat -A POSTROUTING \ + -s 192.168.1.0/24 -o eth0 -j MASQUERADE + +# MASQUERADE vs SNAT: +# MASQUERADE: 自动获取 eth0 的当前 IP(适合动态 IP,如 DHCP/拨号) +# SNAT: 指定固定 IP (适合静态 IP,性能略高) +# 等价写法: +sudo iptables -t nat -A POSTROUTING \ + -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.5 +``` + +```bash +# 查看 NAT 表条目 +$ sudo conntrack -L | head -10 +tcp 6 119 SYN_SENT ... src=192.168.1.100 dst=93.184.216.34 + src=203.0.113.5 dst=93.184.216.34 +``` + +## DNAT(目的地址转换)— 反向代理 + +### 典型场景:将公网 IP 的 80 端口转发到内网 Web 服务器 + +```mermaid +sequenceDiagram + participant Client as 外部客户端
x.x.x.x:12345 + participant FW as 防火墙/NAT
公网: 203.0.113.5:80 + participant Web as 内网 Web
192.168.1.10:80 + + Client->>FW: SYN src=x.x.x.x:12345 dst=203.0.113.5:80 + Note over FW: PREROUTING 链: DNAT
203.0.113.5:80 → 192.168.1.10:80 + FW->>Web: SYN src=x.x.x.x:12345 dst=192.168.1.10:80 + Note over Web: ← 注意: 源IP仍然是客户端真实IP (Full Cone) + Web-->>FW: SYN-ACK dst=x.x.x.x:12345 + FW->>Client: SYN-ACK + + Note over FW,Web: 回程路由需要配置: ip route add x.x.x.x via ... +``` + +### iptables 实现 DNAT + +```bash +sudo iptables -t nat -A PREROUTING \ + -d 203.0.113.5 -p tcp --dport 80 -j DNAT \ + --to-destination 192.168.1.10:80 + +# 还需允许转发 +sudo iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 80 -j ACCEPT + +# 如果希望 Web 能看到客户端真实 IP(不启用 SNAT) +sudo sysctl net.ipv4.conf.all.accept_local=1 +``` + +### Docker 的端口映射本质就是 DNAT + SNAT + +```bash +# docker run -p 8080:80 nginx +# = DNAT: host:8080 → container_ip:80 +# + SNAT: container reply 时把源 IP 改回 host IP + +iptables -t nat -A PREROUTING \ + -d -p tcp --dport 8080 -j DNAT --to-destination :80 +``` + +## PAT / NAPT(端口级 NAT)— 解决地址枯竭的关键 + +### 核心机制 + +``` +内网主机 NAT设备 公网IP:Port池 +───────── ─────── ───────────── +192.168.1.10:45678 ──→ 203.0.113.5:50001 203.0.113.5 +192.168.1.11:45678 ──→ 203.0.113.5:50002 ← 多个内网IP +192.168.1.12:45678 ──→ 203.0.113.5:50003 ← 复用同一个公网IP +192.168.1.10:45679 ──→ 203.0.113.5:50004 +``` + +一个公网 IP 最多可提供 **65535 个端口**,理论上支持约 6.5 万并发内网用户。实际受限于内核文件描述符限制(通常 ~6.5 万)。 + +## NAT 对 TCP 的影响 + +### TIME_WAIT 放大效应 + +由于所有内网主机的连接都映射到同一个公网 IP,NAT 设备的 TIME_WAIT 连接数会远超单机。当 NAT 重启时,所有半关闭的连接都会丢失。 + +### FTP 的问题 + +FTP 使用**独立的控制连接和数据连接**,且在 HTTP body 中嵌入 IP 和端口信息: + +``` +FTP 控制通道: PORT 192,168,1,100,180,13 ← "数据连接请发到这个IP和端口" +``` + +NAT 后的内网 IP 对公网不可达!解决方案: + +| 方案 | 说明 | +|------|------| +| **FTP ALG** | NAT 设备深度解析 FTP 控制流,改写 PORT 命令中的 IP | +| **被动模式 PASV** | 服务器提供监听端口,由客户端发起数据连接 | +| **SFTP over SSH** | 完全绕过 FTP,用加密隧道传输文件 | + +> [!warning] NAT ALG 的隐患 +> ALG (Application Layer Gateway) 需要深度解析应用层协议内容,容易出错且增加安全风险。现代实践中更倾向于 SFTP/SCP 替代 FTP。 + +## NAT 穿透技术 + +### NAT 类型分类 + +| 类型 | 行为 | 连通性 | +|------|------|--------| +| Full Cone | 任何外部地址都能发送 UDP 到你的映射端口 | ⭐⭐⭐⭐⭐ 最佳 | +| Restricted Cone | 仅已知 IP 能发 | ⭐⭐⭐⭐ | +| Port Restricted Cone | 仅已知 IP+Port 能发 | ⭐⭐⭐⭐ | +| Symmetric | 不同目的地获得不同映射端口 | ⭐⭐ 最难穿透 | + +### 常用穿透方案 + +```mermaid +flowchart TD + P1["STUN
Session Traversal Utilities for NAT"] -->|"1. 告诉对方我的公网 IP:Port"| P2["TURN
Traversal Using Relays around NAT"] + P2 -->|"2. 中继服务器转发电文"| P3["ICE
Interactive Connectivity Establishment"] + P3 -->|"3. 尝试直连 → 失败则 fallback 到 TURN"| Final["WebSocket/WebRTC ✅"] + + style P1 fill:#DDA0DD,color:#000 + style P2 fill:#FFD700,color:#000 + style P3 fill:#98FB98,color:#000 +``` + +### ICE 流程 + +``` +Step 1: Host Candidate — 本机直接 IP(局域网内直连最快) +Step 2: SRFLX Candidate — STUN 探测得到的公网映射地址 +Step 3: Relay Candidate — TURN 服务器分配的 relay 地址 + +排序优先级: Host > SRFLX > Relay +尝试顺序: 先试直连,超时后走中继 +``` + +## NAT64 / DNS64 + +IPv6 过渡的重要机制:IPv6-only 客户端访问 IPv4-only 服务器 + +``` +客户端 (IPv6): 2001:db8::1 +DNS64: 将 A 记录 93.184.216.34 → AAAA 记录 ::ffff:93.184.216.34 +NAT64 网关: 拦截 IPv6 包 → 转换为 IPv4 包 → 发送到 Internet +回包: IPv4 → IPv6 转换 → 返回客户端 +``` + +```bash +# Linux NAT64 内核模块 +modprobe nf_nat_ipv6 +sysctl -w net.ipv6.conf.all.forwarding=1 +ip6tables -t nat -A POSTROUTING -s fd00::/64 -j SNAT --to-source 2001:db8:64::1 +``` + +## 关联笔记 + +- [[hhs/NETWORK/CIDR与子网划分]] — NAT 使用 RFC1918 私有地址空间 +- [[hhs/NETWORK/TCP状态机详解]] — NAT 影响 TCP 连接生命周期 +- [[hhs/NETWORK/DNS原理与优化]] — DNS64 将 A 记录转为 AAAA 记录 diff --git a/hhs/NETWORK/04-传输层/01-TCP段结构与状态机.md b/hhs/NETWORK/04-传输层/01-TCP段结构与状态机.md new file mode 100644 index 0000000..027f927 --- /dev/null +++ b/hhs/NETWORK/04-传输层/01-TCP段结构与状态机.md @@ -0,0 +1,155 @@ +--- +tags: [计算机网络, TCP, 状态机, 三次握手, 四次挥手] +create time: 2026-05-18 01:30 +--- + +# TCP 段结构与状态机 + +## 概述 + +TCP(Transmission Control Protocol)是互联网最核心的可靠传输协议。理解其段结构和完整状态机,是排查线上网络问题的基本功。 + +## TCP 首部格式(32-bit fields) + +``` +┌─────────────┬─────────────┬───────────────────┐ +│ Source Port │ Destination Port│ Sequence No │ +│ 2 bytes │ 2 bytes │ 4 bytes │ +├─────────────┼───────────────┤ │ +│ Ack No │ Data Offset | Reserved│ECN│ CWR│ ACK │ PSH │ RST │ SYN │ FIN │ +│ 4 bytes │ 4bits │ 3 bits │ │ │ │ │ │ +├─────────────┴─────────────┴───────────────────┤ +│ Window Size │ +│ 2 bytes │ +├───────────────────┬───────────────────────────┤ +│ Checksum │ Urgent Pointer │ +│ 2 bytes │ 2 bytes │ +├───────────────────┴───────────────────────────┤ +│ Options (variable) │ +│ Max: 40 bytes │ +└───────────────────────────────────────────────┘ +Minimum header: 20 bytes +Maximum header: 60 bytes (with options) +``` + +### 关键字段详解 + +| 字段 | 大小 | 说明 | +|------|------|------| +| **Source/Dest Port** | 各 2B | 源端口和目的端口 | +| **Sequence Number** | 4B | 本报文第一个字节的序号(32 bit,约 4GB) | +| **Acknowledgment Number** | 4B | 期望收到的下一个字节序号(ACK=1 时有效) | +| **Data Offset** | 4 bit | 首部长度,单位 4 bytes(最小 5 = 20B,最大 F = 60B) | +| **Reserved** | 3 bit | 保留字段,必须为 0 | +| **ECN** | 2 bit | ECN-Echo + CWR:显式拥塞通知 | +| **URG** | 1 bit | 紧急指针有效 | +| **ACK** | 1 bit | 确认号有效(除首次 SYN 外几乎所有包都设 ACK=1) | +| **PSH** | 1 bit | 推送标志,告诉接收端立即提交应用层 | +| **RST** | 1 bit | 复位连接(异常关闭) | +| **SYN** | 1 bit | 同步序列号(建立连接) | +| **FIN** | 1 bit | 结束标志(优雅关闭) | +| **Window Size** | 2B | 接收窗口大小,流量控制基础 | +| **Checksum** | 2B | 覆盖首部 + 数据的 CRC | +| **Urgent Pointer** | 2B | URG=1 时偏移量,指示紧急数据位置 | + +### 常用 TCP Options + +| Option | 大小 | 说明 | +|--------|------|------| +| NOP | 1B | No Operation,用于填充对齐 | +| MSS | 4B | Maximum Segment Size,通常 1460(1500-MT-20IP) | +| Window Scale | 4B | 扩大窗口因子(2^x倍,最大 14 = 16384 倍) | +| SACK Perm / SACK | 可变 | Selective Acknowledgment,选择性确认 | +| Timestamps | 10B | TSval + TSecr,RTT 测量和防回绕 | + +## TCP 状态机——完整图 + +```mermaid +stateDiagram-v2 + [*] --> CLOSED + + note right of CLOSED + TCP 不活跃状态 + 只有监听套接字在此 + end note + + CLOSED --> LISTEN: socket() + listen() + + LISTEN --> SYN_RCVD: 收到 SYN (server only) + LISTEN --> ESTABLISHED: connect() + remote SYN-ACK (client side rare) + + note right of SYN_SENT + client sent SYN + waiting for SYN-ACK + end note + + SYN_RCVD --> ESTABLISHED: 3-Way Handshake Complete + SYN_SENT --> ESTABLISHED: Got SYN-ACK + + ESTABLISHED --> CLOSE_WAIT: Application calls close() + ESTABLISHED --> FIN_WAIT_1: Remote sends FIN + + FIN_WAIT_1 --> FIN_WAIT_2: Local ACK received + FIN_WAIT_1 --> CLOSING: Simultaneous close + + FIN_WAIT_2 --> TIME_WAIT: Remote FIN received + + CLOSING --> TIME_WAIT: Remote ACK received + + CLOSE_WAIT --> LAST_ACK: Local application close + LAST_ACK --> CLOSED: Final ACK received + TIME_WAIT --> CLOSED: 2 * MSL timeout + + note right of TIME_WAIT + Must wait 2*MSL + Ensures final ACK arrives + Prevents old duplicate packets + end note + + note right of CLOSE_WAIT + Server side closing + Application must call close() + end note +``` + +### 所有 11 种状态一览 + +| # | 状态 | 触发条件 | 谁处于? | +|---|------|---------|---------| +| 1 | CLOSED | 初始/终止 | 双方初始状态 | +| 2 | LISTEN | `listen()` 系统调用 | 服务端 | +| 3 | SYN_SENT | `connect()` 发出 SYN | 客户端 | +| 4 | SYN_RCVD | 收到 SYN | 服务端 | +| 5 | ESTABLISHED | 三次握手完成 | 双方 | +| 6 | FIN_WAIT_1 | 本地调用 close() | 主动关闭方 | +| 7 | FIN_WAIT_2 | FIN_WAIT_1 的 ACK 到达 | 主动关闭方 | +| 8 | CLOSE_WAIT | 收到远程 FIN | **被动关闭方**(服务器!)| +| 9 | CLOSING | 同时关闭 | 极少见 | +| 10 | LAST_ACK | 本地发送最后一个 FIN | 被动关闭方 | +| 11 | TIME_WAIT | 发送完最后一个 ACK | 主动关闭方 | + +> [!tip] 哪些状态是你最常看到的? +> - **ESTABLISHED**: 正常工作 +> - **TIME_WAIT**: 高频短连接的服务端(如 nginx、API 网关) +> - **CLOSE_WAIT**: ⚠️ **应用 Bug!** 说明服务器收到了客户端的关闭请求但没调用 close() + +### CLOSE_WAIT 堆积排查 + +```bash +# 查看 CLOSE_WAIT 数量 +$ ss -tan state close-wait | wc -l +4521 + +# 是谁引起的?查进程 +$ ss -tan state close-wait src :80 +State Recv-Q Send-Q Local Address:Port Peer Address:Port Process +CLOSE-WAIT 0 0 10.0.0.1:80 203.0.113.5:45678 app=nginx(1234) + +# 解决方案: 修复代码中未正确关闭的文件描述符/HTTP连接 +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — 详细解析握手/挥手过程 +- [[hhs/NETWORK/TCP可靠传输机制]] — 序列号/确认号的深入讲解 +- [[hhs/NETWORK/TCP拥塞控制]] — TCP 窗口的动态管理 diff --git a/hhs/NETWORK/04-传输层/02-TCP三次握手与四次挥手.md b/hhs/NETWORK/04-传输层/02-TCP三次握手与四次挥手.md new file mode 100644 index 0000000..468ae70 --- /dev/null +++ b/hhs/NETWORK/04-传输层/02-TCP三次握手与四次挥手.md @@ -0,0 +1,199 @@ +--- +tags: [计算机网络, TCP握手, TCP挥手, TIME_WAIT] +create time: 2026-05-18 01:40 +--- + +# 三次握手与四次挥手 + +## 概述 + +TCP 连接的建立和拆除是其可靠性机制的核心环节。理解每次交互的目的,能帮助你从协议层面判断连接为什么失败或延迟高。 + +## 三次握手(Three-Way Handshake) + +### 完整流程 + +```mermaid +sequenceDiagram + participant Client as 客户端
ISN=c, seq=c + participant Server as 服务端
ISN=s, seq=s + + Note over Client: state=SYN_SENT
allocate TCBSocket + + Client->>Server: SYN (seq=c, flags=SYN) + Note over Server: state=LISTEN → SYN_RCVD
allocate TCB
window=w, MSS=m + + Server-->>Client: SYN-ACK (seq=s, ack=c+1, flags=SYN|ACK)
window=w, MSS=m + Note over Client: state=SYN_SENT → ESTABLISHED + + Client->>Server: ACK (seq=c+1, ack=s+1, flags=ACK) + Note over Server: state=SYN_RCVD → ESTABLISHED + Note over Client,Server: ← ESTABLISHED ✅ 连接就绪 --> + + Note over Client: send() buffer flushed + Note over Server: accept()/recv() returns data +``` + +### 为什么必须三次?两次够吗? + +| 假设 | 问题 | +|------|------| +| 如果只要两次(SYN + ACK) | 服务器发出 SYN-ACK 后就认为对方已收到——但**服务器不知道客户端是否真的收到了 SYN-ACK**。如果 SYN-ACK 丢失,客户端不会发起第三次 ACK,连接永远不会进入 ESTABLISHED | +| 如果是两次且客户端重传 SYN | 服务器收到重复 SYN 会创建新的连接(没有确认上一轮的机制),导致资源泄漏 | +| **三次解决了什么** | 双方都确认了彼此的收发能力:客户端确认服务器收到了自己的 SYN;服务器确认客户端收到了自己的 SYN-ACK | + +> [!question] SYN-ACK 丢失后会发生什么? +> 客户端超时重传 SYN(通常 3s、6s、12s... 指数退避)。服务器收到重传的 SYN 后会回复新的 SYN-ACK。整个过程由内核完成,应用层无感知。 + +### 各阶段的窗口大小 + +握手期间还没有实际数据传输,窗口信息用于协商参数: + +| 阶段 | 携带的信息 | +|------|-----------| +| SYN | MSS (Maximum Segment Size)、Window Scale、Timestamps | +| SYN-ACK | MSS、Window Scale、SACK Permitted | +| ACK | 不携带新选项(可选 Window Scale 确认)| + +```bash +# 通过 tcpdump 观察握手 +$ sudo tcpdump -nn 'tcp port 80 and (tcp[13] & 2 != 0)' +IP 192.168.1.100.54321 > 93.184.216.34.80: S 1000:1000(0) win 64240 + ↑ SYN ↑ Options +IP 93.184.216.34.80 > 192.168.1.100.54321: S 2000:2000(0) ack 1001 win 65535 + ↑ SYN-ACK ↑ ACK number = ISN+1 +IP 192.168.1.100.54321 > 93.184.216.34.80: . 1001:1001(0) ack 2001 win 512 + ↑ ACK +``` + +## 四次挥手(Four-Way Teardown) + +### 为什么需要四次? + +因为 TCP 是全双工的——每条方向的连接独立关闭。A 说"我完了"和 B 说"我也完了"是两个独立的动作。 + +```mermaid +sequenceDiagram + participant A as 主动关闭方 (Client) + participant B as 被动关闭方 (Server) + + Note over A: Application calls close() + + A->>B: FIN (seq=x) ──→ Step 1: FIN + Note over B: state=ESTABLISHED → CLOSE_WAIT + Note over B: "Client is done, but I may still have data!" + + B->>A: ACK (ack=x+1) ──→ Step 2: ACK (确认FIN) + Note over B: Application calls close() after sending remaining data + + B->>A: FIN (seq=y) ──→ Step 3: FIN + Note over A: state=FIN_WAIT_2 → FIN_WAIT_1 + Note over A: "OK, I'm done too." + + A->>B: ACK (ack=y+1) ──→ Step 4: ACK + + Note over A: → TIME_WAIT (wait 2*MSL) + Note over B: → CLOSED ✅ + + Note over A: After 2*MSL timeout → CLOSED ✅ +``` + +### 关键时机说明 + +``` +Time A (主动方) B (被动方) +──── ─────────────────────────────────────────────────────────── +t=0 close() → send(FIN) ESTABLISHED + → FIN_WAIT_1 ↓ recv(FIN) +t=1 → CLOSE_WAIT (等应用调用close) + ← ACK(FIN) send(...) remaining data + FIN_WAIT_2 ↓ close() +t=2 → send(FIN) + ← FIN → LAST_ACK +t=3 ACK(FIN) → TIME_WAIT → CLOSED + (等待 2*MSL ≈ 120s) +t=120 TIME_WAIT → CLOSED ✅ +``` + +## TIME_WAIT 的深度解析 + +### 为什么要等 2 * MSL? + +| 原因 | 说明 | +|------|------| +| 确保最后一个 ACK 到达 | 如果 ACK 丢失,对方会重发 FIN — TIME_WAIT 状态下可以重新回复 ACK | +| 让旧连接的报文在网络中自然消亡 | 避免旧连接的数据包误入新连接(同一四元组) | + +### MSL(Maximum Segment Lifetime)是多少? + +- RFC 标准未定义具体值 +- Linux / Windows / macOS 通常设为 60 秒 +- 所以 `2 * MSL ≈ 120 秒` + +### TIME_WAIT 堆积的影响与应对 + +```bash +# 查看 TIME_WAIT 数量 +$ ss -tan state time-wait | wc -l +15234 + +# TIME_WAIT 的源端口分布 +$ ss -tan state time-wait src :80 | awk '{print $5}' | cut -d: -f2 | sort | uniq -c | sort -rn | head + +# 解决方案 +``` + +| 方案 | 说明 | 风险 | +|------|------|------| +| 增加可用端口数 | `/proc/sys/net/ipv4/ip_local_port_range` 调整为 `1024 65535` | 安全扫描器可能利用短连接 | +| 启用 TIME\_WAIT 复用 | `net.ipv4.tcp_tw_reuse = 1` | 只能复用在 outbound 连接上(RFC 兼容性) | +| 快速回收 | `net.ipv4.tcp_tw_recycle = 0`(已从内核移除)| ❌ 不安全,会破坏 NAT 场景 | +| 使用 keepalive 缩短生命周期 | 降低 keepalive 超时时间 | 效果有限 | +| 架构改造 | gRPC 长连接代替 HTTP 短连接 | 最佳方案 | + +> [!warning] tcp_tw_recycle 已被移除 +> 该选项在 IPv4 NAT 场景下有缺陷(不同内部主机映射到同一公网 IP 时,TS 字段冲突)。Linux 4.12 起已从内核彻底移除。 + +## 半开连接(Half-Open Connection) + +``` +SYN_RECV 状态 = 三次握手中途被卡住的状态 +``` + +```bash +# 查看 SYN_RECV 数量 +$ ss -tan state syn-recv +State Recv-Q Send-Q Local Port Peer Port +SYN-RECV 0 0 :80 203.0.113.5:12345 + +# 大量 SYN_RECV = SYN Flood 攻击! +``` + +防御措施: +- `tcp_syncookies = 1`(开启 SYN Cookie) +- 限制每秒 SYN 速率 +- CDN/WAF 前置过滤 + +## Go 中的实践 + +```go +// 客户端设置 connect timeout +conn, err := net.DialTimeout("tcp", "example.com:80", 5*time.Second) + +// 服务端设置 accept timeout (Go 1.19+) +ln, _ := net.Listen("tcp", ":8080") +for { + c, err := ln.Accept() // 阻塞等待 + if err != nil { + // 处理 Accept 超时 + break + } + go handleConn(c) // goroutine 处理每个连接 +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP段结构与状态机]] — 状态机的详细定义 +- [[hhs/NETWORK/TCP可靠传输机制]] — 序列号如何工作 +- [[hhs/NETWORK/TCP内核参数调优]] — TIME_WAIT 相关的内核参数 diff --git a/hhs/NETWORK/04-传输层/03-TCP可靠传输机制.md b/hhs/NETWORK/04-传输层/03-TCP可靠传输机制.md new file mode 100644 index 0000000..1ee2dfc --- /dev/null +++ b/hhs/NETWORK/04-传输层/03-TCP可靠传输机制.md @@ -0,0 +1,168 @@ +--- +tags: [计算机网络, TCP可靠传输, 序列号, 确认号, 重传] +create time: 2026-05-18 01:50 +--- + +# 可靠传输机制 + +## 概述 + +TCP 承诺"可靠交付"——不管中间网络多么不可靠,发送方最终确保每个字节都被接收方完整、有序地收到。这是通过**序列号/确认号 + 重传 + 校验**组合实现的。 + +## 序列号(Sequence Number) + +### 基本原理 + +每个 TCP 段有一个 32 bit 的序列号,代表该段的**第一个数据字节在整个流中的位置**。 + +``` +发送方发出的数据流: +字节: 0 1 2 3 ... 99 100 + ├─────┼─────┼─────┼───┤ └───┤ + │ Seg1│ Seg2│ Seg3│...│ │SegN│ + seq=0 seq=50 seq=100 seq=99 + +Seq 值范围: 0 ~ 2^32 - 1 = 4,294,967,295 (约 4GB) +回绕: 到达最大值后回到初始 ISN (Initial Sequence Number) +``` + +### ISN(Initial Sequence Number)生成策略 + +```go +// Go 的 net包: ISN 使用随机化防止预测攻击 +func getISN() uint32 { + // crypto/rand 或 /dev/urandom + return randomUint32() +} +``` + +Linux 默认 ISN = jiffies + hash(src/dst IP/port),现代内核已改为完全随机化。 + +## 确认号(Acknowledgment Number) + +| 含义 | 说明 | +|------|------| +| ack = N | "我已经收到了 **N-1 及之前**的所有字节" | +| ack = expected next byte | 期望对方下一个收到的字节序号 | +| ACK = 0 的情况 | 仅在 SYN/FIN 阶段出现(它们不占序列号空间)| + +### SACK(Selective Acknowledgment) + +标准 TCP 只能确认"前面的所有字节都到了",SACK 允许接收方告诉发送方"**哪些乱序的块我也收到了**": + +``` +正常 ACK: SACK: +──────── ──────── +收齐了 [0,49] 收到: [0,49] [100,149] [200,249] +ack=50 缺: [50,99] [150,199] + ↑ + 只重传缺的! +``` + +```bash +$ sysctl net.ipv4.tcp_sack +net.ipv4.tcp_sack = 1 # Linux 默认开启 +``` + +## 超时重传(RTO - Retransmission Timeout) + +### RTO 计算(Jacobson/Karels 算法) + +``` +RTT_sample = T_arrival - T_sent // 本次往返时间 +rtt_variance = (1 - β) * rtt_variance + β * |RTT_sample - RTT_avg| + β = 1/4 +RTO = RTT_avg + 4 * rtt_variance + α = 3/4 + +初始 RTO: 至少 1 秒 (RFC 6298) +最大 RTO: 60 秒 +``` + +### 快速重传(Fast Retransmit) + +如果收到**3 个重复 ACK**(duplicate ACK),说明某个包很可能丢了——不等 RTO 过期立即重传: + +```mermaid +flowchart LR + A["Segment 1 → recv ✅"] --> B["Segment 2 → recv ✅"] + B --> C["Segment 3 → LOST ❌"] + C --> D["DupACK for seg 2 ×3"] + D -->|"触发快速重传"| E["Re-send Segment 3 immediately ✅"] + + style C fill:#FF6B6B,color:#fff + style E fill:#98FB98,color:#000 +``` + +``` +传统 RTO 重传 vs 快速重传: +───────────────────────── +传统: 丢包 → 等 RTO(≥1s) → 重传 → 再等一个 RTT → 共 ≥ 2s +快速: 丢包 → 收到3个dup ACK(~1个RTT) → 立即重传 → 共 ~1 RTT +``` + +## Go 中的 TCP KeepAlive + +```go +import "golang.org/x/sys/unix" + +// 设置 TCP keepalive (Go stdlib 1.11+) +ln, _ := net.Listen("tcp", ":8080") +conn, _ := ln.Accept() + +if tc, ok := conn.(*net.TCPConn); ok { + // 每 30s 发送一次 keepalive probe + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(30 * time.Second) + + // 最多重试 3 次 (内核参数 net.ipv4.tcp_keepalive_probes) + // 总超时 = 30s * 3 = 90s +} +``` + +```bash +# Linux 内核默认值 +$ sysctl net.ipv4.tcp_keepalive_time # 首次探测前的空闲时间 +net.ipv4.tcp_keepalive_time = 7200 # 2小时! +$ sysctl net.ipv4.tcp_keepalive_intvl # 两次探测间隔 +net.ipv4.tcp_keepalive_intvl = 75 # 75秒 +$ sysctl net.ipv4.tcp_keepalive_probes # 最多探测次数 +net.ipv4.tcp_keepalive_probes = 9 # 9次 +``` + +> [!tip] 为什么 Go http.Server 默认没有 keepalive? +> `http.Transport` 的 IdleConnTimeout 默认是 90s,但底层的底层 keepalive 由操作系统控制。高并发服务通常需要手动调小 `SetKeepAlivePeriod()` 和内核参数。 + +## 紧急数据(URG) + +``` +[普通数据] [URG flag set] [+1 urgent data byte] [普通数据] + ^^^^ + "请立刻处理这个字节!" +``` + +紧急指针指向 URG 标志后的那个字节。但由于许多应用忽略 URG 功能(Nagle 算法可能与 URG 冲突),现代实践中极少使用。 + +## 粘包与拆包的根因 + +> ⚠️ 注意:这个问题虽然在本节讨论,但它其实是应用层协议的设计问题,详见 [[hhs/NETWORK/TCP粘包与拆包]] + +### 为什么会粘包? + +``` +发送端 write("HELLO") → TCP 缓冲 → 合并成一个段 "HELLOWORLD" → 接收端 read() 拿到一整串 +发送端 write("WORLD") → 因为 Nagle 算法延迟发送,合并了上一段 +``` + +### 为什么会拆包? + +``` +发送端 write("HELLO WORLD THIS IS LONG") → 超过 MSS → TCP 自动分片 +接收端 read(5) → 只读到 "HELLO" → 剩下在缓冲区等待下次 read() +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP粘包与拆包]] — 应用层解决方案详解 +- [[hhs/NETWORK/TCP拥塞控制]] — 重传与拥塞窗口联动 +- [[hhs/NETWORK/TCP流量控制]] — 滑动窗口管理 diff --git a/hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制.md b/hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制.md new file mode 100644 index 0000000..e477928 --- /dev/null +++ b/hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制.md @@ -0,0 +1,189 @@ +--- +tags: [计算机网络, TCP流量控制, 拥塞控制, BBR, Cubic] +create time: 2026-05-18 02:00 +--- + +# 流量控制与拥塞控制 + +## 概述 + +TCP 有两个"刹车"机制:流量控制(防止接收方被淹没)和拥塞控制(防止网络被撑爆)。前者是**端到端**的,后者是**全网协同**的。 + +## 流量控制(Flow Control) + +### 滑动窗口原理 + +```mermaid +flowchart LR + Sender["发送方"] -->|"seq 100..299"| Recv["接收方
rwnd = 300"] + Recv -->|"ACK=100, window=300"| Sender + + Note over Sender:"可用窗口 = min(cwnd, rwnd)" + Note over Recv:"rwnd = RecvBufSize - UnackedData" +``` + +| 概念 | 含义 | +|------|------| +| **rwnd** (Receive Window) | 接收方的剩余缓冲区大小,告诉发送方"我还能收多少" | +| **cwnd** (Congestion Window) | 拥塞窗口,由发送方根据网络状况自行估算 | +| **实际可用窗口** | `min(rwnd, cwnd)` — 取两者较小值 | + +### 零窗口问题 + +当接收方缓冲区满时,会通告窗口大小为 0: + +``` +发送方收到 ACK with window=0 → 停止发送数据 → 进入 persist timer 探测状态 +``` + +### Zero Window Probe (ZWP) + +Linux 内核的 ZWP 定时发送一个字节的数据来探测窗口是否恢复: + +```bash +$ sysctl net.ipv4.tcp_no_metrics_save +net.ipv4.tcp_no_metrics_save = 1 # 避免缓存错误的 RTT 值 +``` + +> [!warning] 死锁场景 +> 如果 ZWP 丢失且对端没有响应,连接会永久僵死。解决方案:应用层实现超时重连。 + +## 拥塞控制(Congestion Control) + +### 四种核心算法 + +```mermaid +flowchart TD + subgraph "慢启动阶段 Slow Start" + A["cwnd = 1 MSS"] -->|"每个RTT翻倍(指数增长)"| B[cwnd ≥ ssthresh?] + end + + B -->|"是"| C["进入拥塞避免
(线性增长)"] + + subgraph "拥塞避免阶段 Congestion Avoidance" + C -->|"每个RTT+1 MSS
(线性增长)"| D[检测到丢包? ] + end + + D -->|"是"| E["ssthresh = cwnd / 2
cwnd = 1 (或 2 MSS)"] + E --> F["回到慢启动
(快速恢复)"] + F --> G["快速恢复后
进入拥塞避免"] + + style A fill:#DDA0DD,color:#000 + style C fill:#FFD700,color:#000 + style E fill:#FF6B6B,color:#fff + style G fill:#98FB98,color:#000 +``` + +### 详细对照表 + +| 阶段 | cwnd 变化 | 增长速度 | 适用场景 | +|------|----------|---------|---------| +| **慢启动** | 每 RTT × 2 | 指数 | 初始探测、快速恢复后 | +| **拥塞避免** | 每 RTT + 1 MSS | 线性 | 接近容量时的精细调节 | +| **快重传** | 收到 3 个 Dup ACK | 立即重传 | 轻微丢包 | +| **快恢复** | cwnd = max(cwnd/2, 2MSS) | 从减半值开始慢启动 | 伴随快重传 | + +### 三种触发事件 + +| 事件 | 操作 | ssthresh | cwnd | +|------|------|---------|------| +| 3个重复 ACK | 快重传 + 快恢复 | cwnd / 2 | max(cwnd/2, 2×MSS) | +| RTO 超时 | 慢开始 | cwnd / 2 | 1 MSS (Reno/Cubic) / 3 segments (NewReno) | +| SACK 确认部分缺失 | SACK-based fast recovery | 同上 | 同上 | + +## Linux 拥塞控制算法对比 + +### 可选算法列表 + +```bash +$ cat /proc/sys/net/ipv4/congestion_control +bbr + +$ ls /lib/modules/$(uname -r)/kernel/net/ipv4/*_cc.ko* +tcp_cubic.ko tcp_dctcp.ko tcp_htcp.ko tcp_highspeed.ko tcp_hybla.ko tcp_illinois.ko tcp_lp.ko tcp_reno.ko tcp_scalable.ko tcp_vegas.ko tcp_westwood.ko tcp_yeah.ko tcp_bbr.ko +``` + +### 主流算法对比 + +```mermaid +flowchart LR + Reno["TCP Reno
• cwnd/2 on drop
• Classic cubic curve"] + Cubic["TCP Cubic (Linux default)
• Non-linear recovery
• Better for high-BDP links"] + BBR["Google BBR v2
• Model bottleneck
• Max throughput, low latency
• No loss-based triggering"] + DCTCP["DCTCP (Datacenter)
• ECN-based
• Near-zero congestion in DC"] + + style Reno fill:#DDA0DD,color:#000 + style Cubic fill:#FFD700,color:#000 + style BBR fill:#98FB98,color:#000 + style DCTCP fill:#B0C4DE,color:#000 +``` + +| 特性 | Reno | Cubic (默认) | BBR v2 | DCTCP | +|------|------|-------------|--------|-------| +| 触发方式 | 丢包/重复ACK | 丢包/重复ACK | **延迟模型** | ECN标记 | +| 高带宽利用 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| 低延迟 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | +| 数据中心 | ❌ | ✅ | ✅ (推荐) | ✅ (需ECN支持) | +| WAN/广域网 | ✅ | ✅ | ✅✅ | ❌ | +| Google 生产环境 | — | — | ✅ 全部使用 | — | +| 配置复杂度 | 零 | 调参选项多 | 最少 (只需设置目标 bw/rtt) | 需交换机支持 ECN | + +### BBR v2 核心思想 + +BBR 不依赖丢包作为拥堵信号——它直接建立网络管道模型: + +``` +BBR 维护三个核心测量值: +┌─────────────┬──────────────┬─────────────┐ +│ Bottleneck │ Propagation │ In-flight │ +│ Bandwidth │ Delay (BDP) │ Data Limit │ +│ (最高吞吐) │ (最低延迟) │ (当前水量) │ +└─────────────┴──────────────┴─────────────┘ + +然后动态调整: send_rate ≤ bw AND in_flight ≤ bdp + buffer +``` + +```bash +# 切换为 BBR +$ sudo sysctl net.ipv4.tcp_congestion_control=bbr +$ echo 'net.ipv4.tcp_congestion_control=bbr' >> /etc/sysctl.conf + +# 验证 +$ ss --info +... cubic bbr ... +``` + +## Go 中的实践 + +### 设置 TCP 选项 + +```go +import "golang.org/x/net/ipv4" + +// 设置 socket 级别的拥塞控制相关参数 +conn, _ := net.Dial("tcp", "example.com:80") +c := ipv4.NewConn(conn.RawConn()) +c.SetTrafficClass(0) // DSCP/ECN +c.SetNoDelay(false) // Nagle 算法关闭 → 小包立刻发 +``` + +```go +// http.Transport 的连接管理 +transport := &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, +} + +// 自定义 DialContext 以启用 keepalive +dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, // 替代内核默认的 7200s +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP段结构与状态机]] — cwnd/rwnd 在 TCP 首部中的 Window 字段 +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — 握手中协商的 Window Scale 选项 +- [[hhs/NETWORK/TCP粘包与拆包]] — PSH 标志与流量控制的关联 diff --git a/hhs/NETWORK/04-传输层/05-TCP粘包与拆包.md b/hhs/NETWORK/04-传输层/05-TCP粘包与拆包.md new file mode 100644 index 0000000..652c80c --- /dev/null +++ b/hhs/NETWORK/04-传输层/05-TCP粘包与拆包.md @@ -0,0 +1,222 @@ +--- +tags: [计算机网络, TCP粘包, TCP拆包, 应用层协议] +create time: 2026-05-18 02:10 +--- + +# TCP 粘包与拆包 + +## 概述 + +TCP 是字节流协议(Byte Stream Protocol),没有"消息边界"的概念。这导致发送方多次 write 可能合并成一个段发出(粘包),也可能一个 write 被分成多个段到达(拆包)。**这不是 Bug,这是 TCP 的设计特性**——需要在应用层解决。 + +> [!QUESTION] 为什么 HTTP/gRPC 不需要处理粘包? +> HTTP/1.1 用 `Content-Length` 或 `Chunked Transfer Encoding` 定义边界;gRPC/Protobuf 每个消息带长度前缀;WebSocket 有 Frame Header。它们都在 TCP 之上抽象了一层消息协议,隐式解决了这个问题。 + +## 为什么会产生粘包和拆包 + +### 粘包的成因 + +``` +发送端操作: +write("HELLO") → 进入发送缓冲区 +write("WORLD") → Nagle 算法延迟合并 +read()/close() → 触发 Fin + +实际网络上的 TCP 段: "HELLOWORLD" (一次性发出) +``` + +触发条件: +| 条件 | 说明 | +|------|------| +| **Nagle 算法** | 小数据合并后统一发送以减少小包数量 | +| **接收端读取慢** | 发送端的多个 write 堆积在发送缓冲区 | +| **SendBuffer 累积** | 多个连续 write 都未触发 flush | + +### 拆包的成因 + +``` +发送端操作: +write("A very long message that exceeds MTU...") // 超过 MSS(1460B) + +实际网络上的 TCP 段: +Segment 1: [Bytes 0..1459] ← MSS = 1500 - 20(IP) - 20(TCP) = 1460 +Segment 2: [Bytes 1460..end] ← 剩余部分 + +接收端 read(1024): ← 只读了前一部分 +返回: "A very long m..." ← 不完整! +``` + +触发条件: +| 条件 | 说明 | +|------|------| +| **数据包 > MSS** | 超过路径 MTU 自动分片 | +| **接收端 read 大小太小** | 一次只读了一部分 | +| **网络抖动 / RTT 变化** | 数据到达时间不均匀 | + +## 四种解决策略 + +### 方法一:固定长度 + +每条消息固定 N 字节,接收端按 N 字节切割: + +```go +const msgLen = 1024 // 每条消息 1KB + +func ReadFixedLength(conn net.Conn) ([]byte, error) { + buf := make([]byte, msgLen) + n, err := io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + return buf[:n], nil +} +``` + +| 优点 | 缺点 | +|------|------| +| 实现简单 | 短消息浪费带宽,长消息不够用 | +| 性能最高 | — | + +适用场景:**游戏协议、内部 RPC(如 Dubbo 早期版本)** + +### 方法二:特殊分隔符 + +用特定字符(如 `\r\n\r\n`、`\0`)作为消息结束标志: + +```go +func ReadDelimited(conn net.Conn) ([]byte, error) { + var buf bytes.Buffer + for { + b := make([]byte, 1) + _, err := conn.Read(b) + if err != nil { + return nil, err + } + buf.Write(b) + if bytes.HasSuffix(buf.Bytes(), []byte("\r\n\r\n")) { + return buf.Bytes()[:buf.Len()-4], nil + } + } +} +``` + +| 优点 | 缺点 | +|------|------| +| 简单直观 | 消息体中不能包含分隔符 | +| 类似 HTTP 协议 | — | + +适用场景:**HTTP/1.1、SMTP、IMAP** + +### 方法三:长度前缀 ⭐(推荐) + +在消息开头加上表示长度的字段,最常见的有两种变体: + +#### 方案 A:4 字节整数 + 消息体 + +```go +// 最常用格式: [4-byte length][payload...] +// Go encoding/binary 处理 +func WriteWithLength(conn net.Conn, data []byte) error { + length := uint32(len(data)) + binary.Write(conn, binary.BigEndian, length) // 大端序保证跨语言兼容 + conn.Write(data) + return nil +} + +func ReadWithLength(conn net.Conn) ([]byte, error) { + var length uint32 + binary.Read(conn, binary.BigEndian, &length) + + buf := make([]byte, length) + if length > 0 { + _, err := io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + } + return buf, nil +} +``` + +``` +内存布局: +┌─────────────┬──────────────────────────┐ +│ Length(uint32) │ Payload │ +│ 4 bytes │ variable bytes │ +│ 100 │ [actual data of 100B] │ +└─────────────┴──────────────────────────┘ +``` + +#### 方案 B:protobuf 风格(L-Type-V) + +``` +┌────────┬───────┬──────────────────────────┐ +│ Length │ Type │ Value │ +│ VarInt │ VarInt │ variable bytes │ +│ (1-10B) │(1-10B) │ │ +└────────┴───────┴──────────────────────────┘ +``` + +| 优点 | 缺点 | +|------|------| +| ✅ 最灵活,支持变长消息 | ❌ 比固定长度略复杂 | +| ✅ gRPC/Protobuf/Hessian2 都用此方式 | ❌ 需要处理超大消息 | +| ✅ 跨语言天然兼容 | — | +| ✅ 无分隔符冲突问题 | — | + +### 方法四:两者结合(工业级) + +```go +// 工业级协议帧结构 +// ┌──────┬──────┬──────┬──────────┬──────┬──────┐ +│ Magic │Ver │Type │RequestID │Length│Payload │CRC │ +│ 4B │1B │1B │ 4B │4B │Var │4B │ +│ 0xABCD│ 1.0 │REQ │ 0x0001 │1024 │data...│ CRC32│ +│ EF header │ │ Footer │ +└──────┴──────┴──────┴──────────┴──────┴────────┘ +总开销: 4+1+1+4+4+4 = 18 bytes overhead +``` + +```go +type FrameHeader struct { + Magic uint32 // 魔数,防解析错误 + Version byte // 协议版本 + MessageType byte // 请求/响应/心跳等 + RequestID uint32 // 关联请求响应 + Length uint32 // Payload 长度 + Checksum uint32 // CRC32 校验 +} +``` + +## 各语言的内置支持 + +| 语言/框架 | 粘包处理 | 说明 | +|-----------|---------|------| +| **Go** stdlib `net` | ❌ 零拷贝,完全手动 | 但 `bufio.Scanner` 可帮你 | +| Go `encoding/binary` | ✅ Binary 编解码 | 配合 length-prefix | +| Java Netty | ✅ 内建 | `DelimiterBasedFrameDecoder`、`LengthFieldBasedFrameDecoder` | +| Python asyncio | ❌ 需手动 | 或用 `asyncio.Protocol` | +| C++ Boost.Asio | ❌ 需手动 | 或用自定义解包器 | +| Rust tokio | ❌ 需手动 | `tokio_util::codec` crate 支持 | + +## Nagle 算法的影响 + +Nagle 算法的初衷是减少小包的发送数量——它规定:**如果有一个未被确认的小包,就不要发送新的小包**。 + +```go +// Go net.TCPConn 中关闭 Nagle +conn.(*net.TCPConn).SetNoDelay(true) // true = 禁用 Nagle +``` + +| 场景 | 建议 | +|------|------| +| HTTP/Web API | 关闭 (减少首包延迟) | +| 实时游戏/金融 | 关闭 (每毫秒都很重要) | +| 大数据传输 | 开启 (减少小包开销) | +| SSH | 关闭 (交互性要求高) | + +## 关联笔记 + +- [[hhs/NETWORK/TCP段结构与状态机]] — TCP 为何是字节流而非消息流 +- [[hhs/NETWORK/HTTP请求响应]] — HTTP 如何用 Content-Length 解决此问题 +- [[hhs/NETWORK/WebSocket全双工通信]] — WebSocket Frame Header 自带 Length diff --git a/hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md b/hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md new file mode 100644 index 0000000..7990015 --- /dev/null +++ b/hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md @@ -0,0 +1,232 @@ +--- +tags: [计算机网络, UDP, SCTP] +create time: 2026-05-18 02:20 +--- + +# UDP 协议与 SCTP + +## 概述 + +UDP 是 TCP 的对立面:没有连接、不可靠、无拥塞控制——但正因如此,它在延迟敏感场景和无差错检测场景中不可替代。SCTP 则是被低估的"第三条路",结合了 TCP 和 UDP 的优势。 + +## UDP 首部格式 + +``` +┌─────────────┬─────────────┐ +│ Source Port │ Destination Port │ 各 2 bytes +├─────────────┼─────────────┤ +│ Length │ Checksum │ 各 2 bytes +└─────────────┴─────────────┘ +总长: 8 bytes(固定!) +``` + +| 字段 | 说明 | +|------|------| +| Source/Dest Port | 同 TCP | +| **Length** | UDP 包总长(含首部),最小 8 字节 | +| **Checksum** | 可选!(IPv4 中如果为 0 表示无校验;IPv6 中强制启用) | + +### 极简首部的代价 + +``` +TCP 20B 首部 vs UDP 8B 首部 + +一个 IP 包 = IP(20) + TCP(20) + Data = 40B overhead + N bytes payload + IP(20) + UDP(8) + Data = 28B overhead + N bytes payload + +UDP 节省: 12 bytes / packet × 每秒数据包数 = 显著的带宽节省 +``` + +## UDP 核心特性 + +| 特性 | UDP | TCP | +|------|-----|-----| +| 连接 | ❌ 无连接 | ✅ 三次握手 | +| 可靠性 | ❌ 不保证送达 | ✅ ACK + 重传 | +| 顺序保证 | ❌ 可能乱序到达 | ✅ 有序 | +| 流量控制 | ❌ | ✅ 滑动窗口 | +| 拥塞控制 | ❌ | ✅ 慢启动/快恢复等 | +| 头部开销 | 8 bytes | 20-60 bytes | +| 半双工/全双工 | N/A(无方向概念) | 全双工 | +| 流模式/报文模式 | **数据报模式**(保留边界) | 字节流模式 | + +### 为什么 UDP 保留消息边界? + +```go +// UDP: send("HELLO") → recv() = "HELLO" +// 一条 UDP datagram 就是一个完整消息 + +// TCP: write("H"); write("E"); write("L"); write("L"); write("O") +// read() 可能是 "HE", "LLO", "HELL", "LLO"... +``` + +## UDP 适用场景 + +### 典型应用 + +| 应用 | 为什么用 UDP? | +|------|---------------| +| **DNS** | 查询很短(通常 < 512 bytes),等待重传得不偿失 | +| **NTP** | 最新时间最重要,旧值无意义 | +| **VoIP / WebRTC** | 丢一帧比延迟卡顿更可接受 | +| **在线游戏** | 每秒几十次状态同步,延迟比可靠更重要 | +| **DHCP** | 客户端无 IP,无法使用 TCP(需要三次握手)| +| **SNMP** | 监控轮询,丢一次下轮重来即可 | +| **Redis** | 多数操作幂等或客户端自行重试 | + +### DNS 用 UDP 还是 TCP? + +| 情况 | 协议 | 原因 | +|------|------|------| +| 标准查询 (< 512B) | UDP port 53 | 快,RTT + 少量开销 | +| Zone Transfer (AXFR) | TCP port 53 | 传输大量记录,需可靠 | +| Response > 512B (RFC 5102) | EDNS0 + UDP | 扩展至 4096B | +| EDNS0 不支持,response 大 | TCP fallback | 设置 TC (Truncation) 位,客户端自动切 TCP | + +```bash +$ dig example.com +trace # 默认走 UDP +$ dig example.com +tcp # 强制 TCP +$ host -t A example.com # 系统级 dig +``` + +## UDP 的可靠性增强 + +虽然 UDP 本身不提供可靠性,但许多基于 UDP 的应用自行实现了轻量级可靠传输: + +```mermaid +flowchart LR + App["应用层"] --> U["UDP Datagram"] + + subgraph "用户态实现(自加可靠性)" + U --> QUIC["QUIC
内置 ACK+重传+流控"] + U --> DTLS["DTLS
TLS over UDP"] + U --> RTP["RTP/RTCP
媒体流+反馈"] + U --> Custom["自定义可靠协议
如 uTP, KCP, Nakadi"] + end + + QUIC -.->|"最终都到"| IPv4["IP"] + DTLS -.-> IPv4 + RTP -.-> IPv4 + Custom -.-> IPv4 +``` + +### KCP 协议(国内广泛使用) + +由俄罗斯开发者 Stan 设计,专为网游低延迟优化: + +``` +KCP vs TCP in game scenario: +────────────────────────── +TCP: 丢包 → RTO ≥ 1s → 卡顿 +KCP: 丢包 → ARQ 立即重传 → < 100ms 恢复 + FEC(前向纠错) 补包 → 无需等待确认 +``` + +```go +import "github.com/xtaci/kcp-go" + +// Go 中使用 KCP +sess, _ := kcp.Listen(":8080") +conn, _ := sess.AcceptKCP() +conn.SetNoDelay(1, 10, 2, 1) // 无Nagle, 10ms interval, 2 rounds, 1 SSThresh +conn.SetWindowSize(1024, 1024) +conn.SetMtu(1200) +``` + +## SCTP(Stream Control Transmission Protocol) + +### 为何存在? + +SCTP (RFC 4960) 试图结合 TCP 和 UDP 的优点: + +| 特性 | TCP | UDP | **SCTP** | +|------|-----|-----|---------| +| 可靠性 | ✅ | ❌ | ✅ (可选) | +| 顺序保证 | ✅ 全局有序 | ❌ | ✅ **多流独立有序** | +| 多宿支持 | ❌ 单 IP | ❌ | ✅ 多 IP 冗余 | +| 消息边界 | ❌ 字节流 | ✅ 报文 | ✅ 报文 | +| 拥塞控制 | ✅ | ❌ | ✅ | +| 适用场景 | HTTP/Web | DNS/VoIP | **信令(Diameter/SIP)** | + +### Multi-streaming(多流)— SCTP 的王牌特性 + +传统 TCP 的一个问题:**head-of-line blocking**。如果一个包丢了,所有后续消息都要等它重传完。SCTP 通过多流解决这个问题: + +``` +SCTP 连接拥有 3 个独立 stream: + +Stream 0: [Data_0_A][Data_0_B][Data_0_C] ← 信令通道 (有序) +Stream 1: [Data_1_A] [Data_1_C] ← 聊天消息 (无序 OK) +Stream 2: [Data_2_A][Data_2_B] ← 文件传输 + +即使 Stream 0 的 B 丢失了 → Stream 1 和 2 继续前进! +``` + +``` +四元组 vs 六元组: +TCP: src_ip, src_port, dst_ip, dst_port → 一个连接 +SCTP: src_ip, src_port, dst_ip, dst_port → association (关联) + + stream_id → stream within association +``` + +### SCTP 的使用场景 + +| 场景 | 说明 | +|------|------| +| **Diameter / SIP 信令** | 3GPP/ITU-T 标准强制使用 SCTP over IP | +| **SS7 over IP (SIGTRAN)** | 电信信令迁移到 IP 网络 | +| **数据库复制** | Oracle Data Guard 可配置 SCTP 连接 | +| **游戏服务器** | 多玩家流独立,互不影响 | + +### Go 中的 SCTP 支持 + +```go +import "golang.org/x/net/sctp" + +// 创建 SCTP 关联 +conn, _ := sctp.DialSCTP("ip4:sctp", "<local>:3868", "<peer>:3868") + +// 设置多流 +conn.Set SCTPNodeAddresses(sctp.SCTP_PEER_ADDR_CHANGE, ...) + +// 发送指定 stream 的消息 +conn.WriteToSCTP([]byte("hello"), &sctp.SCTPAddr{ + PeerAddr: net.SCTPAddr{IPAddrs: peerIPs}, + Stream: 0, // 选择 stream 0 +}) +``` + +> [!tip] SCTP 的生态局限 +> 虽然技术层面很优秀,但因为运营商部署迟缓和中间设备(NAT/防火墙)对 SCTP 的不兼容,其普及度远不及 TCP。但在电信行业仍是事实标准。 + +## UDP 编程示例 + +```go +package main + +import ( + "net" + "fmt" +) + +func main() { + // 服务端 + addr, _ := net.ResolveUDPAddr("udp4", ":8080") + conn, _ := net.ListenUDP("udp", addr) + defer conn.Close() + + buf := make([]byte, 4096) + n, remote, _ := conn.ReadFromUDP(buf) + fmt.Printf("Received %d bytes from %v\n", n, remote) + + // 客户端 — 注意每条 Write 是一个独立的 UDP 数据报 + client, _ := net.DialUDP("udp", nil, addr) + client.Write([]byte("Hello UDP")) +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP段结构与状态机]] — UDP 缺少的连接管理功能 +- [[hhs/NETWORK/DNS原理与优化]] — DNS 主要使用 UDP port 53 +- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 也有 over UDP 的版本: DTLS diff --git a/hhs/NETWORK/05-应用层协议/01-HTTP-1-1完全指南.md b/hhs/NETWORK/05-应用层协议/01-HTTP-1-1完全指南.md new file mode 100644 index 0000000..115da63 --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/01-HTTP-1-1完全指南.md @@ -0,0 +1,166 @@ +--- +tags: [计算机网络, HTTP, HTTP/1.1, HTTP/2, HTTP/3, QUIC] +create time: 2026-05-18 02:30 +--- + +# HTTP/1.1 完全指南 + +## 概述 + +HTTP (HyperText Transfer Protocol) 是万维网的数据传输协议,从 Mosaic 浏览器的最初需求发展而来,至今仍是互联网上使用最广泛的协议之一。 + +## HTTP 消息结构 + +``` +Request 示例: +POST /api/users HTTP/1.1 ← Request Line +Host: api.example.com ← Headers +Content-Type: application/json ← Header +Content-Length: 42 ← Header +Authorization: Bearer abc123 ← Header + ← Blank line separates headers from body +{"name": "Alice", "email": "alice@example.com"} ← Body + +Response 示例: +HTTP/1.1 201 Created ← Status Line +Date: Mon, 17 May 2026 02:30:00 GMT ← Headers +Location: https://api.example.com/users/42 ← Header +Content-Type: application/json ← Header +Content-Length: 35 ← Header + +{"id": 42, "name": "Alice"} ← Body +``` + +### Request Line 格式 + +``` +METHOD SP Request-URI SP HTTP-Version CRLF +``` + +### 常用 Method + +| Method | 安全(Safe)? | 幂等(Idempotent)? | 含义 | +|--------|-----------|------------------|------| +| GET | ✅ | ✅ | 获取资源 | +| HEAD | ✅ | ✅ | 只获取响应头 | +| POST | ❌ | ❌ | 提交数据创建新资源 | +| PUT | ❌ | ✅ | 替换整个资源 | +| PATCH | ❌ | ❌ | 部分更新资源 | +| DELETE | ❌ | ✅ | 删除资源 | +| OPTIONS | ✅ | ✅ | 查询服务器支持的 Method | +| CONNECT | N/A | N/A | 建立隧道(代理/HTTPS) | +| TRACE | ✅ | ✅ | 回显收到的请求(调试) | + +### 常见 Status Code + +| 类别 | 含义 | 举例 | +|------|------|------| +| 1xx | 信息性 | 100 Continue, 101 Switching Protocols | +| 2xx | 成功 | 200 OK, 201 Created, 204 No Content | +| 3xx | 重定向 | 301 Moved Permanently, 302 Found, 304 Not Modified | +| 4xx | 客户端错误 | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests | +| 5xx | 服务端错误 | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable | + +## Keep-Alive(持久连接)— HTTP/1.1 默认行为 + +HTTP/1.1 中 `Connection: keep-alive` 是默认的——一个 TCP 连接可承载多个 HTTP 请求/响应: + +``` +单一 TCP 连接上的多次请求: + +TCP Established ✅ + +Client → Server: GET /page1.html +Server → Client: 200 + HTML + +Client → Server: GET /style.css +Server → Client: 200 + CSS + +Client → Server: GET /app.js +Server → Client: 200 + JS + +Client → Server: Connection: close (或超时自动关闭) +Server → Client: ACK → TCP FIN +``` + +**好处:** 避免每次请求都进行三次握手 (+TLS 四次握手)。 + +### Pipeline(管道化)— HTTP/1.1 的未启用功能 + +理论上可以在同一个连接上**不等待前一个响应就发送下一个请求**: + +``` +Client 连续发三个请求: +GET /a ← 不等响应 +GET /b +GET /c + +Server 必须按顺序返回: +200 /a +200 /b +200 /c +``` + +**为什么几乎没人用?** +- Server 端实现复杂且 Bug 多 +- 一旦某个响应阻塞,后续所有响应都被卡住(HoL blocking) +- HTTP/2 的多路复用彻底解决了这个问题 + +## Chunked Transfer Encoding + +当不知道 Content-Length 时(如流式生成内容),使用 chunked 编码: + +``` +HTTP/1.1 200 OK +Transfer-Encoding: chunked + +5\r\n\r\nHello\r\n ← 5 bytes of data: "Hello" +7\r\n\r\n World!\r\n ← 7 bytes: " World!" +0\r\n\r\n ← 终止 chunk +``` + +```go +// Go net/http 中自动使用 chunked +w.Header().Set("Transfer-Encoding", "chunked") +w.Write([]byte("part 1")) +w.Write([]byte("part 2")) +w.WriteHeader(200) // WriteHeader 必须在 Write 之后才生效 chunked +``` + +## Go 中的 HTTP 客户端实践 + +```go +package main + +import ( + "fmt" + "io" + "net/http" + "time" +) + +func main() { + client := &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + }, + } + + resp, err := client.Get("https://example.com") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + io.Copy(os.Stdout, resp.Body) +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/HTTP/2多路复用]] — HTTP/2 如何解决 HTTP/1.1 的问题 +- [[hhs/NETWORK/HTTP/3与QUIC]] — 基于 UDP 的下一代 HTTP +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — HTTP 依赖的底层 TCP 连接 diff --git a/hhs/NETWORK/05-应用层协议/02-HTTP-2多路复用.md b/hhs/NETWORK/05-应用层协议/02-HTTP-2多路复用.md new file mode 100644 index 0000000..9ddb455 --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/02-HTTP-2多路复用.md @@ -0,0 +1,214 @@ +--- +tags: [计算机网络, HTTP/2, HPACK, Multiplexing, Server Push] +create time: 2026-05-18 02:40 +--- + +# HTTP/2 多路复用 + +## 概述 + +HTTP/2 (RFC 7540) 是 HTTP 协议自 1999 年 HTTP/1.1 以来的第一次重大修订。核心改进:**二进制分帧层**,让一个 TCP 连接上能并发多个请求,彻底消除 Head-of-Line blocking(应用层层面)。 + +```mermaid +flowchart TD + A["HTTP/1.1
问题重重"] -->|"串行请求"| B["每个请求等响应"] + B --> C["慢 ❌"] + + D["HTTP/2
二进制分帧"] -->|"多路复用"| E["并发请求/响应"] + E --> F["快 ✅"] + + style A fill:#FF6B6B,color:#fff + style D fill:#98FB98,color:#000 +``` + +## HTTP/2 的三层层级 + +``` +┌─────────────────────────────────────┐ +│ Application Layer │ +│ HTTP Frames / Streams │ ← 你操作的对象 +├─────────────────────────────────────┤ +│ Frame Layer │ ← HPACK + Flow Control +│ Binary Framing + Headers │ +├─────────────────────────────────────┤ +│ Transport Layer │ ← TCP (or QUIC for H3) +│ Connection Management │ +└─────────────────────────────────────┘ +``` + +## 二进制帧类型(Frame Types) + +| Frame | 说明 | +|-------|------| +| **HEADERS** | 请求/响应头部 | +| **DATA** | 实际数据载荷 | +| RST_STREAM | 异常关闭流 | +| SETTINGS | 通信参数协商 | +| PUSH_PROMISE | 服务器推送 | +| PING | 延迟测量 | +| GOAWAY | 优雅关闭连接 | +| WINDOW_UPDATE | 流量控制 | +| CONTINUATION | HEADERS 太长时的延续帧 | + +### HEADERS 帧结构 + +``` ++---------------+ +|Pad Length? |(0-7 bits) +|Padding (8-bit)| +|E | +|+---------------+----------------------------------------------+ +| |Header Block Fragment (*) | +|+-----------------------------------------------------+ +|... | ++-----------------------------------------------------+ +``` + +## Stream — 多路复用的核心 + +HTTP/2 将每个请求/响应绑定到一个唯一的 **Stream ID**: + +``` +TCP Connection: client:45678 ↔ server:443 + +Stream 1: GET /index.html → 200 OK +Stream 3: GET /style.css → 200 OK +Stream 5: POST /api/login → 200 OK +Stream 7: GET /app.js → 200 OK + +Client Server +│ │ +│ ───HEADERS(stream=1)──→ │ GET /index.html +│ ───DATA(stream=1, 5KB)──→ │ +│ │ +│ ←──HEADERS(stream=1, 200 OK)── │ +│ ←──DATA(stream=1, 15KB)── │ +│ │ +│ ───HEADERS(stream=3)──→ │ GET /style.css +│ ←──HEADERS(stream=3, 200 OK)── │ +│ ←──DATA(stream=3, 3KB)── │ +│ │ +│ ───HEADERS(stream=5)──→ │ POST /api/login +│ ───DATA(stream=5, 50B)──→ │ body: {"user":"alice"} +│ ←──HEADERS(stream=5, 200 OK)── │ +│ ←──DATA(stream=5, 80B)── │ body: {"token":"abc"} +``` + +### Stream 状态机 + +``` +idle → reserved(local) ———→ half closed(local) —→ closed + ↑ ↓ ↑ + │ half closed(remote) │ + │ ↓ │ + └──── open ←───────── half closed(remote) ◄─────┘ + ↑ ↑ + │ half closed(local) + │ ↓ + send HEADERS closed + recv HEADERS +``` + +### 优先级分组(Stream Dependency) + +``` +Stream 1 (weight=16) [root] + ├── Stream 3 (weight=8): /index.html + │ ├── Stream 7 (weight=4): style.css + │ └── Stream 9 (weight=2): logo.png + ├── Stream 5 (weight=8): API data + └── Stream 11 (weight=1): tracking pixel +``` + +## HPACK — Header 压缩 + +HTTP/1.1 每次请求都重复发送 `User-Agent`、`Cookie`、`Accept` 等头部,浪费带宽。HPACK 用两种技术解决: + +| 技术 | 说明 | +|------|------| +| **Header Table** (动态表) | 服务端缓存常用头,后续请求用索引代替 | +| **Huffman Coding** | 对字符串值做霍夫曼编码压缩 | + +### HPACK 编码示例 + +原始 HTTP/1.1 请求头部(重复发送): +``` +GET /page HTTP/1.1 +Host: example.com +User-Agent: Mozilla/5.0 ... +Accept: text/html +Cookie: session=abc123 +Accept-Encoding: gzip +``` + +重复 10 次后,HTTP/2 只发一次完整头部,后续使用动态表索引: +``` +索引 1: Host: example.com (查表! 不重传) +索引 4: User-Agent: ... (查表!) +索引 5: Accept: text/html (查表!) +索引 6: Cookie: session=abc123 (查表!) +索引 7: Accept-Encoding: gzip (查表!) +``` + +**节省效果:** header 体积减少 70%~90%。 + +## Server Push + +服务器可以主动向客户端推送资源,无需客户端请求: + +``` +Client: GET /index.html +Server: HEADERS(stream=2, 200 OK) — HTML content +Server: PUSH_PROMISE(stream=2, promised_stream=4) + → style.css +Server: HEADERS(stream=4, 200 OK) +Server: DATA(stream=4, CSS content) + +← 客户端在还没解析到 之前就已经收到了 CSS! +``` + +```bash +# Go 中启用 HTTP/2 Server Push +pusher, ok := w.(http.Pusher) +if ok { + pusher.Push("/static/style.css", nil) +} +``` + +> [!warning] Push 的实际争议 +> Push 被广泛认为弊大于利:浏览器可能已经缓存了资源(导致冗余传输),且无法像 Client-initiated 那样利用浏览器已有的缓存决策。现代实践中更多使用 Preload `` 替代 Push。 + +## HTTP/2 vs HTTP/1.1 对比 + +| 特性 | HTTP/1.1 | HTTP/2 | +|------|---------|--------| +| 格式 | 文本 | **二进制** | +| 并发 | 需要 N 个连接 | **单连接多路复用** | +| Header | 明文全量发送 | **HPACK 压缩** | +| 服务端推送 | ❌ | ✅ (可选) | +| 头部压缩 | ❌ (需 HPACK extension) | ✅ 原生支持 | +| 头部顺序 | 按发送顺序 | 可重新排序 | +| HoL Blocking | 严重 (TCP 层) | **消除** (Stream 独立) | +| TLS 要求 | 不需要 | **推荐** (nginx/apache 默认 h2c) | + +## h2c — HTTP/2 Clear Text (未加密) + +HTTP/2 可以通过 ALPN 升级(TLS 下的 h2)或 HTTP Upgrade 机制(纯 TCP 的 h2c): + +```bash +# curl 测试 h2c +curl --http2 -H "Upgrade-H2C: true" http://localhost:8080/api + +# Go net/http 中 h2c 支持需 import golang.org/x/net/http2 +import "golang.org/x/net/http2" +import "golang.org/x/net/http2/h2c" +``` + +> [!tip] 生产环境始终用 HTTPS + h2 +> 浏览器仅支持 TLS 下的 HTTP/2 (ALPN = "h2")。纯 TCP 的 h2c 主要用于微服务间通信。 + +## 关联笔记 + +- [[hhs/NETWORK/HTTP/1.1完全指南]] — HTTP/2 的基础 +- [[hhs/NETWORK/HTTP/3与QUIC]] — HTTP/2 的 UDP 替代品 +- [[hhs/NETWORK/HTTPS与TLS握手]] — HTTP/2 通常配合 HTTPS diff --git a/hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC.md b/hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC.md new file mode 100644 index 0000000..3758fed --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC.md @@ -0,0 +1,175 @@ +--- +tags: [计算机网络, HTTP/3, QUIC, TLS 1.3] +create time: 2026-05-18 02:50 +--- + +# HTTP/3 与 QUIC + +## 概述 + +HTTP/3 (RFC 9114) 基于 **QUIC** 协议,将传输层从 TCP 替换为 UDP + 内嵌的可靠传输机制。核心目标:**消除 TCP 层的 Head-of-Line blocking**,实现更快的连接建立和多路复用。 + +```mermaid +flowchart TD + A["HTTP/3 over QUIC
UDP 443"] -->|"零 RTT 建连"| B["更快 ✅"] + C["HTTP/2 over TCP
TCP 443"] -->|"三次握手 + TLS"| D["较慢"] + + style A fill:#98FB98,color:#000 + style C fill:#FFD700,color:#000 +``` + +## QUIC 协议栈对比 + +``` +HTTP/3 Stack: HTTP/2 Stack: +──────────── ──────────── + HTTP/3 HTTP/2 + │ │ +┌─┴─────────────────┐ ┌──────┴──────────────┐ +│ QUIC │ │ TLS 1.3 │ +│ + Multiplexing │ │ + TCP Flow Control │ +│ + Crypto │ └──────┬──────────────┘ +│ + Reliable Tx │ │ +└──────┬─────────────┘ ┌────┴─────┐ + │ │ TCP │ + ▼ └────┬─────┘ + UDP 443 │ + ▼ + IP / Ethernet +``` + +### 关键设计哲学 + +``` +TCP 的问题: QUIC 的方案: +───────── ────────── +• 一条流 HOL blocking • 多 Stream 独立重传 +• 拥塞控制固定算法 • 应用层可调拥塞算法 (BBRv2 by default) +• TLS in TCP → 双重握手 • TLS 1.3 + 0-RTT session resumption +• NAT 中断需重建 TCP • Connection ID → NAT 变更无感知 +``` + +## QUIC 连接生命周期 + +```mermaid +sequenceDiagram + participant Client + participant Server + + Note over Client,Server: Phase 1: Connection Establishment (TLS Handshake embedded) + Client->>Server: Initial Packet (Client Hello + CRYPTO) + Note over Server: Parses CRYPTO from QUIC packet header + Server-->>Client: Server Hello + CRYPTO + HANDSHAKE + Note over Client: Session ticket cached for resume + Server-->>Client: NEW_CONNECTION_ID + RETIRE_CONNECTION_ID + Client-->>Server: ACK + + Note over Client,Server: ← CONNECTION ESTABLISHED → + + Client->>Server: 0-RTT Data (if session resumed) ⚡ + Server->>Server: Validate 0-RTT anti-replay + Server-->>Client: 0-RTT Accepted / Rejected + + Note over Client,Server: Phase 2: Data Transfer + loop Multiple Streams (independent retransmission) + Client->>Server: HEADERS(stream=X) + DATA(stream=X) + Server-->>Client: HEADERS(stream=X) + DATA(stream=X) + end + + Note over Client,Server: Phase 3: Graceful Close + Client->>Server: FIN (last stream closed) + Server->>Client: FIN + Client->>Server: Final ACK +``` + +## 0-RTT(零往返时间)连接恢复 + +当客户端之前访问过同一服务器时,可以跳过完整握手: + +``` +首次连接: 后续连接: +────────── ────────── +Client → SYN → Server Client → QUIC Initial (with 0-RTT data) +SYN → Client → Server processes immediately! +ACK → Client ↓ +Client → ClientHello 0-RTT data delivered before handshake finishes! +ServerHello + cert → Client +Client → encrypted application data + +Total: 1-RTT (+0-RTT if resumed) Total: 0-RTT ⚡⚡⚡ +``` + +> [!warning] 0-RTT 的安全风险 +> 重放攻击 (Replay Attack): 中间人可捕获并重放 0-RTT 请求。因此 **0-RTT 只能用于幂等操作**(GET、HEAD、OPTIONS)。POST 等不安全操作不应使用 0-RTT。 + +## QUIC 如何消除 HoL Blocking + +``` +HTTP/2 over TCP: QUIC over UDP: +═════════════════ ═══════════════════ + +Stream 1: [Data][Lost❌][More] Stream 1: [✓ Data] [✓ More] +Stream 3: [OK OK OK OK OK] ← 全部卡住! Stream 3: [✓ OK] [✓ OK] [✓ OK] +Stream 5: [OK OK OK OK OK] Stream 5: [✓ OK] [✓ OK] + +原因: TCP 是字节流 原因: 每个 Stream 独立编号 +丢包导致所有后续 Stream 等待 ACK 丢失的数据只重传对应 Stream +``` + +```mermaid +flowchart LR + L1["HTTP/2:
一个包丢了
全连阻塞"] -->|"TCP Hol-blocking"| SLOW["慢 ❌"] + L2["QUIC:
单个stream丢包
其他stream继续"] -->|"独立重传"| FAST["快 ✅"] + + style SLOW fill:#FF6B6B,color:#fff + style FAST fill:#98FB98,color:#000 +``` + +## Go 中启用 HTTP/3 + +```go +import "golang.org/x/net/http3" + +// HTTP/3 服务器 +go http.ListenAndServeTLS(":443", "cert.pem", "key.pem", srv) // h2c handler + +// 自动降级到 HTTP/2 +http3.ListenAndServeTLS(":443", "cert.pem", "key.pem", &http3.Server{ + Handler: yourHandler, +}) + +// gRPC 也原生支持 HTTP/3 +grpc.NewServer(grpc.HTTPSupport()) +``` + +## HTTP/3 vs HTTP/2 对照表 + +| 特性 | HTTP/2 (over TCP) | HTTP/3 (over QUIC) | +|------|-------------------|---------------------| +| 传输层 | TCP | **UDP** | +| 端口 | 443 (h2 via ALPN) | **443** | +| 并发 | 单连接多 Stream | 同左 + UDP 级冗余 | +| 连接迁移 | ❌ 网卡切换 = 断连 | ✅ Connection ID | +| 头压缩 | HPACK | QPACK | +| 拥塞控制 | 内核态固定算法 | **用户态可插拔** | +| NAT 穿透 | 依赖 TCP | UDP 更友好 (运营商限制少) | +| 成熟度 | ✅ 广泛部署 | 🟢 快速普及 (Cloudflare/Google/Facebook) | +| Debug 难度 | tcpdump/Wireshark 成熟 | 工具链仍在完善 | + +## 当前 adoption + +| CDN/服务 | HTTP/3 状态 | +|---------|-----------| +| Cloudflare | ✅ 默认开启 | +| Google | ✅ 大规模部署 | +| Facebook/Meta | ✅ | +| Nginx | ✅ 1.25+ 正式支持 | +| Apache | ✅ mod_http3 | +| AWS ALB/NLB | ✅ | +| Vercel/Netlify | ✅ | + +## 关联笔记 + +- [[hhs/NETWORK/HTTP/2多路复用]] — QUIC 继承了 HTTP/2 的多路复用思想 +- [[hhs/NETWORK/TCP拥塞控制]] — QUIC 的用户态拥塞控制可自定义 +- [[hhs/NETWORK/HTTPS与TLS握手]] — QUIC 内置 TLS 1.3 diff --git a/hhs/NETWORK/05-应用层协议/04-HTTPS与TLS握手.md b/hhs/NETWORK/05-应用层协议/04-HTTPS与TLS握手.md new file mode 100644 index 0000000..6c6d43c --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/04-HTTPS与TLS握手.md @@ -0,0 +1,183 @@ +--- +tags: [计算机网络, HTTPS, TLS, 证书, PKI] +create time: 2026-05-18 03:00 +--- + +# HTTPS 与 TLS 握手 + +## 概述 + +HTTPS = HTTP over TLS(Transport Layer Security)。TLS 在 TCP 和应用层之间插入一个加密通道,确保通信的机密性、完整性和对端身份验证。理解 TLS 握手过程是排查 SSL 错误的核心能力。 + +## TLS 在协议栈中的位置 + +``` +┌─────────────┐ +│ HTTP │ ← 应用层 +├─────────────┤ +│ TLS │ ← "加密壳" — 透明包装下层数据 +├─────────────┤ +│ TCP │ ← 可靠传输 +├─────────────┤ +│ IP │ ← 路由 +└─────────────┘ +``` + +## TLS 握手流程(TLS 1.3) + +### 完整交互序列 + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + + Note over C,S: Phase 1: Key Exchange (Full Handshake) + C->>S: ClientHello
• TLS version (1.3)
• Cipher suites (pref order)
• Random bytes
• PSK/session ticket (optional)
• Extensions (ALPN, SNI...) + + S-->>C: ServerHello
• Selected cipher suite
• Random bytes
• Server's key share (ephemeral ECDHE) + + S->>S: Send certificate + cert_chain + S->>C: CertificateRequest (optional) + S->>C: ServerKeyExchange (if needed) + S->>C: ServerFinished + + C->>C: Verify cert chain → CA ✓ + C->>S: ClientKeyExchange (key share) + C->>C: Compute shared secret (ECDHE) + C->>S: ChangeCipherSpec (implicit in 1.3) + C->>S: ClientFinished + + Note over C,S: ← Encryption Enabled ✨ --> + + C->>S: Application data (HTTP Request) 🔒 + S->>C: Application data (HTTP Response) 🔒 +``` + +### TLS 1.3 vs TLS 1.2 对比 + +| 特性 | TLS 1.2 | TLS 1.3 | +|------|---------|---------| +| **往返次数** | 2 RTT (或 1 RTT with session resumption) | **1 RTT** (0 RTT if resumed) ⚡ | +| 密钥交换 | RSA / DHE / ECDHE | **仅 ECDHE** (前向安全强制) | +| Cipher Suites | ~30+ 种选择 | **仅 4 种** (AES-GCM, ChaCha20-Poly1305) | +| 压缩 | ✅ (导致 CRIME 漏洞) | ❌ **已禁用** | +| 重协商 | ✅ | ❌ (改用 NewSession Ticket) | +| 静态 RSA 密钥交换 | ✅ | ❌ **已移除** | +| CBC 模式密文 | ✅ | ❌ **已移除** | +| Export ciphers | ✅ | ❌ **已移除** | +| Renegotiation | ✅ | ❌ (用 renegotiation_info 扩展替代) | + +> [!tip] TLS 1.3 为什么更快? +> TLS 1.2: ClientHello → ServerHello → Certificate → ServerKeyExchange → ... → ClientKeyExchange → Finished = **2 full RTTs** +> +> TLS 1.3: ClientHello (含 client key share) → ServerHello (含 server key share) + Certificate + Finished = **1 RTT** +> +> 因为客户端在第一个包中就携带了自己的密钥共享信息,无需等待服务器回复后再计算。 + +## Cipher Suite 详解 + +TLS 1.3 只有 4 个 Cipher Suites: + +| 编号 | Cipher Suite | KEM | AEAD | HKDF | +|------|-------------|-----|------|------| +| TLS_AES_128_GCM_SHA256 | AES-128-GCM | X25519 | AES-128-GCM | SHA256 | +| TLS_AES_256_GCM_SHA384 | AES-256-GCM | X25519 | AES-256-GCM | SHA384 | +| TLS_CHACHA20_POLY1305_SHA256 | ChaCha20-Poly1305 | X25519 | ChaCha20-Poly1305 | SHA256 | +| TLS_AES_128_CCM_SHA256 | AES-128-CCM | X25519 | AES-128-CCM | SHA256 | + +**Go 中默认偏好顺序:** `TLS_AES_256_GCM_SHA384 > TLS_AES_128_GCM_SHA256 > TLS_CHACHA20_POLY1305_SHA256` + +## 证书链验证流程 + +```mermaid +flowchart TD + Cert["Server Certificate
CN = example.com"] -->|"issued by"| Inter["Intermediate CA
ISRG Root X1"] + Inter -->|"issued by"| Root["Root CA
DigiCert Global Root G2"] + Root -->|"self-signed"| Root + + Client["Client 浏览器"] -->|"验证:"| V1["1. 签名是否由上层CA签发?"] + V1 -->|"是"| V2["2. CN/SAN 是否匹配域名?"] + V2 -->|"是"| V3["3. 是否过期?"] + V3 -->|"是"| V4["4. 是否在 OCSP/CRL 吊销列表中?"] + V4 -->|"是"| V5["5. 根证书是否在信任库中?"] + V5 -->|"是"| TRUSTED["✅ 信任!"] + + style TRUSTED fill:#98FB98,color:#000 +``` + +## 实际调试命令 + +```bash +# 查看服务器支持的 TLS 版本和 Cipher Suites +$ openssl s_client -connect example.com:443 -tls1_3 +CONNECTED(00000003) +Protocol : TLSv1.3 +Cipher : TLS_AES_256_GCM_SHA384 +Certificate chain: + 0 s:CN = example.com + i:C = US, O = DigiCert Inc, CN = DigiCert TLS Hybrid ECC_SHA384 2020 CA1 + +# 指定特定 Cipher 测试 +$ openssl s_client -connect example.com:443 \ + -cipher 'ECDHE-ECDSA-AES256-GCM-SHA384' + +# 获取证书信息 +$ openssl x509 -in cert.pem -noout -dates -subject -issuer + subject=CN = example.com + notBefore=Jan 1 00:00:00 2025 GMT + notAfter=Jan 1 00:00:00 2026 GMT + +# 检查 OCSP 状态 +$ curl -vI https://example.com + < HTTP/2 200 + < alt-s: h3=":443"; ma=86400 + < alt-s: h2=":443"; ma=86400 + +# Go 中自定义证书验证 +import "crypto/tls" +config := &tls.Config{ + InsecureSkipVerify: false, // 生产环境永远为 false + MinVersion: tls.VersionTLS13, +} +conn, _ := tls.Dial("tcp", "example.com:443", config) +``` + +## Go 标准库中的 TLS + +```go +// 创建安全的 TLS 配置 +var DefaultTLSConfig = tls.Config{ + MinVersion: tls.VersionTLS12, // 最低支持 TLS 1.2 + PreferServerCipherSuites: true, +} + +// http.Server 自动启用 TLS +srv := &http.Server{ + Addr: ":443", + Handler: handler, + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, // ALPN + }, +} +srv.ListenAndServeTLS("cert.pem", "key.pem") + +// 或 Let's Encrypt (auto TLS) +import "crypto/tls" +mux.HandleFunc("/", handler) +go http.ListenAndServe(":80", nil) // ACME Challenge +go http.ListenAndServeTLS(":443", "", "", &http.Server{ + Handler: mux, + TLSConfig: &tls.Config{ + GetCertificate: autoTLSManager.GetCertificate, + NextProtos: []string{"h2", "http/1.1"}, + }, +}) +``` + +## 关联笔记 + +- [[hhs/NETWORK/HTTP/3与QUIC]] — QUIC 内置 TLS 1.3 +- [[hhs/NETWORK/DNS原理与优化]] — SNI 扩展帮助 DNS 和 CDN 调度 +- [[hhs/NETWORK/SSL Strip-Heartbleed]] — TLS 历史安全漏洞 diff --git a/hhs/NETWORK/05-应用层协议/05-DNS与DHCP与WebSocket.md b/hhs/NETWORK/05-应用层协议/05-DNS与DHCP与WebSocket.md new file mode 100644 index 0000000..a43ac5a --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/05-DNS与DHCP与WebSocket.md @@ -0,0 +1,270 @@ +--- +tags: [计算机网络, DNS, DHCP, WebSocket] +create time: 2026-05-18 03:10 +--- + +# DNS / DHCP / WebSocket + +## 一、DNS 原理与优化 + +### DNS 查询流程(递归 vs 迭代) + +```mermaid +sequenceDiagram + participant U as 用户浏览器 + participant LRD as 本地 DNS Resolver
ISP 或 1.1.1.1/9.9.9.9 + participant TLD as TLD Server
.com + participant AUTH as Authoritative NS
example.com + + U->>LRD: 递归查询 "example.com?" + + Note over LRD: 检查本地缓存... + LRD->>TLD: 迭代查询 ".com?" + TLD-->>LRD: "去问 example.com 的 NS" + + LRD->>AUTH: 迭代查询 "example.com A 记录?" + AUTH-->>LRD: 93.184.216.34 + + Note over LRD: TTL=300 → 缓存 5 分钟 + LRD-->>U: ✅ 93.184.216.34 +``` + +### 关键概念 + +| 术语 | 说明 | +|------|------| +| **Recursion Desired (RD)** | 客户端要求 DNS 服务器递归查找 | +| **Authority Section** | 指向下一级的授权 NS | +| **CNAME Chain** | 别名链,最多 63 层嵌套 | +| **TTL** | Time To Live,缓存有效期(秒) | +| **ANAME/ALIAS** | 根域名的 CNAME 替代方案 | +| **Any 查询** | `query type = ANY`,RFC 8753 建议禁用 | + +### 常见记录类型 + +| 类型 | 名称 | 用途 | 示例 | +|------|------|------|------| +| A | Address | IPv4 地址 | `@ A 93.184.216.34` | +| AAAA | Address V6 | IPv6 地址 | `@ AAAA 2606:2800:220:1:248:1893:25c8:1946` | +| CNAME | Canonical Name | 别名 | `www CNAME example.com` | +| MX | Mail Exchange | 邮件服务器优先级 | `@ MX 10 mail.example.com` | +| TXT | Text | SPF/DKIM/域名验证 | `v=spf1 include:_spf.google.com ~all` | +| SRV | Service | 服务定位 | `_sip._tcp SVC 1 0 5060 sip.example.com` | +| NS | Name Server | 授权 NS 记录 | `@ NS ns1.provider.com` | +| PTR | Pointer | 反向解析 (IP → 域名) | `34.216.184.93.in-addr.arpa PTR example.com` | +| SOA | Start of Authority | 区域文件起始权威记录 | `ns1 admin email serial refresh retry expire minimum` | +| DS / DNSKEY | — | DNSSEC 签名验证 | — | + +### DNS 缓存层次 + +``` +┌─────────────────────────────────────┐ +│ Tier 1: Application/CPU Cache │ ← Go sync.Map / HTTP cache header +│ TTL controlled by Content-TTL │ Browser cache varies 0~30min +├─────────────────────────────────────┤ +│ Tier 2: OS Resolver Cache │ ← nscd / systemd-resolved / DNSMasq +│ Linux: nscd (default off!) │ macOS: mDNSResponder +│ macOS: mDNSResponder (~120s) │ Windows: Dnscache +├─────────────────────────────────────┤ +│ Tier 3: ISP / Recursive Resolver │ ← Cloudflare 1.1.1.1, Google 8.8.8.8 +│ TTL from authoritative server │ 通常 60~300s 最小缓存 +├─────────────────────────────────────┤ +│ Tier 4: TLD + Authoritative │ ← .com NS → example.com NS +│ No user-controlable caching │ +└─────────────────────────────────────┘ +``` + +### DNS-over-HTTPS / DNS-over-TLS + +| 协议 | RFC | 端口 | 特点 | +|------|-----|------|------| +| DoH (DNS over HTTPS) | 8484 | 443 (TCP) | 伪装成 HTTPS 流量,CDN 友好 | +| DoT (DNS over TLS) | 7858 | 853 (TCP) | 专用加密通道 | +| DoQ (DNS over QUIC) | 9230 | 853 (UDP via QUIC) | HTTP/3 风格 | + +```bash +# Linux 启用 DoH +$ resolvectl dns eth0 1.1.1.1 +$ resolvectl domain eth0 "~." + +# dig 指定 DNS 服务器 +$ dig @1.1.1.1 example.com +short +93.184.216.34 +``` + +## 二、DHCP 自动分配 + +### DHCP 四步交互(DORA) + +```mermaid +sequenceDiagram + participant Client as 新设备
(无 IP) + participant Server as DHCP Server + + Note over Client: BOOTING state, src_ip=0.0.0.0 + Client->>Server: DHCPDISCOVER (broadcast ff:ff:ff:ff:ff:ff) + Note over Server: 收到后从可用池选择一个 IP + + Server-->>Client: DHCPOFFER (ip=x.x.x.x, lease_time=T, gw=y.y.y.y) + Note over Client: SELECTING state + + Client->>Server: DHCPREQUEST (requested ip=x.x.x.x) + Note over Server: 确认分配 + + Server-->>Client: DHCPACK (confirmed allocation) + Note over Client: BOUND state ✅ IP = x.x.x.x +``` + +### DHCP 续租机制 + +``` +租约时间 = Lease Time(默认通常 24h) + +续约时机: +• T1 = 50% 租约时 → RENEW (单播到原服务器) +• T2 = 87.5% 租约时 → REBIND (广播到新服务器) +• 到期前必须续约成功,否则释放 IP +``` + +```bash +# Linux DHCP 客户端配置 +$ cat /etc/dhcp/dhclient.conf +timeout 300; # 超时重试间隔 +retry 60; # 初始重试间隔 +reboot 10; # 重启时重尝试获取 IP +request subnet-mask, broadcast-address, time-offset, routers; + +# 手动刷新 IP +$ sudo dhclient -r eth0 # release +$ sudo dhclient eth0 # request new +``` + +## 三、WebSocket 全双工通信 + +### 为什么需要 WebSocket? + +``` +传统轮询 vs WebSocket: +═══════════════════════ +Polling: Client ─→ GET /status ─→ empty + Client ─→ GET /status ─→ {"msg": "hi"} + Client ─→ GET /status ─→ empty + ... 浪费带宽,延迟高 + +Long Polling: + Client ─→ GET /status (挂起) ─→ ...等待... ─→ {"msg": "hi"} + Client ─→ GET /status (再挂起) ─→ ... + +WebSocket ✨: + Client ─── 握手升级 ───→ 持久双向通道 + Server ─── 推送消息 ───→ Client + Client ─── 推送消息 ───→ Server + ← 全双工、低开销! --> +``` + +### WebSocket 握手升级过程 + +``` +HTTP 请求 → WebSocket → HTTP 响应 + +Client: Server: +GET /ws/chat HTTP/1.1 101 Switching Protocols +Host: chat.example.com Upgrade: websocket +Upgrade: websocket Connection: Upgrade +Connection: Upgrade Sec-WebSocket-Accept: +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Sec-WebSocket-Version: 13 +``` + +**关键:** 这不是 TCP 层面的升级,而是 HTTP 协议的升级——服务器返回 `101` 状态码告诉客户端:"好的,我们从这里开始用 WebSocket 协议"。 + +### Sec-WebSocket-Accept 计算 + +```go +import ( + "crypto/sha1" + "encoding/base64" + "strings" +) + +const magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +func computeAccept(key string) string { + h := sha1.New() + h.Write([]byte(key + magicGUID)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +// 例: key = "dGhlIHNhbXBsZSBub25jZQ==" +// accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" +``` + +### WebSocket Frame 格式 + +``` +┌───────────┬───────────┬─────────────┬───────────────┬────────────────┐ +│ FIN(1 bit)│ RSV(3bit) │ Opcode(4bit)│ Mask(1 bit) │ Payload Len │ +│ │ │ │ │ + Ext Len │ +├───────────┴───────────┴─────────────┴───────────────┼────────────────┤ +│ Extended Payload Length │ Mask Key │ +│ (0, 126, or 127 bytes) │ (4 bytes) │ +├─────────────────────────────────────────────────────┴────────────────┤ +│ Masked Payload Data (variable) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +| Opcode | 含义 | +|--------|------| +| 0x0 | Continuation frame | +| 0x1 | Text frame | +| 0x2 | Binary frame | +| 0x8 | Connection Close | +| 0x9 | Ping | +| 0xA | Pong | + +### Go 中的 WebSocket + +```go +package main + +import ( + "log" + "net/http" + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true // 生产环境应严格校验 Origin + }, +} + +func wsHandler(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("upgrade error:", err) + return + } + defer conn.Close() + + for { + mt, message, err := conn.ReadMessage() + if err != nil { + break + } + log.Printf("recv: %s", message) + conn.WriteMessage(mt, message) + } +} + +func main() { + http.HandleFunc("/ws", wsHandler) + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/HTTPS与TLS握手]] — SNI 扩展在 DNS 解析中的应用 +- [[hhs/NETWORK/WebSocket全双工通信]] — 心跳机制防止 NAT/代理超时断开 +- [[hhs/NETWORK/NAT原理与应用]] — WebSocket 也受 NAT 影响 diff --git a/hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md b/hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md new file mode 100644 index 0000000..59cb26b --- /dev/null +++ b/hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md @@ -0,0 +1,205 @@ +--- +tags: [计算机网络, SSH, SFTP, SCP] +create time: 2026-05-18 03:20 +--- + +# SSH 远程安全登录 + +## 概述 + +SSH(Secure Shell)是替代 Telnet、rlogin 等明文协议的加密外壳工具。它不仅仅用于远程登录——SSH 的隧道转发能力使其成为网络安全的瑞士军刀。 + +## SSH 架构三层模型 + +``` +┌─────────────────────────────────┐ +│ Application Layer │ ← SSH-CONNECT, SSH-USERAUTH, SSH-CONNECTION +│ • scp / sftp │ 每个子协议独立协商版本 +│ • port forwarding │ +│ • X11 forwarding │ +├─────────────────────────────────┤ +│ Transport Layer │ ← Host-key auth + encryption + integrity +│ • Server host-key authentication│ 一旦建立隧道,所有上层协议自动加密 +│ • Server/pubkey exchange │ +│ • Symmetric encryption │ 默认 AES-128-GCM or ChaCha20-Poly1305 +│ • HMAC integrity │ +├─────────────────────────────────┤ +│ User Authentication Layer │ ← 多种认证方式 +│ • password │ SSH-CONN USER_AUTH_REQUEST +│ • public key │ +│ • keyboard-interactive │ MFA/TOTP +│ • GSSAPI (Kerberos) │ +│ • OS Login / Certificate │ +└─────────────────────────────────┘ +``` + +## SSH 密钥交换过程 + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + + Note over C,S: Phase 1: Key Exchange (Diffie-Hellman) + C->>S: KEXINIT (supported ciphers, DH groups, MACs, compressions) + S-->>C: KEXINIT (negotiated algorithms) + + Note over C,S: 服务器选择双方都支持的最强算法 + S->>C: Server Key Exchange (DH params) + S->>C: Server Host Key (RSA/ED25519) + signature + + Note over C: Verify host key fingerprint! + C->>S: Client Key Exchange (DH response) + + Note over C,S: Both compute shared secret → derive session keys + + Note over C,S: Phase 2: User Authentication + C->>S: SSH_USERAUTH_REQUEST "root" "password" + S-->>C: SSH_USERAUTH_SUCCESS ✅ + + Note over C,S: Phase 3: Channel Open + C->>S: SSH_CHANNEL_OPEN "session" + S-->>C: SSH_CHANNEL_OPEN_CONFIRMATION +``` + +### SSH 支持的公钥算法 + +| 算法 | 密钥大小 | 安全性等级 | 备注 | +|------|---------|-----------|------| +| **ed25519** | 32 bytes | ~128 bits | ✅ **推荐**,EdDSA 椭圆曲线,速度快 | +| rsa | 4096 bits | ~128 bits | 通用兼容,但体积大 | +| ecdsa | 384 bits | ~128 bits | NIST 曲线,争议因 DualECDRBG | +| dsa | 1024 bits | ~80 bits | ❌ **已弃用**,OpenSSH 7.0+ 禁用 | +| ssh-rsa (SHA-1) | variable | 弱 | ❌ OpenSSH 8.8+ 默认禁用 | + +```bash +# 生成 ed25519 密钥 +$ ssh-keygen -t ed25519 -C "alice@example.com" +# 或带注释和自定义路径 +$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "work@company.com" + +# 复制公钥到远程服务器 +$ ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-server + +# 手动添加 +$ cat ~/.ssh/id_ed25519.pub | ssh user@remote 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' +``` + +## SSH 端口转发(Tunneling) + +### 本地端口转发(Local Forwarding)⭐ + +```bash +# 通过 jump server 访问内网数据库 +ssh -L 3307:db.internal:3306 user@jump.example.com + +# 等效命令: ssh -L :: +# 本地 localhost:3307 → SSH 隧道 → jump.example.com → db.internal:3306 +``` + +``` +Client Jump Server DB Server +──────── ─────────── ───────── +localhost:3307 ──→ [SSH Tunnel] ──→ db.internal:3306 + ↑ ↑ +MySQL CLI SSH 加密通道 🔒 +``` + +### 远程端口转发(Remote Forwarding) + +```bash +# 让外网访问我本地服务(反向穿透 NAT) +ssh -R 8080:localhost:3000 user@public-server + +# 我的 Mac:3000 ← SSH -R ← public-server:8080 +# 任何人访问 public-server:8080 都能到我的本地服务! +``` + +### 动态端口转发(SOCKS Proxy) + +```bash +# 创建 SOCKS5 代理 +ssh -D 1080 user@bastion + +# 浏览器设置 SOCKS proxy: 127.0.0.1:1080 +# → 所有流量经过 bastion 转发 +``` + +## SSH 配置文件 + +```bash +# ~/.ssh/config +Host github.com + HostName github.com + User git + IdentityFile ~/.ssh/id_ed25519_github + IdentitiesOnly yes + +Host bastion + HostName 203.0.113.5 + User deploy + IdentityFile ~/.ssh/id_ed25519_bastion + Port 2222 + +Host internal-* + ProxyJump bastion + User admin + IdentityFile ~/.ssh/id_ed25519_internal + ServerAliveInterval 60 + ServerAliveCountMax 3 + +# 使用: ssh internal-webapp1 (自动经 bastion 跳转) +``` + +### 关键参数说明 + +| 参数 | 说明 | +|------|------| +| `ProxyJump` | 通过堡垒机跳转 | +| `ServerAliveInterval` | 客户端发送心跳间隔(秒),防防火墙超时 | +| `ServerAliveCountMax` | 最多连续无响应次数后断开 | +| `IdentitiesOnly` | 仅使用指定的 identity file,不尝试其他密钥 | +| `StrictHostKeyChecking` | `ask`(默认) / `no`(不检查) / `accept-new`(首次接受) | + +## Go 中的 SSH + +```go +import ( + "golang.org/x/crypto/ssh" + "os" +) + +// 读取私钥文件 +key, err := os.ReadFile("~/.ssh/id_ed25519") +if err != nil { panic(err) } + +signer, err := ssh.ParsePrivateKey(key) +if err != nil { panic(err) } + +config := &ssh.ClientConfig{ + User: "admin", + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境用 ssh.FixedHostKey() +} + +client, err := ssh.Dial("tcp", "server.example.com:22", config) +if err != nil { panic(err) } + +// 执行远程命令 +session, _ := client.NewSession() +defer session.Close() +output, _ := session.CombinedOutput("uname -a; uptime") +fmt.Println(string(output)) + +// SFTP client +sftpClient, _ := sftp.NewClient(client) +defer sftpClient.Close() +``` + +## 关联笔记 + +- [[hhs/NETWORK/HTTPS与TLS握手]] — SSH 也使用非对称加密 + 对称加密混合模式 +- [[hhs/NETWORK/NAT原理与应用]] — SSH 端口转发可穿透 NAT +- [[hhs/NETWORK/MAC地址与广播域]] — SSH 在局域网内的常见部署场景 diff --git a/hhs/NETWORK/06-Socket编程/01-SocketAPI与backlog详解.md b/hhs/NETWORK/06-Socket编程/01-SocketAPI与backlog详解.md new file mode 100644 index 0000000..8dbe429 --- /dev/null +++ b/hhs/NETWORK/06-Socket编程/01-SocketAPI与backlog详解.md @@ -0,0 +1,177 @@ +--- +tags: [计算机网络, Socket API, listen, accept, backlog, SYN队列] +create time: 2026-05-18 04:30 +--- + +# Socket API 与 backlog 深度解析 + +## 概述 + +Socket 是应用层与传输层之间的抽象接口。理解 Socket API 如何映射到 TCP/IP 协议栈的各层操作,以及 `listen()` 中 backlogged 参数的真实含义——它是两个队列的组合而非一个——是从入门走向高性能网络编程的关键一步。 + +## Socket API 到 OSI 层的映射 + +| 系统调用 | OSI 层级 | 做了什么? | 关键参数 | +|----------|---------|-----------|---------| +| `socket()` | N/A | 创建 socket 对象 | domain(AF_INET/AF_UNIX), type(SOCK_STREAM/SOCK_DGRAM) | +| `bind()` | L4/L3 | 绑定本地地址+端口 | struct sockaddr_in{family, port, addr} | +| `listen()` | L4 (TCP) | 转为被动监听模式 | backlog = 半连接队列 + 全连接队列 | +| `accept()` | L4 (TCP) | 从已完成队列取出一个连接 | 返回新 conn fd + peer 地址 | +| `connect()` | L4 (TCP) | 发起三次握手 | struct sockaddr_in{remote_port, remote_addr} | +| `send()/write()` | L4→L7 | 将数据放入发送缓冲区 | buffer, length, flags(MSG_DONTWAIT...) | +| `recv()/read()` | L4←L7 | 从接收缓冲区读取 | buffer, length, flags(MSG_PEEK...) | +| `setsockopt()` | 各层 | 修改 socket 选项 | SO_REUSEADDR / TCP_NODELAY / etc. | +| `close()` | L4 | 发起四次挥手 | — | + +```mermaid +flowchart LR + Client["客户端"] -->|"connect() → SYN"| Server + subgraph "服务端" + Server["服务器"] -->|"listen()"+|"创建两层队列"| TwoQ["半连接队列 + 完成队列"] + TwoQ -->|"accept()"+"取出"| App["应用程序"] + end + + style App fill:#DDA0DD,color:#000 +``` + +### Go 中的标准启动流程 + +```go +// Go 中的标准 HTTP 服务器启动流程 +ln, err := net.Listen("tcp", ":8080") +// 等价 C 代码序列: +// fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +// setsockopt(fd, SO_REUSEADDR, 1) // 允许地址快速复用 +// bind(fd, {addr=0.0.0.0, port=8080}) +// listen(fd, SOMAXCONN) // ⬅️ backlog 在这里起作用 +// for { +// conn_fd, _ = accept(fd, &peer_addr) // 从完成队列取 +// go handle(conn_fd) // 每个连接一个 goroutine +// } +``` + +## backlog 参数的奥秘 —— 两个队列! + +这是很多开发者容易误解的地方:`listen(fd, backlog)` 中的 backlog **不是一个队列的大小**,而是控制两个不同队列的组合。 + +```mermaid +flowchart TD + Client["客户端
发出 SYN"] -->|"三次握手进行中"| SyncQ["半连接队列
SYN_RECV 状态
容量: tcp_max_syn_backlog"] + + SyncQ -->|"握手完成"| CompQ["已完成连接队列
ESTABLISHED 状态
容量: min(backlog, somaxconn)"] + + CompQ -->|"accept() 取出"| App["应用程序"] + + SyncQ -.->|"队列满时"| Drop["丢弃后续 SYN
客户端超时重传"] + CompQ -.->|"队列满时"| Reject["拒绝 accept
对客户端表现为延迟"] + + style SyncQ fill:#FFD700,color:#000 + style CompQ fill:#98FB98,color:#000 + style Drop fill:#FF6B6B,color:#fff + style Reject fill:#FFA07A,color:#000 +``` + +```bash +# Linux 相关内核参数(通过 sysctl 查看) +$ sysctl net.ipv4.tcp_max_syn_backlog # SYN 半连接队列最大数 (默认 ~1024) +$ sysctl net.core.somaxconn # 全局 backlog 上限 (默认 128! 🔴 生产要改) +$ sysctl net.ipv4.tcp_abort_on_overflow # 超过限制时的行为: 0=静默丢弃, 1=RST +``` + +> [!QUESTION] 为什么 backlog 默认只有 128? +> 这个保守设计源于通用场景:普通桌面或小型服务器不可能同时有大量连接。但生产级 Web/API 服务器轻松面对上万并发,128 就是瓶颈。 + +### 半连接队列 vs 已完成队列的行为差异 + +| 维度 | 半连接队列 (SYN Queue) | 已完成队列 (Accept Queue) | +|------|----------------------|-------------------------| +| 内容 | 正在三次握手中的连接 | 已完成握手的连接 | +| 增长方式 | connect() 发 SYN 时加入 | 握手完成后自动移入 | +| 减少方式 | 握手完成/超时清理 | accept() 取出时移除 | +| 满了的后果 | 丢弃 SYN,客户端重传 | 客户端收到旧数据而非错误 | +| 可调参数 | tcp_max_syn_backlog | net.core.somaxconn | + +```go +// Go 中如何设置大的 backlog +ln, _ := net.Listen("tcp", ":8080") +// Go 内部调用 syscall.Listen(fd, SOMAXCONN),实际取: +// min(user_specified_backlog, net.core.somaxconn) +// 所以即使 Go 写了 65535,内核 still 受限于 somaxconn=128 +// 必须先 sysctl 调大内核参数! +``` + +> [!tip] 生产环境 backlog 配置黄金公式 +> ```ini +> # /etc/sysctl.conf +> net.core.somaxconn = 65535 +> net.ipv4.tcp_max_syn_backlog = 8192 +> # 重启后生效: sudo sysctl -p +> ``` + +## bind() 的高级选项 + +### SO_REUSEADDR — 快速重启的关键 + +```go +// 在 C 中必须在 bind() 之前设置 +fd, _ := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +opt := 1 +setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, size) +bind(fd, ...) +``` + +```go +// Go 的 net.Listen 已经内置了 SO_REUSEADDR +ln, _ := net.Listen("tcp", ":8080") +// 不需要手动设置!Go runtime 已处理 +``` + +`SO_REUSEADDR` 允许一个新的 socket 绑定到一个处于 `TIME_WAIT` 状态的地址上,避免了等待 2MSL 后才能重新绑定的尴尬。 + +### SO_REUSEPORT — 多进程负载均衡 + +```bash +# 多个进程/线程各自 listen 同一个端口 +# kernel 在收到 SYN 后选择一个进程来 accept +# 避免单进程 accept 竞争锁 +``` + +这在 Nginx master-worker 模型、Go 中使用 `GOMAXPROCS` 多线程监听同一端口时有用。 + +## connect() — 客户端视角 + +```go +// Go 标准连接方式(阻塞) +conn, err := net.Dial("timeout", "server.example.com:443") +// 等价于: +// fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +// connect(fd, {server_ip, 443}) // 阻塞直到握手完成或超时 +``` + +```go +// 非阻塞 connect + poll 实现带超时的连接 +func dialWithTimeout(addr string, timeout time.Duration) (net.Conn, error) { + conn, err := net.DialTimeout("tcp", addr, timeout) + if err != nil { + return nil, err + } + return conn, nil +} + +// 实际 Go net.DialTimeout 内部使用 OS 级超时机制 +// 底层仍是 non-blocking connect + select/poll 等待就绪 +``` + +> [!warning] DNS 解析耗时 +> `net.Dial` 会先做 DNS 查询再发起 connect。如果需要独立控制 DNS 和 TCP 连接时间: +> ```go +> addrs, _ := net.LookupHost("server.example.com") // DNS 阶段 +> conn, _ := net.DialTimeout("tcp", addrs[0]+":443", timeout) // TCP 阶段 +> ``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — connect() 触发的三次握手完整过程 +- [[hhs/NETWORK/TCP段结构与状态机]] — bind/listen/accept 对应 TCP 的状态转换 +- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 握手中的 connect() 阻塞 +- [[hhs/NETWORK/01-带宽延迟RTT与吞吐量]] — connect() 造成的第一个 RTT 开销 diff --git a/hhs/NETWORK/06-Socket编程/02-epoll深度解析ETvsLT.md b/hhs/NETWORK/06-Socket编程/02-epoll深度解析ETvsLT.md new file mode 100644 index 0000000..a11c53c --- /dev/null +++ b/hhs/NETWORK/06-Socket编程/02-epoll深度解析ETvsLT.md @@ -0,0 +1,298 @@ +--- +tags: [计算机网络, epoll, select, poll, ET模式, LT模式, I/O多路复用] +create time: 2026-05-18 04:35 +--- + +# I/O 多路复用:select → poll → epoll 深度解析 + +## 概述 + +I/O 多路复用是让一个线程管理成千上万个 Socket 连接的核心技术。它经历了 select → poll → epoll 三阶段进化,每一代的核心理念都在解决上一代的瓶颈。理解它们的区别,是在高并发场景下做出正确选型的前提。 + +## I/O 模型全景图 + +```mermaid +flowchart LR + BIO["Blocking I/O
阻塞在读/写系统调用
每连接一个线程"] -->|"问题:"| Scalability["扩展性差
万级连接就需要万级线程"] + + AIO["Non-blocking +
I/O 多路复用
单线程管理万级 FD"] -->|"优势:"| HighConc["高并发 ✅"] + + AsyncIO["真正的 Async I/O
aio / io_uring
内核完成通知"] -->|"终极形态:"| ZeroWait["零等待 ✅✅"] + + style BIO fill:#FF6B6B,color:#fff + style AIO fill:#98FB98,color:#000 + style AsyncIO fill:#B0C4DE,color:#000 +``` + +| 模型 | 说明 | 典型框架 | +|------|------|---------| +| **Blocking (BIO)** | read()/write() 阻塞,每连接一线程 | Python threading, PHP-FPM | +| **Non-blocking + 多路复用** | 注册多个 FD,等待就绪事件 | Nginx, Node.js, Go runtime | +| **Async I/O (aio/io_uring)** | 内核完成 I/O 后回调通知 | Linux io_uring, Windows IOCP | +| **Signal-driven (SIGIO)** | 数据就绪时 SIGIO 信号通知 | 较少使用 | + +> [!NOTE] Go 的独特路线 +> Go 选择了第三条路:**goroutine + epoll 后台轮询**。每个请求一个 goroutine(看起来像 BIO),但底层由少量 OS 线程通过 epoll 调度。这被称为 **"M:N 调度"** —— M 个 goroutine 映射到 N 个 OS 线程(通常 N=GOMAXPROCS)。 + +## select —— 第一代多路复用 + +### C API 原型 + +```c +int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +// 返回值:就绪的 fd 总数;-1 = 错误 +``` + +### 核心缺陷 + +``` +┌─────────────────────────────────────┐ +│ select 三大缺陷 │ +├─────────────────────────────────────┤ +│ 1. FD_SETSIZE 固定 1024 │ +│ 必须 recompile 内核才能扩大 │ +│ │ +│ 2. O(n) 线性扫描 │ +│ 每次调用都要遍历全部注册的 fd │ +│ │ +│ 3. 用户态 ↔ 内核态拷贝整个 fd_set │ +│ n=10000 时每次拷贝 12.5KB │ +└─────────────────────────────────────┘ +``` + +```go +// Go 中几乎不会直接用 select 做多路复用 +// Go 的 select 关键字是语言级语法糖,底层走的是自己的 netpoller +select { +case <-ch1: + // channel ch1 可读 +case <-ch2: + // channel ch2 可读 +default: + // 都没有就绪 +} +``` + +## poll —— 第二代改进 + +### C API 原型 + +```c +struct pollfd { + int fd; /* file descriptor */ + short events; /* 关注的事件: POLLIN/POLLOUT/POLLERR */ + short revents; /* 返回的实际事件 */ +}; +int poll(struct pollfd *fds, nfds_t nfds, int timeout); +``` + +### 改进了什么? + +``` +✅ 不再有固定的 FD_SETSIZE 上限(改用动态数组) +❌ 仍然是 O(n) 线性扫描 +❌ 每次仍需将整个 fds 数组拷贝到内核 +❌ 返回时需要逐个检查 revents 位 + +结论:适合中等规模(几百连接),不适合万级 +``` + +### poll 事件类型速查 + +| 宏 | 数值 | 含义 | +|----|------|------| +| `POLLIN` | 0x001 | 有数据可读 | +| `POLLRDNORM` | 0x040 | 正常可读(等效 POLLIN)| +| `POLLOUT` | 0x002 | 可写 | +| `POLLERR` | 0x008 | 错误条件 | +| `POLLHUP` | 0x010 | 挂起(对端关闭)| +| `POLLNVAL` | 0x020 | 无效请求(fd 未打开)| + +## epoll —— Linux 王者方案 + +### 三大系统调用 + +```c +// 1. 创建 epoll 实例 +int epfd = epoll_create(1); // size 参数已被忽略,传 1 即可 + +// 2. 增删改 fd 的事件注册 +int epoll_ctl(epfd, EPOLLADD, fd, &event); // 添加 +int epoll_ctl(epfd, EPOLLMOD, fd, &event); // 修改 +int epoll_ctl(epfd, EPOLLDEL, fd, NULL); // 删除 + +// 3. 等待就绪事件(只返回活跃的 fd!) +int ready = epoll_wait(epfd, events, maxevents, timeout); +// 返回就绪数量,events 数组被填充 +``` + +### 内核数据结构 —— O(1) 的秘密 + +```mermaid +flowchart TD + subgraph "内核空间 (Kernel)" + RBTree["红黑树 rb_root
管理所有注册的 fd
O(log n) 增删查"] + + CallbackList["就绪链表 ready_list
只放真正活跃的 fd
O(1) 插入"] + + DeviceCallback["中断回调函数
当网卡收到数据包时
自动将 fd 加入 ready_list"] + end + + subgraph "用户空间 (User)" + App["用户应用
epoll_wait()"] -->|"仅复制就绪项"| EventArray["epoll_event events[]
只包含就绪 fd"] + end + + DeviceCallback -.->|"fd 有数据时触发"| CallbackList + + style RBTree fill:#DDA0DD,color:#000 + style CallbackList fill:#98FB98,color:#000 + style App fill:#FFD700,color:#000 +``` + +``` +┌──────────────────────────────────────────┐ +│ epoll 三大优势 │ +├──────────────────────────────────────────┤ +│ ✅ O(1) 时间复杂度 │ +│ 就绪事件通过回调链表直接给出 │ +│ │ +│ ✅ 内存拷贝极小 │ +│ 只拷贝就绪事件的个数 × sizeof(event) │ +│ │ +│ ✅ fd 无上限 │ +│ 只受系统可用文件描述符数限制 (~百万级) │ +│ │ +│ ❌ Linux 专属 │ +│ macOS/BSD 用 kqueue, Windows 用 IOCP │ +└──────────────────────────────────────────┘ +``` + +## ET vs LT 模式深度对比 + +epoll 支持两种触发模式,这是面试和生产中最常涉及的话题。 + +### LT(Level Triggered)—— 默认模式 + +``` +📥 fd 上有数据到达 + ↓ +epoll_wait() 返回 ✅ 通知一次 + ↓ +应用只读了部分数据(还剩一半) + ↓ +下次 epoll_wait() 仍然返回 ✅ 继续通知 + ↓ +一直到该 fd 上的数据全部读完毕才停止 +``` + +```c +// LT 模式下可以安全地只读一部分 +n = read(fd, buf, sizeof(buf)); // 可能还有剩余数据 +if (n > 0) handle(buf, n); +// 下次 epoll_wait 还会提醒你有数据没读完 +``` + +> [!tip] LT 类比 +> LT 就像**消息通知**——只要你没读完,消息就一直亮着未读红点。 + +### ET(Edge Triggered)—— 边缘触发 + +``` +📥 fd 状态从「无数据」变为「有数据」 ← 仅在此刻通知! + ↓ +epoll_wait() 返回 ✅ + ↓ +你必须一次性读完所有数据!⚠️ + ↓ +如果有剩余?下次不通知了!🚨 +``` + +```c +// ET 模式的标准 read 循环模板 +while (1) { + n = read(fd, buf, sizeof(buf)); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; // 数据已全部读完,退出循环 + } + perror("read error"); + close(fd); + break; + } else if (n == 0) { + close(fd); // EOF = 对端关闭 + break; + } + // 处理 n 字节数据 +} +``` + +> [!warning] ET 模式的常见陷阱 +> +> 1. **必须配合 non-blocking fd** —— blocking 模式下 read 会在无数据时永久阻塞,破坏 ET 语义 +> 2. **必须用 while 循环读到底** —— `read()` 返回 EAGAIN 前一直读 +> 3. **write 也要同理** —— 写缓冲区满时要重新注册 POLLOUT + +``` +┌────────────────────────────────────────────┐ +│ ET vs LT 决策表 │ +├────────────────┬────────────────┬───────────┤ +│ 特性 │ LT │ ET │ +├────────────────┼────────────────┼───────────┤ +│ 默认模式 │ ✅ 是 │ ❌ 否 │ +│ 编程难度 │ 简单 │ 较复杂 │ +│ 性能 │ 略低 │ 更高 ⭐ │ +│ 误通知 │ 无(有数据就通知)│ 无 │ +│ 遗漏风险 │ 低 │ 高(漏读即丢失)│ +│ 推荐场景 │ 大多数场景 │ 极致性能追求 │ +└────────────────┴────────────────┴───────────┘ +``` + +### Go 的 netpoller 使用哪种模式? + +``` +Linux: epoll 的 ET 模式 +macOS: kqueue 的边缘触发语义 +Windows: IOCP 本身就是边缘触发 +``` + +Go 的 net 包在底层做了大量封装,你不需要关心 ET/LT 的区别。但当你在 Go 中写自定义网络框架(如 RPC 框架、游戏服务器)时,就会直面这个问题。 + +## 实战:最小可用的 epoll 框架 + +```go +package main + +import ( + "fmt" + "golang.org/x/sys/unix" + "time" +) + +func main() { + // 1. 创建 epoll 实例 + epfd, _ := unix.EpollCreate1(0) + defer unix.Close(epfd) + + // 2. 打开文件并注册 EPOLLIN + fd, _ := unix.Open("/tmp/test.txt", unix.O_RDONLY|unix.O_NONBLOCK, 0) + ev := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(fd)} + unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, fd, &ev) + + // 3. 等待就绪 + events := make([]unix.EpollEvent, 128) + ready, _ := unix.EpollWait(epfd, events, 5000) // 5s timeout + for i := 0; i < ready; i++ { + fmt.Printf("fd %d is ready!\n", events[i].Fd) + } +} +``` + +> [!tip] 实际项目建议 +> 除非有特殊需求,否则直接使用 Go 的 `net`、`bufio`、`http` 包即可。epoll 的细节已被这些成熟库封装好了。 + +## 关联笔记 + +- [[hhs/NETWORK/SocketAPI与backlog详解]] — Socket 创建后的第一步:listen + accept +- [[hhs/NETWORK/零拷贝与GoNetpoller]] — epoll 之上的零拷贝技术与 Go runtime 集成 +- [[hhs/NETWORK/TCP段结构与状态机]] — 何时关注 EPOLLRDHUP 等 TCP 特殊事件 diff --git a/hhs/NETWORK/06-Socket编程/03-零拷贝与GoNetpoller.md b/hhs/NETWORK/06-Socket编程/03-零拷贝与GoNetpoller.md new file mode 100644 index 0000000..5952c7c --- /dev/null +++ b/hhs/NETWORK/06-Socket编程/03-零拷贝与GoNetpoller.md @@ -0,0 +1,245 @@ +--- +tags: [计算机网络, 零拷贝, sendfile, Go netpoller, goroutine调度] +create time: 2026-05-18 04:40 +--- + +# 零拷贝技术与 Go netpoller 原理 + +## 概述 + +在前两章掌握了 Socket API 和 epoll 之后,本章聚焦两个工程实践话题:如何通过内核优化减少数据传输的拷贝次数(零拷贝),以及 Go 如何用 goroutine 让高并发变得优雅。 + +## 零拷贝技术演进 + +### 什么是零拷贝? + +传统文件传输中,数据从磁盘到网络经历多次 CPU 参与的内存拷贝。零拷贝的目标是**让数据绕过用户态**,直接在 DMA 硬件和内核空间之间流转。 + +```mermaid +flowchart LR + subgraph "传统方式 (4次拷贝, 2次上下文切换)" + D1["磁盘 Page Cache
Kernel"] -->C1["CPU 拷贝到
User Buffer"] + C1 -->C2["CPU 拷贝到
Socket Buffer Kernel"] + C2 -->DMA1["DMA 传到 NIC"] + end + + subgraph "sendfile() (2次拷贝, 2次上下文切换)" + D2["磁盘 Page Cache
Kernel"] -->S1["DMA 直接到
Socket Buffer"] + S1 -->DMA2["DMA 传到 NIC"] + end + + style D1 fill:#DDA0DD,color:#000 + style D2 fill:#DDA0DD,color:#000 + style DMA2 fill:#B0C4DE,color:#000 +``` + +``` +┌──────────────────────────────────────────────┐ +│ 拷贝次数对比 │ +├───────────────┬──────────┬─────────┬──────────┤ +│ 方式 │ 用户态拷贝 │ 内核态拷贝 │ 上下文切换 │ +├───────────────┼──────────┼─────────┼──────────┤ +│ read()+write() │ 2 │ 2 │ 4 │ +│ sendfile() │ 0 │ 2 │ 2 │ +│ sendmmsg() │ 0 │ 1 │ 1 │ +│ io_uring │ 0 │ 1 │ 1 │ +└───────────────┴──────────┴─────────┴──────────┘ +``` + +### 四种零拷贝方案对比 + +```mermaid +flowchart TD + SF["sendfile()
内核 ≥ 2.1
最简单"] + SM["sendmmsg()
内核 ≥ 2.6.34
批量发送"] + DM["DMA Gather
scatter-gather DMA
零额外拷贝"] + + SF --> DM + SM --> DM + + IR["io_uring
内核 ≥ 5.1
终极异步方案"] + + style DM fill:#98FB98,color:#000 + style IR fill:#B0C4DE,color:#000 +``` + +#### 1. sendfile() — 最经典的零拷贝 + +```c +// C 语言原生 API +ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); +// 数据路径: 磁盘 → page cache → socket buffer → NIC (DMA) +// 用户态完全不参与! +``` + +```go +// Go 中的 sendfile — 自动选择最优路径 +f, _ := os.Open("static/logo.png") +defer f.Close() + +w, _ := net.FileConn(f.File()) // 获取底层的 OS fd +io.Copy(w, f) // Go 会检测是否支持 sendfile + // Linux: 直接调用 sendfile() syscall + // macOS: 使用 sendfile() BSD 版本 + // 其他: 降级为 buffered copy +``` + +#### 2. mmap + writev —— 更灵活的零拷贝 + +```c +// mmap 将文件映射到用户虚拟地址空间 +// 用户态可以直接"看"到文件内容(但不拷贝) +// writev/scatter-gather 一次性提交多块数据给内核 +void *ptr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); +writev(sockfd, &iov, 1); // scatter-write +munmap(ptr, filesize); +``` + +> [!tip] mmap 的特殊之处 +> mmap 不是严格意义上的零拷贝——用户态可以看到数据(页表映射),但如果不去触碰这些数据,就不会发生实际的物理内存拷贝。这是一种 **"按需加载(lazy load)"** 的零拷贝变体。 + +### Go 中的零拷贝最佳实践 + +```go +func serveFileStatic(w http.ResponseWriter, r *http.Request, path string) { + f, err := os.Open(path) + if err != nil { + http.Error(w, "not found", 404) + return + } + defer f.Close() + + info, _ := f.Stat() + w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size())) + w.Header().Set("Content-Type", http.DetectContentType(file)) + + // Go 1.13+ 的 http.ServeContent 会自动尝试零拷贝 + http.ServeContent(w, r, info.Name(), info.ModTime(), f) + // 底层逻辑: + // 1. 检测 OS 是否支持 sendfile + // 2. 如果支持,使用 syscall.Sendfile + // 3. 如果不支持,用 32KB buffer 优化 copy +} +``` + +## Go netpoller 原理 —— C10K 问题的优雅解法 + +### Goroutine 与 OS 线程的关系 + +```mermaid +flowchart LR + G1["goroutine 1
handle request A"] --> P1["OS Thread
M1"] + G2["goroutine 2
handle request B"] --> P1 + G3["goroutine 3
blocking on read..."] -.parked.-.-> Empty["M1 空闲出去服务其他 goroutine"] + + G4["goroutine 4
handle request C"] --> P2["OS Thread
M2"] + G5["goroutine 5
network read ready"] --> P2 + + style P1 fill:#DDA0DD,color:#000 + style P2 fill:#98FB98,color:#000 +``` + +``` +GOMAXPROCS = N 意味着最多 N 个 OS 线程同时运行 goroutine +但可能有 M >> N 个 goroutine 在排队等待执行 +这就是 M:N 调度模型 +``` + +### netpoller 的工作流程 + +```mermaid +sequenceDiagram + participant Acceptor as Accept Goroutine + participant Epoll as epoll ET (后台线程) + participant RunQueue as runq 待执行队列 + participant Handler as Handle Goroutine + + Acceptor->>Epoll: conn, _ := ln.Accept() + Note over Epoll: conn_fd 设为 non-blocking + Epool->>Epoll: unix.SetNonblock(conn.Fd(), true) + Epoll->>Epoll: epoll_ctl(EPOLL_CTL_ADD, conn_fd, EPOLLIN) + Epoll->>RunQueue: goroutine ready (Read 待执行) + RunQueue->>Handler: goroutine 被唤醒 + + Handler->>Handler: conn.Read(buf) + alt 数据已到 + Handler->>Handler: 成功读取,继续处理 + else 数据未到 (EAGAIN) + Handler->>Epoll: goroutine park
移除 netpoller 监控 + Note over Epoll: 等待网卡中断 + Epoll->>Handler: 数据到达 → goroutine ready + Handler->>Handler: 再次 Read,成功 + end +``` + +### Go netpoller 的 OS 差异化实现 + +| 平台 | 多路复用机制 | 触发模式 | 源码位置 | +|------|------------|---------|---------| +| **Linux** | epoll | ET 模式 | `src/runtime/netpoll_epoll.go` | +| **macOS / BSD** | kqueue | 边缘触发 | `src/runtime/netpoll_kqueue.go` | +| **Windows** | IOCP | 边缘触发 | `src/runtime/netpoll_iocp.go` | +| **Solaris** | event ports | 边缘触发 | `src/runtime/netpoll Solaris.go` | + +```go +// Go 1.14 引入的 io_uring 实验性支持 +// src/runtime/netpoll_io_uring.go (GOEXPERIMENT=io_uring) +// 预期优势: +// 1. 统一的异步接口(读写+文件 I/O) +// 2. 减少系统调用次数(submit + wait 合一) +// 3. 完全的用户态环缓冲区,无拷贝 + +// 未来 Go 的 netpoller 可能统一走 io_uring 路径 +``` + +> [!tip] Go 协程的优势总结 +> +> Go 不需要像 C/C++ 那样手动维护 epoll + callback 地狱。每个连接挂起一个 goroutine(栈 2KB 起步),CPU 和内存效率极高。这被称为 **"C10K problem solved by goroutines"**。 +> +> 但这并不意味着可以无视底层细节——了解 netpoller 原理有助于: +> - 理解 goroutine leak(永远在等待的 conn.Read 不会释放) +> - 正确设置超时(context.WithTimeout 最终驱动 epoll_wait timeout) +> - 调试 "stuck" goroutine 的问题 + +## Go http.Server 核心配置回顾 + +```go +srv := &http.Server{ + Addr: ":443", + Handler: myHandler, + + // === 超时控制(防慢连接攻击 Slowloris)=== + ReadTimeout: 10 * time.Second, // 读完整请求体的时间 + WriteTimeout: 30 * time.Second, // 写出响应的时间 + IdleTimeout: 120 * time.Second, // Keep-Alive 空闲多久断开 + MaxHeaderBytes: 1 << 20, // Header 最大 1MB + + // TLS 要求 + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + }, +} + +// ⚠️ 注意:没有 Context 超时! +// 如果用 handler 里接 context.Context,那是业务层面的超时 +// 与 srv.ReadTimeout 是两个维度的控制 +``` + +```go +// http.Server 的 ListenAndServe 内部流程 +ln, _ := net.Listen("tcp", ":443") +for { + conn, _ := ln.Accept() // OS accept() → 拿 conn fd + go func() { // 启动 goroutine 处理 + srv.handleConn(conn) // 内部使用 readLoop + writeLoop + }() +} +// handleConn 中会用到 netpoller 来高效读取数据 +``` + +## 关联笔记 + +- [[hhs/NETWORK/epoll深度解析ETvsLT]] — epoll 的 ET/LT 模式是 netpoller 的基础 +- [[hhs/NETWORK/SocketAPI与backlog详解]] — accept() 拿到 conn 后的一切始于 backlog +- [[hhs/NETWORK/01-带宽延迟RTT与吞吐量]] — BDP 决定需要多大的 send/receive buffer diff --git a/hhs/NETWORK/07-网络工具与诊断/01-连通性与状态探测工具.md b/hhs/NETWORK/07-网络工具与诊断/01-连通性与状态探测工具.md new file mode 100644 index 0000000..af4c9bb --- /dev/null +++ b/hhs/NETWORK/07-网络工具与诊断/01-连通性与状态探测工具.md @@ -0,0 +1,224 @@ +--- +tags: [计算机网络, ping, traceroute, ss, telnet, nc, netstat] +create time: 2026-05-18 04:45 +--- + +# 连通性与状态探测工具 + +## 概述 + +排查网络问题时,正确的第一步是判断"到底通不通"。本章覆盖从物理连通性到应用端口层的基础探测工具。 + +## ping —— ICMP Echo Request/Reply + +### 基本用法速查 + +```bash +$ ping -c 4 example.com # 发 4 个包后自动停止 +$ ping -i 0.2 example.com # 每 0.2s 发一个(需 root) +$ ping -s 1472 example.com # 载荷 1472 bytes (接近 MTU 1500) +$ ping -t 64 example.com # 指定初始 TTL +$ ping -q example.com # 静默模式,仅显示统计摘要 +$ ping -W 2 example.com # 每个包的超时等待 2 秒 +``` + +### 解读输出指标 + +``` +PING example.com (93.184.216.34) 56(84) bytes of data. +64 bytes from 93.184.216.34: icmp_seq=1 ttl=56 time=14.2 ms +64 bytes from 93.184.216.34: icmp_seq=2 ttl=56 time=13.8 ms +--- example.com ping statistics --- +packets transmitted: 2 # 发送包数 +packet loss: 0% # 丢包率 +rtt min/avg/max/mdev = 13.8/14.0/14.2/0.2 ms # 往返时间统计 +``` + +| 指标 | 含义 | 合格阈值 | +|------|------|---------| +| packet loss | 丢包率 | 0% ~ 0.1%(局域网不应有丢包)| +| rtt avg | 平均 RTT | LAN < 1ms / 同城 < 20ms / 跨省 30-80ms / 跨洋 80-150ms | +| mdev | RTT 标准差 | < 5ms 正常;抖动大说明拥塞或不稳定 | + +> [!warning] ping 不通 ≠ 网络故障 +> +> 很多服务器禁用了 ICMP Echo Reply(防火墙策略),此时 ping 不通不代表服务不可用。需要结合 `telnet port` 或 `curl` 综合判断。 + +```bash +# Linux 内置的 ICMP 防御(防 DDoS flood) +$ sysctl net.ipv4.icmp_ratelimit=1000 # 每秒最多处理 1000 个 ICMP 包 +$ sysctl net.ipv4.icmp_ratemask=65535 # 允许的 ICMP 类型掩码 +$ sysctl net.ipv4.icmp_echo_ignore_all=1 # 完全忽略所有 ping(极端场景) +``` + +## telnet / nc —— 端口连通测试 + +### telnet —— 最原始的端口探测 + +```bash +# 纯测试端口是否开放 +$ telnet example.com 80 +Trying 93.184.216.34... +Connected to example.com. # ← Connected = 端口开放 ✅ +Escape character is '^]'. + +# 手动发 HTTP 请求测试 +GET / HTTP/1.1 +Host: example.com +User-Agent: Mozilla/5.0 +Accept: */* + +(blank line 回车) + +HTTP/1.1 200 OK # ← 收到响应 = 链路完整 ✅ +Content-Type: text/html +... +``` + +### nc (Netcat) —— 更强大的瑞士军刀 + +```bash +# 仅测试端口连通 (-z = zero-I/O 模式) +$ nc -zv example.com 80 +Connection to example.com port 80 [http] succeeded! # ✅ TCP 3次握手成功 + +$ nc -zv example.com 443 +Connection to example.com port 443 [https] succeeded! # ✅ + +$ nc -zv example.com 8443 +nc: connect to example.com port 8443 (tcp): Connection refused # ❌ + +# 发送数据并等待响应 +$ echo "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc -w 2 example.com 80 | head -5 + +# DNS 反向查询 +$ nc -dvl 0.0.0.0 12345 # 监听模式 +$ nc -u -z localhost 53 # UDP 端口测试 (DNS 常用) +``` + +## ss —— 连接状态快照(取代 netstat) + +### ss 基础用法 + +```bash +# 查看所有 TCP 连接(数字格式,不解析域名) +$ ss -tan +State Recv-Q Send-Q Local Address:Port Peer Address:Port Process +ESTAB 0 0 192.168.1.10:45678 93.184.216.34:443 users:(("chrome",pid=1234,fd=42)) +SYN-SENT 0 1 192.168.1.10:54321 10.0.0.1:8080 # 正在尝试握手 +LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678,fd=6)) +TIME-WAIT 0 0 192.168.1.10:443 93.184.216.34:52341 # 等待 2MSL + +# 查看特定端口的监听 +$ ss -tlnp sport = :80 +LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678,fd=6)) + +# 查看所有 TIME_WAIT 连接数(高并发服务器常见问题) +$ ss -tan state time-wait | wc -l +1247 + +# 查看某进程的所有连接 +$ ss -tnp | grep nginx | wc -l + +# 诊断 CLOSE_WAIT 堆积(⚠️ 代码 Bug!) +$ ss -tan state close-wait +CLOSE-WAIT 0 0 server:8080 client:54321 # ← 对端已关闭,本地未 close()! +``` + +### ss 状态速查表 + +| ss 状态 | 含义 | 严重程度 | +|---------|------|---------| +| ESTABLISHED | 正常通信中 | ✅ 正常 | +| LISTEN | 等待接受连接 | ✅ 正常 | +| SYN-SENT | 客户端发出的 SYN 未收到回复 | ⚠️ 检查对端是否存活 | +| SYN-RECV | 收到 SYN 但未完成三次握手 | ⚠️ 可能 SYN Flood | +| TIME-WAIT | 己方主动关闭后等待 2MSL | ⚠️ 可调 tcp_tw_reuse | +| CLOSE-WAIT | 对端关闭但己方未 close() | 🔴 **Bug!** 检查代码 | +| LAST-ACK | 等待最后一个 ACK | ⚠️ 短暂状态,持续则异常 | +| CLOSING | 同时关闭,互等对方 ACK | ⚠️ 罕见 | + +```bash +# 按状态过滤 +$ ss -tan state established # 仅已建立 +$ ss -tan state syn-sent # 仅半连接中 +$ ss -tan state closing # 仅 CLOSING + +# 按源/目标 IP 过滤 +$ ss -tn daddr 10.0.0.0/8 # 只看内网连接 +$ ss -tn src :80 # 只看来自 80 端的 +``` + +> [!tip] ss vs netstat +> `ss` 使用 netlink socket 替代 `/proc/net/tcp`,速度更快、信息更全。Linux 5.x+ 系统上 netstat 已被标记为 deprecated。 + +## traceroute —— 逐跳路径探测 + +### 三种实现方式 + +```bash +# UDP 模式(默认)—— 向高端口发 UDP 包递增 TTL +$ traceroute -n example.com + 1 192.168.1.1 0.5ms 0.3ms 0.4ms + 2 10.0.0.1 10.2ms 9.8ms 10.1ms +12 93.184.216.34 45.1ms 44.8ms 45.0ms + +# ICMP 模式(更可靠,不容易被防火墙拦截) +$ traceroute -I example.com + +# 现代替代:tracepath(不需要 root,自动 PMTUD) +$ tracepath example.com + 1: 192.168.1.1 0ms reached + 2: 10.0.0.1 10ms + ... +12: 93.184.216.34 45ms Ascent peak pmtu 1452 +``` + +### traceroute 原理 + +```mermaid +flowchart LR + P1["TTL=1 → 第1跳路由器
回 ICMP Time Exceeded"] -->|"RTT₁"| R1["第1跳: 192.168.1.1"] + P2["TTL=2 → 第2跳路由器
回 ICMP Time Exceeded"] -->|"RTT₂"| R2["第2跳: 10.0.0.1"] + P3["TTL=N → 到达目标
回 ICMP Port Unreachable"] -->|"RTTN"| Rn["目的地: 93.184.216.34"] + + style R1 fill:#DDA0DD,color:#000 + style R2 fill:#FFD700,color:#000 + style Rn fill:#98FB98,color:#000 +``` + +```bash +# 识别路由问题 +$ traceroute -n 8.8.8.8 + ... + 5 * * * # ← 这里丢了?→ 路由器禁了 ICMP + 6 * * * # ← 可能是运营商之间互联点 + 7 108.170.xxx.xxx 50ms 48ms 51ms # ← 回到 Google 边缘 + +# Windows 等价命令 +tracert 8.8.8.8 + +# macOS 等价命令 +traceroute 8.8.8.8 +``` + +## 排错黄金思路回顾 + +``` +问题: 无法访问服务 + ↓ +ping 通吗? → 否 → traceroute 定位断在哪一跳 + ↓ 是 +telnet port 通吗? → 否 → 检查防火墙/selinux → ss 看是否监听 + ↓ 是 +curl 正常吗? → 否 → 应用层问题 → 读日志 + ↓ 是 +浏览器缓存/Cookie/Header 问题 +``` + +## 关联笔记 + +- [[hhs/NETWORK/ICMP与Ping-Traceroute]] — ping/traceroute 的协议底层原理 +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — ss 中看到的状态都对应 TCP 状态机中的某个节点 +- [[hhs/NETWORK/DNS与DHCP与WebSocket]] — DNS 解析慢会影响 ping/curl 的表现 +- [[hhs/NETWORK/抓包HTTPDNS诊断工具]] — 进一步分析可用 tcpdump/curl/dig diff --git a/hhs/NETWORK/07-网络工具与诊断/02-抓包HTTPDNS诊断工具.md b/hhs/NETWORK/07-网络工具与诊断/02-抓包HTTPDNS诊断工具.md new file mode 100644 index 0000000..0e4d946 --- /dev/null +++ b/hhs/NETWORK/07-网络工具与诊断/02-抓包HTTPDNS诊断工具.md @@ -0,0 +1,249 @@ +--- +tags: [计算机网络, tcpdump, tshark, curl, dig, iptables, Wireshark] +create time: 2026-05-18 04:50 +--- + +# 抓包、HTTP 调试与 DNS 诊断工具 + +## 概述 + +在前一章完成了"通不通"的判断之后,本章深入"报文到了没"和"内容对不对"。涵盖 tcpdump 抓包分析、curl HTTP 调试、dig DNS 诊断以及 iptables 规则排查。 + +## tcpdump —— 命令行抓包神器 + +### 基础语法与 BPF 过滤器精选 + +```bash +sudo tcpdump -i eth0 # 抓取 eth0 所有流量 +sudo tcpdump -nn # -n: 不解析域名, -n: 不解析端口名 +sudo tcpdump -c 100 # 抓 100 个包后停止 +sudo tcpdump -w capture.pcap # 写入 pcap 文件(Wireshark 打开) +sudo tcpdump -A 'port 80' # ASCII 模式打印 HTTP 内容 +sudo tcpdump -X 'port 80' # HEX + ASCII 双列输出 +``` + +### BPF (Berkeley Packet Filter) 表达式 + +```bash +# 按 IP 过滤 +sudo tcpdump 'host 93.184.216.34' # 只看这个 IP + +# 按端口过滤 +sudo tcpdump 'port 80' # HTTP +sudo tcpdump 'port 443' # HTTPS +sudo tcpdump 'port range 8000-9000' # 端口范围 + +# 组合过滤 +sudo tcpdump 'src host 10.0.0.1 and dst port 443' # 源 → 目标端口 +sudo tcpdump '(src net 192.168.1.0/24) or (dst net 10.0.0.0/8)' # 多网段 + +# 匹配 TCP 标志位(非常强大!) +sudo tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' # SYN+ACK +sudo tcpdump 'tcp[tcpflags] & tcp-syn != 0' # SYN 包(新连接) +sudo tcpdump 'tcp[13] & 0x04 != 0' # 同样匹配 SYN +sudo tcpdump 'tcp[13] & 0x10 != 0' # ACK 标志位 +sudo tcpdump 'tcp[tcpflags] & tcp-rst != 0' # RST 包(异常断开) +sudo tcpdump 'tcp[tcpflags] & tcp-fin != 0' # FIN 包(正常关闭) + +# 排除法 +sudo tcpdump 'not host 192.168.1.1 and not port 22' # 排除网关和 SSH +``` + +### SYN 包实时观察 + +```bash +# 实时监控新连接建立 +sudo tcpdump -nn 'tcp[tcpflags] & tcp-syn != 0' +08:23:45.123456 IP 192.168.1.100:54321 > 10.0.0.1:8080: S 12345678:12345678(...) +08:23:45.123789 IP 10.0.0.1:8080 > 192.168.1.100:54321: S 87654321:87654321(...) +# ↑ 这就是三次握手的前两个包(SYN 和 SYN-ACK) + +# 监控 RST(异常断开) +sudo tcpdump -nn 'tcp[tcpflags] & (tcp-rst) != 0' +``` + +> [!tip] tcpdump → Wireshark 工作流 +> ```bash +> # 1. tcpdump 后台抓包 +> sudo tcpdump -w /tmp/app.pcap 'host 10.0.0.1 and port 8080' & +> # 2. 复现问题 +> # 3. Ctrl+C 停止抓包 +> # 4. Wireshark 打开分析 +> wireshark /tmp/app.pcap +> # 5. Wireshark 支持深度解码 HTTP/gRPC/TLS 等协议 +> ``` + +## tshark —— CLI 版 Wireshark + +```bash +# 实时过滤 HTTP 请求 +$ tshark -i eth0 -Y 'http.request' +No. Time Source Destination Protocol Length Info + 12 0.123456 10.0.0.1 93.184.xxx HTTP 450 GET /api/users HTTP/1.1 + +# 导出字段(CSV 风格) +tshark -r capture.pcap -Y 'http' \ + -T fields -e ip.src -e http.host -e http.request.uri + +# 只追踪 TLS 握手的 ClientHello +tshark -Y 'tls.handshake.type == 1' +# 1 = ClientHello, 11 = ServerHello, 12 = Certificate, etc. +``` + +## curl —— HTTP 调试利器 + +### 详细输出与计时分解 + +```bash +# -v: verbose 模式,看到完整的握手过程和头部交换 +curl -v https://example.com +* Trying 93.184.216.34:443... +* Connected to example.com (93.184.216.34) port 443 +* ALPN, offering h2 +* ALPN, offering http/1.1 +* TLSv1.3, TLS handshake, Client hello (1): +* TLSv1.3, TLS handshake, Server hello (2): +* TLSv1.3, Encrypted Extensions (8): +* TLSv1.3, Certificate (11): +* TLSv1.3, TLS handshake, Finished (5): +* TLSv1.3, Encrypted Change Cipher (1): +* ... SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 + +# -w: custom output 精确测量各阶段耗时 💡 +curl -w '\nDNS Resolve: %{time_namelookup}s\nTCP Connect: %{time_connect}s\nTLS Handshake: %{time_appconnect}s\nFirst Byte: %{time_starttransfer}s\nTotal: %{time_total}s\n' \ + -o /dev/null -s https://example.com + +# 输出示例: +# DNS Resolve: 0.01234s +# TCP Connect: 0.01567s +# TLS Handshake: 0.04567s +# First Byte: 0.15678s +# Total: 0.23456s +``` + +``` +耗时瓶颈分析: +├─ time_namelookup > 0.1s → DNS 慢 → 考虑 DNS 缓存 +├─ time_connect - time_namelookup > 0.1s → TCP 连接慢 → 距离远/NAT 问题 +├─ time_appconnect - time_connect > 0.2s → TLS 握手慢 → 证书链长/无 session resumption +└─ time_starttransfer - time_appconnect > 0.5s → 服务端处理慢 → 查应用日志 +``` + +### curl 实战用法 + +```bash +# 自定义 Header 和认证 +curl -H 'Authorization: Bearer token' \ + -H 'Content-Type: application/json' \ + -X POST -d '{"name":"alice"}' \ + https://api.example.com/users + +# 跟踪重定向(最多 5 层) +curl -L --max-redirs 5 https://short.link + +# 查看响应头(快速判断 CDN/缓存状态) +curl -I https://cdn.example.com/style.css +HTTP/2 200 +cache-control: public, max-age=86400 +age: 1234 +x-cache: HIT # ← CDN 命中 +cf-cache-status: HIT # ← Cloudflare 标记 +content-encoding: br # ← Brotli 压缩 + +# SSL 证书检查 +openssl s_client -connect example.com:443 -servername example.com +# 关注: +# Verify return code: 0 (ok) ✅ +# Verify return code: 20 (unable to get local issuer certificate) ❌ + +# JSON 美化输出 +curl https://jsonplaceholder.typicode.com/users/1 | python3 -m json.tool +``` + +## dig —— DNS 诊断最强工具 + +### 常用查询模式 + +```bash +# A 记录简洁输出 +$ dig example.com +short +93.184.216.34 + +# AAAA 记录(IPv6) +$ dig example.com AAAA +short +2606:2800:220:1:248:1893:25c8:1946 + +# 权威追踪(从根域名开始逐级查询) +$ dig example.com +trace +;; ->>HEADER<<- opcode: QUERY, status: NOERROR +;; QUESTION SECTION: ;example.com. IN A +;; ANSWER SECTION: example.com. 300 IN A 93.184.216.34 + +# 指定 DNS 服务器对比 +$ dig @8.8.8.8 example.com # Google DNS +$ dig @1.1.1.1 example.com # Cloudflare DNS +$ dig @local_dns_server example.com # 本地 DNS(可能被污染) + +# TXT 记录(SPF / DKIM 验证邮件配置) +$ dig example.com TXT +short +"v=spf1 include:_spf.google.com ~all" + +# MX 记录(邮件服务器) +$ dig example.com MX +short +10 mail.example.com. +20 mail2.example.com. + +# SRV 记录(服务发现,gRPC/Kafka 常用) +$ dig _grpc._tcp.service.consul SRV +short +0 100 8080 api.service.consul. + +# 查看 CNAME 链全貌 +$ dig www.example.com +multiline +``` + +### dig 高级用法 + +```bash +# 区域传输(AXFR,通常被禁止) +$ dig @ns1.example.com example.com axfr + +# EDNS Client Subnet(CDN 优化用户体验的关键) +$ dig +ecs=203.0.113.0/24 example.com @1.1.1.1 +# DNS resolver 把自己的子网发给 authoritative server +# → authoritative server 返回离用户最近的 CDN 节点 IP + +# DoH(DNS over HTTPS)检测 +$ curl https://dns.google/resolve?name=example.com&type=A +{"Status":0,"Answer":[{"name":"example.com","type":5,"data":"93.184.216.34",...}]} +``` + +## iptables / nftables 规则排查 + +```bash +# iptables 规则排查步骤 +sudo iptables -L -n -v # 列出 FILTER 表所有规则及计数器 +sudo iptables -t nat -L -n -v # 查看 NAT 表(SNAT/DNAT/MASQUERADE) +sudo iptables -t mangle -L -n -v # 查看 MANGLE 表(TOS/TTL 修改) + +# 关键排查命令 +sudo iptables-save | grep -A 5 INPUT # 只看 INPUT chain 的上下文 +sudo iptables -L FORWARD -n -v --line-numbers # 转发规则(容器/代理场景必查) + +# 常见规则导致的"假故障" +# ❌ 忘记放行容器的桥接流量 +iptables -I FORWARD -i docker0 -j ACCEPT + +# ❌ 没有启用 IP forwarding +sysctl net.ipv4.ip_forward=1 + +# nftables(iptables 的现代替代品) +sudo nft list ruleset # 列出所有 nft 规则 +sudo nft add rule inet filter input tcp dport 22 accept +``` + +## 关联笔记 + +- [[hhs/NETWORK/连通性与状态探测工具]] — 先看 ping/ss/telnet 再决定要不要抓包 +- [[hhs/NETWORK/ICMP与Ping-Traceroute]] — ping/traceroute 协议原理 +- [[hhs/NETWORK/DNS与DHCP与WebSocket]] — DNS 递归查询流程补充 dig 实操 +- [[hhs/NETWORK/TCP三次握手与四次挥手]] — tcpdump 中观察 SYN/ACK 交互细节 diff --git a/hhs/NETWORK/08-网络安全/01-DDoS与MITM防御.md b/hhs/NETWORK/08-网络安全/01-DDoS与MITM防御.md new file mode 100644 index 0000000..e7bd988 --- /dev/null +++ b/hhs/NETWORK/08-网络安全/01-DDoS与MITM防御.md @@ -0,0 +1,154 @@ +--- +tags: [计算机网络, DDoS, SYN Flood, HTTP Flood, MitM, ARP spoofing, DNS劫持] +create time: 2026-05-18 04:55 +--- + +# DDoS 与中间人攻击(MitM)防御 + +## 概述 + +网络攻击的防护是 Defense in Depth——每一层都有自己的攻击面和对应的缓解措施。本章覆盖最常见的 L3/L4 DDoS、MitM(ARP/DNS 欺骗)、以及 DNS 劫持的技术原理和 Linux/运维层面的防御手段。 + +## DDoS(分布式拒绝服务) + +### 各层攻击矩阵 + +```mermaid +flowchart TD + subgraph "L3/L4 流量型攻击" + S["SYN Flood
伪造源 IP 发大量 SYN"] + U["UDP Flood
海量 UDP 包淹没带宽"] + I["ICMP Flood
ping of death"] + A["Amplification
DNS/NTP amplification (>100x)"] + end + + subgraph "L7 应用层攻击" + H["HTTP Flood
模拟正常请求压垮应用"] + SL["Slowloris
半开连接占满线程池"] + RA["Resource Exhaustion
解析超大 JSON/图片"] + end + + style S fill:#FFD700,color:#000 + style H fill:#FF6B6B,color:#fff +``` + +### L3/L4 DDoS 防御 + +| 攻击类型 | 原理 | 防御手段 | +|---------|------|---------| +| **SYN Flood** | 伪造源 IP 发送大量 SYN,耗尽半连接队列 | SYN Cookie, rate limit, 增大 tcp_max_syn_backlog | +| **UDP Flood** | 海量 UDP 包吞没出口带宽 | upstream filtering, CDN 清洗 | +| **ICMP Flood** | ping 风暴 | firewall rule, icmp_ratelimit | +| **NTP Amplification** | 利用 open resolver 做放大反射 | BCP38 Source Guard | + +```bash +# Linux 内置防御命令 +$ sysctl net.ipv4.tcp_syncookies=1 # SYN Cookie: 内核自动处理 SYN Flood +$ sysctl net.ipv4.tcp_max_syn_backlog=8192 # 增大 SYN 半连接队列 +$ sysctl net.ipv4.icmp_echo_ignore_all=1 # 极端:完全忽略所有 ping(不推荐) +$ sysctl net.ipv4.icmp_ratelimit=1000 # 每秒最多处理 1000 个 ICMP 包 + +# iptables 限速规则 +sudo iptables -A INPUT -p tcp --syn -m limit --limit 100/s --limit-burst 200 -j ACCEPT +sudo iptables -A INPUT -p tcp --syn -j DROP # 超过阈值丢包 + +# fail2ban —— 基于日志的动态封禁 +# /etc/fail2ban/jail.local +[sshd] +enabled = true +maxretry = 3 +bantime = 3600 +findtime = 60 +``` + +### L7 DDoS 防御 + +``` +┌─────────────────────────────────────────────────┐ +│ L7 攻击无法在单台服务器上有效防御 │ +│ │ +│ ✅ CDN (Cloudflare/AWS CloudFront) 清洗 │ +│ ✅ WAF (Web Application Firewall) 拦截异常请求 │ +│ ✅ Rate Limiting (API 限流, Redis + Lua) │ +│ ✅ CAPTCHA 人机验证 │ +│ ✅ 连接超时 (Go http.Server IdleTimeout) │ +│ │ +│ ❌ 自己写代码挡不住百万 QPS 的 HTTP Flood │ +└─────────────────────────────────────────────────┘ +``` + +> [!tip] Slowloris 的原理与 Go 的免疫方式 +> +> Slowloris 发起成千上万的 HTTP 连接,每个只发送部分请求头并保持活着——永远不发完整的 `\r\n\r\n`。这会让服务器的连接池被占满。 +> +> **Go 天然免疫**:`http.Server.ReadHeaderTimeout` 会在超时后自动关闭未完成请求头的连接。这是 Go 高并发安全的一大优势。 + +## MITM(中间人攻击) + +### ARP 欺骗实现 MITM + +```mermaid +sequenceDiagram + participant C as 受害者主机
192.168.1.100 + participant A as 攻击者
192.168.1.200 + participant R as 真实网关
192.168.1.1 + + C->>R: ARP: "Who has 192.168.1.1?" + R-->>C: "I am 192.168.1.1, MAC=aa:bb:cc" + + A->>C: Gratuitous ARP: "192.168.1.1 is ME aa:dd:ee!" ⚡ + Note over C: C 更新了 ARP 缓存 → 把网关 MAC 指向攻击者! + + C->>A: 所有流量 → 攻击者的网卡 (二层交换) + A->>R: 转发流量到真实网关 (ip_forward=1) + R-->>A: 响应回传 + A-->>C: 解密/篡改后再转发给 C + + Note over C,A,R: C 和 R 都以为在和对方通信 🚨 +``` + +**ARP 欺骗的检测与防御:** + +```bash +# 检测 ARP Spoofing +arp -a # 查看本地 ARP 缓存表 +watch -n 1 'arp -n | grep 192.168.1' # 实时监控 ARP 条目变化 + +# 静态绑定(适用于服务器) +arp -s 192.168.1.1 aa:bb:cc:dd:ee:ff # 手动固定网关 MAC +# 写入 /etc/network/interfaces 持久化 + +# arpon/arpoison 工具检测 +arping -I eth0 -c 3 192.168.1.1 # 主动探测是否有重复响应 +``` + +### DNS 欺骗与污染 + +``` +正常流程: DNS 污染: +client → recursive DNS ─→ A record → 正确 IP client → malicious DNS ─→ wrong IP (跳转至攻击者) + ↑ ↑ + Cloudflare/Google DNS ISP/路由器被劫持 +``` + +```bash +# 检测 DNS 污染的对比方法 +$ dig example.com @8.8.8.8 # Google DNS —— 干净的参考结果 +$ dig example.com @1.1.1.1 # Cloudflare DNS —— 交叉验证 +$ dig example.com @local_dns # 本地 DNS —— 可能被污染 + +# 使用 DoH 防止 DNS 劫持 +$ curl https://dns.google/resolve?name=example.com&type=A \ + -H 'accept: application/dns-json' +{"Status":0,"Answer":[{"name":"example.com","type":5,...}]} + +# 使用 doh-proxy 或 cloudflared 为系统级别启用 DoH +sudo systemctl enable --now cloudflared-dns-proxy +``` + +## 关联笔记 + +- [[hhs/NETWORK/TLS安全实践]] — TLS/HSTS/OCSP Stapling 是 MitM 的核心防线 +- [[hhs/NETWORK/HTTPS与TLS握手]] — 证书链验证如何抵抗 MitM +- [[hhs/NETWORK/Web应用攻击面]] — L7 攻击面的补充(CSRF/XSS/XXE) +- [[hhs/NETWORK/IPv6地址与扩展头部]] — ND(Neighbor Discovery)替代 ARP,同样有安全风险 diff --git a/hhs/NETWORK/08-网络安全/02-SSRF与DNS安全.md b/hhs/NETWORK/08-网络安全/02-SSRF与DNS安全.md new file mode 100644 index 0000000..2314224 --- /dev/null +++ b/hhs/NETWORK/08-网络安全/02-SSRF与DNS安全.md @@ -0,0 +1,217 @@ +--- +tags: [计算机网络, SSRF, DNS安全, DNSSEC, DoH, NAT穿透] +create time: 2026-05-18 05:00 +--- + +# SSRF 与 DNS 安全 + +## 概述 + +SSRF(服务端请求伪造)是近年来增长最快的 Web 漏洞之一,它利用的是"服务器代替用户发起请求"的能力。DNS 层面则有从传统查询到 DoH/DoT 的安全演进。 + +## SSRF(Server-Side Request Forgery) + +### 什么是 SSRF? + +当应用程序接受一个 URL 作为输入,并在服务端向该 URL 发起请求时,攻击者可以构造特殊 URL 让服务器访问**内部资源**(内网 API、元数据服务、数据库端口等)。 + +### 典型攻击场景 + +```go +// ❌ 危险代码:用户控制 URL,服务器代替用户发起请求 +func fetchImageHandler(w http.ResponseWriter, r *http.Request) { + url := r.URL.Query().Get("url") // 用户可传入内网地址! + resp, err := http.Get(url) // GET http://169.254.169.254/latest/meta-data/ + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer resp.Body.Close() + io.Copy(w, resp.Body) + // AWS EC2 返回: access_key, secret_key, role... 💀 +} +``` + +### SSRF 常见探测目标 + +``` +目标 说明 危害 +────────────────────── ─────────────────────── ──────────────────── +169.254.169.254 AWS/Azure/GCP 元数据 获取云实例凭证 +10.0.0.x / 172.16.x.x 内网扫描 发现内网服务 +file:///etc/passwd 文件读取协议 读取服务器敏感文件 +gopher:// / dict:// Gopher/Dict 协议 利用回显放大攻击 +docker.sock Docker Socket 远程执行容器命令 +localhost:6379 Redis/Memcached 内网渗透跳板 +``` + +### 安全的 SSRF 防御方案 + +```go +package main + +import ( + "errors" + "net" + "net/http" + "net/url" + "time" +) + +var safeClient = &http.Client{ + Timeout: 10 * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return errors.New("no redirect") // 禁止重定向!攻击者可以用 302 绕校验 + }, +} + +// SafeFetch 安全地从一个 URL 获取内容 +func SafeFetch(urlStr string) ([]byte, error) { + parsed, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + // 第一步:解析域名,拿到 IP + ips, err := net.LookupIP(parsed.Host) + if err != nil { + return nil, err + } + + // 第二步:检查所有可能的 IP(IPv4 + IPv6) + for _, ip := range ips { + if isUnsafeIP(ip) { + return nil, errors.New("unsafe IP blocked: " + ip.String()) + } + } + + // 第三步:额外检查 Host 字段(防止 DNS rebinding 攻击) + if hostIP := net.ParseIP(parsed.Host); hostIP != nil { + if isUnsafeIP(hostIP) { + return nil, errors.New("host IP unsafe: " + hostIP.String()) + } + } + + resp, err := safeClient.Get(urlStr) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + return body, nil +} + +func isUnsafeIP(ip net.IP) bool { + if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || + ip.IsLinkLocalMulticast() || ip.IsUnspecified() { + return true + } + // 额外检查:云元数据地址段 + if ip.Equal(net.ParseIP("169.254.169.254")) { + return true + } + return false +} +``` + +```go +// Nginx 层面的 SSRF 防御(反向代理入口) +location /api/fetch { + # 禁止直接访问内网 + internal; # 只允许内部 rewrite 访问 + + proxy_pass $arg_url; + proxy_hide_header Access-Control-Allow-Origin; +} +``` + +> [!warning] DNS Rebinding 攻击 +> +> 即使你检查了初始 IP,攻击者仍可以将 DNS TTL 设为极小值(如 0 秒),让你的代码先校验合法 IP,然后在第二次请求时 DNS 已返回内网 IP。 +> **应对策略**:在整个请求生命周期中持续校验;使用白名单域名而非黑名单 IP;禁用重定向。 + +## DNS 安全 + +### DNSSEC —— 给 DNS 加上数字签名 + +``` +传统 DNS(明文无签名): DNSSEC(带签名验证): +Resolver → authoritative → answer Resolver → authoritative → answer + SIG + Resolver 用 DNSKEY 验证签名 ✅ + + 如果签名不对 → 返回 SERVFAIL ❌ +``` + +```bash +# 检查某域名的 DNSSEC 状态 +$ dig +dnssec example.com +;; flags: ad; AD 表示 Answer data verified by DNSSEC ✅ + +# 本地递归解析器配置(bind / unbound) +# /etc/bind/named.conf +dnssec-validation auto; # bind 自动下载根密钥 +``` + +### DoH / DoT / DoQ —— 加密 DNS 查询 + +| 协议 | 载体 | 端口 | 特点 | +|------|------|------|------| +| **DNS over TLS (DoT)** | TCP + TLS | 853 | RFC 7858, 传输层加密 | +| **DNS over HTTPS (DoH)** | HTTP/2 + TLS | 443 | RFC 8484, 伪装成 HTTPS 流量 | +| **DNS over QUIC (DoQ)** | QUIC | 853 | 最新标准, 低延迟 + 抗干扰 | + +```bash +# curl 测试 DoH +$ curl -v https://dns.google/resolve?name=example.com&type=A \ + -H 'accept: application/dns-json' +* TLS 1.3 connection using TLS_AES_256_GCM_SHA384 +* DNS query goes over encrypted HTTPS channel ✅ + +# unbound 配置 DoT 递归 +# /etc/unbound/unbound.conf.d/doth-forwarders.conf +forward-zone: + name: "." + forward-ssl-upstream: yes + forward-addr: 1.1.1.1@853 # Cloudflare DoT + forward-addr: 8.8.8.8@853 # Google DoT +``` + +### DHCP 安全注意事项 + +``` +DHCP DORA 四步本身是无认证的: +Discover → Offer → Request → Acknowledge + +风险: +• Rogue DHCP Server:恶意 DHCP 分配错误的网关/DNS +• DHCP Starvation:耗尽 DHCP 地址池 + +防御: +• Switch 端口安全:DHCP Snooping +• 802.1X 认证:未认证的端口不参与 DHCP +• 静态绑定:关键设备用 static IP + DHCP Reservation +``` + +## NAT 穿透技术补充 + +```mermaid +flowchart TD + STUN["STUN
穿越 NAT 发现自己对外暴露的地址/port"] --> ICE + TURN["TURN
中继服务器转发流量
兜底方案"] --> ICE + ICE["ICE 框架
按优先级尝试: direct → STUN → TURN"] --> SUCCESS["✅ 建立 P2P 连接"] + + style STUN fill:#98FB98,color:#000 + style TURN fill:#FFD700,color:#000 + style SUCCESS fill:#DDA0DD,color:#000 +``` + +- **STUN**:客户端向 STUN 服务器发请求,拿到自己的公网 IP:Port。但对称型 NAT 下 STUN 失效。 +- **TURN**:所有流量经中继服务器中转,兼容性最好但延迟最高。 +- **ICE**:WebRTC 的标准框架,同时收集候选地址并按连通性排序。 + +## 关联笔记 + +- [[hhs/NETWORK/NAT原理与应用]] — NAT 穿透技术(STUN/TURN/ICE)的详细原理 +- [[hhs/NETWORK/DNS与DHCP与WebSocket]] — DNS 递归查询与 DoH/DoT 的完整实现 +- [[hhs/NETWORK/04-寻址体系MAC-IPTPort]] — 私有 IP 地址范围用于 SSRF 校验判断 diff --git a/hhs/NETWORK/08-网络安全/03-CSRFxss与XXE防御.md b/hhs/NETWORK/08-网络安全/03-CSRFxss与XXE防御.md new file mode 100644 index 0000000..befead7 --- /dev/null +++ b/hhs/NETWORK/08-网络安全/03-CSRFxss与XXE防御.md @@ -0,0 +1,208 @@ +--- +tags: [计算机网络, CSRF, XSS, XXE, CORS, Web安全] +create time: 2026-05-18 05:05 +--- + +# Web 应用攻击面:CSRF / XSS / XXE + +## 概述 + +L7(应用层)攻击是黑客最常利用的层面。CSRF、XSS、XXE 三个名字相似但原理迥异,理解它们的区别和对应的防御手段是每个后端开发者的必修课。 + +## CSRF vs XSS vs XXE —— 三巨头对比 + +```mermaid +flowchart LR + subgraph "CSRF: 借用户的身份做操作" + direction TB + C1["用户已登录银行网站"] --> C2["访问恶意页面
包含 "] + C2 --> C3["浏览器自动带上 cookie → 转账成功"] + end + + subgraph "XSS: 在用户浏览器执行脚本" + direction TB + X1["网站注入恶意 JS"] --> X2["其他用户打开含攻击页面的链接"] + X2 --> X3["脚本在受害者上下文中执行 → 窃取 cookie"] + end + + style C1 fill:#DDA0DD,color:#000 + style X1 fill:#FFD700,color:#000 +``` + +### 详细对比表 + +| 特性 | CSRF | XSS (Reflected) | XSS (Stored) | XXE | +|------|------|-----------------|--------------|-----| +| **全名** | Cross-Site Request Forgery | Cross-Site Scripting | Stored/Persistent XSS | XML External Entity | +| **攻击目标** | 服务器操作 | 客户端浏览器 | 客户端浏览器 | 服务器文件系统/内网 | +| **核心漏洞** | 服务端没验证请求来源 | 输入未过滤直接输出到页面 | 持久化存储了恶意内容 | XML 解析器处理了外部实体 | +| **用户需操作** | 只需打开恶意页面 | 只需点击带payload链接 | 完全被动 | 无需用户参与 | +| **防御方式** | SameSite Cookie, CSRF Token, Referer 检查 | CSP, 输入编码, HTTPOnly Cookie | 同上 + 富文本 sanitizer | 禁用 DTD/外部实体 | +| **危害等级** | 高(能代用户操作) | 中高(能盗取会话) | 最高(持久传播) | 极高(RCE/文件读取) | +| **OWASP排名** | #9 | #3 | #3 | N/A | + +### CSRF 详解 + +``` +攻击流程: +───────────────────────────────────── +1. 用户在 bank.com 已登录(cookie 有效中) +2. 用户访问 evil.com +3. evil.com 包含: +
+ + + +
+4. 用户点击提交 → 浏览器自动携带 bank.com 的 cookie +5. bank.com 以为是合法操作 → 转账成功 💸 +``` + +```go +// ❌ 没有 CSRF 保护的 Go handler +func TransferHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + to := r.FormValue("to") + amount := r.FormValue("amount") + // ⚠️ 没有验证请求来源!任何网站都能伪造 POST 请求 + transferMoney(to, amount) + } +} + +// ✅ 方案一:CSRF Token +type CSRFStore struct { + tokens sync.Map // token → userID + expiry +} + +func GenerateCSRFToken(userID string) (string, error) { + tokenBytes := make([]byte, 32) + rand.Read(tokenBytes) + token := base64.URLEncoding.EncodeToString(tokenBytes) + + store.tokens.Store(token, userID) + go func() { + time.Sleep(24 * time.Hour) + store.tokens.Delete(token) // token 过期清理 + }() + return token, nil +} + +// ✅ 方案二:SameSite Cookie(现代浏览器原生支持) +http.SetCookie(w, &http.Cookie{ + Name: "session", + Value: sessionID, + Path: "/", + HttpOnly: true, // JS 无法读取 + SameSite: http.SameSiteStrictMode, // ← 关键!禁止跨站携带 +}) +``` + +```nginx +# Nginx 层面的 CSRF 防御 +location /api { + # 校验 Referer(可被浏览器插件或旧浏览器绕过,仅作第二道防线) + if ($http_referer !~* ^https://(www\.)?yourdomain\.com) { + return 403; + } + proxy_pass http://backend; +} +``` + +### XSS 详解 + +``` +反射型 XSS 流程: +─────────────────────── +1. 攻击者构造恶意 URL: + https://site.com/search?q= +2. 受害者点击该 URL +3. 服务器把 q 参数原样放回 HTML 响应中 +4. 浏览器执行脚本 → cookie 被盗 🚨 + +存储型 XSS 流程: +─────────────────────── +1. 攻击者在评论区发帖: +2. 帖子被存入数据库 +3. 所有浏览此页面的用户都会执行恶意脚本 +4. 比反射型 XSS 危害大 10 倍(一次注入,无限受害) +``` + +```go +// ✅ Go 的 html/template 自动转义,是最强的 XSS 防御 +import "html/template" + +tmpl, _ := template.New("search").Parse(` +

Search results for: {{.Query}}

+ +`) +// tmpl.Execute(w, data) → 输出中的特殊字符已被转义 +``` + +```bash +# CSP (Content Security Policy) 头 — XSS 的第二道防线 +# 即使 XSS payload 被执行了,CSP 也能阻止它加载外部资源 +HTTP/1.1 +Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.trusted.com; style-src 'self' 'unsafe-inline' +# default-src 'self' → 只允许同源资源 +# script-src 'self' ... → 禁止内联 script 和外部未知域 +``` + +### XXE 详解 + +```xml + + + + Alice + alice@example.com + + + + + +]> + + &xxe; + + + + + +``` + +```go +// ✅ Go 的 encoding/xml 默认禁用外部实体,相对安全 +// 但仍需注意 SAX parser 等第三方库的安全配置 + +// Java 中需要明确关闭 XXE +DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); +factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); +factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); +factory.setExpandEntityReferences(false); +``` + +## CORS vs CSRF —— 经常被混淆的两个概念 + +``` +CORS (Cross-Origin Resource Sharing): +────────────────────────────────────── +• 浏览器的安全策略,防止网页加载跨域资源 +• 由浏览器自动执行,服务器通过响应头配合 +• Access-Control-Allow-Origin: https://trusted.com + +CSRF (Cross-Site Request Forgery): +──────────────────────────────────── +• 攻击者利用已登录用户的凭证伪造请求 +• 与 CORS 无关!CORS 不防 CSRF +• 防御用 SameSite Cookie + CSRF Token +``` + +> [!important] 核心结论 +> CORS 不是 CSRF 的替代品。即使一个站点禁用了 CORS(`Access-Control-Allow-Origin: *`),依然会受到 CSRF 攻击。两者保护的是不同层面的安全问题。 + +## 关联笔记 + +- [[hhs/NETWORK/DDoS与MITM防御]] — L3/L4 攻击面的补充 +- [[hhs/NETWORK/JWT认证与授权安全]] — JWT 也是攻击重灾区 +- [[hhs/NETWORK/TLS安全实践]] — HSTS 是防范协议降级攻击的基础 diff --git a/hhs/NETWORK/08-网络安全/04-JWT认证与TLS安全.md b/hhs/NETWORK/08-网络安全/04-JWT认证与TLS安全.md new file mode 100644 index 0000000..44a7907 --- /dev/null +++ b/hhs/NETWORK/08-网络安全/04-JWT认证与TLS安全.md @@ -0,0 +1,319 @@ +--- +tags: [计算机网络, JWT, OAuth2, OIDC, mTLS, TLS安全, CipherSuite, OCSP] +create time: 2026-05-18 05:10 +--- + +# JWT 认证与 TLS 安全实践 + +## 概述 + +本章覆盖两个关键领域:Web API 最常用的认证机制 JWT 的安全性陷阱,以及 HTTPS/TLS 在生产环境中的正确配置方式。这两者是保护 API 安全的左右手。 + +## JWT 安全深度剖析 + +### JWT 结构回顾 + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. ← Header (base64url) +eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9. ← Payload (base64url) +SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature (HMACSHA256) +``` + +```json +// Header +{"alg":"HS256","typ":"JWT"} + +// Payload +{"sub":"1234567890","name":"John","iat":1516239022,"exp":1516242622} +``` + +### JWT 六大常见漏洞 + +| # | 漏洞 | 攻击手法 | 修复方案 | +|---|------|---------|---------| +| 1 | **alg: none** | 篡改 header 为 `{"alg":"none"}`, 签名变为空字符串即可通过验证 | 服务端严格白名单验签算法 | +| 2 | **无 exp 字段** | Token 永远有效,一旦泄露无法撤销 | 设置合理的 TTL(通常 ≤ 1h)+ refresh token | +| 3 | **弱密钥** | HS256 用短/简单密钥可暴力破解 | 使用 ≥ 32 字节的随机密钥,或改用 RS256/ES256 | +| 4 | **密钥复用** | 多个服务用同一个 secretKey | 每个服务独立密钥,或使用 JWK Set 动态获取公钥 | +| 5 | **时钟偏差** | 服务器时间不同步导致 exp 判断异常 | NTP 同步 + leeway 容忍窗口 | +| 6 | **敏感数据明文** | base64 ≠ 加密,payload 中放了密码/身份证 | payload 只能放非敏感声明 (standard claims) | + +### 安全的 JWT 实现 + +```go +import ( + "github.com/golang-jwt/jwt/v5" + "crypto/rand" + "time" +) + +// 生成足够强度的密钥(至少 32 bytes) +func generateSecretKey() []byte { + key := make([]byte, 32) + rand.Read(key) + return key +} + +// ✅ 安全的 JWT 签发 +const jwtExpiry = 15 * time.Minute // access token 有效期短 + +func GenerateJWT(userID string) (string, error) { + claims := jwt.MapClaims{ + "sub": userID, // subject: 用户 ID + "iat": time.Now().Unix(), // issued at + "exp": time.Now().Add(jwtExpiry).Unix(), // ⚠️ 必须有 exp! + "nbf": time.Now().Unix(), // not before (防止提前使用) + "jti": randomUUID(), // JWT ID: 唯一标识,用于黑名单撤销 + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(secretKey) +} + +// ✅ 安全的 JWT 验证 +func VerifyJWT(tokenStr string) (*jwt.Token, error) { + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + // 1. 强制校验算法(防止 alg: none 攻击) + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return secretKey, nil + }) + + if err != nil || !token.Valid { + return nil, fmt.Errorf("invalid token") + } + + // 2. 提取并检查标准声明 + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, fmt.Errorf("invalid claims format") + } + + // 3. 检查 jti 是否在黑名单中(手动撤销场景) + jti, _ := claims["jti"].(string) + if blacklist.Check(jti) { + return nil, fmt.Errorf("token revoked") + } + + return token, nil +} +``` + +### Refresh Token 模式 + +``` +access token: 有效期 15 分钟,用于每次 API 请求 +refresh token: 有效期 7 天,用于换取新的 access token +─────────────────────────────────────────────────── + +┌─────────────┐ ┌──────────────┐ +│ Client │ │ Auth Server │ +└──────┬──────┘ └──────┬───────┘ + │ │ + │ 1. Login → access + refresh │ + │─────────────────────────────────→│ + │ │ + │ 2. API request with access token │ + │─────────────────────────────────→│ + │ (15min later...) │ + │ │ + │ 3. access expired → 401 │ + │←─────────────────────────────────│ + │ │ + │ 4. exchange refresh for new │ + │─────────────────────────────────→│ + │ new access token │ + │←─────────────────────────────────│ + │ │ + │ 5. Continue with new access token│ + │─────────────────────────────────→│ +``` + +```go +// Go 中间件: 拦截 401 并自动刷新 +func authMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := extractBearerToken(r) + claims, err := VerifyJWT(token) + if err != nil { + // token 无效 → 尝试用 refresh token 换新 token + rt := extractRefreshToken(r) + newAccessToken, err := RefreshAccessToken(rt) + if err != nil { + http.Error(w, "unauthorized", 401) + return + } + r.Header.Set("Authorization", "Bearer "+newAccessToken) + } + next(w, r) + } +} +``` + +## OAuth 2.0 vs OpenID Connect (OIDC) + +| 协议 | 定位 | 典型场景 | 返回内容 | +|------|------|---------|---------| +| **OAuth 2.0** | Authorization Framework | 第三方代用户操作(微信登录、GitHub 授权) | access_token(有时有 refresh_token)| +| **OIDC** | Authentication Overlay on OAuth 2.0 | 身份验证(确认"你是谁") | id_token (JWT) + user info | + +```go +// OAuth 2.0 Authorization Code Flow 简化版 +func oauthLoginHandler(w http.ResponseWriter, r *http.Request) { + state := randomState() + url := fmt.Sprintf( + "https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s", + clientID, redirectURI, state, + ) + http.Redirect(w, r, url, http.StatusFound) +} + +func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) { + // 1. 验证 state 防 CSRF + // 2. code exchange → access_token + token, _ := client.Exchange(ctx, r.URL.Query().Get("code")) + // 3. use token to fetch user info + user, _ := client.UserInfos.Get(token) +} +``` + +## TLS 安全最佳实践 + +### TLS 版本与 Cipher Suite 选择 + +```go +import "crypto/tls" + +// ✅ Go 中生产环境 TLS 配置 +tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, // 最低 TLS 1.2(TLS 1.3 优先) + MaxVersion: tls.VersionTLS13, // 限制在 1.3(更安全、更快) + + // 首选 Elliptic Curve + CurvePreferences: []tls.CurveID{ + tls.X25519, // 首选 Ed25519 曲线(最快最安全) + tls.CurveP256, // NIST P-256(广泛兼容) + }, + + // ALPN 协商(告诉客户端用什么应用层协议) + NextProtos: []string{"h2", "http/1.1"}, + + // 服务器优先选择 cipher suite + PreferServerCipherSuites: true, + + // RC4 必须禁用(已知弱点) + // Go 1.15+ 已移除所有 RC4 cipher suite +} +``` + +### 推荐与禁用的 Cipher Suite 速查 + +``` +✅ 推荐使用 (TLS 1.3): +├── TLS_AES_256_GCM_SHA384 (首选) +├── TLS_CHACHA20_POLY1305_SHA256 (移动端友好) +└── TLS_AES_128_GCM_SHA256 (备选) + +⚠️ 勉强可用 (TLS 1.2): +├── ECDHE-RSA-AES256-GCM-SHA384 +├── ECDHE-RSA-CHACHA20-POLY1305 +└── ECDHE-ECDSA-... (如果用的是 EC 证书) + +❌ 必须禁用: +├── RSA 密钥交换 (无前向保密 PFS) +├── CBC 模式 (BEAST/Sweet32 攻击) +├── SHA-1 哈希 (碰撞攻击) +├── RC4 (严重泄漏漏洞) +├── 3DES (Sweet32) +└── 出口级加密 (export ciphers) +``` + +### OCSP Stapling —— 证书吊销检查的优化 + +``` +传统 OCSP (慢 + 隐私泄露): OCSP Stapling (快 + 隐私保护): +Browser → CA: Is cert valid? Server → CA: Fetch status + sign +CA → Browser: Valid/Invalid Server caches stapled response +Server → Browser: cert + stale Server sends: cert + signed assertion + checking happens in browser Browser validates CA-signed assertion + directly from server + +问题: 优势: +• 每次都要连 CA • 无需浏览器直连 CA +• 暴露浏览习惯给 CA • 速度快 (缓存有效期内无需额外查询) +• 有些 CA 响应慢 • 隐私好 (服务器知道你在访问谁) +``` + +```nginx +# Nginx 启用 OCSP Stapling +ssl_stapling on; +ssl_stapling_verify on; +resolver 8.8.8.8 8.8.4.4 valid=300s; +resolver_timeout 5s; + +# 确保完整的证书链(包括 intermediate CA) +ssl_trusted_certificate /etc/ssl/certs/fullchain.pem; +``` + +### HSTS —— 强制 HTTPS + +```bash +# 告诉浏览器"此后只能用 HTTPS" +$ curl -I https://example.com +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload + +# 参数解读: +# max-age=31536000 → 一年内所有请求自动转 HTTPS(60 天起步,建议一年) +# includeSubDomains → 子域也适用 +# preload → 提交到浏览器预加载列表 +``` + +```bash +# 提交到 HSTS Preload List +$ curl https://hstspreload.org/api/v2/domain/example.com +# 检查状态: pending / preloaded / opt-out + +# Chrome/HSTS Preload 内置列表 ≈ 50000+ 域名 +# 一旦被 preload,即使第一次访问也不会发 HTTP 请求! +``` + +## mTLS(双向 TLS 认证) + +```mermaid +sequenceDiagram + participant C as Client
(持客户端证书) + participant S as Server
(持服务端证书) + + C->>S: ClientHello + Client Certificate + S->>C: ServerHello + Server Certificate + Note over S,C: 双方互相验证书! + S->>S: 验证客户端证书 CN/ SAN + C->>C: 验证服务端证书 (常规) + + alt 双方证书都有效 + Note over S,C: ✅ 建立 mTLS 连接 + else 客户端证书无效/过期 + S-->>C: Alert: bad_certificate 🔴 + end +``` + +```bash +# 生成客户端证书 +openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=user1" +openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ + -out client.crt -days 365 -extfile client.ext + +# Nginx 启用 mTLS +ssl_certificate /etc/ssl/server.crt; +ssl_certificate_key /etc/ssl/server.key; +ssl_client_certificate /etc/ssl/ca.crt; # CA 根证书 +ssl_verify_client on; # on / optional / off +``` + +## 关联笔记 + +- [[hhs/NETWORK/TLS安全实践]] — OCSP Stapling / HSTS / Cipher Suite 的详细展开 +- [[hhs/NETWORK/DdoS与MITM防御]] — DDoS MitM 的互补安全知识 +- [[hhs/NETWORK/Web应用攻击面]] — CSRF/XSS/XXE 的 Web 应用防护 +- [[hhs/OAuth2/01-OAuth2基础]] — OAuth 2.0 的完整流程详解(如已创建) diff --git a/hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用.md b/hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用.md new file mode 100644 index 0000000..5a2a8e0 --- /dev/null +++ b/hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用.md @@ -0,0 +1,198 @@ +--- +tags: [计算机网络, TCP调优, sysctl, BBR, TIME_WAIT, KeepAlive, 连接复用] +create time: 2026-05-18 05:15 +--- + +# TCP 内核调优与连接复用 + +## 概述 + +网络性能优化的核心公式是:**用户体验 = ∑(RTT_i × 系数_i) + 数据量 / 实际吞吐量**。本章从操作系统内核参数入手,到传输层之上的 HTTP/gRPC keep-alive,逐层展开优化手段。 + +## TCP 内核参数全面调优 + +### 参数速查表(按优先级排序) + +```bash +# ⭐ 第一优先级 — 这些不改,高并发必瓶颈 +$ sysctl net.core.somaxconn # socket 监听 backlog 上限 (默认 128 → 改为 65535!) +$ sysctl net.ipv4.tcp_max_syn_backlog # SYN 半连接队列大小 (默认 ~1024 → 改为 8192) +$ sysctl net.ipv4.tcp_congestion_control # 拥塞控制算法 (默认 cubic → bbr) + +# ⭐ 第二优先级 — 缓冲区大小决定高带宽延迟积场景的吞吐量 +$ sysctl net.core.rmem_max # max recv buffer (默认 212992 → 改为 16MB) +$ sysctl net.core.wmem_max # max send buffer (默认 212992 → 改为 16MB) +$ sysctl net.ipv4.tcp_rmem # auto/min/max recv buffer (默认 4096 87380 6291456) +$ sysctl net.ipv4.tcp_wmem # auto/min/max send buffer + +# ⭐ 第三优先级 — TIME_WAIT 管理 +$ sysctl net.ipv4.tcp_tw_reuse # 复用 TIME_WAIT 套接字做 outbound (默认 0 → 改为 1) +$ ⚠️ tcp_tw_recycle 已从 Linux 4.12 永久移除!不要尝试启用 +``` + +```bash +# 第四优先级 — 高级功能 +$ sysctl net.ipv4.tcp_fastopen # TCP Fast Open (0=off, 3=enable both) +$ sysctl net.ipv4.tcp_timestamps # 时间戳选项 (SACK/BBR 需要, 默认 1) +$ sysctl net.ipv4.tcp_sack # Selective ACK (默认 1, 别关) +$ sysctl net.ipv4.tcp_fack # Forward ACK (配合 SACK) +$ sysctl net.ipv4.tcp_limit_output_bytes # TCP 输出队列限制 +``` + +```bash +# 第五优先级 — Keepalive 调优 +$ sysctl net.ipv4.tcp_keepalive_time # 首次探测前的空闲时间 (默认 7200s → 改为 600s) +$ sysctl net.ipv4.tcp_keepalive_intvl # 两次探测间隔 (默认 75s → 改为 30s) +$ sysctl net.ipv4.tcp_keepalive_probes # 最多探测次数 (默认 9 → 改为 3) +# 总断开时间 = tcp_keepalive_time + (tcp_keepalive_intvl × tcp_keepalive_probes) +# 原来: 7200 + (75 × 9) = 7875s ≈ 2.2h +# 新值: 600 + (30 × 3) = 690s ≈ 11.5min + +# 端口范围(连接池场景下扩大临时端口) +$ sysctl net.ipv4.ip_local_port_range # 默认 32768-60999 → 改为 1024-65535 +``` + +### /etc/sysctl.conf 生产级持久化配置 + +```ini +# ========================================== +# Web/API 服务器 TCP 调优配置 +# ========================================== + +# === 连接队列 === +net.core.somaxconn = 65535 +net.ipv4.tcp_max_syn_backlog = 8192 +net.ipv4.tcp_abort_on_overflow = 0 + +# === 缓冲区 (BDP 适配: Buffer = Bandwidth × RTT) === +net.core.rmem_max = 16777216 +net.core.wmem_max = 16777216 +net.core.rmem_default = 16777216 +net.core.wmem_default = 16777216 +net.ipv4.tcp_rmem = 4096 87380 16777216 +net.ipv4.tcp_wmem = 4096 65536 16777216 + +# === 拥塞控制算法 === +net.ipv4.tcp_congestion_control = bbr +net.ipv4.tcp_mux = 1 # BBR 推荐开启 multiplex + +# === TIME_WAIT === +net.ipv4.tcp_tw_reuse = 1 + +# === Keepalive === +net.ipv4.tcp_keepalive_time = 600 +net.ipv4.tcp_keepalive_intvl = 30 +net.ipv4.tcp_keepalive_probes = 3 + +# === 端口范围 === +net.ipv4.ip_local_port_range = 1024 65535 + +# === 高级特性 === +net.ipv4.tcp_fastopen = 3 +net.ipv4.tcp_timestamps = 1 +net.ipv4.tcp_sack = 1 +``` + +```bash +# 生效配置 +$ sudo sysctl -p +$ # 验证拥塞控制算法 +$ cat /proc/net/sockstat +$ ss -i # 查看当前连接的拥塞控制算法 +``` + +## Keep-Alive 与连接复用全栈方案 + +### 为什么 connection reuse 这么重要? + +``` +一次全新 TCP 连接的开销: +───────────────────────────── +TCP 三次握手 = 1 RTT (TCP Fast Open 可 0-RTT) +TLS 握手 TLS1.3 = 1 RTT (Session Resumption 可 0-RTT) +HTTP Request = 1 RTT +───────────────────────────── +总计 = 2~3 RTT + +如果复用连接: +───────────────────────────── +已有连接上新请求 = 0 RTT (零额外等待) +─────────────────────────────────── +每次请求节省 2~3 RTT! +对于跨洋链路 (RTT ≈ 150ms), 每次请求省 450ms! +``` + +### HTTP Keep-Alive —— Go 客户端侧 + +```go +// Go http.Transport 的连接池配置 +transport := &http.Transport{ + MaxIdleConns: 100, // 全局最大空闲连接数 + MaxIdleConnsPerHost: 10, // 每个 host 最大空闲连接数 + IdleConnTimeout: 90 * time.Second, // 空闲超此时间关闭 + + // TLS 优化: Session Ticket 实现 TLS 1.3 0-RTT resumption + TLSClientConfig: &tls.Config{ + SessionTicketsDisabled: false, // 默认开启 + }, + + // 禁用压缩 header(避免 CRIME/BREACH 攻击) + DisableCompression: true, +} + +client := &http.Client{Transport: transport} +// 后续的 client.Get() 自动复用已建立的 TCP + TLS 连接 +``` + +### HTTP Keep-Alive —— Nginx 反向代理侧 + +```nginx +upstream backend { + server app1:8080; + server app2:8080; + keepalive 32; # 保持 32 个空闲连接到后端 + keepalive_requests 10000; # 单个连接最多服务 10000 个请求 + keepalive_timeout 60s; # 空闲超时 +} + +server { + location /api { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; # 清空 Connection: close + + # 关键: Nginx 默认使用 HTTP/1.0 向后端转发,不带 keepalive + # 必须显式设置 proxy_http_version 1.1 + 清空 Connection header + } +} +``` + +### gRPC KeepAlive —— 双向心跳 + +```go +import "google.golang.org/grpc/keepalive" + +grpc.NewServer( + // 服务端参数 + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: 10 * time.Second, // 每 10s 发 ping + Timeout: 5 * time.Second, // 等 5s 回复 pong + MaximumConnectionIdle: 5 * time.Minute, // 最大空闲连接存活时间 + }), + // 客户端策略强制执行 + grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + MinTime: 5 * time.Second, // 客户端至少每 5s 发一次 ping + PermitWithoutStream: true, // 即使没有活跃流也允许 ping + }), +) +``` + +> [!question] gRPC 为什么需要独立于 HTTP/2 的 KeepAlive? +> HTTP/2 本身有 PING frame,但 gRPC 在应用层额外加了一层——这是因为中间可能有 L7 load balancer(如 Envoy),它会主动关闭长连接。gRPC 的 KeepAlive 可以让负载均衡器认为连接仍然活跃。 + +## 关联笔记 + +- [[hhs/NETWORK/TCP流量控制与拥塞控制]] — BBR vs Cubic 算法原理 +- [[hhs/NETWORK/TCP段结构与状态机]] — TIME_WAIT / CLOSE_WAIT 分析 +- [[hhs/NETWORK/CDN与负载均衡]] — CDN 缓存策略与 Load Balancer 选型 +- [[hhs/NETWORK/GoServer终极调优]] — Go http.Server 超时配置的深入讲解 diff --git a/hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优.md b/hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优.md new file mode 100644 index 0000000..0f3fbcb --- /dev/null +++ b/hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优.md @@ -0,0 +1,261 @@ +--- +tags: [计算机网络, CDN, CacheControl, LoadBalancer, L4, L7, Nginx, Go http.Server] +create time: 2026-05-18 05:20 +--- + +# CDN、负载均衡与 Go Server 调优 + +## 概述 + +在内核层面做好调优之后,架构层面的优化才是真正拉开差距的地方。本章覆盖 CDN 缓存策略、L4/L7 负载均衡选型,以及 Go http.Server 的极致调优实践。 + +## CDN 缓存策略 + +### CDN 缓存命中流程 + +```mermaid +sequenceDiagram + participant U as 用户浏览器 + participant CDN as CDN Edge Cache + participant Origin as 源站服务器 + + Note over U,Origin: Case 1: Cache HIT ✅ + U->>CDN: GET /static/logo.png?v=2 + Note over CDN: Cache HIT! Age: 3600 + CDN-->>U: 200 OK (Age: 3600, X-Cache: HIT) + + Note over U,Origin: Case 2: Cache MISS ❌ → 回源 + U->>CDN: GET /api/user/profile + Note over CDN: Cache MISS (no-cache) + CDN->>Origin: GET /api/user/profile + Origin-->>CDN: 200 + body + Cache-Control + CDN-->>U: 200 OK (X-Cache: MISS) + + Note over CDN: Set TTL for next request +``` + +### Cache-Control 指令速查表 + +| 指令 | 含义 | CDN 行为 | 适用场景 | +|------|------|---------|---------| +| `Cache-Control: no-cache` | 每次向源站验证 | 返回 ETag/If-Modified-Since | API 响应(需要新鲜数据但不想每次都全量回源)| +| `Cache-Control: no-store` | 不缓存任何内容 | 永远回源 | 敏感数据、个人信息接口 | +| `Cache-Control: max-age=3600` | 缓存 1 小时 | 命中期间不回源 | 静态资源、首页 HTML | +| `Cache-Control: immutable` | 资源永不改变(版本化 URL)| 浏览器永久缓存 | CSS/JS/图片(带 hash 文件名)| +| `Cache-Control: public` | 任何节点都可以缓存 | 边缘节点缓存 | 公开静态资源 | +| `Vary: Accept-Encoding` | 按编码方式分别缓存 | gzip/webp/原文件分开存 | 支持多种压缩方式的资源 | + +```bash +# curl 实战:检查 CDN 缓存状态 +$ curl -I https://cdn.example.com/style.css +HTTP/2 200 +cache-control: public, max-age=86400 +age: 1234 # ← 被 CDN 缓存了 1234 秒 +x-cache: HIT # ← Cloudflare 命中标记 +cf-cache-status: HIT # ← Cloudflare 自己的标记 +content-encoding: br # ← Brotli 压缩版 +etag: "abc123" # ← 可用于 If-None-Match 验证 + +$ curl -I https://cdn.example.com/api/data +HTTP/2 200 +cache-control: no-cache +x-cache: MISS # ← 未命中,回源了 +``` + +### CDN 刷新策略 + +``` +被动失效(自然过期): 由 max-age + 到达时间自动控制 +主动刷新 (Purge): API/cURL 立即清除特定 URL 缓存 +批量刷新: CDN 提供 API 清除整个目录前缀 + +# Cloudflare Purge API +curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache" \ + -H "Authorization: Bearer ${CF_TOKEN}" \ + -H "Content-Type: application/json" \ + --data '{"files":["https://cdn.example.com/js/app.a1b2c3.js"]}' + +# 最佳实践: 带版本号的 URL → 发布新版本不需要 purge +# 旧: /js/app.js → 更新需 purge +# 新: /js/app.a1b2c3.js → 直接新增 URL,旧 URL 仍命中旧缓存 +``` + +## Load Balancer —— L4 vs L7 深度对比 + +### 选型决策树 + +```mermaid +flowchart TD + Q{"你的协议是什么?"} + + Q -->|"TCP/UDP (非HTTP)"|"选择 L4 LB" + Q -->|"HTTP/gRPC/WebSocket"|"选择 L7 LB" + + L4["L4 负载均衡
基于 IP+Port 转发
⚡ 更快, 更轻量"] --> Examples["典型产品:
IPVS, HAProxy(L4), AWS NLB,
iptables/ipset"] + + L7["L7 负载均衡
基于 URL/Header/Cookie 路由
🧠 更智能, 功能丰富"] --> Examples2["典型产品:
Nginx, Envoy, Traefik,
AWS ALB, Kong"] + + style L4 fill:#DDA0DD,color:#000 + style L7 fill:#FFD700,color:#000 +``` + +### L4 vs L7 特性对照表 + +| 维度 | L4 (传输层) | L7 (应用层) | +|------|-----------|------------| +| OSI 层级 | TCP/UDP | HTTP/gRPC/WebSocket | +| 决策依据 | IP + Port | URL Path / Header / Cookie | +| TLS 卸载 | ✅(需配证书) | ✅(更常用,且支持 SNI) | +| 路由策略 | 轮询/加权/最少连接 | URI-based, header-match, regex | +| 健康检查 | TCP connect / UDP echo | HTTP GET, gRPC health check | +| 重试/熔断 | ❌ | ✅ (Envoy/Istio) | +| 可观测性 | 连接数/流量/错误率 | 请求/响应头/Body/Delay/Jitter | +| 适用场景 | 数据库代理、Redis, UDP 流量 | Web API, WebSocket, HTTP microservices | + +### Nginx upstream 策略详解 + +```nginx +# 定义 upstream 池 +upstream backend { + least_conn; # 最少连接优先(适合不等长请求) + # ip_hash; # 基于源 IP 哈希(会话保持) + # random two; # 随机选两个中连接少的 + + server app1:8080 weight=3; # 权重 3:1 + server app2:8080; + server app3:8080 backup; # 备份服务器(仅当主节点全挂时才用) + + keepalive 32; # 保持 32 个空闲连接到后端 + keepalive_requests 10000; + keepalive_timeout 60s; + + # 健康检查(NGINX Plus 专有, OSS 版可用 ngx_http_upstream_check_module) + # max_fails 3 fail_timeout 30s; # 连续 3 次失败,停 30s +} + +server { + listen 443 ssl http2; + server_name api.example.com; + + location /api/v1 { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + + # 超时配置 + proxy_connect_timeout 5s; # 连接后端超时 + proxy_send_timeout 10s; # 写入后端超时 + proxy_read_timeout 30s; # 读取后端超时 + + # 缓冲优化 + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 16k; + } +} +``` + +## Go http.Server 终极调优 + +### 服务端完整配置 + +```go +srv := &http.Server{ + Addr: ":443", + Handler: myHandler, + + // === 超时设置(防慢连接攻击 Slowloris)=== + ReadTimeout: 10 * time.Second, // 读完整请求体(含 Body)的时间 + ReadHeaderTimeout: 5 * time.Second, // ⭐ 单独控制头部读取超时 + WriteTimeout: 30 * time.Second, // 写出响应的总时间 + IdleTimeout: 120 * time.Second, // Keep-Alive 空闲多久断开 + + // === 安全边界 === + MaxHeaderBytes: 1 << 20, // 1MB 最大 Header + // TLSConfig 继承自 crypto/tls 最佳实践 + + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + PreferServerCipherSuites: true, + }, +} + +// 优雅停机 +ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +defer cancel() +srv.Shutdown(ctx) // 停止接受新连接,等待已有连接处理完 +``` + +### 出站请求 Transport 调优 + +```go +// 为 http.Client 配置高性能 Transport +outboundTransport := &http.Transport{ + // 连接池大小 + MaxIdleConns: 200, + MaxIdleConnsPerHost: 50, // Go 默认值是 2,这是最大的坑之一! + IdleConnTimeout: 90 * time.Second, + + // 超时控制 + TLSHandshakeTimeout: 10 * time.Second, + ResponseHeaderTimeout: 30 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + + // 是否复用 + DisableKeepAlives: false, // 必须开启! + DisableCompression: true, // 自行压缩更安全(防 BREACH 攻击) + + // DialContext 自定义(可选:带 DNS 预解析的 dialer) + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, +} + +outboundClient := &http.Client{ + Transport: outboundTransport, + Timeout: 45 * time.Second, // 顶层总超时 +} +``` + +```go +// 🐛 经典 Bug: MaxIdleConnsPerHost 默认只有 2! +// 这意味着你访问同一个后端时,最多只有 2 个连接可以复用 +// 高并发场景下会频繁创建新连接,浪费 TIME_WAIT +// +// 修复: 显式设置为合理的值(根据 QPS 和连接池大小反推) +transport := &http.Transport{ + MaxIdleConnsPerHost: 50, // ← 这行不能省! +} +``` + +### 优雅停机模式 + +```go +func gracefulShutdown(srv *http.Server, sig os.Signal) { + // 等待 SIGINT/SIGTERM + <-sig + + log.Println("shutting down...") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + log.Printf("server forced to shutdown: %v", err) + } + + log.Println("server exited") +} + +// systemd 或 k8s 都会发送 SIGTERM +// gracefulShutdown 确保正在处理的请求完成后再退出 +``` + +## 关联笔记 + +- [[hhs/NETWORK/TCP内核调优与连接复用]] — 内核参数与 keep-alive 的全栈配置 +- [[hhs/NETWORK/HTTP-1.1完全指南]] — HTTP 协议的细节基础 +- [[hhs/NETWORK/HTTP/2多路复用]] — HTTP/2 对负载均衡的影响 +- [[hhs/NETWORK/TCP段结构与状态机]] — 分析 TIME_WAIT / CLOSE_WAIT 问题 diff --git a/hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析.md b/hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析.md new file mode 100644 index 0000000..e5b1dee --- /dev/null +++ b/hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析.md @@ -0,0 +1,185 @@ +--- +tags: [计算机网络, QUIC, HTTP/3, ConnectionID, 0-RTT, QPACK, NAT迁移] +create time: 2026-05-18 05:25 +--- + +# QUIC 协议深度解析 + +## 概述 + +QUIC (Quick UDP Internet Connections) 正在静默地替代 TCP——Google、Cloudflare、Microsoft 的流量中已有超过 40% 走的是 QUIC。理解 QUIC 的设计哲学和具体机制,是把握下一代网络协议的钥匙。 + +## 为什么需要 QUIC? + +```mermaid +flowchart TD + Problem["TCP 的三大历史包袱"] --> P1["• 拥塞控制算法写死在内核
无法按需调整"] + Problem --> P2["• IP/port 变化 → 四元组变化
连接必须重建"] + Problem --> P3["• TLS in TCP → 双重握手延迟
三次握手 + TLS 握手 = 最少 2 RTT"] + + Solution["QUIC 的三个解答"] --> S1["✅ 用户态实现 BBRv2 等算法
应用层可编程、随时升级"] + Solution --> S2["✅ Connection ID 独立于 IP/port
WiFi↔5G 无缝切换"] + Solution --> S3["✅ 内置 TLS 1.3 + 0-RTT
首次握手即加密, 续连零延迟"] + + style Problem fill:#FFD700,color:#000 + style Solution fill:#98FB98,color:#000 +``` + +### TCP vs QUIC 核心对比 + +| 维度 | TCP + TLS | QUIC + TLS 1.3 | +|------|-----------|---------------| +| 传输协议 | TCP (可靠,面向字节流) | UDP (不可靠,自带可靠性层) | +| 拥塞控制 | 内核固定 (cubic/bbr) | 用户态可插拔 (BBRv2, NewReno...) | +| 连接标识 | 四元组 (src_ip, src_port, dst_ip, dst_port) | Connection ID (16 bytes, 自定义) | +| 握手延迟 | 1 RTT (TCP) + 1 RTT (TLS 1.3) = 2 RTT | 1 RTT 完成 TCP+TLS (0-RTT 可选) | +| 多路复用 | 应用层自己搞 (HTTP/2) | 原生支持多 Stream,零队头阻塞 | +| NAT 穿透 | 断开重连 | CID 不变,自动迁移 | +| 头部压缩 | HPACK (HTTP/2) / QPACK (HTTP/3) | QPACK(解决队头阻塞) | + +## QUIC 的连接管理 + +### Connection ID —— NAT 迁移的关键 + +``` +TCP 连接标识 = (src_ip, src_port, dst_ip, dst_port) +→ IP 变了 → 四元组变 → 连接断了 ❌ + +QUIC 连接标识 = Connection ID (16 bytes,由端方自定义) +→ IP/port 变了 → CID 不变 → 连接继续 ✅ + +典型场景: iPhone 从 WiFi 切换到 5G +旧 IP: fe80::abcd → 新 IP: fe80::efgh +TCP: 连接断开,需重连 (RTT × 2) +QUIC: CID 不变,数据包自动迁移到新 IP +``` + +``` +Connection ID 的生命周期: +───────────────────────── +Client → Server: Initial Packet {CID: client_cid} +Server → Client: Initial Packet {CID: server_cid} +Server → Client: Handshake Packet {CID: server_cid} +Client → Server: Handshake Packet {CID: client_cid} +Client → Server: 1-RTT Packet {CID: client_cid} ← 加密载荷, 业务数据 +``` + +> [!tip] 为什么 QUIC 用 UDP 而不是直接改 TCP? +> +> 修改 TCP 需要在操作系统内核层面做全球部署,这几乎是不可能的(每个发行版都有不同)。而把可靠性搬到用户态用 UDP 承载——虽然看起来是"倒退"——但部署极其灵活:SDK 更新即可生效。Google 的 QUIC 实现就是作为一个 Chrome 扩展库无声更新的。 + +### QUIC Stream 独立性 —— 消除队头阻塞 + +``` +QUIC Stream 独立性: +┌──────────────┐ +│ Stream 0 │ ← 控制通道 (HTTP/3 信令) +│ Stream 1 │ ← Request A [✓ Data ✓ ACK] ✅ 正常 +│ Stream 2 │ ← Request B [✓ Data ✓ ACK] ✅ 正常 +│ Stream 3 │ ← Request C [✗ Lost 🔄 仅重传此流!] +│ Stream 4 │ ← Response A [✓ Data ✓ ACK] ←不受 Stream 3 影响! +│ Stream 5 │ ← Request D [🔄 正在发送...] +└──────────────┘ + +对比 HTTP/2 (共用 TCP 流): +请求 C 丢包 → 所有后续请求(包括 D、响应A)都要等 C 重传完成! +这就是 HTTP/2 在弱网下不如 HTTP/3 的原因。 +``` + +## 0-RTT 握手与重放攻击 + +### 0-RTT 的工作原理 + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + + Note over C,S: 首次连接: 1-RTT handshake + C->>S: ClientHello + HelloRetryRequest + Finished + S-->>C: 1-RTT Ready + + Note over C,S: Session 缓存 (Session Ticket) + C->>S: ClientHello + 0-RTT data + Finished + Note over S: 验证 Session Ticket + 重放检测 + alt 允许 0-RTT + S-->>C: Early data accepted ✅ + else 拒绝 0-RTT + S-->>C: 0-RTT rejected ⚠️ (但仍处理后续数据) + end +``` + +``` +⚠️ 0-RTT 的重放攻击风险: +───────────────────────── +攻击者截获了客户端的 0-RTT 请求 (如 POST /transfer?to=hacker&amount=10000) +然后把这个请求重新发给服务器 +服务器认为是新的合法请求 → 重复转账 💸 + +防御措施: +1. 服务端对 0-RTT 请求标记 "early data" +2. 幂等操作 (GET/HEAD/PUT 删除) 可以安全使用 0-RTT +3. 非幂等操作需在应用层加防重放 Token +4. QUIC 的 MaxEarlyData 字段限制重放窗口大小 +``` + +### HTTP/3 的 QPACK —— 区别于 HPACK + +``` +HPACK (HTTP/2) 的问题: +• Header 压缩表与数据共享同一 TCP 流 +• 如果 TCP 数据包丢失 → 解压失败 → 所有后续 header 阻塞 +• 这就是 HTTP/2 的队头阻塞在头部压缩层面的体现 + +QPACK (HTTP/3) 的解法: +• 压缩表独立于数据流 (unblocked streams) +• 即使某个 QUIC stream 丢了,header 仍能正确解码 +• 通过 encoder/decoder 两侧维护独立的动态表实现 +``` + +## QUIC 连接生命周期 + +``` +QUIC 连接的建立: +───────────────── +1. Client → Send Initial Packet (无 CID 或临时 CID) +2. Server → Send Initial Packet (含 server_cid) +3. Server → Send Handshake Packet +4. Client → Send Handshake Packet +5. Both → Send 1-RTT Packets (加密载荷) +6. Connection established ✅ + +关闭: +───────────────── +• QUIC 没有 RST/FIN 标志位 +• 用 CLOSE_CONNECTION_FRAME 通知对方 +• 双方确认后关闭 +• 类似 TCP 的四次挥手但在应用层实现 +``` + +## Go 中的 QUIC 实现 + +```go +import ( + "github.com/quic-go/quic-go" +) + +// QUIC 服务器 +config := &quic.Config{ + Tracer: quic.TraceWriter, // 可观测性 +} +listener, err := quic.Listen(nil, nil, config) + +conn, err := listener.Accept(ctx) // 接受新连接 +stream, err := conn.OpenStream() // 打开一个独立的 Stream +stream.Write([]byte("hello")) // Stream 3 的数据不会影响 Stream 5 +``` + +> [!tip] 为什么 Go 原生不支持 QUIC? +> Go 的 net/http 标准库目前仍基于 TCP。第三方实现 `quic-go` (by @malgorithms) 是最成熟的 Go QUIC 库,已在多个生产环境使用。Go 官方对加入 QUIC 支持持开放态度。 + +## 关联笔记 + +- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 1.3 是 QUIC 的安全基础 +- [[hhs/NETWORK/TCP段结构与状态机]] — QUIC 要替代的正是 TCP 的这些机制 +- [[hhs/NETWORK/前沿与进阶综述]] — QUIC 在更广泛前沿技术中的位置 diff --git a/hhs/NETWORK/10-前沿与进阶/02-eBPF与ServiceMesh.md b/hhs/NETWORK/10-前沿与进阶/02-eBPF与ServiceMesh.md new file mode 100644 index 0000000..814e6f8 --- /dev/null +++ b/hhs/NETWORK/10-前沿与进阶/02-eBPF与ServiceMesh.md @@ -0,0 +1,296 @@ +--- +tags: [计算机网络, eBPF, XDP, kprobe, Tracepoint, ServiceMesh, Istio, Envoy] +create time: 2026-05-18 05:30 +--- + +# eBPF 与 Service Mesh + +## 概述 + +eBPF 让 Linux 内核变得可编程而不需要编译内核模块;Service Mesh(Istio/Linkerd)让微服务间的流量治理从代码中剥离到基础设施层。这两者代表了可观测性和流量治理的两个重要方向。 + +## eBPF(extended Berkeley Packet Filter) + +### 什么是 eBPF? + +eBPF 允许在**内核态安全地运行沙箱程序**,无需修改内核源码或加载内核模块。它最初是一个 packet filter 增强版,现在已扩展到 tracing、monitoring、security 等多个领域。 + +``` +传统方案: eBPF 方案: +───────── ───────── +编译内核模块 (.ko) ↔ 编写 eBPF C 程序 +手动符号导出 ↔ kprobe/kfunc/tracepoint 直接挂钩 +系统重启加载 ↔ insmod bpf 即时生效 +无类型安全检查 ↔ 内核验证器严格检查 (verifier) +崩溃影响全局 ↔ 沙箱隔离,失败不崩内核 +无法卸载或调试 ↔ 可卸载,bpftool 可调试 +``` + +### eBPF 的核心架构 + +```mermaid +flowchart TD + subgraph "用户态 (User Space)" + Prog["eBPF 程序
(C/BPF 汇编)"] + Map["bpf_map
KV 存储
hash/array/perf_event/..."] + Tool["bpftool
BCC tools
bpftrace"] + end + + subgraph "内核态 (Kernel Space)" + Verifier["Verifier
严格安全检查"] + JIT["JIT Compiler
C → x86/arm/riscv 机器码"] + + subgraph "Attachment Points" + KP["kprobe / kretprobe
hook 任意内核函数入口/出口"] + TP["tracepoint
内核预定义探测点"] + TC["Traffic Control
qdisc classifier"] + XDP["XDP (eXpress Data Path)
网卡驱动级别最快路径"] + end + + Map -->|"写入"| PerfEvent["perf event → 用户态采集"] + end + + Tool -->|"加载"| Prog + Prog -->|"提交"| Verifier + Verifier -->|"验证通过"| JIT + JIT -->|"注册到"| KP + JIT -->|"注册到"| TP + JIT -->|"注册到"| TC + JIT -->|"注册到"| XDP +``` + +### eBPF 在网络中的典型应用层级 + +```mermaid +flowchart LR + NIC["Network Interface Card"] -.->|XDP 微秒级处理| XDP["XDP
最快路径: 网卡驱动级别"] + + TC["Traffic Control
tc filter/qdisc"] -.->|中等路径| Stack["Linux Network Stack"] + + Stack -.->|kprobe hook| Kprobe["kprobe/kretprobe
TCP/IP 栈钩子"] + + XDP -->|"drop/mirror/redirect"| NIC + TC -->|"police/packet/cgroup"| Stack + Kprobe -->|"tcp_connect/tcp_rcv_skb..."| App["可观测性工具"] + + style XDP fill:#FF6B6B,color:#fff + style TC fill:#FFD700,color:#000 + style Kprobe fill:#98FB98,color:#000 +``` + +| 挂载点 | 性能 | 用途 | 典型工具 | +|--------|------|------|---------| +| **XDP** | 微秒级,最快 | DDoS 过滤、L4 LB、镜像 | AF_XDP, cilium-hubble | +| **tc (Traffic Control)** | 纳秒~微秒级 | qdisc 调度、带宽限制、重定向 | tc-bpf, polycube | +| **kprobe/kretprobe** | 低开销 | 追踪任意内核函数 | bcc tcpconnect, bpftrace | +| **tracepoint** | 极低开销 | 内核预定义事件 | perf trace | +| **CGroup** | 进程级 | 网络隔离、限速 | cgroup-bpf | +| **LSM** | 安全级别 | 强制访问控制 | libbpf-security | + +### 常用 eBPF 工具实战 + +```bash +# === BCC Tools (Python-based eBPF 工具集) === + +# 追踪所有新建 TCP 连接 +sudo ./tcpconnect +PID COMM IP SADDR DADDR DPORT +1234 curl 4 192.168.1.10 93.184.216.34 443 + +# 查看 TCP 连接存活时间 +sudo ./tcplife +PID COMM FD LADDR:LADDR → RADDR:RADDR state msec +1234 chrome 42 192.168.1.10:54321→ 93.184.216.34:443 ESTAB 12345 + +# 查看 TCP 状态分布 +sudo ./tcpstates +Tracing TCP states... Output every 1 seconds. +TIME_WAIT 42 +ESTABLISHED 128 +CLOSE_WAIT 0 + +# === bpftrace (一行脚本) === + +# 实时打印每个 TCP 包的大小 +sudo bpftrace -e 'kprobe:tcp_sendmsg { @bytes[tid] = arg0; }' + +# 统计每秒 DNS 查询数 +sudo bpftrace -e 'tracepoint:dns:dns_query { @count++; } interval:s:1 { print(@count); clear(@count); }' + +# === Cilium Hubble (Kubernetes 可观测性) === + +# 服务间流量可视化 +hubble observe --namespace=default +FLOW 08:23:45.123 DefaultPod → api-server (TCP NEW) DPT=8080 +FLOW 08:23:45.125 api-server → DefaultPod (TCP FIN) + +# 按命名空间聚合 +hubble observe --aggregate 5s +``` + +### eBPF 监控网络问题的经典场景 + +```bash +# 场景 1: 哪个进程的 TCP 连接最多? +sudo bpftrace -e ' +kprobe:sock_alloc +/@pids[comm]/++ +' + +# 场景 2: 慢连接诊断——每个 TCP 连接耗时多久? +sudo bpftrace -e ' +kprobe:tcp_v4_connect /arg0/ { + @start[tid] = nsecs; +} +kretprobe:tcp_v4_connect /arg0 >= 0/ { + $delta = (nsecs - @start[tid]) / 1000; + @latency_us = hist($delta); + delete(@start[tid]); +}' + +# 场景 3: SYN Flood 检测 +sudo bpftrace -e ' +kprobe:tcp_v4_do_rcvd +@syn_count["SYN_RECV"]++; +interval:1s { + print(@syn_count); + clear(@syn_count); +}' +``` + +## Service Mesh (Istio / Linkerd) + +### Sidecar 模式原理 + +```mermaid +flowchart LR + Pod["Pod / VM"] -->|"app container"| App["my-service v1"] + Pod -->|"proxy container"| Proxy["envoy sidecar"] + + Proxy -->|"xDS API"| CP["Istiod
Pilot(路由) + Citadel(mTLS)"] + CP -->|"推送配置"| Proxy + + subgraph "Sidecar 自动接管:" + MTLS["mTLS 双向认证"] + Retry["重试/熔断/超时"] + Tracing["分布式链路追踪 (Jaeger)"] + Metrics["指标收集 (Prometheus)"] + Canary["金丝雀/蓝绿发布"] + Dashboard["流量仪表盘"] + end + + Proxy -.-> MTLS + Proxy -.-> Retry + Proxy -.-> Tracing + Proxy -.-> Metrics + Proxy -.-> Canary + Proxy -.-> Dashboard + + style Proxy fill:#DDA0DD,color:#000 + style CP fill:#FFD700,color:#000 +``` + +### Istio Traffic Management 核心资源 + +```yaml +# VirtualService: 路由规则 +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: my-service +spec: + hosts: ["my-service.default.svc.cluster.local"] + http: + - match: + - headers: + x-canary: {exact: "true"} + route: + - destination: {host: my-service, subset: v2} # canary 版 + weight: 100 + - route: + - destination: {host: my-service, subset: v1} + weight: 90 # 正式版 90% + - destination: {host: my-service, subset: v2} + weight: 10 # 金丝雀 10% + timeout: 5s # 超时控制 + retries: # 重试策略 + attempts: 3 + perTryTimeout: 2s + retryOn: "5xx,reset,connect-failure,gateway-error" + +# DestinationRule: 定义 subset (版本分组) +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: my-service +spec: + host: my-service.default.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL # 自动 mTLS + connectionPool: + tcp: + maxConnections: 100 # 连接池上限 + http: + h2UpgradePolicy: DEFAULT # 升级到 HTTP/2 + http1MaxPendingRequests: 100 + http2MaxRequests: 1000 + subsets: + - name: v1 # 打标签为 v1 + labels: {app-version: v1} + - name: v2 # 打标签为 v2 + labels: {app-version: v2} + +# Gateway: 入站流量入口 +apiVersion: networking.istio.io/v1beta1 +kind: Gateway +metadata: + name: my-gateway +spec: + selector: {istio: ingressgateway} + servers: + - port: {number: 443, name: https, protocol: HTTPS} + tls: {mode: SIMPLE, credentialName: my-cert} + hosts: ["api.example.com"] +``` + +### Istio 工作原理 + +``` +开发者视角: +────────────── +我的代码完全不知道 Service Mesh 的存在! +不需要引入任何 SDK、注解或配置。 + +流量全部被 envoy sidecar 拦截和转发。 + +运维视角: +────────────── +Istiod (control plane) 统一管理所有 envoy proxy +↓ 通过 xDS API (CDS/EDS/LDS/RDS) +推送路由规则、监听配置、集群发现给每个 sidecar +↓ +sidecar 热更新配置,无需重启应用 +``` + +```bash +# 确认 sidecar 注入成功 +kubectl get pods -n default +NAME READY STATUS +my-service-abc123-xk9qp 2/2 Running ← 2/2 = app + envoy sidecar + +# 检查 envoy 配置 +kubectl exec -it my-service-abc123-xk9qp -c envoy -- \ + envoy --admin-address-port localhost:15000 stats + +# istioctl 诊断 +istioctl analyze # 分析配置正确性 +istioctl proxy-status # sidecar 同步状态 +istioctl proxy-config routes my-service-pod # 查看某 pod 的路由表 +``` + +## 关联笔记 + +- [[hhs/NETWORK/QUIC协议深度解析]] — QUIC 也是用户态实现的协议 +- [[hhs/NETWORK/WireGuardVPN原理与实践]] — WireGuard 同样在内核态运行 +- [[hhs/NETWORK/01-SocketAPI与backlog详解]] — Socket API 是 eBPF kprobe 最常挂钩的地方 diff --git a/hhs/NETWORK/10-前沿与进阶/03-WireGuardVPN原理与实践.md b/hhs/NETWORK/10-前沿与进阶/03-WireGuardVPN原理与实践.md new file mode 100644 index 0000000..7318ec0 --- /dev/null +++ b/hhs/NETWORK/10-前沿与进阶/03-WireGuardVPN原理与实践.md @@ -0,0 +1,182 @@ +--- +tags: [计算机网络, WireGuard, VPN, ChaCha20, Curve25519, OpenVPN, IPsec] +create time: 2026-05-18 05:35 +--- + +# WireGuard VPN 原理与实践 + +## 概述 + +WireGuard 是一个极简的 VPN 协议,代码量不到 OpenVPN 的 1/10、IPsec 的 1/25。它凭借极低的延迟和超高的吞吐量,正在成为新一代标准 VPN 方案——甚至已合入 Linux 内核(≥ 5.6)。 + +## 为什么比 OpenVPN / IPsec 快? + +```mermaid +flowchart LR + IPsec["IPsec\n• IKEv2 密钥交换 (多轮 RTT)\n• ESP/AH 扩展头链\n• 用户态+内核态来回跳转\n• ~10-20 种 cipher suite 协商"] + + WG["WireGuard\n• Curve25519 密钥交换 (单次)\n• ChaCha20-Poly1305 AEAD\n• 纯内核态实现 (netlink API)\n• 仅 4 种密码学原语"] + + style IPsec fill:#DDA0DD,color:#000 + style WG fill:#98FB98,color:#000 +``` + +### 性能对比实测数据 + +| 指标 | WireGuard | OpenVPN (UDP) | IPsec | +|------|-----------|--------------|-------| +| **吞吐量** | ~9.5 Gbps | ~1.5 Gbps | ~3.0 Gbps | +| **CPU 占用 (Gbps)** | 0.8% | 12.5% | 3.2% | +| **握手延迟** | < 1ms | 50-200ms | 100-300ms | +| **代码行数** | ~4,000 LoC | ~50,000 LoC | ~100,000 LoC | +| **配置复杂度** | 极简 (INI 格式) | 中等 | 复杂 | +| **NAT 遍历** | 原生支持 | 需要辅助 (UDP encapsulation) | 复杂 | +| **移动性** | 支持 (PersistentKeepalive + endpoint change) | 部分支持 | 有限支持 | + +> [!tip] 代码行数少 = 更少的 bug = 更高的安全性 +> +> 审计 ~4000 行 C 代码的成本远低于审计 ~50000 行。而且每少一行代码就少一个潜在的漏洞入口。 + +## WireGuard 的密码学基础 + +``` +WireGuard 仅使用 4 种经过严格审查的密码学原语: +─────────────────────────────────────────────── +1. Curve25519 — ECDH 密钥交换 (x25519) +2. ChaCha20 — 对称加密 (AES 替代) +3. Poly1305 — MAC / AEAD 认证 +4. SHA-512 — 哈希函数 + +没有: RSA, ECC(除Curve25519外), CBC, HMAC, DRBG... +→ 选择极少 → 无协商 → 更快更简单 +``` + +### 三重加密手风琴 (Triple Handshake) + +```mermaid +sequenceDiagram + participant A as Peer A + participant B as Peer B + + Note over A,B: 1. A → B: 主动握手 (A 发起) + A->>B: {Ephemeral Key A} 用 B's Static Key 加密 + B->>A: {Ephemeral Key B} 用 A's Ephemeral Key + B's Static Key 加密 + A->>B: {Empty} 用 A's Ephemeral Key + B's Ephemeral Key 加密 + Note over A,B: ✅ 三方加密完成, 会话密钥建立 + + Note over A,B: 2. B → A: 响应式重握手 (B 轮换密钥) + B->>A: {Ephemeral Key B'} 用 A's Static Key 加密 + A->>B: {Ephemeral Key A'} 用 B's Ephemeral Key + A's Static Key 加密 + B->>A: {Empty} 用 B's Ephemeral Key + A's Ephemeral Key 加密 + Note over A,B: ✅ 新会话密钥, 前向保密保证 +``` + +> [!question] 什么是前向保密 (Forward Secrecy)? +> 即使长期密钥(Static Key)未来被泄露,也无法解密过去的通信——因为每次会话都使用了临时的 Ephemeral Key。三重手风琴保证了每一轮的临时密钥都在下一轮中被"销毁"。 + +## WireGuard 配置详解 + +### server.conf + +```ini +[Interface] +Address = 10.0.0.1/24 # WireGuard 虚拟网卡 IP +ListenPort = 51820 # UDP 监听端口 +PrivateKey = # 私钥 (安全保存!) +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # NAT 转发 +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +[Peer] # 客户端 1 +PublicKey = +AllowedIPs = 10.0.0.2/32 # 只允许这个 IP 通过这个 peer +PersistentKeepalive = 25 # 每 25s 发一次 keepalive (穿透 NAT) + +[Peer] # 客户端 2 +PublicKey = +AllowedIPs = 10.0.0.3/32 +PersistentKeepalive = 25 +``` + +### client.conf + +```ini +[Interface] +Address = 10.0.0.2/24 # 客户端自己的虚拟 IP +PrivateKey = # 客户端私钥 + +# DNS 解析通过隧道走 VPN +DNS = 10.0.0.1 + +[Peer] +PublicKey = # ⚠️ 注意方向: 客户端存服务端的公钥 +Endpoint = vpn.example.com:51820 # 服务器地址 +AllowedIPs = 0.0.0.0/0 # 所有流量走 VPN (全隧模式) +# AllowedIPs = 10.0.0.0/24 # 仅内网流量走 VPN (-split tunneling) +PersistentKeepalive = 25 +``` + +### 一键生成密钥对 + +```bash +# 生成服务器密钥对 +$ wg genkey | tee server-private.key | wg pubkey > server-public.key + +# 生成客户端密钥对 (可重复 N 次) +$ wg genkey | tee client1-private.key | wg pubkey > client1-public.key +$ wg genkey | tee client2-private.key | wg pubkey > client2-public.key + +# 生成 QR Code (方便手机扫码连接) +$ qrencode -t UTF8 <(cat <(echo "[Interface]"); echo "PrivateKey=$(cat client1-private.key)"; echo "[Peer]"; echo "PublicKey=$(cat server-public.key)"; echo "AllowedIPs=0.0.0.0/0"; echo "Endpoint=vpn.example.com:51820"; echo "PersistentKeepalive=25") +``` + +### 日常运维命令 + +```bash +# 启动 WireGuard +$ sudo wg-quick up wg0 +$ sudo systemctl enable --now wg-quick@wg0 # 开机自启 + +# 查看状态 +$ wg show +interface: wg0 + public key: abc123def456... + private key: (hidden) + listening port: 51820 + +peer: xyz789ghi012... + endpoint: 203.0.113.5:45678 + allowed ips: 10.0.0.2/32 + latest handshake: 3 seconds ago ← 3 秒前还在握手! 连接活跃 + transfer: 1.23 GiB received, 890 MiB sent + +# 动态添加/移除 peer (不需要重启!) +$ wg set wg0 peer allowed-ips 10.0.0.4/32 +$ wg remove wg0 peer + +# 导入导出配置 +$ wg syncconf wg0 <(wg-quick strip wg0) +``` + +## WireGuard 的限制与注意事项 + +``` +⚠️ WireGuard 不适合的场景: +───────────────────────────────── +• 需要传统用户认证 (PAP/CHAP/LDAP) → WireGuard 只认密钥 +• 细粒度 per-user 带宽控制 → 需配合 tc (traffic control) +• 大规模动态节点 (千级以上) → 管理 1000 个 peer 的 AllowedIPs 很痛苦 +• 与现有 IPsec 设备互通 → WireGuard 不能做 IPsec gateway + +✅ WireGuard 非常适合: +───────────────────────────────── +• 个人/小团队私有 VPN +• 云服务器到 IDC 的专线 +• IoT 设备远程管理 +• Kubernetes 集群跨 AZ 通信 +``` + +## 关联笔记 + +- [[hhs/NETWORK/eBPF与ServiceMesh]] — eBPF 可用于监控 WireGuard 接口的流量 +- [[hhs/NETWORK/SDNSRv6与网络编程最佳实践]] — SDN + SRv6 是更宏大的网络可编程愿景 +- [[hhs/NETWORK/IPv6地址与扩展头部]] — IPv6 SLAAC 自动配置与 WireGuard 的静态分配对比 diff --git a/hhs/NETWORK/10-前沿与进阶/04-SDNSRv6与网络编程最佳实践.md b/hhs/NETWORK/10-前沿与进阶/04-SDNSRv6与网络编程最佳实践.md new file mode 100644 index 0000000..f840c1b --- /dev/null +++ b/hhs/NETWORK/10-前沿与进阶/04-SDNSRv6与网络编程最佳实践.md @@ -0,0 +1,267 @@ +--- +tags: [计算机网络, SRv6, SDN, OpenFlow, P4, Go网络编程] +create time: 2026-05-18 05:40 +--- + +# SDN、SRv6 与 Go 网络编程最佳实践 + +## 概述 + +当 eBPF 让内核可编程之后,SDN 让整个网络架构变得可编程。SRv6 将路由策略编码进 IPv6 地址本身,而 Source Routing 让源端可以精确控制数据包经过的每一跳。本章从原理走向工程实践。 + +## SRv6(Segment Routing over IPv6) + +### 核心理念 + +SRv6 是 MPLS 的 IPv6 等价物,但有一个关键区别:**路径信息直接编码在 IPv6 地址中**。 + +``` +传统 MPLS: SRv6: +标签栈 (stacked labels) IPv6 Ext Header: SRH (Segment Routing Header) +每个中间节点查转发表 每个 Segment = 一个操作 +中间节点依赖 LSP 源端指定整条路径 (source routing) +部署需要全局配置 LDP/RSVP-TE Controller 集中下发 +``` + +### SRv6 数据包结构 + +``` +IPv6 Header: + Src: controller-assigned address + Dst: SRH (Segment Routing Header) + Next Header: 59 (NoNextHeader, 表示后面没有额外 header) + Hdr Ext Len: N-1 + Routing Type: 4 + Segments Left: N + Last Entry: N-1 + Flags: 0 + Tag: 0 + Segments[N]: endpoint6::action1 ← 第 N 段 + Segments[N-1]: endpoint6::action2 ← 第 N-1 段 + ... + Segments[1]: next-hop-ip ← 最后一段就是实际目的 IP + Payload: actual data (TCP/UDP/ICMP) +``` + +``` +SRv6 典型路径: +────────────────── +Client ──→ PE1 ──→ PE2 ──→ PE3 ──→ Provider Edge ──→ Destination + ↑ ↑ + SRH[2]=PE2 SRH[1]=PE3 + +数据包发出时 SRH = [PE2, PE3, Dest] +经过 PE1 后: Segments Left=2, 目的地址变为 PE2::action +经过 PE2 后: Segments Left=1, 目的地址变为 PE3::action +到达 PE3: Segments Left=0, 交付给最终目的地 +``` + +``` +SRv6 的 End.SID 动作类型: +├── End : 转发到目的地址 (最基本) +├── End.X : 转发到指定邻接关系 (二层转发) +├── End.DX : 解封装并三层转发 (VxLAN 出口) +├── End.DT4 : 解封装并从 IPv4 VRF 转发 +├── End.DT6 : 解封装并从 IPv6 VRF 转发 +├── End.M : 添加到 MPLS 标签栈 +└── End.B6 : 绑定列表 + 执行 SRGB 操作 +``` + +## SDN(Software Defined Networking) + +### SDN 三层架构 + +```mermaid +flowchart TB + subgraph "Application Plane" + App["Traffic Engineering
Security Policies
Multi-tenant Isolation
Bandwidth On-Demand"] + end + + subgraph "Control Plane" + Controller["SDN Controller
OpenDaylight / ONOS / Ryu / FRRouting"] + end + + subgraph "Data Plane" + SW1["Switch A
OpenFlow Protocol"] + SW2["Switch B
OpenFlow Protocol"] + SW3["Router C
P4 可编程"] + end + + App -->|"REST API"| Controller + Controller -->|"OpenFlow
NetConf/YANG"| SW1 + Controller -->|"OpenFlow"| SW2 + Controller -->|"P4Runtime"| SW3 + + style Controller fill:#DDA0DD,color:#000 + style App fill:#98FB98,color:#000 +``` + +### OpenFlow 工作原理 + +``` +传统交换机: OpenFlow 交换机: +数据面 + 控制面耦合 数据面与控制面分离 +每台设备自己算路由 控制器决定一切转发逻辑 + +主机收到包 → 本地查表 → 转发 主机收到包 → 查 Flow Table + ├─ 命中 → 按 Action 转发 + └─ 未命中 → Packet-In 问 Controller + Controller → Flow-Mod 加规则 + ↓ + 下次命中 → 直接转发 (高速) +``` + +```python +# 用 ryu 框架写一个简单的 SDN Controller (Python) +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryun.ofproto import ofproto_v1_3 + +class SimpleSDN(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def packet_in_handler(self, ev): + msg = ev.msg + dp = msg.datapath + ofp = dp.ofproto + ofp_parser = dp.ofproto_parser + + # 添加 FLOW_MOD: match 全部流量 → 泛洪到所有端口 + actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)] + + out = ofp_parser.OFPPacketOut( + datapath=dp, buffer_id=msg.buffer_id, + in_port=msg.match['in_port'], actions=actions + ) + dp.send_msg(out) +``` + +### P4 —— 可编程数据平面 + +``` +P4 (Programming Protocol-independent Packet Processors): +允许你定义数据包的解析器和处理流水线,然后编译成 +特定 ASIC/FPGA/NIC 上的硬件代码。 + +核心概念: +parser → 如何解析报文头 +deparser → 如何组装报文 +pipeline → 匹配- action 表的流处理逻辑 +control → 整个管道的编排 +``` + +```p4 +// P4 示例: 简单的负载均衡器 +header ethernet_t ethernet; +header ipv4_t ipv4; + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; +} + +parser parse_headers(packet_in& p, out headers_t hdr) { + p.extract(hdr.ethernet); + p.extract(hdr.ipv4); +} + +control ingress(packet_in& p, inout headers_t hdr) { + // 基于目标 IP 选择后端 + action forward_to_server1() { + modify_field(hdr.ipv4.dstAddr, SERVER1_IP); + } + action forward_to_server2() { + modify_field(hdr.ipv4.dstAddr, SERVER2_IP); + } + + table lb_table { + key = { + hdr.ipv4.dstAddr: exact; + } + actions = { forward_to_server1; forward_to_server2; } + } +} +``` + +## Go 中的网络编程最佳实践汇总 + +### 高性能 HTTP Server 模板 + +```go +package main + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "time" +) + +func NewServer(addr string, handler http.Handler) *http.Server { + return &http.Server{ + Addr: addr, + Handler: handler, + + // 超时设置 (防 Slowloris) + ReadTimeout: 10 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + MaxHeaderBytes: 1 << 20, // 1MB + + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + NextProtos: []string{"h2", "http/1.1"}, + }, + } +} + +// StartListener 用自定义 listener 启动 +func StartListener(srv *http.Server, network, addr string) error { + ln, err := net.Listen(network, addr) + if err != nil { + return err + } + // 底层使用 SO_REUSEPORT 实现多 worker 共享端口 + return srv.Serve(ln) +} +``` + +### 出站请求的高性能 Transport + +```go +import "golang.org/x/net/http2" + +func HighPerfTransport() *http.Transport { + transport := &http.Transport{ + MaxIdleConns: 200, + MaxIdleConnsPerHost: 50, // ← Go 默认只有 2, 务必显式设置! + IdleConnTimeout: 90 * time.Second, + + TLSHandshakeTimeout: 10 * time.Second, + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, // RFC 6724 happy eyeballs + }).DialContext, + } + + // 强制 HTTP/2 (Go 1.6+ 自动协商, 显式配置确保) + http2.ConfigureTransport(transport) + + return transport +} +``` + +## 关联笔记 + +- [[hhs/NETWORK/WireGuardVPN原理与实践]] — WireGuard 也可以作为 SDN 的数据平面组件 +- [[hhs/NETWORK/eBPF与ServiceMesh]] — eBPF 可以与 SDN Controller 协同工作 +- [[hhs/NETWORK/03-零拷贝与GoNetpoller]] — Go 网络编程的基础技术 diff --git a/hhs/NETWORK/README.md b/hhs/NETWORK/README.md new file mode 100644 index 0000000..d99bd00 --- /dev/null +++ b/hhs/NETWORK/README.md @@ -0,0 +1,268 @@ +--- +tags: [计算机网络, 网络协议, TCP/IP, HTTP, 网络安全, 运维] +create time: 2026-05-17 22:00 +--- + +# 计算机网络知识索引 + +## 概述 + +计算机网络是现代软件工程的基石——从浏览器发出一个请求到服务器返回响应,中间经历了 DNS 解析、TCP 三次握手、TLS 握手、HTTP 报文传输等十几道工序。本知识库从零开始,沿着 **OSI / TCP-IP 模型**的层次逐层深入,同时覆盖工程实践中不可或缺的工具链和安全体系。 + +> [!QUESTION] 为什么学网络这么重要? +> - 排查线上问题(连接超时、延迟飙升、SSL 错误)需要理解协议底层行为 +> - API 设计、服务间通信选择(HTTP/gRPC/WebSocket/gRPC-TCP)依赖于对协议的深度理解 +> - 高并发场景下的连接复用、零拷贝、epoll 等技术本质都是网络问题的解法 +> - 安全审计、渗透防御需要掌握攻击面和各层漏洞原理 + +## 文档统计 + +| 章节 | 文档数 | 文件路径前缀 | +|------|--------|-------------| +| 一、基础概念与分层模型 | 4 | `01-基础概念/` | +| 二、链路层 | 5 | `02-链路层/` | +| 三、网络层 | 8 | `03-网络层/` | +| 四、传输层 | 6 | `04-传输层/` | +| 五、应用层协议 | 6 | `05-应用层协议/` | +| 六、Socket 编程 | 3 | `06-Socket编程/` | +| 七、网络工具与诊断 | 2 | `07-网络工具与诊断/` | +| 八、网络安全 | 4 | `08-网络安全/` | +| 九、性能优化 | 2 | `09-性能优化/` | +| 十、前沿与进阶 | 4 | `10-前沿与进阶/` | +| **总计** | **44** | — | + +## 知识体系 + +### 一、基础概念与分层模型 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 1.1 | OSI 七层 vs TCP/IP 四层模型 | [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] | 两种模型的对比、每层职责映射、为什么现实中使用五层模型 | +| 1.2 | 数据封装与解封装 | [[hhs/NETWORK/01-基础概念/02-数据封装与解封装]] | 发送端逐层加头 → 网线比特流 → 接收端逐层剥头 | +| 1.3 | 寻址体系:MAC / IP / Port | [[hhs/NETWORK/01-基础概念/03-寻址体系MAC-IPTPort]] | 三层地址 vs 四层端口,`ip:port = 进程` 的经典比喻 | +| 1.4 | 带宽、延迟、RTT、吞吐量 | [[hhs/NETWORK/01-基础概念/04-带宽延迟RTT与吞吐量]] | 核心性能指标定义与关系公式 `BDP = 带宽 × 延迟` | + +```mermaid +flowchart TD + subgraph "应用层 L5" + A["HTTP / DNS / SMTP / FTP"] + end + subgraph "传输层 L4" + B["TCP — 可靠面向连接"] + C["UDP — 无连接尽最大努力"] + end + subgraph "网络层 L3" + D["IP (v4/v6) / ICMP / ARP / NAT"] + end + subgraph "链路层 L2" + E["Ethernet / Wi-Fi / VLAN / ARP*"] + end + subgraph "物理层 L1" + F["双绞线 / 光纤 / 无线电波"] + end + A --> B + A --> C + B --> D + C --> D + D --> E + E --> F +``` + +> [!tip] 记住一句话 +> 每一层只管自己的事,不知道上层在传什么内容,也不关心下层怎么搬。这就是 **分层解耦** 的威力。 + +### 二、链路层 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 2.1 | Ethernet 帧结构 | [[hhs/NETWORK/02-链路层/01-Ethernet帧结构]] | MAC 地址、EtherType、Payload 长度、FCS 校验 | +| 2.2 | CSMA/CD 与以太网退避 | [[hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避]] | 碰撞检测、二进制指数退避算法 | +| 2.3 | MAC 地址与广播域 | [[hhs/NETWORK/02-链路层/03-MAC地址与广播域]] | 全球唯一性、OUI 查询、二层广播 vs 三层组播 | +| 2.4 | Switch vs Hub vs Router | [[hhs/NETWORK/02-链路层/04-Switch与路由器]] | 冲突域隔离、MAC 表学习、VLAN 划分 | +| 2.5 | VLAN 与 Trunk | [[hhs/NETWORK/02-链路层/05-VLAN与Trunk]] | 802.1Q 标签、不同 Switch 间的 VLAN 透传 | + +> [!question] ARP 为什么跨不了网段? +> ARP 走的是 **二层广播**(目的 MAC `ff:ff:ff:ff:ff:ff`),路由器不转发广播包,因此跨网段必须靠网关做 **ARP 代理**或发起 **Gratuitous ARP**。 + +### 三、网络层(核心) + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 3.1 | IPv4 首部与分段重组 | [[hhs/NETWORK/03-网络层/01-IPv4首部与分段重组]] | TTL、Identification、DF/MF 标志、MTU 概念 | +| 3.2 | CIDR 与子网划分 | [[hhs/NETWORK/03-网络层/02-CIDR与子网划分]] | 无类编址、子网掩码计算、超网聚合 | +| 3.3 | IPv6 地址与扩展头部 | [[hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部]] | 简化首部、128位地址空间、SLAAC 自动配置 | +| 3.4 | ARP 协议完整流程 | [[hhs/NETWORK/03-网络层/04-ARP协议完整流程]] | 免费 ARP(Gratuitous ARP)、ARP 缓存老化、ARP Spoofing 防护 | +| 3.5 | ICMP 与 Ping / Traceroute | [[hhs/NETWORK/03-网络层/05-ICMP与Ping-Traceroute]] | Echo Request/Reply、TTL 超时机制实现路由追踪 | +| 3.6 | 静态路由与默认网关 | [[hhs/NETWORK/03-网络层/06-静态路由与默认网关]] | route 命令、路由表优先级、多网卡策略路由 | +| 3.7 | 动态路由协议 | [[hhs/NETWORK/03-网络层/07-动态路由协议OSPF-BGP]] | OSPF 区域内/区域间/LSA、BGP 选路规则 | +| 3.8 | NAT 原理与应用 | [[hhs/NETWORK/03-网络层/08-NAT原理与应用]] | SNAT / DNAT / PAT(端口复用)、NAT 对 TCP 的影响、穿透技术(STUN/TURN/ICE) | + +```mermaid +sequenceDiagram + participant Client as 客户端
192.168.1.100 + participant GW as 网关/NAT
203.0.113.5:12345 + participant Server as 服务端
93.184.216.34:80 + + Client->>GW: SYN 192.168.1.100:54321 → 93.184.216.34:80 + Note over GW: SNAT: 源地址改写 + GW->>Server: SYN 203.0.113.5:12345 → 93.184.216.34:80 + Server-->>GW: SYN-ACK ack=1 + Note over GW: DNAT: 端口反向映射 + GW-->>Client: SYN-ACK ack=1 + Client->>GW: ACK ack=2 + GW->>Server: ACK ack=2 +``` + +> [!warning] NAT 的陷阱 +> NAT 破坏了端到端原则——它在修改 IP 甚至端口,相当于在网络中间强行「翻译」数据包。某些协议(如 FTP 主动模式、P2P)在 NAT 后无法正常工作,需要 ALG 或打洞技术。 + +### 四、传输层(重点) + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 4.1 | TCP 段结构与状态机 | [[hhs/NETWORK/04-传输层/01-TCP段结构与状态机]] | 34 字节最小首部、11 种状态、全图详解 | +| 4.2 | 三次握手与四次挥手 | [[hhs/NETWORK/04-传输层/02-TCP三次握手与四次挥手]] | 为什么不能两次?TIME_WAIT 的意义和代价 | +| 4.3 | 可靠传输机制 | [[hhs/NETWORK/04-传输层/03-TCP可靠传输机制]] | 序列号/确认号、重传策略、快重传/快恢复 | +| 4.4 | 流量控制与拥塞控制 | [[hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制]] | 滑动窗口、BBR vs Cubic 算法对比 | +| 4.5 | TCP 粘包与拆包 | [[hhs/NETWORK/04-传输层/05-TCP粘包与拆包]] | 边界的成因、应用层解决方案(定长/分隔符/长度前缀)| +| 4.6 | UDP 协议与 SCTP | [[hhs/NETWORK/04-传输层/06-UDP协议与SCTP]] | UDP 低延迟特性、SCTP 多流独立有序 | + +```mermaid +stateDiagram-v2 + [*] --> CLOSED + CLOSED --> LISTEN: Accept / Connect + LISTEN --> SYN_RCVD: Accept + SYN_RCVD --> ESTABLISHED: 3-Way Handshake Complete + ESTABLISHED --> FIN_WAIT_1: Application closes + ESTABLISHED --> CLOSE_WAIT: Remote close + CLOSE_WAIT --> LAST_ACK: Local close + FIN_WAIT_1 --> FIN_WAIT_2: Local ACK received + FIN_WAIT_1 --> CLOSING: Simultaneous close + FIN_WAIT_2 --> TIME_WAIT: Remote close + CLOSING --> TIME_WAIT: Remote ACK + TIME_WAIT --> CLOSED: 2MSL timeout + LAST_ACK --> CLOSED: Final ACK received + + note right of TIME_WAIT + 等待 2 * MSL + 确保最后一个 ACK 到达 + 防止旧连接的重复报文干扰新连接 + end note +``` + +> [!question] 为什么 TCP 挥手需要 TIME_WAIT? +> 如果服务端直接关闭,客户端的 ACK 丢失会导致 RST。TIME_WAIT(2MSL)给时间让 ACK 重试,同时也让旧连接的报文在网络中自然消亡。但这意味着高并发服务器的 TIME_WAIT 堆积是常态而非异常。 + +### 五、应用层协议 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 5.1 | HTTP/1.1 完全指南 | [[hhs/NETWORK/05-应用层协议/01-HTTP-1-1完全指南]] | Method/Status/Header/Payload、Keep-Alive、管道化缺陷 | +| 5.2 | HTTP/2 多路复用 | [[hhs/NETWORK/05-应用层协议/02-HTTP-2多路复用]] | 二进制分帧、Header 压缩(HPACK)、Server Push、Stream Priority | +| 5.3 | HTTP/3 与 QUIC | [[hhs/NETWORK/05-应用层协议/03-HTTP-3与QUIC]] | 基于 UDP、内置拥塞控制、0-RTT 握手、Head-of-Line 消除 | +| 5.4 | HTTPS 与 TLS 握手 | [[hhs/NETWORK/05-应用层协议/04-HTTPS与TLS握手]] | 对称+非对称混合加密、证书链验证、Cipher Suite 协商 | +| 5.5 | DNS / DHCP / WebSocket | [[hhs/NETWORK/05-应用层协议/05-DNS与DHCP与WebSocket]] | 递归 vs 权威查询、DORA 四步、握手升级 | +| 5.6 | SSH 与邮件协议 | [[hhs/NETWORK/05-应用层协议/06-SSH与邮件协议]] | 密钥交换、公钥认证、端口转发、SMTP/IMAP/POP3 三大协议对比 | + +```mermaid +sequenceDiagram + participant B as Browser + participant S as Server + participant CA as CA/证书机构 + + B->>S: TCP 三次握手 + B->>S: ClientHello (cipher suites, extensions) + S->>B: ServerHello + Certificate (含公钥) + CA-->>B: 证书验签通过 (可选 OCSP/Stapling) + B->>S: ClientKeyExchange + Finished + S->>B: ServerFinished + Note over B,S: TLS 握手完成 ✨ 后续通信全部加密 + B->>S: HTTP Request (TLS 保护下) + S->>B: HTTP Response (TLS 保护下) +``` + +> [!tip] HTTPS 为什么叫「双重握手」? +> TCP 三次握手建通道 → TLS 握手协商加密参数并验证书 → 之后才是真正的 HTTP 请求。完整的 HTTPS 页面加载至少需要 2~3 个 RTT。 + +### 六、Socket 编程 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 6.1 | Socket API 与 backlog | [[hhs/NETWORK/06-Socket编程/01-SocketAPI与backlog详解]] | socket/bind/listen/accept 映射到 TCP 状态,backlog 双队列机制 (SYN Queue + Accept Queue) | +| 6.2 | I/O 多路复用:select → poll → epoll | [[hhs/NETWORK/06-Socket编程/02-epoll深度解析ETvsLT]] | 三阶段进化,ET vs LT 模式深度对比,最小可用 epoll 框架 | +| 6.3 | 零拷贝技术与 Go netpoller | [[hhs/NETWORK/06-Socket编程/03-零拷贝与GoNetpoller]] | sendfile/sendmmsg/io_uring,Go M:N 调度模型,net/http.Server 超时配置 | + +```mermaid +flowchart LR + App["应用程序"] -->|"accept()"| FD["file descriptor"] + FD -->|"register"| EPOLL["epoll instance
红黑树 + 就绪链表"] + EPOLL -->|"wait"| App + FD -.->|"内核回调加入就绪链表"| EPOLL + + style EPOLL fill:#DDA0DD,color:#000 +``` + +> [!tip] Go 的优势 +> Go 不需要像 C 那样手动维护 epoll + callback 地狱。每个连接挂起一个 goroutine(栈 2KB 起步),CPU 和内存效率极高。这被称为 **"C10K problem solved by goroutines"**。 + +### 七、网络工具与诊断 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 7.1 | 连通性与状态探测 | [[hhs/NETWORK/07-网络工具与诊断/01-连通性与状态探测工具]] | ping 指标解读、nc/telnet 端口测试、ss 连接状态快照(含全状态速查表)、traceroute | +| 7.2 | 抓包、HTTP 与 DNS 诊断 | [[hhs/NETWORK/07-网络工具与诊断/02-抓包HTTPDNS诊断工具]] | tcpdump BPF 表达式精选、curl 计时分解、dig 查询模式、iptables 规则排查 | + +> [!tip] 排错黄金思路 +> `ping`(通不通?)→ `telnet port`(端口通不通?)→ `tcpdump`(报文到了没?)→ `ss`(本地连接状态如何?)→ `traceroute`(哪一跳丢了?)→ 读应用日志 + +### 八、网络安全 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 8.1 | DDoS 与 MitM 防御 | [[hhs/NETWORK/08-网络安全/01-DDoS与MITM防御]] | L3/L4/D7 攻击矩阵,Linux 内置防御命令,ARP 欺骗 MITM 全流程,DNS 劫持检测,Slowloris | +| 8.2 | SSRF 与 DNS 安全 | [[hhs/NETWORK/08-网络安全/02-SSRF与DNS安全]] | Go SSRF 完整防御代码(含 DNS Rebinding),NAT 穿透 (STUN/TURN/ICE),DNSSEC / DoH / DoT | +| 8.3 | CSRF / XSS / XXE 防御 | [[hhs/NETWORK/08-网络安全/03-CSRFxss与XXE防御]] | 三巨头对比表,SameSite Cookie,CSP,html/template 自动转义,CORS vs CSRF | +| 8.4 | JWT 认证与 TLS 安全 | [[hhs/NETWORK/08-网络安全/04-JWT认证与TLS安全]] | JWT 六大漏洞+修复,Refresh Token 模式,OAuth 2.0 vs OIDC,Cipher Suite 选择,OCSP Stapling,mTLS | + +### 九、网络性能优化 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 9.1 | TCP 内核调优与连接复用 | [[hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用]] | sysctl 二十余参数生产级配置,BBR 启用,TCP Buffer BDP 适配,HTTP/gRPC/Nginx Keep-Alive 全栈方案 | +| 9.2 | CDN、负载均衡与 Go Server 调优 | [[hhs/NETWORK/09-性能优化/02-CDN负载均衡与GoServer调优]] | Cache-Control 速查,L4 vs L7 LB 选型决策树,Nginx upstream 策略,Go http.Server 终极配置 | + +### 十、前沿与进阶 + +| # | 主题 | 链接 | 说明 | +|---|------|------|------| +| 10.1 | QUIC 协议深度解析 | [[hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析]] | Connection ID NAT 迁移,Stream 独立性消除队头阻塞,0-RTT + 重放攻击风险,QPACK vs HPACK | +| 10.2 | eBPF 与 Service Mesh | [[hhs/NETWORK/10-前沿与进阶/02-eBPF与ServiceMesh]] | XDP/TC/kprobe 三层次架构,bpftrace/bcc/cilium-hubble 实战,Istio sidecar + VirtualService 路由 | +| 10.3 | WireGuard VPN 原理与实践 | [[hhs/NETWORK/10-前沿与进阶/03-WireGuardVPN原理与实践]] | Curve25519 + ChaCha20-Poly1305 AEAD,三重加密手风琴握手,运维命令全解,性能对比实测 | +| 10.4 | SDN / SRv6 与 Go 网络编程最佳实践 | [[hhs/NETWORK/10-前沿与进阶/04-SDNSRv6与网络编程最佳实践]] | Segment Routing over IPv6 包结构,OpenFlow/P4 可编程数据平面,Go 高性能 Server 模板 | + +## 学习路径建议 + +```mermaid +flowchart LR + BASE["一、基础概念
OSI/TCP-IP/地址"] --> LINK["二、链路层
Ethernet/ARP/Switch"] + LINK --> NET["三、网络层
IPv4/IPv6/Routing/NAT"] + NET --> TRANSP["四、传输层
TCP/UDP/状态机/拥塞控制"] + TRANSP --> APP["五、应用层
HTTP/DNS/TLS/WebSocket"] + TRANSP --> SOCKETS["六、Socket编程
epoll/零拷贝"] + APP --> TOOLS["七、网络工具
tcpdump/curl/dig"] + TOOLS --> SEC["八、网络安全"] + SEC --> PERF["九、性能优化"] + PERF --> FRONTIER["十、前沿进阶"] + + style BASE fill:#DDA0DD + style TRANSP fill:#FFD700 + style APP fill:#98FB98 + style PERF fill:#FFA07A + style FRONTIER fill:#B0C4DE +``` + +## 关联笔记 + +- [[hhs/gRPC/README]] — gRPC 底层的 HTTP/2 + Protobuf 与网络层强相关 +- [[hhs/Redis/README]] — Redis 客户端的网络连接池与持久化 TCP 通信 +- [[hhs/GORM/README]] — Web 框架底层依赖 HTTP 协议栈