package scans import ( "encoding/binary" "errors" "fmt" "net" "regexp" "strings" ) // maxCIDRSExpanded — ограничение на число подсетей после раскрытия диапазонов (защита от опечаток). const maxCIDRSExpanded = 16384 var cidrRangeRE = regexp.MustCompile(`^\s*(?P.+?/\d+)\s*-\s*(?P.+?/\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 }