一些鲜为人知的 IP 地址怪异写法

原文链接:blog.dave.tf/post/ip-add...

原文作者:David Anderson

译者:菜小鸟魔王

为了开发一款高性能的 IPv4/IPv6 解析器,我首先构建了一个虽然效率不高但能确保正确的解析器作为参考。在此过程中,我意外发现了许多鲜为人知的 IP 地址怪异写法,下面我就带大家一探究竟!

先从标准格式入手:IPv4 的"192.168.0.1"采用 dotted quad(纯十进制点分四段的标准格式 )(业界也称 dotted decimal(纯十进制数值+点号分隔)),以点号分隔的各数值对应 1 字节;IPv6 的"1:2:3:4:5:6:7:8"则采用冒号分隔的十六进制格式,每组对应 2 字节数据。

IPv6 的复杂性首先体现在零压缩规则上。标准格式中连续的零值区块可以用双冒号"::"简写。例如"1:2::3:4"实际展开为"1:2:0:0:0:0:3:4",这种语法允许将任意长度的 16 位零值区块压缩为双冒号表示。

有趣的是,出于兼容性的考虑,IPv6 地址的最后 32 位允许采用 IPv4 的 dotted quad 格式。这种设计使得 IPv4 地址可以直接拼接在 IPv6 地址尾部!例如,1:2:3:4:5:6:77.77.88.88 实际上等效于 1:2:3:4:5:6:4d4d:5858(将十进制转换为十六进制后)。

更妙的是,缩写符号(::)也能与之结合使用。譬如 fe80::1.2.3.4 会被解析为 fe80:0:0:0:0:0:102:304。

但"::"的存在给地址解析带来了一种特殊的边界情况:当"::"出现在地址首尾时,需处理"空侧"的抽象范围,解析复杂度更高。例如"::1"展开后是 0:0:0:0:0:0:0:1,而"1::"则对应 1:0:0:0:0:0:0:0,甚至使用单独的"::"代表全零的地址。虽然这是双冒号逻辑的自然延伸,但确实增加了地址解析器的编写复杂度。

最后补充一个书写规范:严格来说,每个冒号分隔的十六进制段应当包含 4 位数字,但前导零可以省略(正如前文示例所示)。在最规范的全写形式中,"::"等价于 0000:0000:0000:0000:0000:0000:0000:0000,密集恐惧症患者请见谅。

以上便是 IPv6 的核心规则,现在让我们将目光投向IPv4!

有趣的是,在 IPv6 需要为其奇怪的"trailing dotted quad"语法制定规则之前,IPv4 的文本表示形式从未在任何文档中被正式标准化。因此,这实际上只是一种事实标准,其核心主要在于"4.2BSD 操作系统如何解析?"以及"其他操作系统在参考 4.2BSD 时保留了哪些规则?"

接下来的内容请做好心理准备,因为 4.2BSD 确实有一些非常古怪的观点!以 192.168.140.255 为例。这是一个大家看一眼就会说:"是的,这是一个 IPv4 地址"的例子。但我们还可以用哪些方式表示这个地址呢?

这也是同一个 IP 地址:3232271615。其原理是将 IP 地址的 4 个字节视为大端序无符号 32 位整数 (big-endian unsigned 32-bit integer)进行解析并输出。这引出了一个经典的小技巧:如果你尝试访问 http://3232271615,Chrome 会加载 http://192.168.140.255。

Okey,这也还算合理,对吧?IPv4 地址包含 4 个字节,将其作为单个数字输出虽然对人类不太友好,但大致上还是合理的,不是吗?

那 0300.0250.0214.0377 呢?这仍然是同一个地址。还是 dotted quad 形式,只是每个字段都采用八进制表示。

既然支持八进制,是不是还会支持十六进制。你的直觉是对的!根据 4.2BSD 的规则,192.168.140.255 还可以表示为 0xc0.0xa8.0x8c.0xff。

请记住,在 CIDR(无类别域间路由)出现之前,IPv4 地址分为 A 类、B 类或 C 类。这是一个奇怪的时代!

而那个时代甚至也影响到了 IP 地址的表示!我们熟悉的 192.168.140.255 本质上是「C 类」表示法。你也可以用「B 类」表示法写作 192.168.36095,或用「A 类」表示法写作 192.11046143。其本质是将地址的末尾字节合并为 16 位或 24 位整数字段。

这也解释了为何像 ping 这样的工具会接受 127.1 这种看似不规范的地址(对应 127.0.0.1)。与 IPv6 不同,它不会进行「缺失字段补零」的扩展,而是将 127.1 视为「A 类」表示法 ------ 即「网络 127 的主机 1」,其中 1 是一个 24 位的数字。

最后,还有一个未被明确定义的争议点:IPv4 地址的每个四元组是否允许无限前导零?还是最多 3 位?001.002.003.004 是公认有效的。但 0000000001.0000000002.0000000003.000000004 呢?

你可能还会疑惑:既然在计算机领域,前导零常被用于表示八进制(如前文所述(0300.0250.0214.0377 )),这些数字是否需要按八进制解析?也要看情况!有些系统两者都支持,但大多数较新的系统已废弃八进制/十六进制表示法,而是将前导零按十进制处理。

前导零的争议也波及到了 IPv6。例如,000001::00001.00002.00003.00004 是否合法(对应常规形式 1::1.2.3.4 或 1::102:304)?大多数较新的解析器似乎允许无限前导零,可能是因为它们依赖的某些「解析整数」的代码库实现了这种行为。

这个参考解析器(虽然效率不高)目前做了大量精简,舍弃了很多过时的规范,只专注支持我认为合理且必要的功能范围。具体来说它能处理:

  • 经典的 IPv4 关于IPv6地址中内嵌点分十进制的写法(例如::192.168.0.1),允许任意数量的前导零

  • 不支持处理 A/B 类网络划分标记法,或十六进制/八进制表示法

  • 不支持处理"uint32整型转写格式"(如将地址转换为3232235521这类单数字形式)

  • 对于 IPv6,它能理解规范的冒号分隔十六进制格式,以及 :: 符号和尾部 IPv4 风格(其中尾部 IPv4 遵循 "dotted decimal")。每个字段允许任意数量的前导零。

关于 IPv6 地址中内嵌 "dotted decimal" 的写法(例如::192.168.0.1),我还在犹豫。我选择作为参考的解析器(Go 的 net.ParseIP)能识别这种格式,但在实际应用场景中这种方式显得非常鸡肋。回想IPv6的初期阶段,这种写法被设想为一种过渡方案------通过在原有IPv4地址前添加双冒号(如::1.2.3.4),就能将其"升级"为IPv6地址。但现在的过渡机制已不再提供如此直白的转换方式,导致这类混合表示法在真实网络环境中近乎绝迹。

相关推荐
runnerdancer几秒前
解构shopify,从0到1实现落地页低代码编辑器
前端
A阳俊yi17 分钟前
Spring Boot日志配置
java·spring boot·后端
WEI_Gaot19 分钟前
react19 的项目创建和组件使用
前端·react.js
起风了布布22 分钟前
配置版本化是怎么实现的
后端
资深前端外卖员23 分钟前
【nodejs高可用】前端APM应用监控方案 + 落地
前端·后端
OhBonsai23 分钟前
Shader 图像处理1_ToneMap技术处理过曝
前端
突头小恐龙23 分钟前
Chrome devTools - Lighthouse
前端·javascript·chrome
谦谦橘子23 分钟前
手写tiny webpack,理解webpack原理
前端·javascript·webpack
土豆125025 分钟前
Tailwind CSS 精通指南:提升效率、可维护性与最佳实践
前端·css
花生了什么树lll25 分钟前
面试中被问到过的前端八股(四)
前端·面试