-
-
Save jan-bar/1142275c933c13a3f61a3bc20cc6da49 to your computer and use it in GitHub Desktop.
Go: Convert IP to CIDR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"log" | |
"net" | |
) | |
func ipRange(str string) (net.IP, net.IP) { | |
_, mask, err := net.ParseCIDR(str) | |
if err != nil { | |
log.Fatalf("Error parsing CIDR - %s - %v", str, err) | |
} | |
first := mask.IP.Mask(mask.Mask).To16() | |
second := make(net.IP, len(first)) | |
copy(second, first) | |
ones, _ := mask.Mask.Size() | |
if first.To4() != nil { | |
ones += 96 | |
} | |
lastBytes := (8*16 - ones) / 8 | |
lastBits := 8 - ones%8 | |
or := 0 | |
for x := 0; x < lastBits; x++ { | |
or = or*2 + 1 | |
} | |
for x := 16 - lastBytes; x < 16; x++ { | |
second[x] = 0xff | |
} | |
if lastBits < 8 { | |
second[16-lastBytes-1] |= byte(or) | |
} | |
return first, second | |
} | |
func main() { | |
tests := [][3]string{ | |
{"2001:db8:abcd:0012::0/64", "2001:db8:abcd:12::", "2001:db8:abcd:12:ffff:ffff:ffff:ffff"}, | |
{"10.10.10.10/25", "10.10.10.0", "10.10.10.127"}, | |
{"10.10.10.10/24", "10.10.10.0", "10.10.10.255"}, | |
{"10.10.9.10/23", "10.10.8.0", "10.10.9.255"}, | |
} | |
for x := range tests { | |
first, second := ipRange(tests[x][0]) | |
if first.String() != tests[x][1] { | |
log.Printf("Source %s - expected first %s, got %s - %08b", tests[x][0], tests[x][1], first, first) | |
} | |
if second.String() != tests[x][2] { | |
log.Printf("Source %s - expected second %s, got %s - %08b", tests[x][0], tests[x][2], second, second) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Convert IPv4 range into CIDR | |
// https://blog.ip2location.com/knowledge-base/how-to-convert-ip-address-range-into-cidr/ | |
func iPv4RangeToCIDRRange(ipStart string, ipEnd string) (cidrs []string, err error) { | |
cidr2mask := []uint32{ | |
0x00000000, 0x80000000, 0xC0000000, | |
0xE0000000, 0xF0000000, 0xF8000000, | |
0xFC000000, 0xFE000000, 0xFF000000, | |
0xFF800000, 0xFFC00000, 0xFFE00000, | |
0xFFF00000, 0xFFF80000, 0xFFFC0000, | |
0xFFFE0000, 0xFFFF0000, 0xFFFF8000, | |
0xFFFFC000, 0xFFFFE000, 0xFFFFF000, | |
0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, | |
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, | |
0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8, | |
0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF, | |
} | |
ipStartUint32 := iPv4ToUint32(ipStart) | |
ipEndUint32 := iPv4ToUint32(ipEnd) | |
if ipStartUint32 > ipEndUint32 { | |
log.Fatalf("start IP:%s must be less than end IP:%s", ipStart, ipEnd) | |
} | |
for ipEndUint32 >= ipStartUint32 { | |
maxSize := 32 | |
for maxSize > 0 { | |
maskedBase := ipStartUint32 & cidr2mask[maxSize - 1] | |
if maskedBase != ipStartUint32 { | |
break | |
} | |
maxSize-- | |
} | |
x := math.Log(float64(ipEndUint32 - ipStartUint32 + 1)) / math.Log(2) | |
maxDiff := 32 - int(math.Floor(x)) | |
if maxSize < maxDiff { | |
maxSize = maxDiff | |
} | |
cidrs = append(cidrs, uInt32ToIPv4(ipStartUint32) + "/" + strconv.Itoa(maxSize)) | |
ipStartUint32 += uint32(math.Exp2(float64(32 - maxSize))) | |
} | |
return cidrs, err | |
} | |
//Convert IPv4 to uint32 | |
func iPv4ToUint32(iPv4 string ) uint32 { | |
ipOctets := [4]uint64{} | |
for i, v := range strings.SplitN(iPv4,".", 4) { | |
ipOctets[i], _ = strconv.ParseUint(v, 10, 32) | |
} | |
result := (ipOctets[0] << 24) | (ipOctets[1] << 16) | (ipOctets[2] << 8) | ipOctets[3] | |
return uint32(result) | |
} | |
//Convert uint32 to IP | |
func uInt32ToIPv4(iPuInt32 uint32) (iP string) { | |
iP = fmt.Sprintf ("%d.%d.%d.%d", | |
iPuInt32 >> 24, | |
(iPuInt32 & 0x00FFFFFF)>> 16, | |
(iPuInt32 & 0x0000FFFF) >> 8, | |
iPuInt32 & 0x000000FF) | |
return iP | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://go.dev/play/p/nJE2EPx88- | |
package main | |
import ( | |
"fmt" | |
"net" | |
) | |
var allFF = net.ParseIP("255.255.255.255").To4() | |
func x(s string)net.IP { return net.ParseIP(s)} | |
var tests = []struct {a, b string} { | |
{"0.0.0.0", "0.0.0.0"}, | |
{"0.0.0.0", "0.0.0.1"}, | |
{"0.0.0.1", "0.0.0.2"}, | |
{"255.255.255.255", "255.255.255.255"}, | |
{"0.0.0.254", "0.0.1.0"}, | |
{"0.0.0.0", "255.255.255.255"}, | |
{"1.2.3.4", "5.6.7.8"}, | |
{"0.0.0.1", "255.255.255.254"}, | |
} | |
func main() { | |
for _, t := range tests { | |
fmt.Printf("%v .. %v=>\n", t.a, t.b) | |
for _, n := range range2CIDRs(x(t.a), x(t.b)) { | |
fmt.Printf(" %v\n", n.String()) | |
} | |
} | |
} | |
func range2CIDRs(a1, a2 net.IP) (r []*net.IPNet) { | |
maxLen := 32 | |
a1 = a1.To4() | |
a2 = a2.To4() | |
for cmp(a1, a2) <= 0 { | |
l := 32 | |
for l > 0 { | |
m := net.CIDRMask(l-1, maxLen) | |
if cmp(a1, first(a1, m)) != 0 || cmp(last(a1, m), a2) > 0 { | |
break | |
} | |
l-- | |
} | |
r = append(r, &net.IPNet{IP:a1, Mask:net.CIDRMask(l, maxLen)}) | |
a1 = last(a1, net.CIDRMask(l, maxLen)) | |
if cmp(a1, allFF) == 0 { | |
break | |
} | |
a1 = next(a1) | |
} | |
return r | |
} | |
func next(ip net.IP) net.IP { | |
n := len(ip) | |
out := make(net.IP, n) | |
copy := false | |
for n > 0 { | |
n-- | |
if copy { | |
out[n] = ip[n] | |
continue | |
} | |
if ip[n] < 255 { | |
out[n] = ip[n] + 1 | |
copy = true | |
continue | |
} | |
out[n] = 0 | |
} | |
return out | |
} | |
func cmp(ip1, ip2 net.IP) int { | |
l := len(ip1) | |
for i := 0; i < l; i++ { | |
if ip1[i] == ip2[i] { | |
continue | |
} | |
if ip1[i] < ip2[i] { | |
return -1 | |
} | |
return 1 | |
} | |
return 0 | |
} | |
func first(ip net.IP, mask net.IPMask) net.IP { | |
return ip.Mask(mask) | |
} | |
func last(ip net.IP, mask net.IPMask) net.IP { | |
n := len(ip) | |
out := make(net.IP, n) | |
for i := 0; i < n; i++ { | |
out[i] = ip[i] | ^mask[i] | |
} | |
return out | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"errors" | |
"fmt" | |
"io" | |
"math" | |
"math/big" | |
"net/http" | |
"net/netip" | |
"strconv" | |
"strings" | |
) | |
func InetNtoA(ip *big.Int, ipv4 uint32) string { | |
if ip == nil { | |
return netip.AddrFrom4([4]byte{byte(ipv4 >> 24), byte(ipv4 >> 16), | |
byte(ipv4 >> 8), byte(ipv4)}).String() | |
} | |
var ( | |
buf [16]byte | |
ipv6 = ip.Bytes() | |
index = len(buf) - len(ipv6) | |
) | |
if index < 0 || index >= 16 { | |
return "" | |
} | |
copy(buf[index:], ipv6) | |
return netip.AddrFrom16(buf).String() | |
} | |
func InetAtoN(ip string) (*big.Int, uint32, error) { | |
addr, err := netip.ParseAddr(ip) | |
if err != nil { | |
return nil, 0, err | |
} | |
if addr.Is4() { | |
ipv4 := addr.As4() | |
return nil, uint32(ipv4[0])<<24 | uint32(ipv4[1])<<16 | | |
uint32(ipv4[2])<<8 | uint32(ipv4[3]), nil | |
} | |
ipv6 := addr.As16() | |
return new(big.Int).SetBytes(ipv6[:]), 0, nil | |
} | |
func IpRangeToCIDRRange(ipFrom, ipTo string) ([]string, error) { | |
url := "https://www.ipaddressguide.com/cidr" | |
if strings.IndexByte(ipFrom, '.') < 0 { | |
url = "https://www.ipaddressguide.com/ipv6-cidr" | |
} | |
resp, err := http.Post(url, "application/x-www-form-urlencoded", | |
strings.NewReader("mode=range&ipFrom="+ipFrom+"&ipTo="+ipTo)) | |
if err != nil { | |
return nil, err | |
} | |
data, err := io.ReadAll(resp.Body) | |
_ = resp.Body.Close() | |
if err != nil { | |
return nil, err | |
} | |
if e := bytes.Index(data, []byte("</code></pre></div>")); e > 0 { | |
if s := bytes.LastIndexByte(data[:e], '>'); s > 0 { | |
return strings.Split(string(data[s+1:e]), "\n"), nil | |
} | |
} | |
return nil, errors.New("not find") | |
} | |
func IPv4RangeToCIDRRange(ipStart, ipEnd string, notLast ...bool) ([]string, error) { | |
// https://blog.ip2location.com/knowledge-base/how-to-convert-ip-address-range-into-cidr/ | |
cidr2mask := []uint32{ | |
0x00000000, 0x80000000, 0xC0000000, | |
0xE0000000, 0xF0000000, 0xF8000000, | |
0xFC000000, 0xFE000000, 0xFF000000, | |
0xFF800000, 0xFFC00000, 0xFFE00000, | |
0xFFF00000, 0xFFF80000, 0xFFFC0000, | |
0xFFFE0000, 0xFFFF0000, 0xFFFF8000, | |
0xFFFFC000, 0xFFFFE000, 0xFFFFF000, | |
0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, | |
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, | |
0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8, | |
0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF, | |
} | |
_, ipStartUint32, err := InetAtoN(ipStart) | |
if err != nil { | |
return nil, err | |
} | |
_, ipEndUint32, err := InetAtoN(ipEnd) | |
if err != nil { | |
return nil, err | |
} | |
if len(notLast) > 0 && notLast[0] { | |
ipEndUint32-- // 不包含ipEnd本身 | |
} | |
if ipStartUint32 > ipEndUint32 { | |
return nil, fmt.Errorf("start IP:%s must be less than end IP:%s", ipStart, ipEnd) | |
} | |
var cidr []string | |
for ipEndUint32 >= ipStartUint32 { | |
maxSize := 32 | |
for maxSize > 0 && (ipStartUint32&cidr2mask[maxSize-1]) == ipStartUint32 { | |
maxSize-- | |
} | |
x := math.Log(float64(ipEndUint32-ipStartUint32+1)) / math.Log(2) | |
if maxDiff := 32 - int(math.Floor(x)); maxSize < maxDiff { | |
maxSize = maxDiff | |
} | |
cidr = append(cidr, InetNtoA(nil, ipStartUint32)+"/"+strconv.Itoa(maxSize)) | |
ipStartUint32 += uint32(math.Exp2(float64(32 - maxSize))) | |
} | |
return cidr, err | |
} | |
// 参考这两个地方的代码 | |
// https://wang.yuxuan.org/blog/?itemid=102 | |
// https://oomake.com/question/2766391 | |
// IpRangeToCIDR | |
// @Description: 根据IP范围生成CIDR结果 | |
// @param cidr: 复用内存,nil时内部扩容 | |
// @param start: 起始IP | |
// @param end: 结束IP | |
// @return []string: 返回CIDR | |
// @return error | |
func IpRangeToCIDR(cidr []string, start, end string) ([]string, error) { | |
ips, err := netip.ParseAddr(start) | |
if err != nil { | |
return nil, err | |
} | |
ipe, err := netip.ParseAddr(end) | |
if err != nil { | |
return nil, err | |
} | |
isV4 := ips.Is4() | |
if isV4 != ipe.Is4() { | |
return nil, errors.New("start and end types are different") | |
} | |
if ips.Compare(ipe) > 0 { | |
return nil, errors.New("start > end") | |
} | |
var ( | |
ipsInt = new(big.Int).SetBytes(ips.AsSlice()) | |
ipeInt = new(big.Int).SetBytes(ipe.AsSlice()) | |
tmpInt = new(big.Int) | |
mask = new(big.Int) | |
one = big.NewInt(1) | |
buf []byte | |
bits, maxBit uint | |
) | |
if isV4 { | |
maxBit = 32 | |
buf = make([]byte, 4) | |
} else { | |
maxBit = 128 | |
buf = make([]byte, 16) | |
} | |
for { | |
bits = 1 | |
mask.SetUint64(1) | |
for bits < maxBit { | |
if (tmpInt.Or(ipsInt, mask).Cmp(ipeInt) > 0) || | |
(tmpInt.Lsh(tmpInt.Rsh(ipsInt, bits), bits).Cmp(ipsInt) != 0) { | |
bits-- | |
mask.Rsh(mask, 1) | |
break | |
} | |
bits++ | |
mask.Add(mask.Lsh(mask, 1), one) | |
} | |
addr, _ := netip.AddrFromSlice(ipsInt.FillBytes(buf)) | |
cidr = append(cidr, addr.String()+"/"+strconv.FormatUint(uint64(maxBit-bits), 10)) | |
if tmpInt.Or(ipsInt, mask); tmpInt.Cmp(ipeInt) >= 0 { | |
break | |
} | |
ipsInt.Add(tmpInt, one) | |
} | |
return cidr, nil | |
} | |
func main() { | |
start, end := "10.5.6.0", "10.23.25.255" | |
s0, err := IpRangeToCIDRRange(start, end) | |
if err != nil { | |
panic(err) | |
} | |
s1, err := IpRangeToCIDR(nil, start, end) | |
if err != nil { | |
panic(err) | |
} | |
if !cmpArr(s0, s1) { | |
panic(start + "," + end + " no cmp") | |
} | |
fmt.Println(strings.Join(s1, "\n")) | |
start = "2001:4860:4860:0000:0000:0000:0000:8888" | |
end = "2001:4860:4860:0000:0000:0000:0000:FFFF" | |
s0, err = IpRangeToCIDRRange(start, end) | |
if err != nil { | |
panic(err) | |
} | |
s1, err = IpRangeToCIDR(s1[:0], start, end) | |
if err != nil { | |
panic(err) | |
} | |
if !cmpArr(s0, s1) { | |
panic(start + "," + end + " no cmp") | |
} | |
fmt.Println(strings.Join(s1, "\n")) | |
} | |
func cmpArr(l, r []string) bool { | |
if len(l) != len(r) { | |
return false | |
} | |
for i, v := range l { | |
if r[i] != v { | |
return false | |
} | |
} | |
return true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
online check https://www.ipaddressguide.com/cidr
additional discussion https://groups.google.com/g/golang-nuts/c/rJvVwk4jwjQ
my share:https://go.dev/play/p/Ynx1liLAGs2