13

Given:

  • a network address A: (172.17.0.0/16)
  • and an IP address from a host B: (172.17.0.2/16)

how can we say if B is in A?

All addresses are string variables in the following form: [IP address in dot-decimal notation]/[subnet mask]. Should I try to do it by manipulating strings (initial thoughts). Is there a different path?

Here is the same question for Python:

and another approach with Go:

UPDATE March 2022
for Go 1.18, check the answer below by blackgreen

tgogos
  • 19,659
  • 17
  • 88
  • 119

6 Answers6

21

The Go net package includes the following functions:

  • ParseCIDR: takes a string representing an IP/mask and returns an IP and an IPNet
  • IPNet.Contains: checks whether an IP is in a network

This should cover your needs.

Zoyd
  • 3,339
  • 1
  • 17
  • 27
11

UPDATE March 2022
for Go 1.18, check the answer below by blackgreen

Based on Zoyd's feedback...

https://play.golang.org/p/wdv2sPetmt

package main

import (
    "fmt"
    "net"
)

func main() {

    A := "172.17.0.0/16"
    B := "172.17.0.2/16"
    
    ipA,ipnetA,_ := net.ParseCIDR(A)
    ipB,ipnetB,_ := net.ParseCIDR(B)
    
    fmt.Println("Network address A: ", A)
    fmt.Println("IP address      B: ", B)
    fmt.Println("ipA              : ", ipA)
    fmt.Println("ipnetA           : ", ipnetA)
    fmt.Println("ipB              : ", ipB)
    fmt.Println("ipnetB           : ", ipnetB)
    
    
    fmt.Printf("\nDoes A (%s) contain: B (%s)?\n", ipnetA, ipB)

    if ipnetA.Contains(ipB) {
        fmt.Println("yes")
    } else {
        fmt.Println("no")
    }

}
tgogos
  • 19,659
  • 17
  • 88
  • 119
9

Based on tgogos's answer:

package main

import (
    "fmt"
    "net"
)

func main() {
    A := "172.17.0.0/16"
    B := "172.17.0.2"

    _, ipnetA, _ := net.ParseCIDR(A)
    ipB := net.ParseIP(B)

    fmt.Printf("\nDoes A (%s) contain: B (%s)?\n", ipnetA, ipB)

    if ipnetA.Contains(ipB) {
        fmt.Println("yes")
    } else {
        fmt.Println("no")
    }
}
SahilW
  • 101
  • 1
  • 3
4

Go 1.18

You can use the new package net/netip. The package defines the type netip.Addr which is a significant improvement over the old one:

Compared to the net.IP type, this package's Addr type takes less memory, is immutable, and is comparable (supports == and being a map key).

You can check if an IP is within a network with the method Prefix.Contains:

Contains reports whether the network p includes ip.

An IPv4 address will not match an IPv6 prefix. A v6-mapped IPv6 address will not match an IPv4 prefix. A zero-value IP will not match any prefix. If ip has an IPv6 zone, Contains returns false, because Prefixes strip zones.

An example:

package main

import (
    "fmt"
    "net/netip"
)

func main() {
    network, err := netip.ParsePrefix("172.17.0.0/16")
    if err != nil {
        panic(err)
    }

    ip, err := netip.ParseAddr("172.17.0.2")
    if err != nil {
        panic(err)
    }

    b := network.Contains(ip)
    fmt.Println(b) // true
}

If the IP you want to check also has the subnet mask, like 172.17.0.2/16 as in your example, you can use ip, err := netip.ParsePrefix again, and then obtain the address with ip.Addr() and pass that to Contains.

Code in the playground: https://go.dev/play/p/ikWRpPa1egI


For those who are interested in the implementation details, you can see this blog post by Brad Fitzpatrick (former member of the Go team). The net/netip package is based on that work.

iBug
  • 32,728
  • 7
  • 79
  • 117
blackgreen
  • 18,419
  • 19
  • 55
  • 71
  • 1
    Thanks for sharing! I've edited both my question and my answer and made them pointing to this. I'll keep an eye and as it gains more upvotes I'll make it the accepted one. – tgogos Mar 16 '22 at 10:01
  • 1
    @tgogos cool, I also added a link to Brad Fitzpatrick's post that talks about the implementation details, and why this is better than the old `net.IP`. It's really informative – blackgreen Mar 16 '22 at 10:07
  • I have massive CIDRs as a whole network, and I want to check whether one IP is in the network or not. I wonder if there is a simple approch to it. – AndyChow Apr 19 '22 at 11:05
2

The ipaddress-go Go library supports both IPv4 and IPv6 in a polymorphic manner and supports subnets, including methods that check for containment of an address or subnet in a containing subnet. It also allows for more than just CIDR subnets. Disclaimer: I am the project manager of that library.

Example code:

contains("172.17.0.0/16", "172.17.0.2/16")
contains("10.10.20.0/30", "10.10.20.3")
contains("10.10.20.0/30", "10.10.20.5")
contains("10.10.20.0/30", "10.10.20.0/31")
contains("1::/64", "1::1")
contains("1::/64", "2::1")
contains("1::/64", "1::/32")
contains("1::/64", "1::/112")
contains("1::3-4:5-6", "1::4:5")
contains("1-2::/64", "2::")
contains("bla", "foo")

func contains(network, address string) {
    one, two := ipaddr.NewIPAddressString(network),
        ipaddr.NewIPAddressString(address)
    fmt.Printf("%v contains %v %v\n", one, two, one.Contains(two))
}

Output:

172.17.0.0/16 contains 172.17.0.2/16 true
10.10.20.0/30 contains 10.10.20.3 true
10.10.20.0/30 contains 10.10.20.5 false
10.10.20.0/30 contains 10.10.20.0/31 true
1::/64 contains 1::1 true
1::/64 contains 2::1 false
1::/64 contains 1::/32 false
1::/64 contains 1::/112 true
1::3-4:5-6 contains 1::4:5 true
1-2::/64 contains 2:: true
bla contains foo false
Sean F
  • 3,868
  • 14
  • 25
0

Based on the answer above so people can easily copy and paste the code into their projects.

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    // True
    firstCheck, err := cidrRangeContains("10.0.0.0/24", "10.0.0.1")
    if err != nil {
        log.Println(err)
    }
    fmt.Println(firstCheck)
    // False
    secondCheck, err := cidrRangeContains("10.0.0.0/24", "127.0.0.1")
    if err != nil {
        log.Println(err)
    }
    fmt.Println(secondCheck)

}

// Check if a certain ip in a cidr range.
func cidrRangeContains(cidrRange string, checkIP string) (bool, error) {
    _, ipnet, err := net.ParseCIDR(cidrRange)
    if err != nil {
        return false, err
    }
    secondIP := net.ParseIP(checkIP)
    return ipnet.Contains(secondIP), err
}