Files
cs-note/hhs/NETWORK/03-网络层/02-CIDR与子网划分.md
T
2026-05-27 23:01:37 +08:00

11 KiB
Raw Blame History

tags, create time
tags create time
计算机网络
CIDR
子网划分
IP计算
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.254.0 510 大型部门
/22 255.255.252.0 1022 数据中心租户
/21 255.255.248.0 2046 大规模部署
/16 255.255.0.0 65534 大型企业内网

[!tip] 快速计算可用主机数 $N_{usable} = 2^h - 2$,其中 h 是主机位数量($h = 32 - \text{prefix}$)

  • -2 因为网络地址和广播地址不可用
  • 对于 /31(点对点链路),RFC 3021 允许使用 2 个地址(无网络/广播位)

CIDR vs 分类地址:为什么需要 CIDR?

CIDR(1993 年,RFC 1518/1519)诞生之前,IP 地址按 A/B/C 三类硬性划分,带来两个致命问题:

对比项 分类地址(Classful) CIDR(Classless)
前缀长度 A=/8, B=/16, C=/24 固定三档 任意 /0 ~ /32
分配灵活性 差——中等规模网络要么浪费 B 类(/16 多余),要么 C 类(/24)不够 精确按需分配
路由聚合 不支持,每个网络单独一条路由 支持超网聚合,压缩路由表
路由表规模 随网络数线性增长 聚合后显著缩小

[!QUESTION] 分类地址到底浪费有多严重? 一个需要 2000 台主机的机构,在分类体系下只能申请 B 类地址(/16,65534 个 IP),实际使用率仅 3%——剩下 63000+ 个地址被白白锁死。CIDR 只需分配 /21(2046 个 IP),利用率直接拉满。

子网划分的核心算法

示例:从 /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
flowchart LR
    S0["192.168.1.0/26<br/>.1-.62"] -->|"PCs, Servers"| SW0["Switch-A"]
    S1["192.168.1.64/26<br/>.65-.126"] -->|"IP Phones"| SW1["Switch-B"]
    S2["192.168.1.128/26<br/>.129-.190"] -->|"Guest WiFi"| AP1["AP-C"]
    S3["192.168.1.192/26<br/>.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(可变长子网掩码)

不同子网可以用不同长度的掩码——这就是可变长子网掩码(VLSM)。在 CIDR 出现之前,同一网络内所有子网必须等长,造成大量地址浪费。VLSM 允许按需"量体裁衣"。

场景:公司申请了 192.168.0.0/22(1022 个可用 IP),4 个部门需求如下:

部门 主机数 需要 ≥ 分配掩码 可用 IP
研发部 200 202 /24 254
市场部 50 52 /26 62
行政部 10 12 /28 14
互联链路 2 2 /30 2

分配原则:从大到小,依次切割(先给需求最大的部门分配,避免碎片化):

总地址池: 192.168.0.0/22(含 .0.0 ~ .3.255,共 1024 地址)

① 研发部 → 192.168.0.0/24   用掉 256 地址(.0.0 ~ .0.255)
   剩余: 192.168.1.0 ~ 192.168.3.255

② 市场部 → 192.168.1.0/26   用掉 64 地址(.1.0 ~ .1.63)
   剩余: 192.168.1.64 ~ 192.168.3.255

③ 行政部 → 192.168.1.64/28  用掉 16 地址(.1.64 ~ .1.79)
   剩余: 192.168.1.80 ~ 192.168.3.255

④ 互联链 → 192.168.1.80/30  用掉 4 地址(.1.80 ~ .1.83)
   剩余: 192.168.1.84 ~ 192.168.3.255(留给未来扩展)

[!tip] VLSM 规划要点

  • 先大后小:优先满足最大子网需求,剩余空间再切分,避免碎片
  • 注意边界对齐:每个子网起始地址必须是其掩码块大小的整数倍(例如 /26 块大小 64,起址必须是 64 的倍数)
  • 实际操作中常用 Excel 或 ipcalc/sipcalc 工具辅助规划

实用计算工具

Linux ipcalc

$ 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 将 IP 地址与子网掩码都转为二进制
2 网络地址 = IP 按位 AND 掩码(两列都为 1 才写 1)
3 广播地址 = 网络地址的主机位全部置 1
4 可用范围 = 网络地址+1 ~ 广播地址-1

[!example] 动手练:求 172.16.50.100/18 的网络地址、广播地址、可用范围

步骤 1 — 写出第三字节的二进制(/18 意味着前两个字节 + 第三字节前 2 位是网络位):

IP  第三字节: 50  = 00110010
掩码第三字节: 192 = 11000000   ← /18 对应 255.255.192.0

步骤 2 — 按位 AND:

IP:    00110010
Mask:  11000000
AND:   00000000  → 网络地址第三字节 = 0
网络地址 = 172.16.0.0

步骤 3 — 主机位全 1(第三字节后 6 位 + 第四字节 8 位 = 14 位主机位):

00111111.11111111 = 63.255
广播地址 = 172.16.63.255

步骤 4 — 可用范围: 172.16.0.1 ~ 172.16.63.254,共 2^{14} - 2 = 16382 个可用 IP

私有地址空间(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

特殊地址段速查

除了 RFC 1918 私有地址,还有几个常考的特殊地址段:

地址段 名称 用途
127.0.0.0/8 环回地址(Loopback) 本机测试,最常用 127.0.0.1
169.254.0.0/16 链路本地地址(Link-local) DHCP 失败时自动分配(APIPA)
224.0.0.0/4 组播地址(Multicast) OSPF 用 224.0.0.5/224.0.0.6
0.0.0.0/8 本网络 表示「当前网络」,路由表中代表默认路由

[!QUESTION] 为什么 127.0.0.1 能 Ping 通自己? 发送到 127.x.x.x 的数据包不会离开主机——操作系统内核的网络栈直接将它环回到传输层,不经过任何物理网卡。这也是它作为服务健康检查首选地址的原因。

最长前缀匹配(Longest Prefix Match)

CIDR 引入了一个关键的路由查找规则:当一个目标 IP 匹配多条路由时,前缀越长(掩码越精确)的路由优先。

路由表:
  192.168.0.0/16  → 下一跳 A
  192.168.1.0/24  → 下一跳 B
  192.168.1.0/26  → 下一跳 C

目标 IP: 192.168.1.50
  匹配 /16 ✅  /24 ✅  /26 ✅
  最长前缀 → /26 → 选择下一跳 C

[!tip] 面试高频题 最长前缀匹配是路由器硬件(TCAM)的核心算法。理解它,就理解了为什么 CIDR 能让路由表既精简又灵活——聚合路由用短前缀覆盖大范围,精确路由用长前缀处理例外。

超网聚合(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
         (注意:实际只用了其中 3 个 /24 块,浪费了 192.168.0.0/24 这 1 个)

路由聚合减少了全球 BGP 路由表的大小。没有 CIDR 聚合,BGP 路由表会膨胀到百万级别。

Go 中的子网判断

package main

import (
    "fmt"
    "net"
)

func main() {
    ip := net.ParseIP("192.168.1.50")
    _, cidr, _ := net.ParseCIDR("192.168.1.0/26")

    // 判断 IP 是否在子网内
    if cidr.Contains(ip) {
        fmt.Println("IP 在子网范围内 ✅")
    }

    // 计算广播地址(网络地址 | ^子网掩码)
    broadcast := make(net.IP, len(cidr.IP))
    for i := range cidr.IP {
        broadcast[i] = cidr.IP[i] | ^cidr.Mask[i]
    }

    // 遍历所有可用 IP(跳过网络地址和广播地址)
    for ip := cidr.IP.Mask(cidr.Mask); cidr.Contains(ip); inc(ip) {
        if ip.Equal(cidr.IP) || ip.Equal(broadcast) {
            continue
        }
        fmt.Println(ip) // 处理每个可用 IP
    }
}

// IP 地址自增(逐字节进位)
func inc(ip net.IP) {
    for i := len(ip) - 1; i >= 0; i-- {
        ip[i]++
        if ip[i] > 0 { break }
    }
}

关联笔记