10

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

Everton
  • 12,589
  • 9
  • 47
  • 59

3 Answers3

12

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)
}
dahc
  • 534
  • 5
  • 6
  • How so? Pretty sure the final value in the IP will **never** be the same... You need to compare subnet masks or a substring of the IP. – evanmcdonnal Jan 11 '16 at 20:07
  • 2
    @evanmcdonnal I'm not sure what you mean. IPNet.IP is the network number, not the IP that was parsed, and we just need containment. – dahc Jan 11 '16 at 20:13
  • Should work fine in that case. Guess I need to read the docs. – evanmcdonnal Jan 11 '16 at 20:24
4

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

helmbert
  • 35,797
  • 13
  • 82
  • 95
  • "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)." Thanks. However, I really need to detect any intersection, if n1 is subnet of n2, or if n2 is subnet of n1. I'll update the question to make this clear. – Everton Jan 11 '16 at 20:03
1

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 []
Sean F
  • 4,344
  • 16
  • 30