From a551595bfd19b3a377f66c46603c7246313873eb Mon Sep 17 00:00:00 2001 From: jjhwan-h Date: Tue, 16 Sep 2025 18:07:26 +0900 Subject: [PATCH 1/2] feat: add support for multi-octet and dash-based IP ranges --- cmd/mapcidr/main.go | 24 ++++++++++++-- cmd/mapcidr/main_test.go | 61 +++++++++++++++++++++++++++++++++++ ip.go | 68 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/cmd/mapcidr/main.go b/cmd/mapcidr/main.go index 8c0dab3..5183bf2 100644 --- a/cmd/mapcidr/main.go +++ b/cmd/mapcidr/main.go @@ -419,8 +419,28 @@ func process(wg *sync.WaitGroup, chancidr, outputchan chan string) { } for _, cidr := range cidrsToProcess { - // Add IPs into ipRangeList which are passed as input. Example - "192.168.0.0-192.168.0.5" + if strings.Contains(cidr, "-") { + // Try to parse as multi-octet range + if strings.Count(cidr, ".") == 3 { + ips, err := mapcidr.ExpandIPPattern(cidr) + if err != nil { + gologger.Fatal().Msgf("%s\n", err) + } + + for _, ip := range ips { + ipCidr := ip.String() + "/32" + if options.Aggregate || options.Shuffle || hasSort || options.AggregateApprox || options.Count { + _, ipnet, _ := net.ParseCIDR(ipCidr) + allCidrs = append(allCidrs, ipnet) + } else { + commonFunc(ipCidr, outputchan) + } + } + continue + } + + // Add IPs into ipRangeList which are passed as input. Example - "192.168.0.0-192.168.0.5" var ipRange []net.IP for _, ipstr := range strings.Split(cidr, "-") { ipRange = append(ipRange, net.ParseIP(ipstr)) @@ -452,8 +472,8 @@ func process(wg *sync.WaitGroup, chancidr, outputchan chan string) { continue } + // In case of coalesce/shuffle we need to know all the cidrs and aggregate them by calling the proper function if options.Aggregate || options.Shuffle || hasSort || options.AggregateApprox || options.Count { - // In case of coalesce/shuffle we need to know all the cidrs and aggregate them by calling the proper function _ = ranger.Add(cidr) allCidrs = append(allCidrs, pCidr) } else { diff --git a/cmd/mapcidr/main_test.go b/cmd/mapcidr/main_test.go index c13c7fc..f034e5b 100644 --- a/cmd/mapcidr/main_test.go +++ b/cmd/mapcidr/main_test.go @@ -230,6 +230,67 @@ func TestProcess(t *testing.T) { Aggregate: true, }, expectedOutput: []string{"10.0.0.0/32", "10.0.0.2/31"}, + }, { + name: "MultiOctetRangeExpansion", + chancidr: make(chan string), + outputchan: make(chan string), + options: Options{ + FileCidr: []string{"192.168.0-1.1-2"}, + }, + expectedOutput: []string{ + "192.168.0.1", "192.168.0.2", + "192.168.1.1", "192.168.1.2", + }, + }, + { + name: "MultiOctetRangeWithFilter", + chancidr: make(chan string), + outputchan: make(chan string), + options: Options{ + FileCidr: []string{"192.168.0-1.1-2"}, + FilterIP: []string{"192.168.1.1"}, + }, + expectedOutput: []string{ + "192.168.0.1", "192.168.0.2", + "192.168.1.2", + }, + }, + { + name: "MultiOctetRangeAggregate", + chancidr: make(chan string), + outputchan: make(chan string), + options: Options{ + FileCidr: []string{"10.0-1.0-1.0-1"}, + Aggregate: true, + }, + expectedOutput: []string{ + "10.0.0.0/31", "10.0.1.0/31", + "10.1.0.0/31", "10.1.1.0/31", + }, + }, + { + name: "MultiOctetRangeSortAscending", + chancidr: make(chan string), + outputchan: make(chan string), + options: Options{ + FileCidr: []string{"10.0.0-0.2-3"}, + SortAscending: true, + }, + expectedOutput: []string{ + "10.0.0.2", "10.0.0.3", + }, + }, + { + name: "MultiOctetRangeSortDescending", + chancidr: make(chan string), + outputchan: make(chan string), + options: Options{ + FileCidr: []string{"10.0.1-2.1"}, + SortDescending: true, + }, + expectedOutput: []string{ + "10.0.2.1", "10.0.1.1", + }, }, } var wg sync.WaitGroup diff --git a/ip.go b/ip.go index 606e867..dfa7a8b 100644 --- a/ip.go +++ b/ip.go @@ -1209,3 +1209,71 @@ func IpRangeToCIDR(start, end string) ([]string, error) { } return cidr, nil } + +/* +ExpandIPPattern expands an IPv4 pattern string into a list of net.IP addresses. +The pattern must be in the form of four octets separated by dots (a.b.c.d). +*/ +func ExpandIPPattern(pattern string) ([]net.IP, error) { + parts := strings.Split(pattern, ".") + if len(parts) != 4 { + return nil, fmt.Errorf("invalid IP pattern: %s", pattern) + } + + var octets [][]int + for _, part := range parts { + if strings.Contains(part, "-") { + bounds := strings.Split(part, "-") + if len(bounds) != 2 { + return nil, fmt.Errorf("invalid range in %s", part) + } + + start, err1 := strconv.Atoi(bounds[0]) + end, err2 := strconv.Atoi(bounds[1]) + + if err1 != nil || err2 != nil || start > end { + return nil, fmt.Errorf("invalid range: %s", part) + } + + var nums []int + for i := start; i <= end; i++ { + nums = append(nums, i) + } + octets = append(octets, nums) + + } else { + v, err := strconv.Atoi(part) + if err != nil { + return nil, fmt.Errorf("invalid octet: %s", part) + } + + octets = append(octets, []int{v}) + } + } + + var ips []net.IP + for _, o1 := range octets[0] { + if o1 < 0 || o1 > 255 { + return nil, fmt.Errorf("invalid octet value: %d", o1) + } + for _, o2 := range octets[1] { + if o2 < 0 || o2 > 255 { + return nil, fmt.Errorf("invalid octet value: %d", o2) + } + for _, o3 := range octets[2] { + if o3 < 0 || o3 > 255 { + return nil, fmt.Errorf("invalid octet value: %d", o3) + } + for _, o4 := range octets[3] { + if o4 < 0 || o4 > 255 { + return nil, fmt.Errorf("invalid octet value: %d", o4) + } + ip := net.IPv4(byte(o1), byte(o2), byte(o3), byte(o4)) + ips = append(ips, ip) + } + } + } + } + + return ips, nil +} From 5bf3985302927916be4bea36eafd83008fcc7841 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Thu, 9 Oct 2025 01:54:33 +0200 Subject: [PATCH 2/2] lint --- cidr.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cidr.go b/cidr.go index f8b1098..0aeec80 100644 --- a/cidr.go +++ b/cidr.go @@ -256,9 +256,9 @@ func nextSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, error) { // specific check. // // nolint:all -func isPowerOfTwoPlusOne(x int) bool { - return isPowerOfTwo(x - 1) -} +// func isPowerOfTwoPlusOne(x int) bool { +// return isPowerOfTwo(x - 1) +// } // isPowerOfTwo returns if a number is a power of 2 func isPowerOfTwo(x int) bool { @@ -268,11 +268,11 @@ func isPowerOfTwo(x int) bool { // reverseIPNet reverses an ipnet slice // // nolint:all -func reverseIPNet(ipnets []*net.IPNet) { - for i, j := 0, len(ipnets)-1; i < j; i, j = i+1, j-1 { - ipnets[i], ipnets[j] = ipnets[j], ipnets[i] - } -} +// func reverseIPNet(ipnets []*net.IPNet) { +// for i, j := 0, len(ipnets)-1; i < j; i, j = i+1, j-1 { +// ipnets[i], ipnets[j] = ipnets[j], ipnets[i] +// } +// } // IPAddresses returns all the IP addresses in a CIDR func IPAddresses(cidr string) ([]string, error) {