├── .gitignore ├── LICENSE ├── README.md ├── cidr.go ├── cidr_test.go ├── go.mod ├── go.sum ├── ip.go ├── ip_test.go ├── sort.go └── sort_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thomas Tan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CIDR 2 | 3 | ## Features 4 | * easy to iterate through each ip in segment 5 | * check ipv4 or ipv6 segment 6 | * check whether segment contain ip 7 | * segments sort、split、merge 8 | * ip incr & decr 9 | * ip compare 10 | 11 | ## Code Example 12 | ``` 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "github.com/3th1nk/cidr" 18 | ) 19 | 20 | func main() { 21 | // parses a network segment as a CIDR 22 | c, _ := cidr.Parse("192.168.1.0/28") 23 | fmt.Println("network:", c.Network(), "broadcast:", c.Broadcast(), "mask", c.Mask()) 24 | 25 | // ip range 26 | beginIP, endIP := c.IPRange() 27 | fmt.Println("ip range:", beginIP, endIP) 28 | 29 | // iterate through each ip 30 | fmt.Println("ip total:", c.IPCount()) 31 | c.Each(func(ip string) bool { 32 | fmt.Println("\t", ip) 33 | return true 34 | }) 35 | c.EachFrom("192.168.1.10", func(ip string) bool { 36 | fmt.Println("\t", ip) 37 | return true 38 | }) 39 | 40 | fmt.Println("subnet plan based on the subnets num:") 41 | cs, _ := c.SubNetting(cidr.MethodSubnetNum, 4) 42 | for _, c := range cs { 43 | fmt.Println("\t", c.CIDR()) 44 | } 45 | 46 | fmt.Println("subnet plan based on the hosts num:") 47 | cs, _ = c.SubNetting(cidr.MethodHostNum, 4) 48 | for _, c := range cs { 49 | fmt.Println("\t", c.CIDR()) 50 | } 51 | 52 | fmt.Println("merge network:") 53 | c, _ = cidr.SuperNetting([]string{ 54 | "2001:db8::/66", 55 | "2001:db8:0:0:8000::/66", 56 | "2001:db8:0:0:4000::/66", 57 | "2001:db8:0:0:c000::/66", 58 | }) 59 | fmt.Println("\t", c.CIDR()) 60 | } 61 | ``` -------------------------------------------------------------------------------- /cidr.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math" 7 | "math/big" 8 | "net" 9 | ) 10 | 11 | // CIDR https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing 12 | type CIDR struct { 13 | ip net.IP 14 | ipNet *net.IPNet 15 | } 16 | 17 | // Parse parses s as a CIDR notation IP address and mask length, 18 | // like "192.0.2.0/24" or "2001:db8::/32", as defined in RFC4632 and RFC4291 19 | func Parse(s string) (*CIDR, error) { 20 | i, n, err := net.ParseCIDR(s) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &CIDR{ip: i, ipNet: n}, nil 25 | } 26 | 27 | func ParseNoError(s string) *CIDR { 28 | c, _ := Parse(s) 29 | return c 30 | } 31 | 32 | // Equal reports whether cidr and ns are the same CIDR 33 | func (c CIDR) Equal(ns string) bool { 34 | c2, err := Parse(ns) 35 | if err != nil { 36 | return false 37 | } 38 | return c.ipNet.IP.Equal(c2.ipNet.IP) 39 | } 40 | 41 | // IsIPv4 reports whether the CIDR is IPv4 42 | func (c CIDR) IsIPv4() bool { 43 | _, bits := c.ipNet.Mask.Size() 44 | return bits/8 == net.IPv4len 45 | } 46 | 47 | // IsIPv6 reports whether the CIDR is IPv6 48 | func (c CIDR) IsIPv6() bool { 49 | _, bits := c.ipNet.Mask.Size() 50 | return bits/8 == net.IPv6len 51 | } 52 | 53 | // Contains reports whether the CIDR includes ip 54 | func (c CIDR) Contains(ip string) bool { 55 | ipObj := net.ParseIP(ip) 56 | if ipObj == nil { 57 | return false 58 | } 59 | return c.ipNet.Contains(ipObj) 60 | } 61 | 62 | // CIDR return the CIDR which ip prefix be corrected by the mask length. 63 | // For example, "192.0.2.10/24" return "192.0.2.0/24" 64 | func (c CIDR) CIDR() *net.IPNet { 65 | return c.ipNet 66 | } 67 | 68 | // String returns the CIDR string 69 | func (c CIDR) String() string { 70 | return c.ipNet.String() 71 | } 72 | 73 | // IP returns the original IP prefix of the input CIDR 74 | func (c CIDR) IP() net.IP { 75 | return c.ip 76 | } 77 | 78 | // Network returns network of the CIDR 79 | func (c CIDR) Network() net.IP { 80 | return c.ipNet.IP 81 | } 82 | 83 | // MaskSize returns the number of leading ones and total bits in the CIDR mask 84 | func (c CIDR) MaskSize() (ones, bits int) { 85 | ones, bits = c.ipNet.Mask.Size() 86 | return 87 | } 88 | 89 | // Mask returns mask of the CIDR 90 | func (c CIDR) Mask() net.IP { 91 | mask, _ := hex.DecodeString(c.ipNet.Mask.String()) 92 | return mask 93 | } 94 | 95 | // Broadcast returns broadcast of the CIDR 96 | func (c CIDR) Broadcast() net.IP { 97 | mask := c.ipNet.Mask 98 | bcast := make(net.IP, len(c.ipNet.IP)) 99 | copy(bcast, c.ipNet.IP) 100 | for i := 0; i < len(mask); i++ { 101 | ipIdx := len(bcast) - i - 1 102 | bcast[ipIdx] = c.ipNet.IP[ipIdx] | ^mask[len(mask)-i-1] 103 | } 104 | return bcast 105 | } 106 | 107 | // IPRange returns begin and end ip of the CIDR 108 | func (c CIDR) IPRange() (begin, end net.IP) { 109 | return c.Network(), c.Broadcast() 110 | } 111 | 112 | // IPCount returns ip total of the CIDR 113 | func (c CIDR) IPCount() *big.Int { 114 | ones, bits := c.ipNet.Mask.Size() 115 | return big.NewInt(0).Lsh(big.NewInt(1), uint(bits-ones)) 116 | } 117 | 118 | // Each iterate through each ip in the CIDR 119 | func (c CIDR) Each(iterator func(ip string) bool) { 120 | next := make(net.IP, len(c.ipNet.IP)) 121 | copy(next, c.ipNet.IP) 122 | for c.ipNet.Contains(next) { 123 | if !iterator(next.String()) { 124 | break 125 | } 126 | if next.Equal(c.Broadcast()) { 127 | break 128 | } 129 | IPIncr(next) 130 | } 131 | } 132 | 133 | // EachFrom begin with specified ip, iterate through each ip in the CIDR 134 | func (c CIDR) EachFrom(beginIP string, iterator func(ip string) bool) error { 135 | next := net.ParseIP(beginIP) 136 | if next == nil { 137 | return fmt.Errorf("invalid begin ip") 138 | } 139 | for c.ipNet.Contains(next) { 140 | if !iterator(next.String()) { 141 | break 142 | } 143 | if next.Equal(c.Broadcast()) { 144 | break 145 | } 146 | IPIncr(next) 147 | } 148 | return nil 149 | } 150 | 151 | type SubNettingMethod int 152 | 153 | const ( 154 | // MethodSubnetNum SubNetting based on the number of subnets 155 | MethodSubnetNum = SubNettingMethod(0) 156 | // MethodHostNum SubNetting based on the number of hosts 157 | MethodHostNum = SubNettingMethod(1) 158 | // MethodSubnetMask SubNetting based on the mask prefix length of subnets 159 | MethodSubnetMask = SubNettingMethod(2) 160 | ) 161 | 162 | // SubNetting split network segment based on the number of hosts or subnets 163 | func (c CIDR) SubNetting(method SubNettingMethod, num int) ([]*CIDR, error) { 164 | var newOnes float64 165 | ones, bits := c.MaskSize() 166 | switch method { 167 | default: 168 | return nil, fmt.Errorf("unsupported method") 169 | 170 | case MethodSubnetNum: 171 | if num < 1 || (num&(num-1)) != 0 { 172 | return nil, fmt.Errorf("num must the power of 2") 173 | } 174 | 175 | newOnes = float64(ones) + math.Log2(float64(num)) 176 | 177 | case MethodSubnetMask: 178 | newOnes = float64(num) 179 | 180 | case MethodHostNum: 181 | if num < 1 || (num&(num-1)) != 0 { 182 | return nil, fmt.Errorf("num must the power of 2") 183 | } 184 | 185 | newOnes = float64(bits) - math.Log2(float64(num)) 186 | } 187 | 188 | // can't split when subnet mask greater than parent mask 189 | if newOnes < float64(ones) || newOnes > float64(bits) { 190 | return nil, fmt.Errorf("num must be between %v and %v", ones, bits) 191 | } 192 | 193 | // calculate subnet num 194 | // !!! if ones delta is too large, it will cause big memory allocation, even make slice panic when integer overflow !!! 195 | subnetNum := int(math.Pow(float64(2), newOnes-float64(ones))) 196 | 197 | cidrArr := make([]*CIDR, 0, subnetNum) 198 | network := make(net.IP, len(c.ipNet.IP)) 199 | copy(network, c.ipNet.IP) 200 | for i := 0; i < subnetNum; i++ { 201 | cidr := ParseNoError(fmt.Sprintf("%v/%v", network.String(), int(newOnes))) 202 | cidrArr = append(cidrArr, cidr) 203 | network = cidr.Broadcast() 204 | IPIncr(network) 205 | } 206 | 207 | return cidrArr, nil 208 | } 209 | 210 | // SuperNetting merge network segments, must be contiguous 211 | func SuperNetting(ns []string) (*CIDR, error) { 212 | num := len(ns) 213 | if num < 1 || (num&(num-1)) != 0 { 214 | return nil, fmt.Errorf("ns length must the power of 2") 215 | } 216 | 217 | mask := "" 218 | cidrs := make([]*CIDR, 0, num) 219 | for _, n := range ns { 220 | c, err := Parse(n) 221 | if err != nil { 222 | return nil, fmt.Errorf("invalid CIDR:%v", n) 223 | } 224 | cidrs = append(cidrs, c) 225 | 226 | // TODO only network segments with the same mask are supported 227 | if len(mask) == 0 { 228 | mask = c.Mask().String() 229 | } else if c.Mask().String() != mask { 230 | return nil, fmt.Errorf("not the same mask") 231 | } 232 | } 233 | SortCIDRAsc(cidrs) 234 | 235 | // check whether contiguous segments 236 | var network net.IP 237 | for _, c := range cidrs { 238 | if len(network) > 0 { 239 | if !network.Equal(c.ipNet.IP) { 240 | return nil, fmt.Errorf("not the contiguous segments") 241 | } 242 | } 243 | network = c.Broadcast() 244 | IPIncr(network) 245 | } 246 | 247 | // calculate parent segment by mask 248 | c := cidrs[0] 249 | ones, bits := c.MaskSize() 250 | ones = ones - int(math.Log2(float64(num))) 251 | c.ipNet.Mask = net.CIDRMask(ones, bits) 252 | c.ipNet.IP.Mask(c.ipNet.Mask) 253 | 254 | return c, nil 255 | } 256 | -------------------------------------------------------------------------------- /cidr_test.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestEach(t *testing.T) { 9 | c := ParseNoError("192.168.1.0/24") 10 | c.Each(func(ip string) bool { 11 | t.Log(ip) 12 | return true 13 | }) 14 | } 15 | 16 | func TestEachFrom(t *testing.T) { 17 | c := ParseNoError("192.168.1.0/24") 18 | _ = c.EachFrom("192.168.1.230", func(ip string) bool { 19 | t.Log(ip) 20 | return true 21 | }) 22 | } 23 | 24 | func TestMask(t *testing.T) { 25 | c1 := ParseNoError("192.168.1.0/24") 26 | t.Log(c1.Mask()) 27 | 28 | c2 := ParseNoError("2001:db8::/64") 29 | t.Log(c2.Mask()) 30 | } 31 | 32 | func TestBroadcast(t *testing.T) { 33 | c1 := ParseNoError("192.168.2.0/24") 34 | t.Log(c1.Broadcast()) 35 | 36 | c2 := ParseNoError("2001:db8::/64") 37 | t.Log(c2.Broadcast()) 38 | } 39 | 40 | func TestIPRange(t *testing.T) { 41 | c1 := ParseNoError("192.168.1.0/24") 42 | start1, end1 := c1.IPRange() 43 | t.Log(c1.IPCount().String(), start1, end1) 44 | 45 | c2 := ParseNoError("2001:db8::/64") 46 | start2, end2 := c2.IPRange() 47 | t.Log(c2.IPCount().String(), start2, end2) 48 | 49 | c3 := ParseNoError("2001:db8::/8") 50 | start3, end3 := c3.IPRange() 51 | t.Log(c3.IPCount().String(), start3, end3) 52 | } 53 | 54 | func TestSubNetting(t *testing.T) { 55 | c1 := ParseNoError("192.168.1.0/24") 56 | cs1, _ := c1.SubNetting(MethodSubnetNum, 4) 57 | t.Log(c1.CIDR(), "按子网数量划分:") 58 | for _, c := range cs1 { 59 | t.Log(c.CIDR()) 60 | } 61 | 62 | c2 := ParseNoError("2001:db8::/64") 63 | cs2, _ := c2.SubNetting(MethodSubnetNum, 4) 64 | t.Log(c2.CIDR(), "按子网数量划分:") 65 | for _, c := range cs2 { 66 | t.Log(c.CIDR()) 67 | } 68 | 69 | c3 := ParseNoError("192.168.1.0/24") 70 | cs3, _ := c3.SubNetting(MethodHostNum, 64) 71 | t.Log(c3.CIDR(), "按主机数量划分:") 72 | for _, c := range cs3 { 73 | t.Log(c.CIDR()) 74 | } 75 | 76 | c4 := ParseNoError("192.168.1.0/24") 77 | cs4, err := c4.SubNetting(MethodSubnetMask, 26) 78 | assert.NoError(t, err) 79 | t.Log(c4.CIDR(), "按子网掩码划分:") 80 | for _, c := range cs4 { 81 | t.Log(c.CIDR()) 82 | } 83 | 84 | c5 := ParseNoError("2001:db8::/64") 85 | cs5, err := c5.SubNetting(MethodSubnetMask, 66) 86 | assert.NoError(t, err) 87 | t.Log(c5.CIDR(), "按子网掩码划分:") 88 | for _, c := range cs5 { 89 | t.Log(c.CIDR()) 90 | } 91 | } 92 | 93 | func TestSuperNetting(t *testing.T) { 94 | ns4 := []string{ 95 | "192.168.1.0/26", 96 | "192.168.1.192/26", 97 | "192.168.1.128/26", 98 | "192.168.1.64/26", 99 | } 100 | c1, err := SuperNetting(ns4) 101 | if err != nil { 102 | t.Log(err.Error()) 103 | return 104 | } 105 | t.Log(c1.CIDR()) 106 | 107 | ns6 := []string{ 108 | "2001:db8::/66", 109 | "2001:db8:0:0:8000::/66", 110 | "2001:db8:0:0:4000::/66", 111 | "2001:db8:0:0:c000::/66", 112 | } 113 | c2, err := SuperNetting(ns6) 114 | if err != nil { 115 | t.Log(err.Error()) 116 | return 117 | } 118 | t.Log(c2.CIDR()) 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/3th1nk/cidr 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.7.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 7 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 11 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /ip.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func fillIPBytes(ip net.IP, start, end int, fill byte) { 12 | if start > end { 13 | return 14 | } 15 | if start < 0 { 16 | start = 0 17 | } 18 | if end > len(ip) { 19 | end = len(ip) - 1 20 | } 21 | for i := start; i <= end; i++ { 22 | ip[i] = fill 23 | } 24 | } 25 | 26 | func toIPv4Zero(ip net.IP) { 27 | fillIPBytes(ip, 0, 10, 0) 28 | fillIPBytes(ip, 10, 11, 0xFF) 29 | fillIPBytes(ip, 11, 15, 0) 30 | } 31 | 32 | func toIPv4Broadcast(ip net.IP) { 33 | fillIPBytes(ip, 0, 10, 0) 34 | fillIPBytes(ip, 10, 15, 0xFF) 35 | } 36 | 37 | // IPIncr ip increase 38 | func IPIncr(ip net.IP) { 39 | if ip == nil || (len(ip) != net.IPv4len && len(ip) != net.IPv6len) { 40 | return 41 | } 42 | 43 | isV4 := ip.To4() != nil 44 | for i := len(ip) - 1; i >= 0; i-- { 45 | ip[i]++ 46 | if ip[i] > 0 { 47 | break 48 | } 49 | } 50 | 51 | if isV4 && ip.To4() == nil { 52 | toIPv4Zero(ip) 53 | } 54 | } 55 | 56 | // IPDecr ip decrease 57 | func IPDecr(ip net.IP) { 58 | if ip == nil || (len(ip) != net.IPv4len && len(ip) != net.IPv6len) { 59 | return 60 | } 61 | 62 | isV4 := ip.To4() != nil 63 | for i := len(ip) - 1; i >= 0; i-- { 64 | if ip[i] > 0 { 65 | ip[i]-- 66 | break 67 | } else { 68 | ip[i] = 0xFF 69 | } 70 | } 71 | 72 | if isV4 && ip.To4() == nil { 73 | toIPv4Broadcast(ip) 74 | } 75 | } 76 | 77 | // IPIncr2 input ip no change 78 | func IPIncr2(ip net.IP) net.IP { 79 | if ip == nil || (len(ip) != net.IPv4len && len(ip) != net.IPv6len) { 80 | return nil 81 | } 82 | 83 | ipCopy := make(net.IP, len(ip)) 84 | copy(ipCopy, ip) 85 | 86 | for i := len(ipCopy) - 1; i >= 0; i-- { 87 | ipCopy[i]++ 88 | if ipCopy[i] > 0 { 89 | break 90 | } 91 | } 92 | 93 | if ip.To4() != nil && ipCopy.To4() == nil { 94 | toIPv4Zero(ipCopy) 95 | } 96 | return ipCopy 97 | } 98 | 99 | // IPDecr2 input ip no change 100 | func IPDecr2(ip net.IP) net.IP { 101 | if ip == nil || (len(ip) != net.IPv4len && len(ip) != net.IPv6len) { 102 | return nil 103 | } 104 | 105 | ipCopy := make(net.IP, len(ip)) 106 | copy(ipCopy, ip) 107 | 108 | for i := len(ipCopy) - 1; i >= 0; i-- { 109 | if ipCopy[i] > 0 { 110 | ipCopy[i]-- 111 | break 112 | } else { 113 | ipCopy[i] = 0xFF 114 | } 115 | } 116 | 117 | if ip.To4() != nil && ipCopy.To4() == nil { 118 | toIPv4Broadcast(ipCopy) 119 | } 120 | return ipCopy 121 | } 122 | 123 | // IPCompare returns an integer comparing two ip 124 | // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. 125 | func IPCompare(a, b net.IP) int { 126 | return bytes.Compare(a.To16(), b.To16()) 127 | } 128 | 129 | // IPEqual reports whether a and b are the same IP 130 | func IPEqual(a, b net.IP) bool { 131 | return bytes.Equal(a.To16(), b.To16()) 132 | } 133 | 134 | // IP4StrToInt ipv4 ip to number 135 | func IP4StrToInt(s string) int64 { 136 | obj := net.ParseIP(s) 137 | if obj == nil || len(obj.To4()) != net.IPv4len { 138 | return 0 139 | } 140 | 141 | bits := strings.Split(obj.String(), ".") 142 | b0, _ := strconv.Atoi(bits[0]) 143 | b1, _ := strconv.Atoi(bits[1]) 144 | b2, _ := strconv.Atoi(bits[2]) 145 | b3, _ := strconv.Atoi(bits[3]) 146 | 147 | var sum int64 148 | sum += int64(b0) << 24 149 | sum += int64(b1) << 16 150 | sum += int64(b2) << 8 151 | sum += int64(b3) 152 | return sum 153 | } 154 | 155 | // IP4IntToStr number to ipv4 ip 156 | func IP4IntToStr(n int64) string { 157 | var b [4]byte 158 | b[0] = byte(n & 0xFF) 159 | b[1] = byte((n >> 8) & 0xFF) 160 | b[2] = byte((n >> 16) & 0xFF) 161 | b[3] = byte((n >> 24) & 0xFF) 162 | return net.IPv4(b[3], b[2], b[1], b[0]).String() 163 | } 164 | 165 | // IP4Distance return the number of ip between two v4 ip 166 | func IP4Distance(src, dst string) (int64, error) { 167 | srcIp := net.ParseIP(src) 168 | if srcIp == nil || srcIp.To4() == nil { 169 | return 0, fmt.Errorf("invalid v4 ip: %v", src) 170 | } 171 | 172 | dstIp := net.ParseIP(dst) 173 | if dstIp == nil || dstIp.To4() == nil { 174 | return 0, fmt.Errorf("invalid v4 ip: %v", dst) 175 | } 176 | 177 | return IP4StrToInt(dstIp.String()) - IP4StrToInt(srcIp.String()), nil 178 | } 179 | -------------------------------------------------------------------------------- /ip_test.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "net" 7 | "testing" 8 | ) 9 | 10 | func TestIPIncr(t *testing.T) { 11 | tests := []struct { 12 | ip net.IP 13 | valid bool 14 | }{ 15 | {net.ParseIP("0.0.0.0"), true}, 16 | {net.ParseIP("::"), true}, 17 | // 边界 18 | {net.ParseIP("255.255.255.255"), true}, 19 | {net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), true}, 20 | // 非法输入 21 | {nil, false}, 22 | {[]byte{1, 2}, false}, 23 | } 24 | 25 | for _, test := range tests { 26 | srcIP := make(net.IP, len(test.ip)) 27 | copy(srcIP, test.ip) 28 | IPIncr(test.ip) 29 | fmt.Printf("IPIncr Input: %v -> Output: %v\n", srcIP, test.ip) 30 | } 31 | } 32 | 33 | func TestIPDecr(t *testing.T) { 34 | tests := []struct { 35 | ip net.IP 36 | valid bool 37 | }{ 38 | {net.ParseIP("255.255.255.255"), true}, 39 | {net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), true}, 40 | // 边界 41 | {net.ParseIP("0.0.0.0"), true}, 42 | {net.ParseIP("::"), true}, 43 | // 非法输入 44 | {nil, false}, 45 | {[]byte{1, 2}, false}, 46 | } 47 | 48 | for _, test := range tests { 49 | srcIP := make(net.IP, len(test.ip)) 50 | copy(srcIP, test.ip) 51 | IPDecr(test.ip) 52 | fmt.Printf("IPDecr Input: %v -> Output: %v\n", srcIP, test.ip) 53 | } 54 | } 55 | 56 | func TestIPIncr2(t *testing.T) { 57 | tests := []struct { 58 | ip net.IP 59 | valid bool 60 | }{ 61 | {net.ParseIP("255.255.255.255"), true}, 62 | {net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), true}, 63 | // 边界 64 | {net.ParseIP("0.0.0.0"), true}, 65 | {net.ParseIP("::"), true}, 66 | // 非法输入 67 | {nil, false}, 68 | {[]byte{1, 2}, false}, 69 | } 70 | 71 | for _, test := range tests { 72 | result := IPIncr2(test.ip) 73 | fmt.Printf("IPIncr2 Input: %v -> Output: %v\n", test.ip, result) 74 | } 75 | } 76 | 77 | func TestIPDecr2(t *testing.T) { 78 | tests := []struct { 79 | ip net.IP 80 | valid bool 81 | }{ 82 | {net.ParseIP("255.255.255.255"), true}, 83 | {net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), true}, 84 | // 边界 85 | {net.ParseIP("0.0.0.0"), true}, 86 | {net.ParseIP("::"), true}, 87 | // 非法输入 88 | {nil, false}, 89 | {[]byte{1, 2}, false}, 90 | } 91 | 92 | for _, test := range tests { 93 | result := IPDecr2(test.ip) 94 | fmt.Printf("IPDecr2 Input: %v -> Output: %v\n", test.ip, result) 95 | } 96 | } 97 | 98 | func TestIPCompare(t *testing.T) { 99 | assert.Equal(t, IPCompare(net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.20")), -1) 100 | assert.Equal(t, IPCompare(net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.10")), -1) 101 | assert.Equal(t, IPCompare(net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.2")), 0) 102 | assert.Equal(t, IPCompare(net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.3")), -1) 103 | assert.Equal(t, IPCompare(net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.1")), 1) 104 | assert.Equal(t, IPCompare(net.ParseIP("2001:db8::"), net.ParseIP("2001:db8::1")), -1) 105 | assert.Equal(t, IPCompare(net.ParseIP("2001:db8::"), net.ParseIP("192.168.1.1")), 1) 106 | } 107 | 108 | func TestIPEqual(t *testing.T) { 109 | assert.Equal(t, false, IPEqual(net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"))) 110 | assert.Equal(t, true, IPEqual(net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.1"))) 111 | assert.Equal(t, false, IPEqual(net.ParseIP("fd00::"), net.ParseIP("fd00::1"))) 112 | assert.Equal(t, true, IPEqual(net.ParseIP("fd00::"), net.ParseIP("fd00::"))) 113 | } 114 | 115 | func TestIP4StrToInt(t *testing.T) { 116 | assert.Equal(t, int64(3232235777), IP4StrToInt("192.168.1.1")) 117 | assert.Equal(t, int64(4294967295), IP4StrToInt("255.255.255.255")) 118 | assert.Equal(t, int64(0), IP4StrToInt("0.0.0.0")) 119 | } 120 | 121 | func TestIP4IntToStr(t *testing.T) { 122 | assert.Equal(t, "192.168.1.1", IP4IntToStr(3232235777)) 123 | assert.Equal(t, "255.255.255.255", IP4IntToStr(4294967295)) 124 | assert.Equal(t, "0.0.0.0", IP4IntToStr(0)) 125 | } 126 | 127 | func TestIP4Distance(t *testing.T) { 128 | n, _ := IP4Distance("192.168.1.0", "192.168.1.1") 129 | assert.Equal(t, int64(1), n) 130 | 131 | n, _ = IP4Distance("192.168.1.1", "192.168.1.0") 132 | assert.Equal(t, int64(-1), n) 133 | 134 | n, _ = IP4Distance("192.168.0.255", "192.168.1.255") 135 | assert.Equal(t, int64(256), n) 136 | } 137 | -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import "sort" 4 | 5 | // SortCIDRAsc sort cidr slice order by ip,mask asc 6 | func SortCIDRAsc(cs []*CIDR) { 7 | sortCIDR(cs, "asc") 8 | } 9 | 10 | // SortCIDRDesc sort cidr slice order by ip,mask desc 11 | func SortCIDRDesc(cs []*CIDR) { 12 | sortCIDR(cs, "desc") 13 | } 14 | 15 | func sortCIDR(cs []*CIDR, order string) { 16 | sort.Slice(cs, func(i, j int) bool { 17 | if n := IPCompare(cs[i].ipNet.IP, cs[j].ipNet.IP); n != 0 { 18 | if order == "desc" { 19 | return n >= 0 20 | } 21 | return n < 0 22 | } 23 | 24 | i1, _ := cs[i].ipNet.Mask.Size() 25 | j1, _ := cs[j].ipNet.Mask.Size() 26 | if order == "desc" { 27 | return i1 > j1 28 | } 29 | return i1 < j1 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /sort_test.go: -------------------------------------------------------------------------------- 1 | package cidr 2 | 3 | import "testing" 4 | 5 | func TestSortCIDR(t *testing.T) { 6 | var arr = []*CIDR{ 7 | ParseNoError("192.168.1.192/26"), 8 | ParseNoError("192.168.1.0/26"), 9 | ParseNoError("192.168.1.64/26"), 10 | ParseNoError("192.168.1.128/26"), 11 | } 12 | t.Log("order by asc:") 13 | SortCIDRAsc(arr) 14 | for _, c := range arr { 15 | t.Log(c.CIDR()) 16 | } 17 | 18 | t.Log("order by desc:") 19 | SortCIDRDesc(arr) 20 | for _, c := range arr { 21 | t.Log(c.CIDR()) 22 | } 23 | } 24 | --------------------------------------------------------------------------------