如何检测两个 Golang 的 net.IPNet 对象是否相交?

huangapple go评论89阅读模式
英文:

How to detect if two Golang net.IPNet objects intersect?

问题

如何检测两个 Golang 的 net.IPNet 对象之间是否存在交集?

也就是说,如何检查第一个网络是否是第二个网络的子网或者第二个网络是否是第一个网络的子网。

Go 语言是否提供了用于此特定任务的实用函数?

请参考下面的测试代码。

package main

import (
	"fmt"
	"net"
)

func main() {
	_, net1, _ := net.ParseCIDR("1.1.1.1/24")
	_, net2, _ := net.ParseCIDR("1.1.0.2/16")
	_, net3, _ := net.ParseCIDR("1.1.1.3/25")
	_, net4, _ := net.ParseCIDR("1.2.0.4/16")

	test(net1, net2, true)
	test(net2, net1, true)
	test(net1, net3, true)
	test(net3, net1, true)
	test(net1, net4, false)
	test(net4, net1, false)
}

func test(n1, n2 *net.IPNet, expect bool) {
	result := intersect(n1, n2)
	var label string
	if result == expect {
		label = "good"
	} else {
		label = "FAIL"
	}
	fmt.Printf("test intersect(%v,%v)=%v expected=%v => %s\n", n1, n2, result, expect, label)
}

func intersect(n1, n2 *net.IPNet) bool {
	return false // FIXME WRITEME
}

Go Playground 上运行它。

英文:

How to detect if there is intersection between two Golang net.IPNet objects?

That is, how to check both if the first network is subnet of the second one OR if the second network is subnet of the first one.

Does Go provide any utility function ready for this specific task?

See test code below.

package main

import (
	"fmt"
	"net"
)

func main() {
	_, net1, _ := net.ParseCIDR("1.1.1.1/24")
	_, net2, _ := net.ParseCIDR("1.1.0.2/16")
	_, net3, _ := net.ParseCIDR("1.1.1.3/25")
	_, net4, _ := net.ParseCIDR("1.2.0.4/16")

	test(net1, net2, true)
	test(net2, net1, true)
	test(net1, net3, true)
	test(net3, net1, true)
	test(net1, net4, false)
	test(net4, net1, false)
}

func test(n1, n2 *net.IPNet, expect bool) {
	result := intersect(n1, n2)
	var label string
	if result == expect {
		label = "good"
	} else {
		label = "FAIL"
	}
	fmt.Printf("test intersect(%v,%v)=%v expected=%v => %s\n", n1, n2, result, expect, label)
}

func intersect(n1, n2 *net.IPNet) bool {
	return false // FIXME WRITEME
}

Run it on Go Playground

答案1

得分: 12

如果(根据你的测试用例似乎暗示)你不关心哪一边包含哪一边,只关心是否有重叠,那么这应该足够了。

func intersect(n1, n2 *net.IPNet) bool {
    return n2.Contains(n1.IP) || n1.Contains(n2.IP)
}
英文:

If (as your test cases seem to imply) you don't care about which side contains which, but just that there's overlap, this should be sufficient.

func intersect(n1, n2 *net.IPNet) bool {
    return n2.Contains(n1.IP) || n1.Contains(n2.IP)
}

答案2

得分: 4

你可以利用IP地址(net.IP)和子网掩码(net.IPMask)是包含二进制IP地址的字节切片([]byte)的事实。你可以对网络地址及其掩码使用常规的位运算符来确定一个网络是否是另一个网络的子网:

func intersect(n1, n2 *net.IPNet) bool {
    for i := range n1.IP {
        if n1.IP[i] & n1.Mask[i] != n2.IP[i] & n2.Mask[i] & n1.Mask[i] {
            return false
        }
    }
    return true
}

这个函数缺少一些基本的合法性检查(例如,当传入一个IPv4地址和一个IPv6地址时,它会出错),但这个示例应该足以理解其要点。

它在你提出的所有测试案例中都成功,除了第一个案例。但毕竟,1.1.0.2/16并不真正是1.1.1.1/24的子网(反过来才是)。

代码示例链接

英文:

You can use the fact that IP addresses (net.IP) and netmasks (net.IPMask) are simply byte slices ([]byte) that contain the binary IP addresses. You can use the usual bitwise-operators on the network addresses and their masks to determine if one network is a subnet of another:

func intersect(n1, n2 *net.IPNet) bool {
    for i := range n1.IP {
        if n1.IP[i] & n1.Mask[i] != n2.IP[i] & n2.Mask[i] & n1.Mask[i] {
            return false
        }
    }
    return true
}

This function is missing some basic sanity checks (for example, it would break when passed one IPv4 and one IPv6 address), but the example should be sufficient to get the gist of it.

It succeeds in all test cases from your question, except the first one. But after all, 1.1.0.2/16 is not really a subnet of 1.1.1.1/24 (it's the other way around).

> https://play.golang.org/p/Kur5n2hfLg

答案3

得分: 1

与其逐个比较每对CIDR,使用地址前缀树将每个CIDR与其他所有CIDR进行比较更高效。您可以使用IPAddress Go库来实现这一点。请参考以下代码。请注意,此代码同样适用于IPv6地址字符串。

import (
	"fmt"
	"github.com/seancfoley/ipaddress-go/ipaddr"
)

func main() {
	blockStrs := []string{
		"1.1.1.1/24", "1.1.0.2/16", "1.1.1.3/25", "1.2.0.4/16",
	}
	blocks := make([]*ipaddr.IPAddress, 0, len(blockStrs))
	for _, str := range blockStrs {
		blocks = append(blocks,
			ipaddr.NewIPAddressString(str).GetAddress().ToPrefixBlock())
	}
	trie := ipaddr.AddressTrie{}
	for _, block := range blocks {
		trie.Add(block.ToAddressBase())
	}
	fmt.Printf("trie is %v\n", trie)
	for _, block := range blocks {
		intersecting(trie, block)
	}
}

func intersecting(trie ipaddr.AddressTrie, cidr *ipaddr.IPAddress) {
	intersecting := make([]*ipaddr.IPAddress, 0, trie.Size())

	addr := cidr.ToAddressBase() // convert IPAddress to Address
	containingBlocks := trie.ElementsContaining(addr)
	containedBlocks := trie.ElementsContainedBy(addr)

	for block := containingBlocks.ShortestPrefixMatch(); 
		block != nil; block = block.Next() {
		next := block.GetKey().ToIP()
		if !next.Equal(cidr) {
			intersecting = append(intersecting, next)
		}
	}
	iter := containedBlocks.Iterator()
	for block := iter.Next(); block != nil; block = iter.Next() {
		next := block.ToIP()
		if !next.Equal(cidr) {
			intersecting = append(intersecting, next)
		}
	}
	fmt.Printf("CIDR %s intersects with %v\n", cidr, intersecting)
}

输出:

○ 0.0.0.0/0 (4)
└─○ 1.0.0.0/14 (4)
├─● 1.1.0.0/16 (3)
│ └─● 1.1.1.0/24 (2)
│   └─● 1.1.1.0/25 (1)
└─● 1.2.0.0/16 (1)
CIDR 1.1.1.0/24 intersects with [1.1.0.0/16 1.1.1.0/25]
CIDR 1.1.0.0/16 intersects with [1.1.1.0/25 1.1.1.0/24]
CIDR 1.1.1.0/25 intersects with [1.1.0.0/16 1.1.1.0/24]
CIDR 1.2.0.0/16 intersects with []
英文:

Instead of comparing each pair individually, it is more efficient to compare each CIDR with all the others, using an address trie. You can do this with the IPAddress Go library as follows. Note that this code works equally well with IPv6 address strings.

import (
	"fmt"
	"github.com/seancfoley/ipaddress-go/ipaddr"
)

func main() {
	blockStrs := []string{
		"1.1.1.1/24", "1.1.0.2/16", "1.1.1.3/25", "1.2.0.4/16",
	}
	blocks := make([]*ipaddr.IPAddress, 0, len(blockStrs))
	for _, str := range blockStrs {
		blocks = append(blocks,
			ipaddr.NewIPAddressString(str).GetAddress().ToPrefixBlock())
	}
	trie := ipaddr.AddressTrie{}
	for _, block := range blocks {
		trie.Add(block.ToAddressBase())
	}
	fmt.Printf("trie is %v\n", trie)
	for _, block := range blocks {
		intersecting(trie, block)
	}
}

func intersecting(trie ipaddr.AddressTrie, cidr *ipaddr.IPAddress) {
	intersecting := make([]*ipaddr.IPAddress, 0, trie.Size())

	addr := cidr.ToAddressBase() // convert IPAddress to Address
	containingBlocks := trie.ElementsContaining(addr)
	containedBlocks := trie.ElementsContainedBy(addr)

	for block := containingBlocks.ShortestPrefixMatch(); 
		block != nil; block = block.Next() {
		next := block.GetKey().ToIP()
		if !next.Equal(cidr) {
			intersecting = append(intersecting, next)
		}
	}
	iter := containedBlocks.Iterator()
	for block := iter.Next(); block != nil; block = iter.Next() {
		next := block.ToIP()
		if !next.Equal(cidr) {
			intersecting = append(intersecting, next)
		}
	}
	fmt.Printf("CIDR %s intersects with %v\n", cidr, intersecting)
}

Output:

○ 0.0.0.0/0 (4)
└─○ 1.0.0.0/14 (4)
├─● 1.1.0.0/16 (3)
│ └─● 1.1.1.0/24 (2)
│   └─● 1.1.1.0/25 (1)
└─● 1.2.0.0/16 (1)
CIDR 1.1.1.0/24 intersects with [1.1.0.0/16 1.1.1.0/25]
CIDR 1.1.0.0/16 intersects with [1.1.1.0/25 1.1.1.0/24]
CIDR 1.1.1.0/25 intersects with [1.1.0.0/16 1.1.1.0/24]
CIDR 1.2.0.0/16 intersects with []

huangapple
  • 本文由 发表于 2016年1月12日 03:08:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/34729158.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定