Golang IP工具库(排序、合并网段、IPv6格式化)

3,974 阅读1分钟

本文提供一套Golang编写的IP工具库,包含:IP列表合并为网段列表、IPv6书写格式转化、IP排序、随机生成IP序列等功能。

1 IP工具库介绍

工具库目录如图:

image.png

本节将介绍该工具库的功能。

1.1 IP排序

将IP列表排序。示例:

func main() {
    var ips []net.IP
    for i := 0; i < 3; i++ {
        ips = append(ips, ip_utils.RandomIPv4())
        ips = append(ips, ip_utils.RandomIPv6())
    }
    fmt.Println(ips)
    ip_utils.SortIP(ips)
    fmt.Println(ips)
}

示例中随机生成了6个IPv4/IPv6,结果:

[53.77.76.9 c82b:6d6e:2139:c99d:c431:5bb5:9c36:62c3 151.170.54.188 57d8:f5e5:bdf7:f80d:755a:4952:2d87:92bd 157.133.15.97 582:23cd:cea9:6787:238b:3c4d:727:81a0]
[53.77.76.9 151.170.54.188 157.133.15.97 582:23cd:cea9:6787:238b:3c4d:727:81a0 57d8:f5e5:bdf7:f80d:755a:4952:2d87:92bd c82b:6d6e:2139:c99d:c431:5bb5:9c36:62c3]

1.2 合并网段

将IP列表合并为网段列表。其中IP列表为多段连续的IP,示例:

func main() {
    var ips []net.IP
    for i := 0; i < 2; i++ {
        ips = append(ips, ip_utils.RandomIPv4Seq(rand.Intn(16)+1)...)
        ips = append(ips, ip_utils.RandomIPv6Seq(rand.Intn(16)+1)...)
    }
    ip_utils.SortIP(ips)
    fmt.Println(ips)

    ipMerge := ip_utils.NewIPMerge(ips)
    res, ok := ipMerge.Next()
    for ok {
        fmt.Println(res.Cidr, res.Count)
        res, ok = ipMerge.Next()
    }
}

示例中随机生成了IP列表(包含4段连续的IPv4/IPv6),然后使用IPMerge.Next()解析出网段。

结果:

[1.34.198.104 1.34.198.105 186.149.200.62 186.149.200.63 186.149.200.64 186.149.200.65 186.149.200.66 186.149.200.67 186.149.200.68 186.149.200.69 97dd:5e84:a852:310:b56c:57ac:4d30:2138 97dd:5e84:a852:310:b56c:57ac:4d30:2139 97dd:5e84:a852:310:b56c:57ac:4d30:213a 97dd:5e84:a852:310:b56c:57ac:4d30:213b 97dd:5e84:a852:310:b56c:57ac:4d30:213c 97dd:5e84:a852:310:b56c:57ac:4d30:213d 97dd:5e84:a852:310:b56c:57ac:4d30:213e 97dd:5e84:a852:310:b56c:57ac:4d30:213f 97dd:5e84:a852:310:b56c:57ac:4d30:2140 97dd:5e84:a852:310:b56c:57ac:4d30:2141 97dd:5e84:a852:310:b56c:57ac:4d30:2142 97dd:5e84:a852:310:b56c:57ac:4d30:2143 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c898 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c899 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89a dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89b dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89c dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89d dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89e dab5:f60c:f254:7e7f:88bf:3d85:dde1:c89f dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a0 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a1 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a2 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a3 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a4 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a5 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a6 dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a7]
1.34.198.104/31 2
186.149.200.62/31 2
186.149.200.64/30 4
186.149.200.68/31 2
97dd:5e84:a852:310:b56c:57ac:4d30:2138/125 8
97dd:5e84:a852:310:b56c:57ac:4d30:2140/126 4
dab5:f60c:f254:7e7f:88bf:3d85:dde1:c898/125 8
dab5:f60c:f254:7e7f:88bf:3d85:dde1:c8a0/125 8

1.3 IPv6书写格式转化

IPv6根据缩写规则可以有多种写法,如:全写、省略前导零、最简格式。示例:

func main() {
    ips := []string{
        "0000:ff06:0000:0000:0000:0000:0000:0000",
        "0000:0000:0000:0000:0000:0000:0000:0000",
        "ff02:0000:0000:0000:0000:0001:ff00:0001",
        "fd82:139b:8752:0000:246e:0031:888c:36db",
        "fd82:0000:8752:0000:0000:0031:888c:36db",
        "fd82:0000:0000:8752:0000:0031:888c:36db",
    }
    for _, ip := range ips {
        fmt.Printf("%39s  %39s  %39s\n", ip, ip_utils.FormatZero(ip), ip_utils.Format(ip))
    }
}

结果:

0000:ff06:0000:0000:0000:0000:0000:0000                       0:ff06:0:0:0:0:0:0                                 0:ff06::
0000:0000:0000:0000:0000:0000:0000:0000                          0:0:0:0:0:0:0:0                                       ::
ff02:0000:0000:0000:0000:0001:ff00:0001                    ff02:0:0:0:0:1:ff00:1                           ff02::1:ff00:1
fd82:139b:8752:0000:246e:0031:888c:36db       fd82:139b:8752:0:246e:31:888c:36db       fd82:139b:8752:0:246e:31:888c:36db
fd82:0000:8752:0000:0000:0031:888c:36db             fd82:0:8752:0:0:31:888c:36db                fd82:0:8752::31:888c:36db
fd82:0000:0000:8752:0000:0031:888c:36db             fd82:0:0:8752:0:31:888c:36db                fd82::8752:0:31:888c:36db

2 源码

直接上代码。

ip_common.go

package ip_utils

import (
    "encoding/hex"
    "net"
    "strconv"
)

func BitLen(ip net.IP) int {
    if ip.To4() != nil {
        return 32
    }
    return 128
}

func Copy(ip net.IP) net.IP {
    dup := make(net.IP, len(ip))
    copy(dup, ip)
    return dup
}

func GenMask(ones int, bits int) string {
    mask, _ := hex.DecodeString(net.CIDRMask(ones, bits).String())
    return net.IP(mask).String()
}

func GenCidr(ip net.IP, ones int) string {
    return ip.String() + "/" + strconv.Itoa(ones)
}

ip_format.go

package ip_utils

import (
    "net"
)

func IsIPv4(ip string) bool {
    if net.ParseIP(ip).To4() != nil {
        return true
    }
    return false
}

// Format ipv6最简格式,示例:240e:f7:c000:103:13::f4
func Format(ip string) string {
    netIp := net.ParseIP(ip)
    if netIp == nil {
        return ip
    }
    return netIp.String()
}

// FormatZero ipv6省略前导零格式,示例:240e:f7:c000:103:13:0:0:f4
func FormatZero(ip string) string {
    p := net.ParseIP(ip)
    if p == nil || p.To4() != nil || len(p) != net.IPv6len {
        return ip
    }

    const maxLen = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
    b := make([]byte, 0, maxLen)

    for i := 0; i < net.IPv6len; i += 2 {
        if i > 0 {
            b = append(b, ':')
        }
        b = appendHex(b, (uint32(p[i])<<8)|uint32(p[i+1]))
    }
    return string(b)
}

const hexDigit = "0123456789abcdef"

// Convert i to a hexadecimal string. Leading zeros are not printed.
func appendHex(dst []byte, i uint32) []byte {
    if i == 0 {
        return append(dst, '0')
    }
    for j := 7; j >= 0; j-- {
        v := i >> uint(j*4)
        if v > 0 {
            dst = append(dst, hexDigit[v&0xf])
        }
    }
    return dst
}

ip_merge.go

package ip_utils

import (
    "bytes"
    "math"
    "net"
    "sort"
)

type IPMerge struct {
    Index int
    IPs   []net.IP
}

type MergeResult struct {
    Cidr         string
    Mask         string
    FirstAddress string
    Count        int
}

func NewIPMerge(ips []net.IP) *IPMerge {
    sort.Slice(ips, func(i, j int) bool {
        return bytes.Compare(ips[i].To16(), ips[j].To16()) < 0
    })
    return &IPMerge{IPs: ips}
}

// 计算下一个最大的子网段
func (m *IPMerge) Next() (res MergeResult, ok bool) {
    if m.Index >= len(m.IPs) {
        return
    }

    ip := m.IPs[m.Index]
    bits := BitLen(ip)
    n := 1 // 子网段的掩码0的位数
    num := 2
    for {
        lastIndex := m.Index + int(math.Pow(2, float64(n))) - 1
        if lastIndex >= len(m.IPs) {
            break
        }
        _, ipNet, err := net.ParseCIDR(GenCidr(ip, bits-n))
        if err != nil {
            break
        }
        start, end := IpNetRange(ipNet)
        sIP, eIP := net.ParseIP(start), net.ParseIP(end)
        if !ip.Equal(sIP) || !m.IPs[lastIndex].Equal(eIP) {
            break
        }
        n++
        num *= 2
    }
    ones := bits - n + 1
    ipCount := num / 2
    m.Index += ipCount

    res.Cidr = GenCidr(ip, ones)
    res.Mask = GenMask(ones, bits)
    res.FirstAddress = ip.String()
    res.Count = ipCount
    return res, true
}

ip_merge_test.go

package ip_utils

import (
    "math/rand"
    "net"
    "testing"
)

func TestIPMerge_Next(t *testing.T) {
    var ips []net.IP
    for i := 0; i < 2; i++ {
        ips = append(ips, RandomIPv6Seq(rand.Intn(32)+1)...)
        ips = append(ips, RandomIPv4Seq(rand.Intn(32)+1)...)
    }
    t.Log(ips)

    ipMerge := NewIPMerge(ips)
    res, ok := ipMerge.Next()
    for ok {
        t.Log(res.Cidr, res.Count)
        res, ok = ipMerge.Next()
    }
}

ip_random.go

package ip_utils

import (
    "encoding/binary"
    "math/rand"
    "net"
    "time"
)

func RandomIPv4() net.IP {
    rand32 := rand.New(rand.NewSource(time.Now().UnixNano())).Uint32()
    ip := make(net.IP, net.IPv4len)
    copy(ip, net.IPv4zero)
    binary.BigEndian.PutUint32(ip.To4(), rand32)
    return ip
}

func RandomIPv6() net.IP {
    var rand128 [2]uint64
    rand128[0] = rand.New(rand.NewSource(time.Now().UnixNano())).Uint64()
    rand128[1] = rand.New(rand.NewSource(time.Now().UnixNano())).Uint64()
    ip := make(net.IP, net.IPv6len)
    copy(ip, net.IPv6zero)
    binary.BigEndian.PutUint64(ip[:8], rand128[0])
    binary.BigEndian.PutUint64(ip[8:16], rand128[1])
    return ip
}

func RandomIPv4Seq(num int) []net.IP {
    result := make([]net.IP, 0)
    ip := RandomIPv4()
    for i := 0; i < num; i++ {
        result = append(result, Copy(ip))
        IncreaseIP(ip)
    }
    return result
}

func RandomIPv6Seq(num int) []net.IP {
    result := make([]net.IP, 0)
    ip := RandomIPv6()
    for i := 0; i < num; i++ {
        result = append(result, Copy(ip))
        IncreaseIP(ip)
    }
    return result
}

ip_range.go

package ip_utils

import "net"

// IpNetRange 返回网段的起始IP、结束IP
func IpNetRange(ipNet *net.IPNet) (start, end string) {
    mask := ipNet.Mask
    broadcast := Copy(ipNet.IP)
    for i := 0; i < len(mask); i++ {
        ipIdx := len(broadcast) - i - 1
        broadcast[ipIdx] = ipNet.IP[ipIdx] | ^mask[len(mask)-i-1]
    }
    return ipNet.IP.String(), broadcast.String()
}

// IncreaseIP IP地址自增
func IncreaseIP(ip net.IP) {
    for i := len(ip) - 1; i >= 0; i-- {
        ip[i]++
        if ip[i] > 0 {
            break
        }
    }
}

// DecreaseIP IP地址自减
func DecreaseIP(ip net.IP) {
    length := len(ip)
    for i := length - 1; i >= 0; i-- {
        ip[length-1]--
        if ip[length-1] < 0xFF {
            break
        }
        for j := 1; j < length; j++ {
            ip[length-j-1]--
            if ip[length-j-1] < 0xFF {
                return
            }
        }
    }
}

ip_sort.go

package ip_utils

import (
    "bytes"
    "net"
    "sort"
)

// CompareLess return true if ip1 < ip2
func CompareLess(ipStr1, ipStr2 string) bool {
    ip1 := net.ParseIP(ipStr1)
    ip2 := net.ParseIP(ipStr2)
    if ip1 == nil || ip2 == nil {
        return ipStr1 < ipStr2
    }
    return IPCompareLess(ip1, ip2)
}

func IPCompareLess(ip1, ip2 net.IP) bool {
    if bytes.Compare(ip1.To16(), ip2.To16()) < 0 {
        return true
    }
    return false
}

// Sort ips从小到大排序
func Sort(ips []string) {
    sort.Slice(ips, func(i, j int) bool {
        return CompareLess(ips[i], ips[j])
    })
}

// SortIP ips从小到大排序
func SortIP(ips []net.IP) {
    sort.Slice(ips, func(i, j int) bool {
        return IPCompareLess(ips[i], ips[j])
    })
}