Files
nettopo-go/internal/scans/cidr_parse.go
T
2026-04-10 15:39:52 +10:00

126 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package scans
import (
"encoding/binary"
"errors"
"fmt"
"net"
"regexp"
"strings"
)
// maxCIDRSExpanded — ограничение на число подсетей после раскрытия диапазонов (защита от опечаток).
const maxCIDRSExpanded = 16384
var cidrRangeRE = regexp.MustCompile(`^\s*(?P<left>.+?/\d+)\s*-\s*(?P<right>.+?/\d+)\s*$`)
// expandCIDRSFromRequest раскрывает каждую строку: перечисление через запятую, один CIDR или диапазон a/b - c/d.
func expandCIDRSFromRequest(inputs []string) ([]string, error) {
var all []string
for _, raw := range inputs {
raw = strings.TrimSpace(raw)
if raw == "" {
continue
}
for _, piece := range strings.Split(raw, ",") {
piece = strings.TrimSpace(piece)
if piece == "" {
continue
}
part, err := expandCIDREntry(piece)
if err != nil {
return nil, err
}
all = append(all, part...)
}
}
if len(all) == 0 {
return nil, errors.New("cidrs must not be empty")
}
seen := make(map[string]struct{}, len(all))
var out []string
for _, c := range all {
if _, ok := seen[c]; ok {
continue
}
seen[c] = struct{}{}
out = append(out, c)
}
if len(out) > maxCIDRSExpanded {
return nil, fmt.Errorf("слишком много подсетей после раскрытия (%d), максимум %d", len(out), maxCIDRSExpanded)
}
return out, nil
}
func expandCIDREntry(s string) ([]string, error) {
if m := cidrRangeRE.FindStringSubmatch(s); m != nil {
left, right := strings.TrimSpace(m[1]), strings.TrimSpace(m[2])
return expandCIDRRange(left, right)
}
if _, _, err := net.ParseCIDR(strings.TrimSpace(s)); err != nil {
return nil, fmt.Errorf("некорректный CIDR: %w", err)
}
return []string{strings.TrimSpace(s)}, nil
}
func expandCIDRRange(a, b string) ([]string, error) {
_, na, err := net.ParseCIDR(a)
if err != nil {
return nil, fmt.Errorf("начало диапазона: %w", err)
}
_, nb, err := net.ParseCIDR(b)
if err != nil {
return nil, fmt.Errorf("конец диапазона: %w", err)
}
bitsA, _ := na.Mask.Size()
bitsB, _ := nb.Mask.Size()
if bitsA != bitsB {
return nil, fmt.Errorf("маски начала и конца должны совпадать (/ %d и / %d)", bitsA, bitsB)
}
ones := bitsA
if ones < 0 || ones > 32 {
return nil, errors.New("диапазон сетей поддерживается только для IPv4 с маской /0/32")
}
ipA := na.IP.To4()
ipB := nb.IP.To4()
if ipA == nil || ipB == nil {
return nil, errors.New("диапазон вида CIDR–CIDR пока поддерживается только для IPv4")
}
netA := ipv4NetworkBase(ipA, na.Mask)
netB := ipv4NetworkBase(ipB, nb.Mask)
sa := binary.BigEndian.Uint32(netA)
sb := binary.BigEndian.Uint32(netB)
if sb < sa {
return nil, errors.New("конечная сеть в диапазоне раньше начальной")
}
var step uint64 = 1 << (32 - uint(ones))
if ones == 32 {
step = 1
}
if uint64(sa)%step != 0 || uint64(sb)%step != 0 {
return nil, errors.New("границы диапазона должны быть адресами сетей (кратны размеру подсети)")
}
var out []string
for cur := uint64(sa); cur <= uint64(sb); cur += step {
if len(out) >= maxCIDRSExpanded {
return nil, fmt.Errorf("диапазон даёт больше %d подсетей", maxCIDRSExpanded)
}
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, uint32(cur))
out = append(out, fmt.Sprintf("%s/%d", ip.String(), ones))
}
return out, nil
}
func ipv4NetworkBase(ip net.IP, mask net.IPMask) net.IP {
n := ip.Mask(mask)
out := make(net.IP, 4)
copy(out, n.To4())
return out
}