feat: v6 rewrite
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user