做一个算地址的工具
目标
使用 Python 编写一个工具,根据地址和掩码计算网络地址、广播地址、可用地址范围和主机数, 再利用该工具判断地址是否属于同一子网、对地址段分类并检查重叠。最后根据所需主机数 选择合适的前缀,并留下设计说明。
为什么重要
防火墙规则、网络策略和 VPC 设计全都建立在这些计算之上。 但如果反复手算,迟早一定会出错,而错误通常要等到故障发生后才会暴露。 因此,更好的做法是制作一次工具并持续复用。
尤其需要注意 /31 和 /32。如果主机数一律按“总数减 2”计算,
两者会分别得到 0 和 -1,而工具不会报错,只会原样返回错误结果。
这是一种检查器无法发现的谎言,只有了解例外的人才能避免。
步骤
- 手工计算三个地址段,并逐行写入
/root/cidr/01-hand.txt。地址段为192.168.40.130/26、10.10.6.75/20、172.20.200.9/23,每行必须包含 CIDR、网络地址、广播地址、第一个主机地址、最后一个主机地址和主机数量。 - 创建
/root/cidr/subnet.py。python3 subnet.py 203.0.113.77/28与python3 subnet.py 203.0.113.77 255.255.255.240必须给出相同答案,输出为prefix=network=broadcast=first=last=hosts=六行。将两次运行结果保存到/root/cidr/02-tool.txt。 - 修改工具,使其正确处理
/31和/32。两者均为broadcast=none;/31的两个地址都可作为主机(hosts=2),/32只有自身一个地址(hosts=1)。将运行10.0.0.5/31与10.0.0.5/32的结果及其原因写入/root/cidr/03-edge.txt。 - 创建
/root/cidr/same.py。python3 same.py <주소/프리픽스> <상대주소>只能输出same或different其中一个单词。运行五组地址并保存到/root/cidr/04-same.txt,采用주소/프리픽스 상대주소 판정格式。 - 创建
/root/cidr/classify.py。python3 classify.py <주소>输出privatelink-localloopbackcgnatmulticastpublic中的一个。运行九个地址并保存到/root/cidr/05-class.txt,采用주소 분류格式。 - 将
10.60.8.0/22拆分为/24网络,在/root/cidr/06-split.txt中逐行按네트워크/24 first= last= hosts=格式记录。 - 判断候选网络是否与现有网络重叠,并写入
/root/cidr/07-overlap.txt。重叠时写후보 overlap=겹치는대역,不重叠时写후보 free。 - 在
/root/cidr/08-report.md中编写设计说明。内容必须包括满足三种主机数需求(120 台、600 台、30 台)的最小前缀、连续分配地址段的好处,以及何时使用/31和/32。
参考
- 只使用标准库中的
ipaddress。此 Pod 无法访问外网,因此不能执行pip install。 ipaddress.ip_network("10.10.6.75/20", strict=False)即使传入主机地址,也会将其识别为该地址所属的网络。使用strict=True(默认值)则会报错。- 第 4 步的五组地址:
10.30.4.9/22 10.30.7.250、10.30.4.9/23 10.30.7.250、192.168.9.60/26 192.168.9.65、172.20.15.1/12 172.31.255.254、100.64.3.1/10 100.127.0.9。前两组地址相同,只有掩码不同。 - 第 5 步的九个地址:
10.4.4.4、172.31.255.1、172.32.0.1、192.168.0.1、169.254.10.9、127.0.0.53、100.100.5.5、224.0.0.251、8.8.8.8。 - 第 7 步的现有网络:
10.10.0.0/16、10.20.0.0/16、10.244.0.0/16、172.17.0.0/16。候选网络:10.20.5.0/24、10.100.0.0/16、172.17.30.0/24、10.245.0.0/16。 - 172 私有地址段的边界(因为是
/12,所以范围为 172.16~172.31)以及100.64.0.0/10(CGNAT)是容易出错之处。 - 评分器还会使用**说明中没有给出的值再次运行你编写的工具。**如果把答案硬编码进去,会立即失败。
手工计算三个地址段
前缀不是 8 的倍数时最容易出错。先计算块大小(2 的主机位数次方),再从其倍数中找出包含该地址的范围,其起点就是网络地址。
例如,/26 的块大小为 64,因此边界为 0、64、128、192。130 位于从 128 开始的块中。
使用 python3 -c "import ipaddress; n=ipaddress.ip_network('192.168.40.130/26', strict=False); print(n.network_address, n.broadcast_address, n.num_addresses-2)" 进行复核。
制作计算工具
/26 与 255.255.255.192 表达的是同一含义。如果传入两个参数,可以将它们拼接成 주소/마스크,再交给同一个函数。
例如 ipaddress.ip_network("203.0.113.77 255.255.255.240".replace(' ', '/'), strict=False)。
评分器会原样查找输出中的键名(prefix= network= broadcast= first= last= hosts=),因此不要修改。主机数等于总地址数减去网络地址和广播地址。
处理 /31 与 /32 的例外
num_addresses - 2 对 /31 会得到 0,对 /32 会得到 -1。工具不会报错,只会原样返回这些值,因此只有了解例外的人才能避开错误。
当前缀大于等于 31 时,请进入另一个分支。在该分支中不设置广播地址(broadcast=none),第一个主机地址就是网络地址,最后一个主机地址是该网络的最后一个地址,主机数等于总地址数。
请在说明文件中写明哪一份 RFC 针对哪类链路允许这种用法。
判断是否属于同一子网
判断只需一行——对方地址是否位于我的网络中。即 ipaddress.ip_address(other) in ipaddress.ip_network(mine, strict=False)。
前两组的地址相同,只有掩码不同。在这里可以看出,只要掩码错一位,旁边的计算机就会变成位于地球另一端。
输出必须只有 same 或 different 一个单词。评分器还会使用说明中没有给出的地址组再询问一次。
对地址段的性质分类
建立规则列表,从上到下寻找匹配项;如果均不匹配,则为 public。
需要的地址段如下——环回 127.0.0.0/8、链路本地 169.254.0.0/16、CGNAT 100.64.0.0/10、组播 224.0.0.0/4、私有 10.0.0.0/8·172.16.0.0/12·192.168.0.0/16。
172.32.0.1 和 100.100.5.5 是陷阱。172 私有地址段是 /12,范围只从 172.16 到 172.31;100.64.0.0/10 是运营商分配给用户的地址,既不属于私有地址,也不属于公网地址。
细分地址段
把 /22 划分为 /24 时,会得到 2 的 (24-22) 次方,也就是四个网络。ipaddress.ip_network("10.60.8.0/22").subnets(new_prefix=24) 会依次给出这四个网络。
每行应包含网络(含前缀)、第一个主机地址、最后一个主机地址和主机数量。每个子网都有 254 个主机地址。
检查地址段是否重叠
靠肉眼判断重叠一定会出错。ipaddress 的 overlaps() 会告诉你两个网络是否共享哪怕一个地址。
列表中包含 172.17.0.0/16 是有原因的。Docker 的默认网桥使用该地址段;如果公司内网也使用 172.17,容器就无法访问内网服务器。
重叠时写 후보 overlap=겹치는대역,不重叠时写 후보 free。
留下设计说明
找到能够容纳“所需主机数加 2”的最小 2 的幂即可。以 120 台为例,至少需要 122 个地址,因此选择 128,即 7 个主机位,前缀为 /25。
连续分配是指可以将相邻网络合并为一条路由进行通告。这也是有类网络最终消失的实际原因。
最好一并注明,在云环境中还需要扣除更多保留地址。