'Go - check if IP address is in a network

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



Solution 1:[1]

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.

Solution 2:[2]

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")
    }

}

Solution 3:[3]

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")
    }
}

Solution 4:[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.

Solution 5:[5]

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

Solution 6:[6]

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
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zoyd
Solution 2
Solution 3 SahilW
Solution 4 iBug
Solution 5
Solution 6