From 353524325acf919fe19dfccadd305fdf6cc4cf49 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Sat, 16 May 2026 00:07:15 +0800 Subject: [PATCH] CIDR to ip range supports --- maker/golang/xdb/segment.go | 45 +++++++++++++++----- maker/golang/xdb/util.go | 77 +++++++++++++++++++++++++++++++++++ maker/golang/xdb/util_test.go | 57 ++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 11 deletions(-) diff --git a/maker/golang/xdb/segment.go b/maker/golang/xdb/segment.go index 831bdf9..94bada0 100644 --- a/maker/golang/xdb/segment.go +++ b/maker/golang/xdb/segment.go @@ -16,19 +16,42 @@ type Segment struct { } func SegmentFrom(seg string, cRegion func(string) *Region) (*Segment, error) { - var ps = strings.SplitN(strings.TrimSpace(seg), "|", 3) - if len(ps) != 3 { + var count = 0 + var ps = StringTokenizer(strings.TrimSpace(seg), "|", func(s string, start int) bool { + // CIDR format + if strings.Index(s, "/") > 0 { + return false + } + + count++ + return count < 2 + }) + + var err error + var sip, eip []byte + var rIdx = 0 + switch pl := len(ps); pl { + case 2: + // CIDR format + rIdx = 1 + case 3: + // triditional ip range + rIdx = 2 + sip, err = ParseIP(ps[0]) + if err != nil { + return nil, fmt.Errorf("parser start ip `%s`: %s", ps[0], err) + } + + eip, err = ParseIP(ps[1]) + if err != nil { + return nil, fmt.Errorf("check end ip `%s`: %s", ps[1], err) + } + default: return nil, fmt.Errorf("invalid ip segment `%s`", seg) } - sip, err := ParseIP(ps[0]) - if err != nil { - return nil, fmt.Errorf("check start ip `%s`: %s", ps[0], err) - } - - eip, err := ParseIP(ps[1]) - if err != nil { - return nil, fmt.Errorf("check end ip `%s`: %s", ps[1], err) + if len(sip) != len(eip) { + return nil, fmt.Errorf("invalid ip segment line `%s`, sip/eip version not match", seg) } if IPCompare(sip, eip) > 0 { @@ -38,7 +61,7 @@ func SegmentFrom(seg string, cRegion func(string) *Region) (*Segment, error) { return &Segment{ StartIP: sip, EndIP: eip, - Region: cRegion(ps[2]), + Region: cRegion(ps[rIdx]), }, nil } diff --git a/maker/golang/xdb/util.go b/maker/golang/xdb/util.go index 4d394ea..fb30b65 100644 --- a/maker/golang/xdb/util.go +++ b/maker/golang/xdb/util.go @@ -10,6 +10,7 @@ import ( "fmt" "math/big" "net" + "net/netip" "os" "strings" ) @@ -35,6 +36,44 @@ func ParseIP(ip string) ([]byte, error) { return nil, fmt.Errorf("invalid ip address: %s", ip) } +var bitMaskList = []uint8{ + 0b1111_1111, // all zero + 0b0111_1111, + 0b0011_1111, + 0b0001_1111, + 0b0000_1111, + 0b0000_0111, + 0b0000_0011, + 0b0000_0001, +} + +func CIDR2Range(cidrStr string) ([]byte, []byte, error) { + prefix, err := netip.ParsePrefix(cidrStr) + if err != nil { + return nil, nil, err + } + + // Get the start IP (Network Address) + // Masked() zeros out the host bits, which gives the starting IP of the subnet. + sip := prefix.Masked().Addr().AsSlice() + eip := make([]byte, len(sip)) + copy(eip, sip) + + // Calculate the end IP (Broadcast Address) + bits := prefix.Bits() + + // border byte rest bit filled with 1 + bByteIdx := bits / 8 + eip[bByteIdx] |= bitMaskList[bits-(bByteIdx*8)] + + // fill all the rest bits with 1 + for bi := bByteIdx + 1; bi < len(sip); bi++ { + eip[bi] |= 0b1111_1111 + } + + return sip, eip, nil +} + func IP2String(ip []byte) string { return net.IP(ip[:]).String() } @@ -309,3 +348,41 @@ func RegionFiltering(region string, fields []int) (string, error) { return strings.Join(sb, "|"), nil } + +// do the string split step by step as caller needed +func StringTokenizer(str, substr string, cb func(s string, start int) bool) []string { + var tokens []string + var token string + var sIdx, oIdx, isEOF = 0, 0, false + for { + // do the token match + nIdx := strings.Index(str[sIdx:], substr) + if nIdx == -1 { + isEOF = true + token = str[sIdx:] + } else { + token = str[sIdx : sIdx+nIdx] + } + + oIdx = sIdx // backup the old index + sIdx = sIdx + nIdx + 1 // reset the next start index + tokens = append(tokens, token) // append the token + + // check and call the callback + if cb(token, oIdx) == false { + // keep the last token + if sIdx < len(str) { + tokens = append(tokens, str[sIdx:]) + } + + break + } + + // check the EOF + if isEOF { + break + } + } + + return tokens +} diff --git a/maker/golang/xdb/util_test.go b/maker/golang/xdb/util_test.go index 7f5b8cb..fe5d1f8 100644 --- a/maker/golang/xdb/util_test.go +++ b/maker/golang/xdb/util_test.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "fmt" "os" + "strings" "testing" ) @@ -265,3 +266,59 @@ func TestIterateSegments(t *testing.T) { return nil }) } + +func TestStringTokenizer(t *testing.T) { + var strList = []string{ + "24.231.126.0/24|14537 2914 29866|IGP", + "24.231.126.0|24.231.126.255|14537 2914 29866|IGP", + } + + var counter = 0 + for _, str := range strList { + tokens := StringTokenizer(str, "|", func(s string, start int) bool { + // fmt.Printf("%s[idx=%d, |]=%s\n", str, start, s) + if counter == 0 { + if strings.Index(s, "/") > 0 { + return false + } + } + + counter++ + return counter < 2 + }) + fmt.Printf("%d tokens: %s\n", len(tokens), strings.Join(tokens, ", ")) + } +} + +func TestCIDR2Range(t *testing.T) { + var strList = [][3]string{ + {"43.247.92.0/22", "43.247.92.0", "43.247.95.255"}, + {"64.252.86.39/29", "64.252.86.32", "64.252.86.39"}, + {"103.37.44.0/22", "103.37.44.0", "103.37.47.255"}, + {"111.223.12.0/22", "111.223.12.0", "111.223.15.255"}, + {"43.248.80.0/20", "43.248.80.0", "43.248.95.255"}, + {"192.168.100.0/22", "192.168.100.0", "192.168.103.255"}, + {"2403:3380::/32", "2403:3380::", "2403:3380:ffff:ffff:ffff:ffff:ffff:ffff"}, + {"2001:db8:85a3::/64", "2001:db8:85a3::", "2001:db8:85a3:0:ffff:ffff:ffff:ffff"}, + {"2001:db8:abcd::/48", "2001:db8:abcd::", "2001:db8:abcd:ffff:ffff:ffff:ffff:ffff"}, + } + + for _, item := range strList { + sip, eip, err := CIDR2Range(item[0]) + if err != nil { + t.Fatalf("CIDR2Range: %s", err) + } + + sStr := IP2String(sip) + eStr := IP2String(eip) + if sStr != item[1] { + t.Fatalf("start ip %s != %s", sStr, item[1]) + } + + if eStr != item[2] { + t.Fatalf("end ip %s != %s", eStr, item[2]) + } + + fmt.Printf("cidr=%s: {sip=%s, eip=%s}\n", item[0], sStr, eStr) + } +}