使用 Objective-C 确定 IPv6 地址是否在范围内

Determine if IPv6 Address is within range using Objective-C

我正在尝试找出最有效的方法来确定 IPv6 地址是否在某个地址范围内(包括起始地址和结束地址)。考虑到起始地址和结束地址之间可能存在的大量地址,我当然不想遍历范围以寻找匹配项。

我收到一些建议,尝试进行这种范围匹配不是一个好方法,或者 practical/useful 对于 IPv6 来说不是,我应该研究前缀匹配。在走那条路之前,我想看看可能存在哪些有效的解决方案来确定 IPv6 地址是否在给定范围内。

我不是网络专家,所以请原谅我在这件事上的一些无知。

我发现 this Stack Overflow post 有一个未被接受的答案,表明最好的方法是将 IPv6 地址的所有十六进制转换为二进制,然后进行比较。这个 post 有两个问题:1. 我不确定如何将 PHP 脚本改编成 Objective-C,以及 2. 这个答案有一些赞成票,但不是一个可以接受的答案所以我不确定这是否值得追求。

我还发现 this other Stack Overflow post 似乎很适合将十六进制转换为二进制,但同样,我不确定如何比较生成的二进制值以确定范围。请注意,我使用的是问题中的示例代码,而不是答案。使用示例 IPv6 地址 fe80::34cb:9850:4868:9d2c:

fe80 = 1111111010000000
0000 = 0
0000 = 0
0000 = 0
34cb = 11010011001011
9850 = 1001100001010000
4868 = 100100001101000
9d2c = 1001110100101100

我试图实现的最终结果是能够在 Mac 的 IP 地址在定义范围内时做出反应。我已经有了适用于 IPv4 地址的解决方案,但我想为我的免费 macOS 应用程序 Amphetamine 添加 IPv6 支持。在此先感谢您的帮助。这就是我用于 IPv4 范围检查的内容...不确定它是否可以适用于 IPv6:

- (bool) ipAddress: (NSString *)ipAddress isBetweenIpAddress: (NSString *)rangeStart andIpAddress: (NSString *)rangeEnd
{
    uint32_t ip = [self convertIpAddress: ipAddress];
    uint32_t start = [self convertIpAddress: rangeStart];
    uint32_t end = [self convertIpAddress: rangeEnd];
    return ip >= start && ip <= end;
}

- (uint32_t) convertIpAddress: (NSString *) ipAddress
{
    struct sockaddr_in sin;
    inet_aton([ipAddress UTF8String], &sin.sin_addr);
    return ntohl(sin.sin_addr.s_addr);
}

这道题需要两步:(1)从字符串中解析出地址和范围start/end; (2) 比较数值。可用于这两个部分的 API 都是基于 C 的,因此对用户来说不是特别友好。

  • inet_pton 可用于解析字符串并将其放入 in6_addr 结构中。
  • in6_addr包含一个.s6_addr,这是一个16字节的大端数组,
  • ...可以与另一个地址 memcmp.
  • 进行比较

放在一起:

NSString *rangeStart = @"2001:db8::";
NSString *rangeEnd = @"2001:db8::ffff:ffff:ffff:ffff";

NSString *address = @"2001:db8::abc";

// Parse strings into in6_addrs
struct in6_addr rangeStartAddr;
struct in6_addr rangeEndAddr;
struct in6_addr addr;
if (inet_pton(AF_INET6, rangeStart.UTF8String, &rangeStartAddr) != 1) {
    abort();
}
if (inet_pton(AF_INET6, rangeEnd.UTF8String, &rangeEndAddr) != 1) {
    abort();
}
if (inet_pton(AF_INET6, address.UTF8String, &addr) != 1) {
    abort();
}

// Use memcmp to compare binary values
if (memcmp(rangeStartAddr.s6_addr, addr.s6_addr, 16) <= 0
    && memcmp(addr.s6_addr, rangeEndAddr.s6_addr, 16) <= 0) {
    NSLog(@"In range");
} else {
    NSLog(@"Not in range");
}

将每个地址转换为单个 __uint128_t 数字也是可能的,但很麻烦。但是,如果您需要做的只是比较,memcmp 似乎就足够了。


同样可以在 Swift 中使用 UnsafeRawBufferPointer.lexicographicallyPrecedes 而不是 memcmp:

import Darwin

extension in6_addr {
    init?(_ str: String) {
        self.init()
        if inet_pton(AF_INET6, str, &self) != 1 {
            return nil
        }
    }
}

extension in6_addr: Comparable {
    public static func ==(lhs: in6_addr, rhs: in6_addr) -> Bool {
        return withUnsafeBytes(of: lhs) { lhsBytes in
            withUnsafeBytes(of: rhs) { rhsBytes in
                lhsBytes.prefix(16).elementsEqual(rhsBytes.prefix(16))
            }
        }
    }
    public static func <(lhs: in6_addr, rhs: in6_addr) -> Bool {
        return withUnsafeBytes(of: lhs) { lhsBytes in
            withUnsafeBytes(of: rhs) { rhsBytes in
                lhsBytes.prefix(16).lexicographicallyPrecedes(rhsBytes.prefix(16))
            }
        }
    }
}

var rangeStartAddr = in6_addr("2001:db8::")!
var rangeEndAddr = in6_addr("2001:db8::ffff:ffff:ffff:ffff")!
var addr = in6_addr("2001:db8::abc")!

(rangeStartAddr...rangeEndAddr).contains(addr)