Skip to content

IP 层:路由、分片与 ICMP ​

#网络 · #IP · #路由 · #分片 · #ICMP · #MTU

IP(Internet Protocol)是 TCP/IP 协议栈的核心,负责将数据包从源主机路由到目标主机。它不保证可靠性(交给 TCP),不保证顺序,只做"尽力而为"的交付。理解 IP 的分片与重组、路由选择、ICMP 诊断,是网络排查的基础技能。


1. IP 数据报格式 ​

IPv4 Header(20~60 字节):
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |    DSCP/ECN   |          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|    Fragment Offset      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live (TTL) | Protocol|       Header Checksum         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options (if IHL > 5)                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段大小说明
Version4 bit4 = IPv4, 6 = IPv6
IHL4 bitHeader 长度(单位 4 字节),最小 5(20B)
Total Length16 bit数据报总长度(含 Header),最大 65535
Identification16 bit分片标识:同一数据报的分片 ID 相同
Flags3 bitDF(不分片), MF(更多分片)
Fragment Offset13 bit分片偏移(单位 8 字节)
TTL8 bit跳数限制,每过一个路由器减 1,归零则丢弃
Protocol8 bit上层协议:1=ICMP, 6=TCP, 17=UDP
Header Checksum16 bit仅校验 Header,每跳重新计算(TTL 会变)
Source/Dest IP32 bitIPv4 地址

2. IP 分片与重组 ​

2.1 为什么需要分片 ​

链路层 MTU(Maximum Transmission Unit)限制:
- 以太网 MTU = 1500 字节
- IP 数据报 > MTU → 必须分片

例:发送 4000 字节的数据报,经过 MTU=1500 的链路:

原始数据报:4000B = 20B(Header) + 3980B(Payload)
                    ↓
分片 1: ID=x, Offset=0,     MF=1, Length=1500 (20+1480)
分片 2: ID=x, Offset=185,   MF=1, Length=1500 (20+1480)
分片 3: ID=x, Offset=370,   MF=0, Length=1040 (20+1020)
                    ↓
目标主机重组:按 Offset 拼接

2.2 分片相关字段 ​

c
// Identification: 标识同一数据报,各分片相同
// Flags:
//   bit 0: Reserved
//   bit 1: DF (Don't Fragment) — 置 1 则路由器不分片,返回 ICMP 差错
//   bit 2: MF (More Fragments) — 1 表示后面还有分片

// Fragment Offset: 本分片在原始数据报中的位置(8 字节为单位)
//   例:Offset=185 → 实际偏移 = 185 × 8 = 1480 字节

2.3 Path MTU Discovery ​

工作原理:
1. 发送方设置 DF=1,发送数据报
2. 路由器发现 MTU 不够 → 丢弃 → 返回 ICMP "Fragmentation Needed"
   (ICMP 消息中附带该链路的 MTU)
3. 发送方降低包大小,重试
4. 重复直到到达目的地

实现:
- IPv4: Path MTU Discovery (RFC 1191),可选
- IPv6: 强制支持,路由器永远不分片(由发送方负责)

2.4 实战查看 MTU ​

bash
# 查看接口 MTU
ip link show eth0 | grep mtu

# 测试路径 MTU(发送 DF=1 的包,逐步调整大小)
ping -M do -s 1472 8.8.8.8   # -M do = DF=1, -s = payload 大小
# 1472 + 20(IP) + 8(ICMP) = 1500(标准以太网 MTU)

# traceroute 同时测 MTU
tracepath 8.8.8.8  # 自动探测路径 MTU

3. IP 路由 ​

3.1 路由表 ​

bash
# Linux 路由表
ip route show
# default via 192.168.1.1 dev eth0       ← 默认网关
# 192.168.1.0/24 dev eth0 proto kernel   ← 直连网络
# 10.0.0.0/8 via 192.168.1.254 dev eth0  ← 到 10.x 走 192.168.1.254

3.2 路由选择算法 ​

收到 IP 数据报 → 查路由表:

1. 最长前缀匹配(Longest Prefix Match)
   - 目标 10.1.2.3 命中 10.1.0.0/16 和 10.1.2.0/24
   - 选择 /24(前缀更长 → 更具体)

2. 如果多处匹配:
   - Metric 最小的路由优先
   - 等 Metric 则 ECMP(多路负载均衡)

3. 目的地址分类:
   127.0.0.0/8   → lo(本地回环)
   224.0.0.0/4   → 多播
   169.254.0.0/16 → link-local(DHCP 失败时自动分配)

3.3 策略路由 ​

bash
# 多出口场景:不同源 IP 走不同出口
# /etc/iproute2/rt_tables
echo "200 isp1" >> /etc/iproute2/rt_tables
echo "201 isp2" >> /etc/iproute2/rt_tables

ip route add default via 1.1.1.1 table isp1
ip route add default via 2.2.2.1 table isp2

# 策略:来自 192.168.1.0/24 走 isp1
ip rule add from 192.168.1.0/24 table isp1
# 来自 192.168.2.0/24 走 isp2
ip rule add from 192.168.2.0/24 table isp2

4. ICMP(Internet Control Message Protocol) ​

4.1 报文类型 ​

TypeCode含义示例场景
00Echo Replyping 回复
30Network Unreachable无路由
31Host UnreachableARP 失败
33Port UnreachableUDP 端口未监听
34Fragmentation Needed (DF set)Path MTU Discovery
40Source Quench(已废弃)拥塞通知
50/1Redirect更优路径通知
80Echo Requestping 请求
110TTL Exceededtraceroute
111Fragment Reassembly Time Exceeded分片重组超时

4.2 ping 原理 ​

bash
ping -c 3 8.8.8.8
# 发送 ICMP Echo Request (Type 8)
# 期待 ICMP Echo Reply (Type 0)
# 计算 RTT
go
// Go 中 ping 的实现(使用 ICMP,需 root/特权)
import "golang.org/x/net/icmp"
import "golang.org/x/net/ipv4"

conn, _ := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
msg := icmp.Message{
    Type: ipv4.ICMPTypeEcho,
    Code: 0,
    Body: &icmp.Echo{
        ID:   os.Getpid() & 0xffff,
        Seq:  1,
        Data: []byte("HELLO"),
    },
}
b, _ := msg.Marshal(nil)
conn.WriteTo(b, &net.IPAddr{IP: net.ParseIP("8.8.8.8")})

4.3 traceroute 原理 ​

bash
traceroute 8.8.8.8

# 原理:利用 TTL 递增
# TTL=1 → 第一跳路由器收到,TTL 减为 0 → 丢弃 → 返回 ICMP Time Exceeded → 知道第一跳 IP
# TTL=2 → 第二跳返回 Time Exceeded → 知道第二跳 IP
# ...重复直到到达目标

# 每跳发 3 个包(测 RTT 波动)
# 如果某跳显示 * * *:可能路由器不回复 ICMP(防火墙)

5. ARP(Address Resolution Protocol) ​

5.1 工作原理 ​

场景:192.168.1.10 要发数据给 192.168.1.20
              知道目标 IP,但需要目标 MAC

ARP 流程:
1. 192.168.1.10 广播 ARP Request:
   "Who has 192.168.1.20? Tell 192.168.1.10"

2. 192.168.1.20 单播 ARP Reply:
   "192.168.1.20 is at aa:bb:cc:dd:ee:ff"

3. 192.168.1.10 缓存到 ARP 表

5.2 ARP 缓存 ​

bash
# 查看 ARP 表
arp -n
ip neigh show

# 条目状态:
# REACHABLE — 正常可用
# STALE     — 超时未使用,下次使用前需确认
# DELAY     — 正在等待确认
# PROBE     — 正在探测
# FAILED    — 不可达

# 手动操作
arp -d 192.168.1.20     # 删除条目
arp -s 192.168.1.20 aa:bb:cc:dd:ee:ff  # 添加静态条目

5.3 ARP 欺骗 ​

攻击者向受害者发送伪造的 ARP Reply:
  "网关 IP 的 MAC 地址 = 攻击者的 MAC"

→ 受害者流量经过攻击者 → 中间人攻击(MITM)

防护:
- 交换机端口安全(Port Security)
- 动态 ARP 检测(DAI)
- 静态 ARP 绑定

6. IPv6 对比 ​

维度IPv4IPv6
地址长度32 bit(4.3×10⁹)128 bit(3.4×10³⁸)
Header 长度20~60 字节40 字节(固定)
分片路由器可分片路由器不可分片(端到端)
Checksum有(每跳计算)无(上层协议负责)
广播有无(用多播替代)
ARP需要不需要(用 NDP 替代)
NAT普遍使用不需要(地址充足)
地址表示192.168.1.12001:db8::1
bash
# IPv6 邻居发现(NDP — 替代 ARP)
ip -6 neigh show

# ping IPv6
ping6 2001:4860:4860::8888    # Google DNS

7. 常见网络问题诊断 ​

7.1 排查流程 ​

mermaid
flowchart TB
    A["网络不通"] --> B["ping 127.0.0.1<br/>(本机协议栈)"]
    B -->|"通"| C["ping 本机 IP<br/>(网卡驱动)"]
    B -->|"不通"| B1["TCP/IP 栈问题"]
    C -->|"通"| D["ping 网关<br/>(局域网)"]
    C -->|"不通"| C1["网卡/IP配置问题"]
    D -->|"通"| E["ping 8.8.8.8<br/>(互联网)"]
    D -->|"不通"| D1["交换机/ARP/VLAN问题"]
    E -->|"通"| F["ping google.com<br/>(DNS)"]
    E -->|"不通"| E1["路由器/ISP/WAN 问题"]
    F -->|"不通"| F1["DNS 问题"]
    F -->|"通"| G["网络正常,检查应用层"]

7.2 常用命令速查 ​

bash
# IP 配置
ip addr show                    # 所有接口 IP
ip link set eth0 up/down        # 启用/禁用接口

# 路由
ip route show                   # 路由表
ip route get 8.8.8.8            # 查看到目标的实际路由

# ARP
ip neigh show                   # ARP 表

# 连通性
ping -c 3 8.8.8.8
traceroute 8.8.8.8
tracepath 8.8.8.8               # 同时测 MTU

# 抓包
tcpdump -i eth0 host 8.8.8.8 and icmp
tcpdump -i eth0 arp

# 查看丢包
netstat -s | grep -i "packet receive errors"
ip -s link show eth0            # 接口统计(丢包/错误)

7.3 常见问题与对策 ​

现象可能原因诊断
ping 网关不通IP 冲突 / ARP 问题arp -a 检查 / tcpdump arp
丢包(间歇性)MTU 问题 / 网卡故障tracepath 测 MTU / ip -s link 看错误
延迟抖动路由变化 / 拥塞mtr 8.8.8.8 持续监控
部分网站不通MTU 黑洞降低 MTU / clamp mss to pmtu
TTL exceeded路由环路traceroute 看跳数

8. 路由协议简介 ​

IP 路由表不是凭空产生的——它通过路由协议动态学习和更新。理解路由协议是理解"互联网如何联通"的关键。

8.1 路由协议分层 ​

mermaid
flowchart TB
    subgraph IGP["IGP: 内部网关协议 — 同一 AS 内"]
        RIP["RIP: 距离矢量<br/>跳数最小 → Bellman-Ford"]
        OSPF["OSPF: 链路状态<br/>最短路径 → Dijkstra"]
        IS-IS["IS-IS: 链路状态<br/>运营商常用"]
    end

    subgraph EGP["EGP: 外部网关协议 — AS 之间"]
        BGP["BGP-4: 路径矢量<br/>策略路由, 非最短路径"]
    end

    EGP <-->|"AS 边界路由器"| IGP

8.2 三大协议核心对比 ​

维度RIPOSPFBGP
算法Bellman-Ford(D-V)Dijkstra(SPF)Path Vector
度量跳数(最大 15)链路成本(带宽)AS-PATH 长度 + 策略
收敛速度慢(计数到无穷)快(洪泛 LSA)慢(策略评估)
适用规模小型网络中型到大型企业互联网骨干
可靠性❌ 路由环路风险✅ 无环路✅ AS-PATH 防环路
策略控制❌ 无有限✅✅ 强大的策略引擎
协议类型UDP 520IP 协议 89TCP 179

8.3 OSPF 工作原理 ​

OSPF 的核心是维护一个全网拓扑图(LSDB),然后对每个目的地
运行 Dijkstra 计算最短路径树。

1. Hello 协议: 发现邻居,选举 DR/BDR(广播网络)
2. LSA 洪泛: 链路状态变化时,洪泛 LSA 到整个区域
3. SPF 计算: 每个路由器独立运行 Dijkstra
4. 路由表注入: 将计算结果写入转发表

区域划分:
  Backbone (Area 0)  ← 必须存在,连接所有其他区域
  Stub Area           ← 不接收外部路由,用默认路由替代
  NSSA                ← 允许注入有限的外部路由

为什么需要区域?
  - 减少 LSA 洪泛范围
  - 减少 LSDB 大小
  - 减少 SPF 计算开销

8.4 BGP 的路径选择 ​

BGP 不是选"最短路径"——它选"最优路径",按以下优先级(简化):

1. 最高 Local Preference(本地优先级)
2. 最短 AS-PATH(经过的 AS 最少)
3. 最低 Origin 类型(IGP < EGP < Incomplete)
4. 最低 MED(Multi-Exit Discriminator)
5. eBGP 优先于 iBGP
6. 最低 IGP 度量到下一跳
7. 最低 Router ID

这解释了为什么两个国家之间的流量可能绕半个地球——
BGP 更关心策略(省钱、合规),而非延迟。

9. NAT 穿透详解 ​

NAT 让内网主机共享一个公网 IP 访问互联网。但 P2P、VoIP、游戏联机等场景需要"穿透"NAT。

9.1 NAT 类型(RFC 4787) ​

类型映射行为过滤行为P2P 穿透难度
Full Cone内网(IP:Port)→固定外网(IP:Port)无过滤⭐ 最简单
Restricted Cone同上只接受该主机发过的 IP⭐⭐
Port Restricted Cone同上只接受该主机发过的 IP:Port⭐⭐⭐
Symmetric NAT内网(IP:Port)→不同外网 Port(按目标不同)同 Port Restricted❌ 最难

9.2 STUN / TURN / ICE ​

mermaid
flowchart TB
    A["A (NAT 后)"] -->|"1. 发到 STUN 服务器"| STUN["STUN Server<br/>返回 A 的公网 IP:Port"]
    B["B (NAT 后)"] -->|"2. B 也获取公网地址"| STUN
    A -->|"3. 尝试直连 B 的公网地址"| NAT_B["B 的 NAT"]
    NAT_B -->|"成功 (Cone NAT)"| B
    NAT_B -->|"失败 (Symmetric NAT)"| TURN["TURN Server<br/>中转流量 (最坏方案)"]
    TURN --> B

    style STUN fill:#4CAF50,color:#fff
    style TURN fill:#FF9800,color:#fff
  • STUN:告诉客户端"你在公网上长什么样",免费但只能穿透 Cone NAT
  • TURN:中继服务器,总能通但带宽成本高
  • ICE:综合 STUN + TURN,优先直连,失败回退中继

WebRTC 使用 ICE 框架,浏览器内置 STUN/TURN 客户端。


10. iptables NAT 与 Go ICMP ping 实战 ​

iptables NAT 完整配置 ​

bash
# 1. 启用 IP 转发
sysctl -w net.ipv4.ip_forward=1

# 2. SNAT — 内网共享公网 IP 上网
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 3. DNAT — 端口映射(公网 80 → 内网 10.0.0.5:8080)
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 \
    -j DNAT --to-destination 10.0.0.5:8080

# 4. 保存规则(持久化)
iptables-save > /etc/iptables/rules.v4

# 5. 查看 NAT 表规则
iptables -t nat -L -n -v

# 6. 查看当前 NAT 连接跟踪
conntrack -L -n
conntrack -L -n | wc -l  # 连接跟踪表大小
bash
# 排查: 策略路由 + NAT 的包流向
# 1. 查看包走哪条路由
ip route get 8.8.8.8 from 192.168.1.10 iif eth0

# 2. 跟踪 NAT 转换
conntrack -E -p tcp --dport 80

# 3. iptables 日志追踪
iptables -t nat -A PREROUTING -p tcp --dport 80 -j LOG --log-prefix "DNAT: "
dmesg | tail

Go ICMP ping 完整实现 ​

go
package main

import (
    "fmt"
    "net"
    "os"
    "time"

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
)

func ping(addr string) error {
    // 监听 ICMP
    conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
    if err != nil {
        return fmt.Errorf("listen: %w (需要 root 权限)", err)
    }
    defer conn.Close()

    dst, err := net.ResolveIPAddr("ip4", addr)
    if err != nil {
        return err
    }

    for seq := 0; seq < 4; seq++ {
        // 构造 ICMP Echo Request
        msg := icmp.Message{
            Type: ipv4.ICMPTypeEcho,
            Code: 0,
            Body: &icmp.Echo{
                ID:   os.Getpid() & 0xffff,
                Seq:  seq,
                Data: []byte("ping"),
            },
        }
        b, _ := msg.Marshal(nil)

        start := time.Now()
        _, err := conn.WriteTo(b, dst)
        if err != nil {
            return err
        }

        // 读取 Echo Reply
        reply := make([]byte, 1500)
        conn.SetReadDeadline(time.Now().Add(3 * time.Second))
        n, peer, err := conn.ReadFrom(reply)
        if err != nil {
            fmt.Printf("seq=%d timeout\n", seq)
            continue
        }

        rtt := time.Since(start)
        rm, _ := icmp.ParseMessage(1, reply[:n])
        echo := rm.Body.(*icmp.Echo)
        fmt.Printf("seq=%d from=%s rtt=%v ttl=?\n",
            echo.Seq, peer.String(), rtt)
    }
    return nil
}
系统调用说明
icmp.ListenPacket("ip4:icmp")创建原始 ICMP socket,需 root/CAP_NET_RAW
ipv4.ICMPTypeEchoType=8 Echo Request
ipv4.ICMPTypeEchoReplyType=0 Echo Reply
SetReadDeadline每次等待最多 3s

参考 ​

批注模式

💬 文章评论

暂无评论,来说点什么吧 👇

编程学习笔记