140 lines
3.0 KiB
Go
140 lines
3.0 KiB
Go
package scan
|
|
|
|
import (
|
|
"fmt"
|
|
"math/bits"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func expandTargetsForScan(targets []string) ([]string, error) {
|
|
expanded := make([]string, 0, len(targets))
|
|
for _, target := range targets {
|
|
value := strings.TrimSpace(target)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
|
|
addrs, ok, err := parseIPv4RangePair(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ok {
|
|
expanded = append(expanded, addrs...)
|
|
continue
|
|
}
|
|
|
|
expanded = append(expanded, value)
|
|
}
|
|
|
|
return expanded, nil
|
|
}
|
|
|
|
// Parse masscan range formats.
|
|
func parseIPv4RangePair(target string) ([]string, bool, error) {
|
|
parts := strings.SplitN(target, "-", 2)
|
|
if len(parts) != 2 {
|
|
return nil, false, nil
|
|
}
|
|
|
|
startValue := strings.TrimSpace(parts[0])
|
|
endValue := strings.TrimSpace(parts[1])
|
|
if startValue == "" || endValue == "" {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// Fall through if this is in nmap range format.
|
|
if endIsOctet(endValue) {
|
|
return nil, false, nil
|
|
}
|
|
|
|
startAddr, startOK := parseIPv4Addr(startValue)
|
|
endAddr, endOK := parseIPv4Addr(endValue)
|
|
if !startOK && !endOK { // Allows the case where the target is just a hostname with a dash.
|
|
return nil, false, nil
|
|
}
|
|
if !startOK || !endOK { // Prevents the case where one is an address and the other part is not.
|
|
return nil, false, fmt.Errorf("invalid range %q", target)
|
|
}
|
|
|
|
startAddr = startAddr.Unmap()
|
|
endAddr = endAddr.Unmap()
|
|
if !startAddr.Is4() || !endAddr.Is4() {
|
|
return nil, true, fmt.Errorf("invalid range %q", target)
|
|
}
|
|
|
|
start := ipv4ToUint32(startAddr)
|
|
end := ipv4ToUint32(endAddr)
|
|
if start > end {
|
|
return nil, true, fmt.Errorf("invalid range %q", target)
|
|
}
|
|
|
|
return expandIPv4RangeToTargets(start, end), true, nil
|
|
}
|
|
|
|
func parseIPv4Addr(value string) (netip.Addr, bool) {
|
|
addr, err := netip.ParseAddr(value)
|
|
if err != nil {
|
|
return netip.Addr{}, false
|
|
}
|
|
return addr, true
|
|
}
|
|
|
|
func endIsOctet(value string) bool {
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return parsed >= 0 && parsed <= 255
|
|
}
|
|
|
|
func expandIPv4RangeToTargets(start, end uint32) []string {
|
|
if start > end {
|
|
return nil
|
|
}
|
|
|
|
const maxUint32 = uint64(^uint32(0))
|
|
remaining := uint64(end) - uint64(start) + 1
|
|
results := make([]string, 0, 16)
|
|
|
|
for current := uint64(start); remaining > 0; {
|
|
if current > maxUint32 {
|
|
return results
|
|
}
|
|
|
|
current32 := uint32(current)
|
|
maxSize := uint64(1) << bits.TrailingZeros32(current32)
|
|
for maxSize > remaining {
|
|
maxSize >>= 1
|
|
}
|
|
|
|
prefixLen := 32 - (bits.Len64(maxSize) - 1)
|
|
addr := uint32ToIPv4(current32)
|
|
if maxSize == 1 {
|
|
results = append(results, addr.String())
|
|
} else {
|
|
results = append(results, fmt.Sprintf("%s/%d", addr.String(), prefixLen))
|
|
}
|
|
|
|
current += maxSize
|
|
remaining -= maxSize
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
func ipv4ToUint32(addr netip.Addr) uint32 {
|
|
value := addr.As4()
|
|
return uint32(value[0])<<24 | uint32(value[1])<<16 | uint32(value[2])<<8 | uint32(value[3])
|
|
}
|
|
|
|
func uint32ToIPv4(value uint32) netip.Addr {
|
|
return netip.AddrFrom4([4]byte{
|
|
byte(value >> 24),
|
|
byte(value >> 16),
|
|
byte(value >> 8),
|
|
byte(value),
|
|
})
|
|
}
|