package main import ( "errors" "net" "strconv" "strings" ) // Safety feature, to avoid expanding subnets into a huge amount of IPs. const maxNetmaskSize = 22 // expands into /10 for IPv4 // RegisterEndpoint builds an Endpoint and puts it to a global list of endpoints. func RegisterEndpoint(ip string, ports []int, isIPv6 bool) int { for _, port := range ports { ep := Endpoint{addr: Address{ip: ip, port: port, v6: isIPv6}} ep.loginPos.Reset() ep.passwordPos.Reset() ep.listElement = endpoints.PushBack(&ep) log("ep", 3, "registered endpoint: %v", &ep) } return len(ports) } func incIP(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ if ip[j] > 0 { break } } } // parseCIDR registers multiple endpoints from a CIDR netmask. func parseCIDR(ip string, ports []int, isIPv6 bool) int { na, nm, err := net.ParseCIDR(ip) if err != nil { log("ep", 0, "failed to parse CIDR notation for \"%v\": %v", ip, err.Error()) return 0 } mask, maskBits := nm.Mask.Size() if mask < maskBits-maxNetmaskSize { log("ep", 0, "ignoring out of safe bounds CIDR netmask for \"%v\": %v (max: %v, allowed: %v)", ip, mask, maskBits, maxNetmaskSize) return 0 } curHost := 0 maxHost := 1<<(maskBits-mask) - 1 numParsed := 0 strict := getParamSwitch("strict-subnets") log("ep", 2, "expanding CIDR: \"%v\" to %v hosts", ip, maxHost+1) for expIP := na.Mask(nm.Mask); nm.Contains(expIP); incIP(expIP) { if strict && (curHost == 0 || curHost == maxHost) && maskBits-mask >= 2 { log("ep", 1, "ignoring network/broadcast address due to strict-subnets: \"%v\"", expIP.String()) } else { numParsed += RegisterEndpoint(expIP.String(), ports, isIPv6) } curHost++ } return numParsed } // parseIPOrCIDR expands plain IP or CIDR to multiple endpoints. func parseIPOrCIDR(ip string, ports []int, isIPv6 bool) int { // ip may be a domain name, a CIDR subnet or an IP address // CIDR subnets must be expanded to plain IPs if strings.LastIndex(ip, "/") >= 0 { // this is a CIDR subnet return parseCIDR(ip, ports, isIPv6) } else if strings.Count(ip, "/") > 1 { // invalid CIDR notation log("ep", 0, "invalid CIDR subnet format: \"%v\", ignoring", ip) return 0 } else { // otherwise, just register return RegisterEndpoint(ip, ports, isIPv6) } } // extractIPAndPort extracts all endpoint components. func extractIPAndPort(str string) (ip string, port int, err error) { var portString string ip, portString, err = net.SplitHostPort(str) if err != nil { return "", 0, err } port, err = strconv.Atoi(portString) if err != nil { return "", 0, err } if port <= 0 || port > 65535 { return "", 0, errors.New("invalid port: " + strconv.Itoa(port)) } return ip, port, nil } // ParseEndpoints takes a string slice of IPs/CIDR subnets and converts it to a list of endpoints. func ParseEndpoints(source []string) { log("ep", 1, "parsing endpoints") totalIPv6Skipped := 0 numParsed := 0 for _, str := range source { if !strings.Contains(str, ":") { // no ":": this is an ipv4/dn without port, // parse it with all known ports numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), false) } else { // either ipv4/dn with port, or ipv6 with/without port isIPv6 := strings.Count(str, ":") > 1 if isIPv6 && getParamSwitch("no-ipv6") { totalIPv6Skipped++ continue } if !strings.Contains(str, "]:") && strings.Contains(str, "::") { // ipv6 without port numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), true) continue } ip, port, err := extractIPAndPort(str) if err != nil { log("ep", 0, "failed to extract ip/port for \"%v\": %v, ignoring endpoint", str, err.Error()) continue } ports := []int{port} // append all default ports if getParamSwitch("append-default-ports") { for _, port2 := range getParamIntSlice("port") { if port != port2 { ports = append(ports, port2) } } } numParsed += parseIPOrCIDR(ip, ports, isIPv6) } } logIf(totalIPv6Skipped > 0, "ep", 0, "skipped %v IPv6 targets due to no-ipv6 flag", totalIPv6Skipped) log("ep", 1, "finished parsing endpoints: parsed %v out of total %v", numParsed, endpoints.Len()) }