Skip to content

Instantly share code, notes, and snippets.

@jan-bar
Forked from P-A-R-U-S/Golang-IP-to-CIDR
Last active November 9, 2022 03:26
Show Gist options
  • Save jan-bar/1142275c933c13a3f61a3bc20cc6da49 to your computer and use it in GitHub Desktop.
Save jan-bar/1142275c933c13a3f61a3bc20cc6da49 to your computer and use it in GitHub Desktop.
Go: Convert IP to CIDR
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)
}
}
}
// 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
}
// 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
}
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
}
@jan-bar
Copy link
Author

jan-bar commented Nov 5, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment