From 13d7ccde2fc7efa91461b1a6ec27dfd89b91327d Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Thu, 16 Nov 2023 21:44:53 +0500 Subject: [PATCH 01/11] rename, remove incorrect cidrs --- lib/cmd_tool_aggregate.go | 40 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 4dbf8bc4..7e7d4c5e 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -192,26 +192,10 @@ func CmdToolAggregate( } // Sort and merge collected CIDRs and IPs. - aggregatedCIDRs := aggregateCIDRs(parsedCIDRs) - outlierIPs := make([]net.IP, 0) - length := len(aggregatedCIDRs) - for _, ip := range parsedIPs { - for i, cidr := range aggregatedCIDRs { - if cidr.Contains(ip) { - break - } else if i == length-1 { - outlierIPs = append(outlierIPs, ip) - } - } - } + merged := mergeOverlapping(parsedCIDRs) // Print the aggregated CIDRs. - for _, r := range aggregatedCIDRs { - fmt.Println(r.String()) - } - - // Print outliers. - for _, r := range outlierIPs { + for _, r := range merged { fmt.Println(r.String()) } @@ -219,30 +203,30 @@ func CmdToolAggregate( } // Helper function to aggregate IP ranges. -func aggregateCIDRs(cidrs []net.IPNet) []net.IPNet { - aggregatedCIDRs := make([]net.IPNet, 0) +func mergeOverlapping(cidrs []net.IPNet) []net.IPNet { + merged := make([]net.IPNet, 0) // Sort CIDRs by starting IP. sortCIDRs(cidrs) for _, r := range cidrs { - if len(aggregatedCIDRs) == 0 { - aggregatedCIDRs = append(aggregatedCIDRs, r) + if len(merged) == 0 { + merged = append(merged, r) continue } - last := len(aggregatedCIDRs) - 1 - prev := aggregatedCIDRs[last] + last := len(merged) - 1 + prev := merged[last] if canAggregate(prev, r) { // Merge overlapping CIDRs. - aggregatedCIDRs[last] = aggregateCIDR(prev, r) + merged[last] = merge(prev, r) } else { - aggregatedCIDRs = append(aggregatedCIDRs, r) + merged = append(merged, r) } } - return aggregatedCIDRs + return merged } // Helper function to sort IP ranges by starting IP. @@ -258,7 +242,7 @@ func canAggregate(r1, r2 net.IPNet) bool { } // Helper function to aggregate two CIDRs. -func aggregateCIDR(r1, r2 net.IPNet) net.IPNet { +func merge(r1, r2 net.IPNet) net.IPNet { mask1, _ := r1.Mask.Size() mask2, _ := r2.Mask.Size() From e00320d44d839d602ad16d394baa11a85f0268b7 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 12:40:21 +0500 Subject: [PATCH 02/11] support adjacent prefixes --- lib/cmd_tool_aggregate.go | 50 +++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 7e7d4c5e..0c644f3d 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -3,6 +3,7 @@ package lib import ( "bufio" "bytes" + "encoding/binary" "fmt" "io" "net" @@ -191,24 +192,65 @@ func CmdToolAggregate( parsedIPs = append(parsedIPs, ips...) } - // Sort and merge collected CIDRs and IPs. + // Sort CIDRs by starting IP. + sortCIDRs(parsedCIDRs) + + // Remove prefixes which are included in another prefix. merged := mergeOverlapping(parsedCIDRs) + // Combine adjacent entries. + adjacentCombined := combineAdjacent(merged) + // Print the aggregated CIDRs. - for _, r := range merged { + for _, r := range adjacentCombined { fmt.Println(r.String()) } return nil } +// The adjacency condition is that the prefixes have the same mask length, +// and the second prefix is exactly one larger than the first prefix. +func areAdjacent(r1, r2 net.IPNet) bool { + prefix1 := binary.BigEndian.Uint32(r1.IP.To4()) + prefix2 := binary.BigEndian.Uint32(r2.IP.To4()) + + fmt.Println("prefix1, prefix2", prefix1, prefix2) + mask1, _ := r1.Mask.Size() + mask2, _ := r2.Mask.Size() + + return mask1 == mask2 && (prefix1%(2<<(32-mask1)) == 0) && (prefix2-prefix1 == (1 << (32 - mask1))) +} + +func combineAdjacentCIDRs(r1, r2 net.IPNet) net.IPNet { + mask1, _ := r1.Mask.Size() + + commonPrefixLen := mask1 - 1 + commonPrefix := r1.IP.Mask(r1.Mask) + + return net.IPNet{IP: commonPrefix, Mask: net.CIDRMask(commonPrefixLen, len(commonPrefix)*8)} +} + +func combineAdjacent(cidrs []net.IPNet) []net.IPNet { + res := make([]net.IPNet, 0) + + for i := 0; i < len(cidrs)-1; i++ { + if areAdjacent(cidrs[i], cidrs[i+1]) { + res = append(res, combineAdjacentCIDRs(cidrs[i], cidrs[i+1])) + i++ + } else { + res = append(res, cidrs[i]) + } + } + + return res +} + // Helper function to aggregate IP ranges. func mergeOverlapping(cidrs []net.IPNet) []net.IPNet { merged := make([]net.IPNet, 0) // Sort CIDRs by starting IP. - sortCIDRs(cidrs) - for _, r := range cidrs { if len(merged) == 0 { merged = append(merged, r) From e38b91ca0ac8cdb6ef9cd6d18a186d0d1f803c5b Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 13:19:33 +0500 Subject: [PATCH 03/11] fix: append last non-adjacent ip --- lib/cmd_tool_aggregate.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 0c644f3d..3c3e1b1e 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -215,7 +215,6 @@ func areAdjacent(r1, r2 net.IPNet) bool { prefix1 := binary.BigEndian.Uint32(r1.IP.To4()) prefix2 := binary.BigEndian.Uint32(r2.IP.To4()) - fmt.Println("prefix1, prefix2", prefix1, prefix2) mask1, _ := r1.Mask.Size() mask2, _ := r2.Mask.Size() @@ -235,11 +234,15 @@ func combineAdjacent(cidrs []net.IPNet) []net.IPNet { res := make([]net.IPNet, 0) for i := 0; i < len(cidrs)-1; i++ { + if areAdjacent(cidrs[i], cidrs[i+1]) { res = append(res, combineAdjacentCIDRs(cidrs[i], cidrs[i+1])) i++ } else { res = append(res, cidrs[i]) + if i == len(cidrs)-2 { + res = append(res, cidrs[i+1]) + } } } From f0bad86e48d2c172294e1f4f52f447c75df2a96d Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 13:25:16 +0500 Subject: [PATCH 04/11] update help: works for IPv4 only --- ipinfo/cmd_tool_aggregate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipinfo/cmd_tool_aggregate.go b/ipinfo/cmd_tool_aggregate.go index 5445a0e2..bd9ffc9a 100644 --- a/ipinfo/cmd_tool_aggregate.go +++ b/ipinfo/cmd_tool_aggregate.go @@ -25,7 +25,7 @@ func printHelpToolAggregate() { Description: Accepts IPs, IP ranges, and CIDRs, aggregating them efficiently. Input can be IPs, IP ranges, CIDRs, and/or filepath to a file - containing any of these. Works for both IPv4 and IPv6. + containing any of these. Works for IPv4 only. If input contains single IPs, it tries to merge them into the input CIDRs, otherwise they are printed to the output as they are. From f3d857af758875b15049711eb10fdebb193dfd88 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 13:50:17 +0500 Subject: [PATCH 05/11] print outlier ips --- lib/cmd_tool_aggregate.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 3c3e1b1e..592683f3 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -200,12 +200,32 @@ func CmdToolAggregate( // Combine adjacent entries. adjacentCombined := combineAdjacent(merged) + outlierIPs := make([]net.IP, 0) + length := len(adjacentCombined) + if length != 0 { + for _, ip := range parsedIPs { + for i, cidr := range adjacentCombined { + if cidr.Contains(ip) { + break + } else if i == length-1 { + outlierIPs = append(outlierIPs, ip) + } + } + } + } else { + outlierIPs = append(outlierIPs, parsedIPs...) + } // Print the aggregated CIDRs. for _, r := range adjacentCombined { fmt.Println(r.String()) } + // Print the outlierIPs. + for _, r := range outlierIPs { + fmt.Println(r.String()) + } + return nil } From b3be3c219dfe3575fc9f8c4fd0b3cc09d2047705 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 17:14:30 +0500 Subject: [PATCH 06/11] check adjacency of ipv4 only --- lib/cmd_tool_aggregate.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 592683f3..d37a08a0 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -3,7 +3,6 @@ package lib import ( "bufio" "bytes" - "encoding/binary" "fmt" "io" "net" @@ -232,13 +231,23 @@ func CmdToolAggregate( // The adjacency condition is that the prefixes have the same mask length, // and the second prefix is exactly one larger than the first prefix. func areAdjacent(r1, r2 net.IPNet) bool { - prefix1 := binary.BigEndian.Uint32(r1.IP.To4()) - prefix2 := binary.BigEndian.Uint32(r2.IP.To4()) - mask1, _ := r1.Mask.Size() mask2, _ := r2.Mask.Size() - return mask1 == mask2 && (prefix1%(2<<(32-mask1)) == 0) && (prefix2-prefix1 == (1 << (32 - mask1))) + if mask1 != mask2 { + return false + } + + var prefix1, prefix2 uint32 + var isAdjacent bool = false + + if r1.IP.To4() != nil && r2.IP.To4() != nil { + prefix1 = ipToUint32(r1.IP.To4()) + prefix2 = ipToUint32(r2.IP.To4()) + isAdjacent = (prefix1%(2<<(32-mask1)) == 0) && (prefix2-prefix1 == (1 << (32 - mask1))) + } + + return isAdjacent } func combineAdjacentCIDRs(r1, r2 net.IPNet) net.IPNet { From a865fe2ba626a1810c2454853c1ac3ab53b36e66 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 18:55:37 +0500 Subject: [PATCH 07/11] use aggregate implementation from ipinfo/data --- lib/cmd_tool_aggregate.go | 229 ++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 108 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index d37a08a0..386d76f4 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -3,8 +3,10 @@ package lib import ( "bufio" "bytes" + "encoding/binary" "fmt" "io" + "math" "net" "os" "sort" @@ -36,6 +38,12 @@ func (f *CmdToolAggregateFlags) Init() { ) } +// CIDR represens a Classless Inter-Domain Routing structure. +type CIDR struct { + IP net.IP + Network *net.IPNet +} + // CmdToolAggregate is the common core logic for aggregating IPs, IP ranges and CIDRs. func CmdToolAggregate( f CmdToolAggregateFlags, @@ -55,26 +63,9 @@ func CmdToolAggregate( return nil } - // Parses a list of CIDRs. - parseCIDRs := func(cidrs []string) []net.IPNet { - parsedCIDRs := make([]net.IPNet, 0) - for _, cidrStr := range cidrs { - _, ipNet, err := net.ParseCIDR(cidrStr) - if err != nil { - if !f.Quiet { - fmt.Printf("Invalid CIDR: %s\n", cidrStr) - } - continue - } - parsedCIDRs = append(parsedCIDRs, *ipNet) - } - - return parsedCIDRs - } - // Input parser. - parseInput := func(rows []string) ([]net.IPNet, []net.IP) { - parsedCIDRs := make([]net.IPNet, 0) + parseInput := func(rows []string) ([]string, []net.IP) { + parsedCIDRs := make([]string, 0) parsedIPs := make([]net.IP, 0) var separator string for _, rowStr := range rows { @@ -96,7 +87,7 @@ func CmdToolAggregate( if strings.ContainsRune(rowStr, ':') { cidrs, err := CIDRsFromIP6RangeStrRaw(rowStr) if err == nil { - parsedCIDRs = append(parsedCIDRs, parseCIDRs(cidrs)...) + parsedCIDRs = append(parsedCIDRs, cidrs...) continue } else { if !f.Quiet { @@ -107,7 +98,7 @@ func CmdToolAggregate( } else { cidrs, err := CIDRsFromIPRangeStrRaw(rowStr) if err == nil { - parsedCIDRs = append(parsedCIDRs, parseCIDRs(cidrs)...) + parsedCIDRs = append(parsedCIDRs, cidrs...) continue } else { if !f.Quiet { @@ -117,7 +108,7 @@ func CmdToolAggregate( } } } else if strings.ContainsRune(rowStr, '/') { - parsedCIDRs = append(parsedCIDRs, parseCIDRs([]string{rowStr})...) + parsedCIDRs = append(parsedCIDRs, []string{rowStr}...) continue } else { if ip := net.ParseIP(rowStr); ip != nil { @@ -165,7 +156,7 @@ func CmdToolAggregate( } // Vars to contain CIDRs/IPs from all input sources. - parsedCIDRs := make([]net.IPNet, 0) + parsedCIDRs := make([]string, 0) parsedIPs := make([]net.IP, 0) // Collect CIDRs/IPs from stdin. @@ -187,24 +178,19 @@ func CmdToolAggregate( rows := scanrdr(file) file.Close() cidrs, ips := parseInput(rows) + parsedCIDRs = append(parsedCIDRs, cidrs...) parsedIPs = append(parsedIPs, ips...) } - // Sort CIDRs by starting IP. - sortCIDRs(parsedCIDRs) + adjacentCombined := CombineAdjacent(StripOverlapping(List(parsedCIDRs))) - // Remove prefixes which are included in another prefix. - merged := mergeOverlapping(parsedCIDRs) - - // Combine adjacent entries. - adjacentCombined := combineAdjacent(merged) outlierIPs := make([]net.IP, 0) length := len(adjacentCombined) if length != 0 { for _, ip := range parsedIPs { for i, cidr := range adjacentCombined { - if cidr.Contains(ip) { + if cidr.Network.Contains(ip) { break } else if i == length-1 { outlierIPs = append(outlierIPs, ip) @@ -228,110 +214,137 @@ func CmdToolAggregate( return nil } -// The adjacency condition is that the prefixes have the same mask length, -// and the second prefix is exactly one larger than the first prefix. -func areAdjacent(r1, r2 net.IPNet) bool { - mask1, _ := r1.Mask.Size() - mask2, _ := r2.Mask.Size() - - if mask1 != mask2 { - return false +// New creates a new CIDR structure. +func New(s string) *CIDR { + ip, ipnet, err := net.ParseCIDR(s) + if err != nil { + panic(err) } - - var prefix1, prefix2 uint32 - var isAdjacent bool = false - - if r1.IP.To4() != nil && r2.IP.To4() != nil { - prefix1 = ipToUint32(r1.IP.To4()) - prefix2 = ipToUint32(r2.IP.To4()) - isAdjacent = (prefix1%(2<<(32-mask1)) == 0) && (prefix2-prefix1 == (1 << (32 - mask1))) + return &CIDR{ + IP: ip, + Network: ipnet, } +} - return isAdjacent +func (c *CIDR) String() string { + return c.Network.String() } -func combineAdjacentCIDRs(r1, r2 net.IPNet) net.IPNet { - mask1, _ := r1.Mask.Size() +// MaskLen returns a network mask length. +func (c *CIDR) MaskLen() uint32 { + i, _ := c.Network.Mask.Size() + return uint32(i) +} - commonPrefixLen := mask1 - 1 - commonPrefix := r1.IP.Mask(r1.Mask) +// PrefixUint32 returns a prefix. +func (c *CIDR) PrefixUint32() uint32 { + return binary.BigEndian.Uint32(c.IP.To4()) +} - return net.IPNet{IP: commonPrefix, Mask: net.CIDRMask(commonPrefixLen, len(commonPrefix)*8)} +// Size returns a size of a CIDR range. +func (c *CIDR) Size() int { + ones, bits := c.Network.Mask.Size() + return int(math.Pow(2, float64(bits-ones))) } -func combineAdjacent(cidrs []net.IPNet) []net.IPNet { - res := make([]net.IPNet, 0) +// List returns a slice of sorted CIDR structures. +func List(s []string) []*CIDR { + out := make([]*CIDR, 0) + for _, c := range s { + out = append(out, New(c)) + } + sort.Sort(cidrSort(out)) + return out +} - for i := 0; i < len(cidrs)-1; i++ { +type cidrSort []*CIDR - if areAdjacent(cidrs[i], cidrs[i+1]) { - res = append(res, combineAdjacentCIDRs(cidrs[i], cidrs[i+1])) - i++ - } else { - res = append(res, cidrs[i]) - if i == len(cidrs)-2 { - res = append(res, cidrs[i+1]) +func (s cidrSort) Len() int { return len(s) } +func (s cidrSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s cidrSort) Less(i, j int) bool { + cmp := bytes.Compare(s[i].IP, s[j].IP) + return cmp < 0 || (cmp == 0 && s[i].MaskLen() < s[j].MaskLen()) +} + +// StripOverlapping returns a slice of CIDR structures with overlapping ranges +// stripped. +func StripOverlapping(s []*CIDR) []*CIDR { + l := len(s) + for i := 0; i < l-1; i++ { + if s[i] == nil { + continue + } + for j := i + 1; j < l; j++ { + if overlaps(s[j], s[i]) { + s[j] = nil } } } - - return res + return filter(s) } -// Helper function to aggregate IP ranges. -func mergeOverlapping(cidrs []net.IPNet) []net.IPNet { - merged := make([]net.IPNet, 0) +func overlaps(a, b *CIDR) bool { + return (a.PrefixUint32() / (1 << (32 - b.MaskLen()))) == + (b.PrefixUint32() / (1 << (32 - b.MaskLen()))) +} - // Sort CIDRs by starting IP. - for _, r := range cidrs { - if len(merged) == 0 { - merged = append(merged, r) - continue +// CombineAdjacent returns a slice of CIDR structures with adjacent ranges +// combined. +func CombineAdjacent(s []*CIDR) []*CIDR { + for { + found := false + l := len(s) + for i := 0; i < l-1; i++ { + if s[i] == nil { + continue + } + for j := i + 1; j < l; j++ { + if s[j] == nil { + continue + } + if adjacent(s[i], s[j]) { + c := fmt.Sprintf("%s/%d", s[i].IP.String(), s[i].MaskLen()-1) + s[i] = New(c) + s[j] = nil + found = true + } + } } - last := len(merged) - 1 - prev := merged[last] - - if canAggregate(prev, r) { - // Merge overlapping CIDRs. - merged[last] = merge(prev, r) - } else { - merged = append(merged, r) + if !found { + break } } - - return merged + return filter(s) } -// Helper function to sort IP ranges by starting IP. -func sortCIDRs(ipRanges []net.IPNet) { - sort.SliceStable(ipRanges, func(i, j int) bool { - return bytes.Compare(ipRanges[i].IP, ipRanges[j].IP) < 0 - }) +func adjacent(a, b *CIDR) bool { + return (a.MaskLen() == b.MaskLen()) && + (a.PrefixUint32()%(2<<(32-b.MaskLen())) == 0) && + (b.PrefixUint32()-a.PrefixUint32() == (1 << (32 - a.MaskLen()))) } -// Helper function to check if two CIDRs can be aggregated. -func canAggregate(r1, r2 net.IPNet) bool { - return r1.Contains(r2.IP) || r2.Contains(r1.IP) +func filter(s []*CIDR) []*CIDR { + out := s[:0] + for _, x := range s { + if x != nil { + out = append(out, x) + } + } + return out } -// Helper function to aggregate two CIDRs. -func merge(r1, r2 net.IPNet) net.IPNet { - mask1, _ := r1.Mask.Size() - mask2, _ := r2.Mask.Size() - - ipLen := net.IPv6len * 8 - if r1.IP.To4() != nil { - ipLen = net.IPv4len * 8 - } +// Aggregate returns a slice of CIDR structures with adjacent ranges combined +// and overlapping ranges stripped. +func Aggregate(s []string) []*CIDR { + return CombineAdjacent(StripOverlapping(List(s))) +} - // Find the common prefix length - commonPrefixLen := mask1 - if mask2 < commonPrefixLen { - commonPrefixLen = mask2 +// Size calculates an overal size of a slice of CIDR structures. +func Size(s []*CIDR) (i int) { + for _, x := range s { + i += x.Size() } - - commonPrefix := r1.IP.Mask(net.CIDRMask(commonPrefixLen, ipLen)) - - return net.IPNet{IP: commonPrefix, Mask: net.CIDRMask(commonPrefixLen, ipLen)} + return } From c56a5843d4c4a5e3db66492c088c64c814d42c30 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 19:15:31 +0500 Subject: [PATCH 08/11] consider IPv4 only --- lib/cmd_tool_aggregate.go | 46 +++++---------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 386d76f4..f4a7b8ed 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -67,51 +67,17 @@ func CmdToolAggregate( parseInput := func(rows []string) ([]string, []net.IP) { parsedCIDRs := make([]string, 0) parsedIPs := make([]net.IP, 0) - var separator string for _, rowStr := range rows { if strings.ContainsAny(rowStr, ",-") { - if delim := strings.ContainsRune(rowStr, ','); delim { - separator = "," - } else { - separator = "-" - } - - ipRange := strings.Split(rowStr, separator) - if len(ipRange) != 2 { - if !f.Quiet { - fmt.Printf("Invalid IP range: %s\n", rowStr) - } - continue - } - - if strings.ContainsRune(rowStr, ':') { - cidrs, err := CIDRsFromIP6RangeStrRaw(rowStr) - if err == nil { - parsedCIDRs = append(parsedCIDRs, cidrs...) - continue - } else { - if !f.Quiet { - fmt.Printf("Invalid IP range %s. Err: %v\n", rowStr, err) - } - continue - } - } else { - cidrs, err := CIDRsFromIPRangeStrRaw(rowStr) - if err == nil { - parsedCIDRs = append(parsedCIDRs, cidrs...) - continue - } else { - if !f.Quiet { - fmt.Printf("Invalid IP range %s. Err: %v\n", rowStr, err) - } - continue - } - } + continue } else if strings.ContainsRune(rowStr, '/') { - parsedCIDRs = append(parsedCIDRs, []string{rowStr}...) + _, ipnet, err := net.ParseCIDR(rowStr) + if err == nil && IsCIDRIPv4(ipnet) { + parsedCIDRs = append(parsedCIDRs, []string{rowStr}...) + } continue } else { - if ip := net.ParseIP(rowStr); ip != nil { + if ip := net.ParseIP(rowStr); IsIPv4(ip) { parsedIPs = append(parsedIPs, ip) } else { if !f.Quiet { From 76d9f173e7b9e7b0a42a4eb952e25fa4891d9eec Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 19:21:27 +0500 Subject: [PATCH 09/11] refactor --- lib/cmd_tool_aggregate.go | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index f4a7b8ed..7c14806d 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -149,7 +149,7 @@ func CmdToolAggregate( parsedIPs = append(parsedIPs, ips...) } - adjacentCombined := CombineAdjacent(StripOverlapping(List(parsedCIDRs))) + adjacentCombined := combineAdjacent(stripOverlapping(list(parsedCIDRs))) outlierIPs := make([]net.IP, 0) length := len(adjacentCombined) @@ -180,8 +180,8 @@ func CmdToolAggregate( return nil } -// New creates a new CIDR structure. -func New(s string) *CIDR { +// newCidr creates a newCidr CIDR structure. +func newCidr(s string) *CIDR { ip, ipnet, err := net.ParseCIDR(s) if err != nil { panic(err) @@ -213,11 +213,11 @@ func (c *CIDR) Size() int { return int(math.Pow(2, float64(bits-ones))) } -// List returns a slice of sorted CIDR structures. -func List(s []string) []*CIDR { +// list returns a slice of sorted CIDR structures. +func list(s []string) []*CIDR { out := make([]*CIDR, 0) for _, c := range s { - out = append(out, New(c)) + out = append(out, newCidr(c)) } sort.Sort(cidrSort(out)) return out @@ -233,9 +233,9 @@ func (s cidrSort) Less(i, j int) bool { return cmp < 0 || (cmp == 0 && s[i].MaskLen() < s[j].MaskLen()) } -// StripOverlapping returns a slice of CIDR structures with overlapping ranges +// stripOverlapping returns a slice of CIDR structures with overlapping ranges // stripped. -func StripOverlapping(s []*CIDR) []*CIDR { +func stripOverlapping(s []*CIDR) []*CIDR { l := len(s) for i := 0; i < l-1; i++ { if s[i] == nil { @@ -255,9 +255,9 @@ func overlaps(a, b *CIDR) bool { (b.PrefixUint32() / (1 << (32 - b.MaskLen()))) } -// CombineAdjacent returns a slice of CIDR structures with adjacent ranges +// combineAdjacent returns a slice of CIDR structures with adjacent ranges // combined. -func CombineAdjacent(s []*CIDR) []*CIDR { +func combineAdjacent(s []*CIDR) []*CIDR { for { found := false l := len(s) @@ -271,7 +271,7 @@ func CombineAdjacent(s []*CIDR) []*CIDR { } if adjacent(s[i], s[j]) { c := fmt.Sprintf("%s/%d", s[i].IP.String(), s[i].MaskLen()-1) - s[i] = New(c) + s[i] = newCidr(c) s[j] = nil found = true } @@ -300,17 +300,3 @@ func filter(s []*CIDR) []*CIDR { } return out } - -// Aggregate returns a slice of CIDR structures with adjacent ranges combined -// and overlapping ranges stripped. -func Aggregate(s []string) []*CIDR { - return CombineAdjacent(StripOverlapping(List(s))) -} - -// Size calculates an overal size of a slice of CIDR structures. -func Size(s []*CIDR) (i int) { - for _, x := range s { - i += x.Size() - } - return -} From 24eb46a7724d3f14c66a8dc80c02c6264b086786 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Mon, 20 Nov 2023 19:34:57 +0500 Subject: [PATCH 10/11] update help --- ipinfo/cmd_tool_aggregate.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ipinfo/cmd_tool_aggregate.go b/ipinfo/cmd_tool_aggregate.go index bd9ffc9a..b3da67ed 100644 --- a/ipinfo/cmd_tool_aggregate.go +++ b/ipinfo/cmd_tool_aggregate.go @@ -23,9 +23,7 @@ func printHelpToolAggregate() { `Usage: %s tool aggregate [] Description: - Accepts IPs, IP ranges, and CIDRs, aggregating them efficiently. - Input can be IPs, IP ranges, CIDRs, and/or filepath to a file - containing any of these. Works for IPv4 only. + Accepts IPv4 IPs and CIDRs, aggregating them efficiently. If input contains single IPs, it tries to merge them into the input CIDRs, otherwise they are printed to the output as they are. @@ -37,9 +35,6 @@ Examples: # Aggregate two CIDRs. $ %[1]s tool aggregate 1.1.1.0/30 1.1.1.0/28 - # Aggregate IP range and CIDR. - $ %[1]s tool aggregate 1.1.1.0-1.1.1.244 1.1.1.0/28 - # Aggregate enteries from 2 files. $ %[1]s tool aggregate /path/to/file1.txt /path/to/file2.txt From 2aedf33c23e21eff8c6aa2a55aae7d14edd3c8c0 Mon Sep 17 00:00:00 2001 From: talhahwahla Date: Tue, 21 Nov 2023 11:48:13 +0500 Subject: [PATCH 11/11] refactor, move generic cidr code to `lib/cidr.go` --- lib/cidr.go | 68 +++++++++++++++++++++++++++++++++++++++ lib/cmd_tool_aggregate.go | 63 ------------------------------------ 2 files changed, 68 insertions(+), 63 deletions(-) create mode 100644 lib/cidr.go diff --git a/lib/cidr.go b/lib/cidr.go new file mode 100644 index 00000000..06b96d81 --- /dev/null +++ b/lib/cidr.go @@ -0,0 +1,68 @@ +package lib + +import ( + "bytes" + "encoding/binary" + "math" + "net" + "sort" +) + +// CIDR represens a Classless Inter-Domain Routing structure. +type CIDR struct { + IP net.IP + Network *net.IPNet +} + +// newCidr creates a newCidr CIDR structure. +func newCidr(s string) *CIDR { + ip, ipnet, err := net.ParseCIDR(s) + if err != nil { + panic(err) + } + return &CIDR{ + IP: ip, + Network: ipnet, + } +} + +func (c *CIDR) String() string { + return c.Network.String() +} + +// MaskLen returns a network mask length. +func (c *CIDR) MaskLen() uint32 { + i, _ := c.Network.Mask.Size() + return uint32(i) +} + +// PrefixUint32 returns a prefix. +func (c *CIDR) PrefixUint32() uint32 { + return binary.BigEndian.Uint32(c.IP.To4()) +} + +// Size returns a size of a CIDR range. +func (c *CIDR) Size() int { + ones, bits := c.Network.Mask.Size() + return int(math.Pow(2, float64(bits-ones))) +} + +// list returns a slice of sorted CIDR structures. +func list(s []string) []*CIDR { + out := make([]*CIDR, 0) + for _, c := range s { + out = append(out, newCidr(c)) + } + sort.Sort(cidrSort(out)) + return out +} + +type cidrSort []*CIDR + +func (s cidrSort) Len() int { return len(s) } +func (s cidrSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s cidrSort) Less(i, j int) bool { + cmp := bytes.Compare(s[i].IP, s[j].IP) + return cmp < 0 || (cmp == 0 && s[i].MaskLen() < s[j].MaskLen()) +} diff --git a/lib/cmd_tool_aggregate.go b/lib/cmd_tool_aggregate.go index 7c14806d..4be9a686 100644 --- a/lib/cmd_tool_aggregate.go +++ b/lib/cmd_tool_aggregate.go @@ -2,14 +2,10 @@ package lib import ( "bufio" - "bytes" - "encoding/binary" "fmt" "io" - "math" "net" "os" - "sort" "strings" "github.com/spf13/pflag" @@ -38,12 +34,6 @@ func (f *CmdToolAggregateFlags) Init() { ) } -// CIDR represens a Classless Inter-Domain Routing structure. -type CIDR struct { - IP net.IP - Network *net.IPNet -} - // CmdToolAggregate is the common core logic for aggregating IPs, IP ranges and CIDRs. func CmdToolAggregate( f CmdToolAggregateFlags, @@ -180,59 +170,6 @@ func CmdToolAggregate( return nil } -// newCidr creates a newCidr CIDR structure. -func newCidr(s string) *CIDR { - ip, ipnet, err := net.ParseCIDR(s) - if err != nil { - panic(err) - } - return &CIDR{ - IP: ip, - Network: ipnet, - } -} - -func (c *CIDR) String() string { - return c.Network.String() -} - -// MaskLen returns a network mask length. -func (c *CIDR) MaskLen() uint32 { - i, _ := c.Network.Mask.Size() - return uint32(i) -} - -// PrefixUint32 returns a prefix. -func (c *CIDR) PrefixUint32() uint32 { - return binary.BigEndian.Uint32(c.IP.To4()) -} - -// Size returns a size of a CIDR range. -func (c *CIDR) Size() int { - ones, bits := c.Network.Mask.Size() - return int(math.Pow(2, float64(bits-ones))) -} - -// list returns a slice of sorted CIDR structures. -func list(s []string) []*CIDR { - out := make([]*CIDR, 0) - for _, c := range s { - out = append(out, newCidr(c)) - } - sort.Sort(cidrSort(out)) - return out -} - -type cidrSort []*CIDR - -func (s cidrSort) Len() int { return len(s) } -func (s cidrSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (s cidrSort) Less(i, j int) bool { - cmp := bytes.Compare(s[i].IP, s[j].IP) - return cmp < 0 || (cmp == 0 && s[i].MaskLen() < s[j].MaskLen()) -} - // stripOverlapping returns a slice of CIDR structures with overlapping ranges // stripped. func stripOverlapping(s []*CIDR) []*CIDR {