在 Golang 中检查 IP 地址切片中的 IP 的有效方法

Ham*_*FzM 1 arrays ip network-programming go slice

我正在 Golang 中开发一个网络应用程序。我有一个 IP 地址片段。每次收到请求时,我都会用来net.LookupIP(host)查找返回net.IP. 比较这些的最佳方法是什么?

顺便说一句,在 Python 中我们有一个set数据结构,这使得上面的问题很容易解决,但是 Go 呢?

icz*_*cza 6

有了“一套”

\n\n

构建我们的集合

\n\n

Go 中没有内置Set类型,但你可以优雅地使用 amap[Type]bool作为集合,例如:

\n\n
// Create a set with 2 values in it: [1, 2]\nm := map[int]bool{1: true, 2: true}\n\n// Test an element:\nfmt.Println(m[1]) // true\nfmt.Println(m[3]) // false\n\n// Set an element:\nm[3] = true\nfmt.Println(m[3]) // true\n\n// Delete an element:\ndelete(m, 1)\nfmt.Println(m[1]) // false\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:我们利用了这样一个事实:如果键不在映射中,则对映射进行索引会导致值类型的零值false(在 的情况下)bool,正确地表明该元素不在映射(集合)中。

\n\n

在Go Playground上尝试一下。

\n\n

注意#2:有一些技巧可以使将映射作为集合处理的代码更短,您可以在这个答案中检查它们:检查值是否在列表中

\n\n

net.IP在集合中使用

\n\n

现在我们只需要一个表示 a 的类型net.IP,它可以用作映射中的键类型(有关映射键类型的构成,请参阅此问题:How can I Prevent a type being used as a map key?)。

\n\n

不幸的是net.IP它本身不符合条件,因为它是一个切片:

\n\n
type IP []byte\n
Run Code Online (Sandbox Code Playgroud)\n\n

而且切片没有可比性。有关详细信息,请参阅此问题:Hash with key as an array type以及:Why have arrays in Go?

\n\n

一个简单的方法是将其转换为规范string值,我们就完成了。为此,我们可以简单地将 IP 的字节转换为十六进制string。但是 IPv4 地址可能会呈现为 IPv6,因此我们应该首先将其转换为 IPv6:

\n\n
func Key(ip net.IP) string {\n    return hex.EncodeToString(ip.To16())\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:IP 地址的字节可能不是有效的 UTF-8 编码string(这是 Gostring在内存中存储 s 的方式),但stringGo 中的值表示任意字节序列,因此以下方法也可以工作,更简单且更高效:

\n\n
func Key(ip net.IP) string {\n    return string(ip.To16())  // Simple []byte => string conversion\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以使用这样的IP字符串作为密钥。使用 IP 填充您的地图以进行检查:

\n\n
// Populate forbidden IPs:\nforbIPs := map[string]bool{\n    Key(ip1): true,\n    Key(ip2): true,\n}\n\n// Now check a single IP:\nipToCheck := ...\nif forbIPs[Key(ipToCheck)] {\n    fmt.Println("Forbidden!")\n} else {\n    fmt.Println("Allowed.")\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您有多个 IP 要检查(由 返回net.LookupIP()),则这是一个for循环:

\n\n
ips, err := net.LookupIP(host)\n// Check err\nfor _, ip := range ips {\n    if forbIPs[Key(ip)] {\n        // FORBIDDEN!\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

备用钥匙类型

\n\n

请注意,\xe2\x80\x93 如上所述,\xe2\x80\x93 切片不可比较,但数组可以。所以我们也可以使用数组作为键。它可能是这样的:

\n\n
func Key(ip net.IP) (a [16]byte) {\n    copy(a[:], ip)\n    return\n}\n\n// And the IP set:\nforbIPs := map[[16]byte]bool{\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

备择方案

\n\n

排序切片

\n\n

或者,我们可以简单地将禁止的 IP 存储在一个切片中[]net.IP,并对其进行排序。如果它是排序的,我们可以使用二分搜索来查找其中的IP(标准库sort.Search())。

\n\n

O(log2(n))是的,与O(1)上面的(哈希)映射解决方案的复杂性相比,二分搜索具有复杂性。但这种替代方案还有另一个优点:

\n\n

枚举各个 IP 并不总是可行的。有时(通常)列出 IP 范围更容易。第一个解决方案对于处理 IP 范围不可行,但此解决方案可能是:您也可以及时找到覆盖 IP 地址的范围O(log2(n))

\n