294 lines
11 KiB
Markdown
294 lines
11 KiB
Markdown
---
|
||
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.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
|
||
```
|
||
|
||
```mermaid
|
||
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
|
||
|
||
```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 | 将 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 中的子网判断
|
||
|
||
```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 }
|
||
}
|
||
}
|
||
```
|
||
|
||
## 关联笔记
|
||
|
||
- [[hhs/NETWORK/03-网络层/01-IPv4首部与分段重组]] — IP 地址在 IPv4 包中的位置
|
||
- [[hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部]] — IPv6 同样使用 CIDR 表示法
|
||
- [[hhs/NETWORK/03-网络层/08-NAT原理与应用]] — NAT 通常映射到私有地址空间
|