Skip to content

Instantly share code, notes, and snippets.

@P-A-R-U-S
Created March 16, 2018 22:53
Show Gist options
  • Save P-A-R-U-S/a090dd90c5104ce85a29c32669dac107 to your computer and use it in GitHub Desktop.
Save P-A-R-U-S/a090dd90c5104ce85a29c32669dac107 to your computer and use it in GitHub Desktop.
Go: Convert IP to CIDR
/ Convert IPv4 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
}
@xinternetx
Copy link

It works perfectly. Thank you. What about doing the same with IPv6? Specially the first function: iPv4RangeToCIDRRange(ipStart string, ipEnd string)

Could be possible? Thanks!

@P-A-R-U-S
Copy link
Author

Thank you for the comment. It's actually a really good idea. I need some time to complete some articles and will update the following repo to support IPv6: https://github.com/P-A-R-U-S/Go-Network-Scanner

And I will appreciate "star" for this repo :))

@xinternetx
Copy link

I'll be waiting. Thank you!

@jan-bar
Copy link

jan-bar commented Nov 5, 2022

Thank you very much, very useful

@P-A-R-U-S
Copy link
Author

Thank you very much, very useful

You're welcome. Looks like time to add IPv6.

@jan-bar
Copy link

jan-bar commented Nov 7, 2022

Thank you very much, very useful

You're welcome. Looks like time to add IPv6.

To meet the needs, I have done this first, it should be able to be used for testing

and I found the same implementation in other languages ​​here

https://blog.ip2location.com/knowledge-base/how-to-convert-ip-address-range-into-cidr/

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

@jan-bar
Copy link

jan-bar commented Nov 8, 2022

@P-A-R-U-S @xinternetx

I found a method that works for both ipv4 and ipv6

package main

import (
	"errors"
	"fmt"
	"math/big"
	"net/netip"
	"strings"
)

func main() {
	ss, err := IpRangeToCIDR("172.17.0.0", "172.17.5.0")
	if err != nil {
		panic(err)
	}
	fmt.Println(strings.Join(ss, "\n"))

	ss, err = IpRangeToCIDR("2001:4860:4860:ffff:ffff:ffff:ffff:ffff", "2001:4860:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF")
	if err != nil {
		panic(err)
	}
	fmt.Println(strings.Join(ss, "\n"))
}

func IpRangeToCIDR(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())
		nextIp = new(big.Int)
		maxBit = new(big.Int)
		cmpSh  = new(big.Int)
		bits   = new(big.Int)
		mask   = new(big.Int)
		one    = big.NewInt(1)
		buf    []byte
		cidr   []string
		bitSh  uint
	)
	if isV4 {
		maxBit.SetUint64(32)
		buf = make([]byte, 4)
	} else {
		maxBit.SetUint64(128)
		buf = make([]byte, 16)
	}

	for {
		bits.SetUint64(1)
		mask.SetUint64(1)
		for bits.Cmp(maxBit) < 0 {
			nextIp.Or(ipsInt, mask)

			bitSh = uint(bits.Uint64())
			cmpSh.Lsh(cmpSh.Rsh(ipsInt, bitSh), bitSh)
			if (nextIp.Cmp(ipeInt) > 0) || (cmpSh.Cmp(ipsInt) != 0) {
				bits.Sub(bits, one)
				mask.Rsh(mask, 1)
				break
			}
			bits.Add(bits, one)
			mask.Add(mask.Lsh(mask, 1), one)
		}

		addr, _ := netip.AddrFromSlice(ipsInt.FillBytes(buf))
		cidr = append(cidr, addr.String()+"/"+bits.Sub(maxBit, bits).String())

		if nextIp.Or(ipsInt, mask); nextIp.Cmp(ipeInt) >= 0 {
			break
		}
		ipsInt.Add(nextIp, one)
	}
	return cidr, nil
}

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