Go/GoLang检查IP地址是否在范围内

36
在Go / GoLang中,检查IP地址是否在特定范围内的最快方法是什么?例如,给定范围216.14.49.184到216.14.49.191,如何检查给定的输入IP地址是否在该范围内?请保留HTML标记。

你的范围是如何表示的? - Sergio Tulentsev
开始:"216.14.49.184",结束:"216.14.49.191"。我在网上找到了一个CIDR的解决方案,但那不是我正在处理的数据。 - Allison A
1
只是想知道,如果你将这些字符串分解成组件并进行数值比较,是否会产生正确的结果? - Sergio Tulentsev
8个回答

46

在 Go 语言中,IP 地址以大端序的 []byte 切片形式表示(类型为 IP),因此可以使用 bytes.Compare 函数进行正确比较。

例如 (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")
}

哪个会产生

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

38
已经存在于"net"包中的函数net.Contains可以完成这个任务,不需要重新编写代码!
请查看文档此处
你只需要解析所需的子网即可使用它。
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)
    }
}

如果以上代码不太清晰,这里有一个golang play链接


3
这是正确的...除非您拥有无法整齐格式化为子网的IP数据。 - Tim Keating
同样适用于IPv6 - undefined

8

这是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
}

还有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)
    }
}

工作完美无缺 - Thom

3

Go 1.18

您可以使用新的包net/netip。该包定义了类型netip.Addr

net.IP类型相比,此包的Addr类型占用更少的内存,是不可变的,并且可以进行比较(支持==和作为映射键)。

您可以使用方法Compare来比较IP地址:

Compare返回一个整数,用于比较两个IP地址。结果将是:

  • 如果ip == ip2,则为0
  • 如果ip < ip2,则为-1
  • 如果ip > ip2,则为+1。

“小于”的定义与Less方法相同。

假设您正在处理IPv4地址,则简单的范围比较可能如下所示:

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
}

注意: 在实际的代码中,不要忽略解析IP字符串时出现的错误。


3
我将代码从这里的C#示例移植过来:https://dev59.com/yXI95IYBdhLWcg3w1BdN#2138724 不知为何,结果比Nick的解决方案快1ms。
我的问题是关于“最快”的方法,所以我想发帖分享我的方法,看看社区的想法。
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
}

我的结果:

=== 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)

2
那些整数ms的时间看起来有点可疑 - 你可以使用Benchmark获得更准确的结果 - 参见:http://golang.org/pkg/testing/中的基准测试部分。 - Nick Craig-Wood

2

是否考虑使用类似 inet_pton 的实现?这样的结果易于存储。

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

1

IPAddress Go library 可以快速地以多态方式处理 IPv4 和 IPv6 地址。这里是代码库。免责声明:我是项目经理。

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

输出:

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

1

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

在gist中查看


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接