云端 Mac 上的 iOS IP 字面量解析验收:地址族与规范化

云端 Mac 上的 iOS IP 字面量解析验收:地址族与规范化

你在 Windows 上改好连接配置,把任务交给云端 Mac 验收,却发现同一台设备的 IPv6 地址被当成两个目标:一处写完整零段,另一处用了双冒号。若连接列表、去重逻辑与访问规则各自比较原始字符串,结果就会分叉。下面用 Swift 和系统解析函数建立一套离线检查,不需要真实服务器,也不依赖网络是否畅通。

先限定输入是什么

本例只接收 IP 地址字面量,不接收域名、端口或完整 URL。IPv4 使用四段十进制形式;IPv6 接受系统支持的标准表达。前后空白直接拒绝,不做静默修剪,避免界面显示值与实际连接值不一致。

IPv6 的方括号属于 URL 的主机表示方式,不是地址本身;%en0 这样的作用域标识也不交给本例解析器。需要连接链路本地地址时,应在另一层明确接收地址与接口信息,不能随手截掉百分号后继续连接。

解析成功只说明文本能表示一个地址,不说明这个地址允许访问,也不说明对应设备在线。

样本中的文档地址仅用于本地解析,脚本不会向它们发起连接。

用地址族与字节保存结果

在云端 Mac 的项目目录创建 ip-contract.swift。先确认 xcrun swiftc --version 能输出编译器信息,再写入以下代码。这里不使用可能解析主机名的接口,避免把 DNS 行为混进字面量校验。

import Darwin
import Foundation

struct IPAddress: Equatable {
    let family: Int32
    let bytes: [UInt8]
}

func parseIP(_ text: String) -> IPAddress? {
    guard !text.isEmpty,
          text.utf8.count <= 45,
          !text.utf8.contains(0) else {
        return nil
    }

    var v4 = in_addr()
    let result4 = text.withCString {
        inet_pton(AF_INET, $0, &v4)
    }
    if result4 == 1 {
        return IPAddress(
            family: AF_INET,
            bytes: withUnsafeBytes(of: &v4) { Array($0) }
        )
    }

    var v6 = in6_addr()
    let result6 = text.withCString {
        inet_pton(AF_INET6, $0, &v6)
    }
    if result6 == 1 {
        return IPAddress(
            family: AF_INET6,
            bytes: withUnsafeBytes(of: &v6) { Array($0) }
        )
    }

    return nil
}

func require(_ condition: Bool, _ message: String) {
    if !condition {
        fatalError(message)
    }
}

let good = [
    "192.0.2.1",
    "2001:db8::1",
    "2001:0DB8:0000:0000:0000:0000:0000:0001",
    "::",
    "::1",
    "::ffff:192.0.2.1"
]

for text in good {
    require(parseIP(text) != nil, "Rejected valid literal")
}

let bad = [
    "",
    " 192.0.2.1",
    "192.0.2.1\n",
    "192.0.2.256",
    "192.0.2",
    "192.0.02.1",
    "0xC0000201",
    "[2001:db8::1]",
    "fe80::1%en0",
    "2001:::1",
    "192.0.2.1\u{0}ignored"
]

for text in bad {
    require(parseIP(text) == nil, "Accepted invalid literal")
}

let short = parseIP("2001:db8::1")!
let expanded = parseIP(
    "2001:0DB8:0000:0000:0000:0000:0000:0001"
)!
require(short == expanded, "Equivalent IPv6 mismatch")
require(short.bytes.count == 16, "Invalid IPv6 length")

let v4 = parseIP("192.0.2.1")!
let mapped = parseIP("::ffff:192.0.2.1")!
require(v4.bytes.count == 4, "Invalid IPv4 length")
require(v4 != mapped, "Address families collapsed")

长度上限是本解析契约的字节预算,不是对所有带作用域地址的通用限制。地址字节直接取自系统结构体,保持网络字节序;不要再把整块数据解释成本机整数来比较。

编译执行,再检查失败方式

把脚本编译成独立程序,既能放进构建任务,也能避免不同启动方式影响解释执行:

set -eu
mkdir -p .ip-check
xcrun swiftc ip-contract.swift -o .ip-check/ip-contract
.ip-check/ip-contract

正常通过时没有输出,退出码为零;任一契约失败都会以非零状态结束。这里使用显式检查而非 assert,因为优化配置可能移除断言。

先临时把等价比较改成不等,确认任务确实失败,再还原。这个负向演练能发现流水线忽略退出码、仅保存日志却仍显示成功的问题。不要只确认脚本“跑过一次”。

把规范化与访问政策分开

等价关系不能随意扩大

同一 IPv6 地址的压缩写法与完整写法应相等,但 IPv4 和 IPv4 映射 IPv6 在本模型里仍是不同地址族。如果业务要把二者归成同一逻辑目标,应新增显式转换,并测试转换前后的访问规则,不能靠删除字符串前缀实现。

展示时可以使用 inet_ntop 输出统一文本;去重与授权则继续使用解析后的模型。不要把某个系统版本输出的字符串形式,当作跨版本永久不变的存储协议。

域名需要独立审核

域名不能通过这个函数,并不代表域名无效,只代表它不属于此输入类型。若产品也支持域名,应提供独立入口,并在解析后检查实际准备连接的地址集合。仅检查用户输入的主机文本,不能覆盖解析结果变化。

回环、私有、链路本地等地址的允许范围应由产品用途决定。设备调试工具与公网资源抓取服务的规则不同,不宜共享一个含义模糊的“安全地址”布尔值。

将失败样本留成回归资产

把解析器放进共享源码后,分别在 macOS 命令行检查与 iOS 测试目标中执行同一组向量。前者适合在远程环境快速筛错,后者负责确认目标系统行为;不能用主机上的通过结果替代目标平台验收。

每次修复都补上原始输入、预期地址族和预期结果。对嵌入空字节尤其要保留原始字节,日志展示可能把它隐藏。C 字符串会在空字节处结束,缺少入口检查就可能只验证到一段合法前缀。

最后检查三件事:不同文本能否归到同一地址、不同地址族是否保持边界、非法输入是否在连接前失败。只要这些契约稳定,后续增加界面校验或连接策略时,就有一组可以重复执行的依据。

常见问题

两个 IPv6 字符串不同,是否就代表不同地址?

不是。压缩零段、前导零和十六进制大小写都可能不同。应先解析,再比较地址族与网络字节序的地址字节。

通过 inet_pton 校验后,可以直接允许网络连接吗?

不能。解析成功只说明地址文本有效;回环、私有、链路本地和映射地址仍需按业务策略判断,域名解析结果也要单独审核。

把任务交给独享的云端 Mac

租用一台独享物理 Mac mini,按你的工作安排选择节点与计费周期。开通后在控制台查看连接信息;使用中遇到问题,可在控制台提交工单。

选择云端 Mac 方案

按你的工作安排选择节点

先核对配置与周期,再开通你自己的独享物理 Mac mini。