Go/GoLang check IP address in range
Asked Answered
N

8

36

In Go/GoLang, what is the fastest way to check if an IP address is in a specific range?

For example, given range 216.14.49.184 to 216.14.49.191, how would I check if a given input IP address is in that range?

Neonatal answered 9/11, 2013 at 20:53 Comment(3)
How are your ranges represented?Bivalve
Start: "216.14.49.184", End: "216.14.49.191". I found a solution online for CIDR, but that's not the data that I'm working with.Neonatal
Just wondering, if you broke those strings to components and compared them numerically, would that produce correct result?Bivalve
D
46

IP addresses are represented as bigendian []byte slices in go (the IP type) so will compare correctly using bytes.Compare.

Eg (play)

package main

import (
    "bytes"
    "fmt"
    "net"
)

var (
    ip1 = net.ParseIP("216.14.49.184")
    ip2 = net.ParseIP("216.14.49.191")
)

func check(ip string) bool {
    trial := net.ParseIP(ip)
    if trial.To4() == nil {
        fmt.Printf("%v is not an IPv4 address\n", trial)
        return false
    }
    if bytes.Compare(trial, ip1) >= 0 && bytes.Compare(trial, ip2) <= 0 {
        fmt.Printf("%v is between %v and %v\n", trial, ip1, ip2)
        return true
    }
    fmt.Printf("%v is NOT between %v and %v\n", trial, ip1, ip2)
    return false
}

func main() {
    check("1.2.3.4")
    check("216.14.49.185")
    check("1::16")
}

Which produces

1.2.3.4 is NOT between 216.14.49.184 and 216.14.49.191
216.14.49.185 is between 216.14.49.184 and 216.14.49.191
1::16 is not an IPv4 address
Doddered answered 9/11, 2013 at 22:42 Comment(0)
E
39

This is already in the stdlib in the "net" package as a function called net.Contains. You dont need to rewrite code that already exists!

See documentation here.

To use it you just have to parse the desired subnets

network := "192.168.5.0/24"
clientips := []string{
    "192.168.5.1",
    "192.168.6.0",
}
_, subnet, _ := net.ParseCIDR(network)
for _, clientip := range clientips {
    ip := net.ParseIP(clientip)
    if subnet.Contains(ip) {
        fmt.Println("IP in subnet", clientip)
    }
}

In case the above code doesn't make sense here is a golang play link

Elastance answered 16/10, 2016 at 16:57 Comment(2)
This is true... unless you have IP data that doesn't format neatly into subnets.Nakitanalani
works for IPV6 as wellWeight
T
8

The generic version for ipv4/ipv6.

ip.go:

package ip

import (
    "bytes"
    "net"

    "github.com/golang/glog"
)

//test to determine if a given ip is between two others (inclusive)
func IpBetween(from net.IP, to net.IP, test net.IP) bool {
    if from == nil || to == nil || test == nil {
        glog.Warning("An ip input is nil") // or return an error!?
        return false
    }

    from16 := from.To16()
    to16 := to.To16()
    test16 := test.To16()
    if from16 == nil || to16 == nil || test16 == nil {
        glog.Warning("An ip did not convert to a 16 byte") // or return an error!?
        return false
    }

    if bytes.Compare(test16, from16) >= 0 && bytes.Compare(test16, to16) <= 0 {
        return true
    }
    return false
}

and ip_test.go:

package ip

import (
    "net"
    "testing"
)

func TestIPBetween(t *testing.T) {
    HandleIpBetween(t, "0.0.0.0", "255.255.255.255", "128.128.128.128", true)
    HandleIpBetween(t, "0.0.0.0", "128.128.128.128", "255.255.255.255", false)
    HandleIpBetween(t, "74.50.153.0", "74.50.153.4", "74.50.153.0", true)
    HandleIpBetween(t, "74.50.153.0", "74.50.153.4", "74.50.153.4", true)
    HandleIpBetween(t, "74.50.153.0", "74.50.153.4", "74.50.153.5", false)
    HandleIpBetween(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "74.50.153.4", "74.50.153.2", false)
    HandleIpBetween(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:8334", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", true)
    HandleIpBetween(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:8334", "2001:0db8:85a3:0000:0000:8a2e:0370:7350", true)
    HandleIpBetween(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:8334", "2001:0db8:85a3:0000:0000:8a2e:0370:8334", true)
    HandleIpBetween(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:8334", "2001:0db8:85a3:0000:0000:8a2e:0370:8335", false)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "::ffff:192.0.2.127", false)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "::ffff:192.0.2.128", true)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "::ffff:192.0.2.129", true)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "::ffff:192.0.2.250", true)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "::ffff:192.0.2.251", false)
    HandleIpBetween(t, "::ffff:192.0.2.128", "::ffff:192.0.2.250", "192.0.2.130", true)
    HandleIpBetween(t, "192.0.2.128", "192.0.2.250", "::ffff:192.0.2.130", true)
    HandleIpBetween(t, "idonotparse", "192.0.2.250", "::ffff:192.0.2.130", false)

}

func HandleIpBetween(t *testing.T, from string, to string, test string, assert bool) {
    res := IpBetween(net.ParseIP(from), net.ParseIP(to), net.ParseIP(test))
    if res != assert {
        t.Errorf("Assertion (have: %s should be: %s) failed on range %s-%s with test %s", res, assert, from, to, test)
    }
}
Teetotal answered 15/9, 2014 at 12:48 Comment(1)
Working perfectHatty
B
4

Go 1.18

You can use the new package net/netip. The package defines the type netip.Addr:

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 compare IPs with the method Compare:

Compare returns an integer comparing two IPs. The result will be:

  • 0 if ip == ip2
  • -1 if ip < ip2
  • +1 if ip > ip2.

The definition of "less than" is the same as the Less method.

Assuming that you're dealing with IPv4 addresses, a simple in-range comparison might look like the following:

package main

import (
    "fmt"
    "net/netip"
)

func main() {
    ip1, _ := netip.ParseAddr("216.14.49.184")
    ip2, _ := netip.ParseAddr("216.14.49.191")

    myIP, _ := netip.ParseAddr("192.168.8.1")
    fmt.Println(inRange(ip1, ip2, myIP)) // false

    myIP, _ = netip.ParseAddr("216.14.49.185")
    fmt.Println(inRange(ip1, ip2, myIP)) // true
}

func inRange(ipLow, ipHigh, ip netip.Addr) bool {
    return ipLow.Compare(ip) <= 0 && ipHigh.Compare(ip) > 0
}

Note: In real-world code don't actually ignore errors from parsing the IP strings.

Bootblack answered 15/3, 2022 at 22:37 Comment(0)
N
3

I ported over the code from a C# example found here: https://mcmap.net/q/153094/-how-to-check-a-input-ip-fall-in-a-specific-ip-range

And for some reason it ends up being 1ms faster than Nick's solution.

My question was for the "fastest" way, so I figured I'd post mine and see what the community thinks.

package iptesting

import (
    "fmt"
    "testing"
    "net"
    "time"
    "bytes"
)

func TestIPRangeTime(t *testing.T) {
    lowerBytes := net.ParseIP("216.14.49.184").To4()
    upperBytes := net.ParseIP("216.14.49.191").To4()
    inputBytes := net.ParseIP("216.14.49.184").To4()

    startTime := time.Now()
    for i := 0; i < 27000; i++ {
        IsInRange(inputBytes, lowerBytes, upperBytes)
    }
    endTime := time.Now()

    fmt.Println("ELAPSED time port: ", endTime.Sub(startTime))

    lower := net.ParseIP("216.14.49.184")
    upper := net.ParseIP("216.14.49.191")
    trial := net.ParseIP("216.14.49.184")

    startTime = time.Now()
    for i := 0; i < 27000; i++ {
        IsInRange2(trial, lower, upper)
    }
    endTime = time.Now()

    fmt.Println("ELAPSED time bytescompare: ", endTime.Sub(startTime))
}

func IsInRange2(trial net.IP, lower net.IP, upper net.IP) bool {
    if bytes.Compare(trial, lower) >= 0 && bytes.Compare(trial, upper) <= 0 {
        return true
    }
    return false
}

func IsInRange(ip []byte, lower []byte, upper []byte) bool {
    //fmt.Printf("given ip len: %d\n", len(ip))
    lowerBoundary := true
    upperBoundary := true
    for i := 0; i < len(lower) && (lowerBoundary || upperBoundary); i++ {
        if lowerBoundary && ip[i] < lower[i] || upperBoundary && ip[i] > upper[i] {
            return false
        }

        if ip[i] == lower[i] {
            if lowerBoundary {
                lowerBoundary = true
            } else {
                lowerBoundary = false
            }
            //lowerBoundary &= true
        } else {
            lowerBoundary = false
            //lowerBoundary &= false
        }

        if ip[i] == upper[i] {
            //fmt.Printf("matched upper\n")
            if upperBoundary {
                upperBoundary = true
            } else {
                upperBoundary = false
            }
            //upperBoundary &= true
        } else {
            upperBoundary = false
            //upperBoundary &= false
        }
    }
    return true
}

My results:

=== RUN TestIPRangeTime
ELAPSED time port:  1.0001ms
ELAPSED time bytescompare:  2.0001ms
--- PASS: TestIPRangeTime (0.00 seconds)

=== RUN TestIPRangeTime
ELAPSED time port:  1ms
ELAPSED time bytescompare:  2.0002ms
--- PASS: TestIPRangeTime (0.00 seconds)

=== RUN TestIPRangeTime
ELAPSED time port:  1.0001ms
ELAPSED time bytescompare:  2.0001ms
--- PASS: TestIPRangeTime (0.00 seconds)

=== RUN TestIPRangeTime
ELAPSED time port:  1.0001ms
ELAPSED time bytescompare:  2.0001ms
--- PASS: TestIPRangeTime (0.00 seconds)
Neonatal answered 10/11, 2013 at 0:24 Comment(1)
Those integer ms timings look a bit suspicious to me - you'll get more accurate results using a Benchmark - see the benchmarks section in: golang.org/pkg/testingDoddered
M
2

How about some implementation like inet_pton? The result is easy to be stored.

func IP2Integer(ip *net.IP) (int64, error) {
    ip4 := ip.To4()
    if ip4 == nil {
        return 0, fmt.Errorf("illegal: %v", ip)
    }

    bin := make([]string, len(ip4))
    for i, v := range ip4 {
        bin[i] = fmt.Sprintf("%08b", v)
    }
    return strconv.ParseInt(strings.Join(bin, ""), 2, 64)
}
Miyamoto answered 13/11, 2013 at 5:50 Comment(0)
L
1

see in gist

ipmatcher.go

type IPMatcher struct {
    IP net.IP
    SubNet *net.IPNet
}
type IPMatchers []*IPMatcher

func NewIPMatcher(ipStr string) (*IPMatcher, error) {
    ip, subNet, err := net.ParseCIDR(ipStr)
    if err != nil {
        ip = net.ParseIP(ipStr)
        if ip == nil {
            return nil, errors.New("invalid IP: "+ipStr)
        }
    }
    return &IPMatcher{ip, subNet}, nil
}

func (m IPMatcher) Match(ipStr string) bool {
    ip := net.ParseIP(ipStr)
    if ip == nil {
        return false
    }
    return m.IP.Equal(ip) || m.SubNet != nil && m.SubNet.Contains(ip)
}

func NewIPMatchers(ips []string) (list IPMatchers, err error) {
    for _, ipStr := range ips {
        var m *IPMatcher
        m, err = NewIPMatcher(ipStr)
        if err != nil {
            return
        }
        list = append(list, m)
    }
    return
}

func IPContains(ipMatchers []*IPMatcher, ip string) bool {
    for _, m := range ipMatchers {
        if m.Match(ip) {
            return true
        }
    }
    return false
}

func (ms IPMatchers) Match(ip string) bool {
    return IPContains(ms, ip)
}

ipmatcher_test.go

import "testing"

func TestIPMatcher(t *testing.T)  {
    a, errA := NewIPMatcher("127.0.0.1")
    if errA != nil {
        t.Error(errA)
    }
    if a.IP.String() != "127.0.0.1" || a.SubNet != nil {
        t.Error("ip parse error")
    }

    b, errB := NewIPMatcher("192.168.1.1/24")
    if errB != nil {
        t.Error(errB)
    }
    if b.IP.String() != "192.168.1.1" || b.SubNet == nil {
        t.Errorf("ip match error: %s, %v", b.IP.String(), b.SubNet)
    }
    if !b.Match("192.168.1.1") || !b.Match("192.168.1.2") {
        t.Error("ip match error")
    }
}

func TestIPMatchers(t *testing.T)  {
    var WhiteListIPs = []string{"127.0.0.1", "192.168.1.0/24", "10.1.0.0/16"}
    M, err := NewIPMatchers(WhiteListIPs)
    if err != nil {
        t.Error(err)
    }
    if !M.Match("127.0.0.1") || !M.Match("192.168.1.1") || !M.Match("192.168.1.199") ||
        !M.Match("10.1.0.1") || !M.Match("10.1.3.1") {
        t.Error("ip match error")
    }
    if M.Match("127.0.0.2") || M.Match("192.168.2.1") || M.Match("10.2.0.1") {
        t.Error("ip match error 2")
    }
}

see in gist

Loomis answered 8/7, 2021 at 9:18 Comment(0)
B
1

The IPAddress Go library can do this quickly in a polymorphic manner with both IPv4 and IPv6 addresses. Repository here. Disclaimer: I am the project manager.

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

func main() {
    isInRange("216.14.49.184", "216.14.49.191", "216.14.49.190")
    isInRange("2001:db8:85a3::8a2e:0370:7334",
        "2001:db8:85a3::8a00:ff:ffff", "2001:db8:85a3::8a03:a:b")
}

func getAddress(str string) *ipaddr.IPAddress {
    return ipaddr.NewIPAddressString(str).GetAddress()
}

func isInRange(range1Str, range2Str, addrStr string) {
    range1, range2 := getAddress(range1Str), getAddress(range2Str)
    addr := getAddress(addrStr)
    rng := range1.SpanWithRange(range2)
    fmt.Printf("%v contains %v %t\n", rng, addr, rng.Contains(addr))
}

Output:

216.14.49.184 -> 216.14.49.191 contains 216.14.49.190 true
2001:db8:85a3::8a00:ff:ffff -> 2001:db8:85a3::8a2e:370:7334 contains 2001:db8:85a3::8a03:a:b true
Banzai answered 15/1, 2022 at 2:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.