package sockaddr import ( "errors" "fmt" "math/big" "net" "regexp" "sort" "strconv" "strings" ) var ( // Centralize all regexps and regexp.Copy() where necessary. signRE *regexp.Regexp = regexp.MustCompile(`^[\s]*[+-]`) whitespaceRE *regexp.Regexp = regexp.MustCompile(`[\s]+`) ifNameRE *regexp.Regexp = regexp.MustCompile(`^Ethernet adapter ([^\s:]+):`) ipAddrRE *regexp.Regexp = regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`) ) // IfAddrs is a slice of IfAddr type IfAddrs []IfAddr func (ifs IfAddrs) Len() int { return len(ifs) } // CmpIfFunc is the function signature that must be met to be used in the // OrderedIfAddrBy multiIfAddrSorter type CmpIfAddrFunc func(p1, p2 *IfAddr) int // multiIfAddrSorter implements the Sort interface, sorting the IfAddrs within. type multiIfAddrSorter struct { ifAddrs IfAddrs cmp []CmpIfAddrFunc } // Sort sorts the argument slice according to the Cmp functions passed to // OrderedIfAddrBy. func (ms *multiIfAddrSorter) Sort(ifAddrs IfAddrs) { ms.ifAddrs = ifAddrs sort.Sort(ms) } // OrderedIfAddrBy sorts SockAddr by the list of sort function pointers. func OrderedIfAddrBy(cmpFuncs ...CmpIfAddrFunc) *multiIfAddrSorter { return &multiIfAddrSorter{ cmp: cmpFuncs, } } // Len is part of sort.Interface. func (ms *multiIfAddrSorter) Len() int { return len(ms.ifAddrs) } // Less is part of sort.Interface. It is implemented by looping along the Cmp() // functions until it finds a comparison that is either less than or greater // than. A return value of 0 defers sorting to the next function in the // multisorter (which means the results of sorting may leave the resutls in a // non-deterministic order). func (ms *multiIfAddrSorter) Less(i, j int) bool { p, q := &ms.ifAddrs[i], &ms.ifAddrs[j] // Try all but the last comparison. var k int for k = 0; k < len(ms.cmp)-1; k++ { cmp := ms.cmp[k] x := cmp(p, q) switch x { case -1: // p < q, so we have a decision. return true case 1: // p > q, so we have a decision. return false } // p == q; try the next comparison. } // All comparisons to here said "equal", so just return whatever the // final comparison reports. switch ms.cmp[k](p, q) { case -1: return true case 1: return false default: // Still a tie! Now what? return false panic("undefined sort order for remaining items in the list") } } // Swap is part of sort.Interface. func (ms *multiIfAddrSorter) Swap(i, j int) { ms.ifAddrs[i], ms.ifAddrs[j] = ms.ifAddrs[j], ms.ifAddrs[i] } // AscIfAddress is a sorting function to sort IfAddrs by their respective // address type. Non-equal types are deferred in the sort. func AscIfAddress(p1Ptr, p2Ptr *IfAddr) int { return AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // AscIfDefault is a sorting function to sort IfAddrs by whether or not they // have a default route or not. Non-equal types are deferred in the sort. // // FIXME: This is a particularly expensive sorting operation because of the // non-memoized calls to NewRouteInfo(). In an ideal world the routeInfo data // once at the start of the sort and pass it along as a context or by wrapping // the IfAddr type with this information (this would also solve the inability to // return errors and the possibility of failing silently). Fortunately, // N*log(N) where N = 3 is only ~6.2 invocations. Not ideal, but not worth // optimizing today. The common case is this gets called once or twice. // Patches welcome. func AscIfDefault(p1Ptr, p2Ptr *IfAddr) int { ri, err := NewRouteInfo() if err != nil { return sortDeferDecision } defaultIfName, err := ri.GetDefaultInterfaceName() if err != nil { return sortDeferDecision } switch { case p1Ptr.Interface.Name == defaultIfName && p2Ptr.Interface.Name == defaultIfName: return sortDeferDecision case p1Ptr.Interface.Name == defaultIfName: return sortReceiverBeforeArg case p2Ptr.Interface.Name == defaultIfName: return sortArgBeforeReceiver default: return sortDeferDecision } } // AscIfName is a sorting function to sort IfAddrs by their interface names. func AscIfName(p1Ptr, p2Ptr *IfAddr) int { return strings.Compare(p1Ptr.Name, p2Ptr.Name) } // AscIfNetworkSize is a sorting function to sort IfAddrs by their respective // network mask size. func AscIfNetworkSize(p1Ptr, p2Ptr *IfAddr) int { return AscNetworkSize(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // AscIfPort is a sorting function to sort IfAddrs by their respective // port type. Non-equal types are deferred in the sort. func AscIfPort(p1Ptr, p2Ptr *IfAddr) int { return AscPort(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // AscIfPrivate is a sorting function to sort IfAddrs by "private" values before // "public" values. Both IPv4 and IPv6 are compared against RFC6890 (RFC6890 // includes, and is not limited to, RFC1918 and RFC6598 for IPv4, and IPv6 // includes RFC4193). func AscIfPrivate(p1Ptr, p2Ptr *IfAddr) int { return AscPrivate(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // AscIfType is a sorting function to sort IfAddrs by their respective address // type. Non-equal types are deferred in the sort. func AscIfType(p1Ptr, p2Ptr *IfAddr) int { return AscType(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // DescIfAddress is identical to AscIfAddress but reverse ordered. func DescIfAddress(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscAddress(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // DescIfDefault is identical to AscIfDefault but reverse ordered. func DescIfDefault(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscIfDefault(p1Ptr, p2Ptr) } // DescIfName is identical to AscIfName but reverse ordered. func DescIfName(p1Ptr, p2Ptr *IfAddr) int { return -1 * strings.Compare(p1Ptr.Name, p2Ptr.Name) } // DescIfNetworkSize is identical to AscIfNetworkSize but reverse ordered. func DescIfNetworkSize(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscNetworkSize(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // DescIfPort is identical to AscIfPort but reverse ordered. func DescIfPort(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscPort(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // DescIfPrivate is identical to AscIfPrivate but reverse ordered. func DescIfPrivate(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscPrivate(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // DescIfType is identical to AscIfType but reverse ordered. func DescIfType(p1Ptr, p2Ptr *IfAddr) int { return -1 * AscType(&p1Ptr.SockAddr, &p2Ptr.SockAddr) } // FilterIfByType filters IfAddrs and returns a list of the matching type func FilterIfByType(ifAddrs IfAddrs, type_ SockAddrType) (matchedIfs, excludedIfs IfAddrs) { excludedIfs = make(IfAddrs, 0, len(ifAddrs)) matchedIfs = make(IfAddrs, 0, len(ifAddrs)) for _, ifAddr := range ifAddrs { if ifAddr.SockAddr.Type()&type_ != 0 { matchedIfs = append(matchedIfs, ifAddr) } else { excludedIfs = append(excludedIfs, ifAddr) } } return matchedIfs, excludedIfs } // IfAttr forwards the selector to IfAttr.Attr() for resolution. If there is // more than one IfAddr, only the first IfAddr is used. func IfAttr(selectorName string, ifAddr IfAddr) (string, error) { attrName := AttrName(strings.ToLower(selectorName)) attrVal, err := ifAddr.Attr(attrName) return attrVal, err } // IfAttrs forwards the selector to IfAttrs.Attr() for resolution. If there is // more than one IfAddr, only the first IfAddr is used. func IfAttrs(selectorName string, ifAddrs IfAddrs) (string, error) { if len(ifAddrs) == 0 { return "", nil } attrName := AttrName(strings.ToLower(selectorName)) attrVal, err := ifAddrs[0].Attr(attrName) return attrVal, err } // GetAllInterfaces iterates over all available network interfaces and finds all // available IP addresses on each interface and converts them to // sockaddr.IPAddrs, and returning the result as an array of IfAddr. func GetAllInterfaces() (IfAddrs, error) { ifs, err := net.Interfaces() if err != nil { return nil, err } ifAddrs := make(IfAddrs, 0, len(ifs)) for _, intf := range ifs { addrs, err := intf.Addrs() if err != nil { return nil, err } for _, addr := range addrs { var ipAddr IPAddr ipAddr, err = NewIPAddr(addr.String()) if err != nil { return IfAddrs{}, fmt.Errorf("unable to create an IP address from %q", addr.String()) } ifAddr := IfAddr{ SockAddr: ipAddr, Interface: intf, } ifAddrs = append(ifAddrs, ifAddr) } } return ifAddrs, nil } // GetDefaultInterfaces returns IfAddrs of the addresses attached to the default // route. func GetDefaultInterfaces() (IfAddrs, error) { ri, err := NewRouteInfo() if err != nil { return nil, err } defaultIfName, err := ri.GetDefaultInterfaceName() if err != nil { return nil, err } var defaultIfs, ifAddrs IfAddrs ifAddrs, err = GetAllInterfaces() for _, ifAddr := range ifAddrs { if ifAddr.Name == defaultIfName { defaultIfs = append(defaultIfs, ifAddr) } } return defaultIfs, nil } // GetPrivateInterfaces returns an IfAddrs that are part of RFC 6890 and have a // default route. If the system can't determine its IP address or find an RFC // 6890 IP address, an empty IfAddrs will be returned instead. This function is // the `eval` equivalent of: // // ``` // $ sockaddr eval -r '{{GetAllInterfaces | include "type" "ip" | include "flags" "forwardable" | include "flags" "up" | sort "default,type,size" | include "RFC" "6890" }}' /// ``` func GetPrivateInterfaces() (IfAddrs, error) { privateIfs, err := GetAllInterfaces() if err != nil { return IfAddrs{}, err } if len(privateIfs) == 0 { return IfAddrs{}, nil } privateIfs, _ = FilterIfByType(privateIfs, TypeIP) if len(privateIfs) == 0 { return IfAddrs{}, nil } privateIfs, _, err = IfByFlag("forwardable", privateIfs) if err != nil { return IfAddrs{}, err } privateIfs, _, err = IfByFlag("up", privateIfs) if err != nil { return IfAddrs{}, err } if len(privateIfs) == 0 { return IfAddrs{}, nil } OrderedIfAddrBy(AscIfDefault, AscIfType, AscIfNetworkSize).Sort(privateIfs) privateIfs, _, err = IfByRFC("6890", privateIfs) if err != nil { return IfAddrs{}, err } else if len(privateIfs) == 0 { return IfAddrs{}, nil } return privateIfs, nil } // GetPublicInterfaces returns an IfAddrs that are NOT part of RFC 6890 and has a // default route. If the system can't determine its IP address or find a non // RFC 6890 IP address, an empty IfAddrs will be returned instead. This // function is the `eval` equivalent of: // // ``` // $ sockaddr eval -r '{{GetAllInterfaces | include "type" "ip" | include "flags" "forwardable" | include "flags" "up" | sort "default,type,size" | exclude "RFC" "6890" }}' /// ``` func GetPublicInterfaces() (IfAddrs, error) { publicIfs, err := GetAllInterfaces() if err != nil { return IfAddrs{}, err } if len(publicIfs) == 0 { return IfAddrs{}, nil } publicIfs, _ = FilterIfByType(publicIfs, TypeIP) if len(publicIfs) == 0 { return IfAddrs{}, nil } publicIfs, _, err = IfByFlag("forwardable", publicIfs) if err != nil { return IfAddrs{}, err } publicIfs, _, err = IfByFlag("up", publicIfs) if err != nil { return IfAddrs{}, err } if len(publicIfs) == 0 { return IfAddrs{}, nil } OrderedIfAddrBy(AscIfDefault, AscIfType, AscIfNetworkSize).Sort(publicIfs) _, publicIfs, err = IfByRFC("6890", publicIfs) if err != nil { return IfAddrs{}, err } else if len(publicIfs) == 0 { return IfAddrs{}, nil } return publicIfs, nil } // IfByAddress returns a list of matched and non-matched IfAddrs, or an error if // the regexp fails to compile. func IfByAddress(inputRe string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { re, err := regexp.Compile(inputRe) if err != nil { return nil, nil, fmt.Errorf("Unable to compile address regexp %+q: %v", inputRe, err) } matchedAddrs := make(IfAddrs, 0, len(ifAddrs)) excludedAddrs := make(IfAddrs, 0, len(ifAddrs)) for _, addr := range ifAddrs { if re.MatchString(addr.SockAddr.String()) { matchedAddrs = append(matchedAddrs, addr) } else { excludedAddrs = append(excludedAddrs, addr) } } return matchedAddrs, excludedAddrs, nil } // IfByName returns a list of matched and non-matched IfAddrs, or an error if // the regexp fails to compile. func IfByName(inputRe string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { re, err := regexp.Compile(inputRe) if err != nil { return nil, nil, fmt.Errorf("Unable to compile name regexp %+q: %v", inputRe, err) } matchedAddrs := make(IfAddrs, 0, len(ifAddrs)) excludedAddrs := make(IfAddrs, 0, len(ifAddrs)) for _, addr := range ifAddrs { if re.MatchString(addr.Name) { matchedAddrs = append(matchedAddrs, addr) } else { excludedAddrs = append(excludedAddrs, addr) } } return matchedAddrs, excludedAddrs, nil } // IfByPort returns a list of matched and non-matched IfAddrs, or an error if // the regexp fails to compile. func IfByPort(inputRe string, ifAddrs IfAddrs) (matchedIfs, excludedIfs IfAddrs, err error) { re, err := regexp.Compile(inputRe) if err != nil { return nil, nil, fmt.Errorf("Unable to compile port regexp %+q: %v", inputRe, err) } ipIfs, nonIfs := FilterIfByType(ifAddrs, TypeIP) matchedIfs = make(IfAddrs, 0, len(ipIfs)) excludedIfs = append(IfAddrs(nil), nonIfs...) for _, addr := range ipIfs { ipAddr := ToIPAddr(addr.SockAddr) if ipAddr == nil { continue } port := strconv.FormatInt(int64((*ipAddr).IPPort()), 10) if re.MatchString(port) { matchedIfs = append(matchedIfs, addr) } else { excludedIfs = append(excludedIfs, addr) } } return matchedIfs, excludedIfs, nil } // IfByRFC returns a list of matched and non-matched IfAddrs that contain the // relevant RFC-specified traits. func IfByRFC(selectorParam string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { inputRFC, err := strconv.ParseUint(selectorParam, 10, 64) if err != nil { return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to parse RFC number %q: %v", selectorParam, err) } matchedIfAddrs := make(IfAddrs, 0, len(ifAddrs)) remainingIfAddrs := make(IfAddrs, 0, len(ifAddrs)) rfcNetMap := KnownRFCs() rfcNets, ok := rfcNetMap[uint(inputRFC)] if !ok { return nil, nil, fmt.Errorf("unsupported RFC %d", inputRFC) } for _, ifAddr := range ifAddrs { var contained bool for _, rfcNet := range rfcNets { if rfcNet.Contains(ifAddr.SockAddr) { matchedIfAddrs = append(matchedIfAddrs, ifAddr) contained = true break } } if !contained { remainingIfAddrs = append(remainingIfAddrs, ifAddr) } } return matchedIfAddrs, remainingIfAddrs, nil } // IfByRFCs returns a list of matched and non-matched IfAddrs that contain the // relevant RFC-specified traits. Multiple RFCs can be specified and separated // by the `|` symbol. No protection is taken to ensure an IfAddr does not end // up in both the included and excluded list. func IfByRFCs(selectorParam string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { var includedIfs, excludedIfs IfAddrs for _, rfcStr := range strings.Split(selectorParam, "|") { includedRFCIfs, excludedRFCIfs, err := IfByRFC(rfcStr, ifAddrs) if err != nil { return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to lookup RFC number %q: %v", rfcStr, err) } includedIfs = append(includedIfs, includedRFCIfs...) excludedIfs = append(excludedIfs, excludedRFCIfs...) } return includedIfs, excludedIfs, nil } // IfByMaskSize returns a list of matched and non-matched IfAddrs that have the // matching mask size. func IfByMaskSize(selectorParam string, ifAddrs IfAddrs) (matchedIfs, excludedIfs IfAddrs, err error) { maskSize, err := strconv.ParseUint(selectorParam, 10, 64) if err != nil { return IfAddrs{}, IfAddrs{}, fmt.Errorf("invalid exclude size argument (%q): %v", selectorParam, err) } ipIfs, nonIfs := FilterIfByType(ifAddrs, TypeIP) matchedIfs = make(IfAddrs, 0, len(ipIfs)) excludedIfs = append(IfAddrs(nil), nonIfs...) for _, addr := range ipIfs { ipAddr := ToIPAddr(addr.SockAddr) if ipAddr == nil { return IfAddrs{}, IfAddrs{}, fmt.Errorf("unable to filter mask sizes on non-IP type %s: %v", addr.SockAddr.Type().String(), addr.SockAddr.String()) } switch { case (*ipAddr).Type()&TypeIPv4 != 0 && maskSize > 32: return IfAddrs{}, IfAddrs{}, fmt.Errorf("mask size out of bounds for IPv4 address: %d", maskSize) case (*ipAddr).Type()&TypeIPv6 != 0 && maskSize > 128: return IfAddrs{}, IfAddrs{}, fmt.Errorf("mask size out of bounds for IPv6 address: %d", maskSize) } if (*ipAddr).Maskbits() == int(maskSize) { matchedIfs = append(matchedIfs, addr) } else { excludedIfs = append(excludedIfs, addr) } } return matchedIfs, excludedIfs, nil } // IfByType returns a list of matching and non-matching IfAddr that match the // specified type. For instance: // // include "type" "IPv4,IPv6" // // will include any IfAddrs that is either an IPv4 or IPv6 address. Any // addresses on those interfaces that don't match will be included in the // remainder results. func IfByType(inputTypes string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { matchingIfAddrs := make(IfAddrs, 0, len(ifAddrs)) remainingIfAddrs := make(IfAddrs, 0, len(ifAddrs)) ifTypes := strings.Split(strings.ToLower(inputTypes), "|") for _, ifType := range ifTypes { switch ifType { case "ip", "ipv4", "ipv6", "unix": // Valid types default: return nil, nil, fmt.Errorf("unsupported type %q %q", ifType, inputTypes) } } for _, ifAddr := range ifAddrs { for _, ifType := range ifTypes { var matched bool switch { case ifType == "ip" && ifAddr.SockAddr.Type()&TypeIP != 0: matched = true case ifType == "ipv4" && ifAddr.SockAddr.Type()&TypeIPv4 != 0: matched = true case ifType == "ipv6" && ifAddr.SockAddr.Type()&TypeIPv6 != 0: matched = true case ifType == "unix" && ifAddr.SockAddr.Type()&TypeUnix != 0: matched = true } if matched { matchingIfAddrs = append(matchingIfAddrs, ifAddr) } else { remainingIfAddrs = append(remainingIfAddrs, ifAddr) } } } return matchingIfAddrs, remainingIfAddrs, nil } // IfByFlag returns a list of matching and non-matching IfAddrs that match the // specified type. For instance: // // include "flag" "up,broadcast" // // will include any IfAddrs that have both the "up" and "broadcast" flags set. // Any addresses on those interfaces that don't match will be omitted from the // results. func IfByFlag(inputFlags string, ifAddrs IfAddrs) (matched, remainder IfAddrs, err error) { matchedAddrs := make(IfAddrs, 0, len(ifAddrs)) excludedAddrs := make(IfAddrs, 0, len(ifAddrs)) var wantForwardable, wantGlobalUnicast, wantInterfaceLocalMulticast, wantLinkLocalMulticast, wantLinkLocalUnicast, wantLoopback, wantMulticast, wantUnspecified bool var ifFlags net.Flags var checkFlags, checkAttrs bool for _, flagName := range strings.Split(strings.ToLower(inputFlags), "|") { switch flagName { case "broadcast": checkFlags = true ifFlags = ifFlags | net.FlagBroadcast case "down": checkFlags = true ifFlags = (ifFlags &^ net.FlagUp) case "forwardable": checkAttrs = true wantForwardable = true case "global unicast": checkAttrs = true wantGlobalUnicast = true case "interface-local multicast": checkAttrs = true wantInterfaceLocalMulticast = true case "link-local multicast": checkAttrs = true wantLinkLocalMulticast = true case "link-local unicast": checkAttrs = true wantLinkLocalUnicast = true case "loopback": checkAttrs = true checkFlags = true ifFlags = ifFlags | net.FlagLoopback wantLoopback = true case "multicast": checkAttrs = true checkFlags = true ifFlags = ifFlags | net.FlagMulticast wantMulticast = true case "point-to-point": checkFlags = true ifFlags = ifFlags | net.FlagPointToPoint case "unspecified": checkAttrs = true wantUnspecified = true case "up": checkFlags = true ifFlags = ifFlags | net.FlagUp default: return nil, nil, fmt.Errorf("Unknown interface flag: %+q", flagName) } } for _, ifAddr := range ifAddrs { var matched bool if checkFlags && ifAddr.Interface.Flags&ifFlags == ifFlags { matched = true } if checkAttrs { if ip := ToIPAddr(ifAddr.SockAddr); ip != nil { netIP := (*ip).NetIP() switch { case wantGlobalUnicast && netIP.IsGlobalUnicast(): matched = true case wantInterfaceLocalMulticast && netIP.IsInterfaceLocalMulticast(): matched = true case wantLinkLocalMulticast && netIP.IsLinkLocalMulticast(): matched = true case wantLinkLocalUnicast && netIP.IsLinkLocalUnicast(): matched = true case wantLoopback && netIP.IsLoopback(): matched = true case wantMulticast && netIP.IsMulticast(): matched = true case wantUnspecified && netIP.IsUnspecified(): matched = true case wantForwardable && !IsRFC(ForwardingBlacklist, ifAddr.SockAddr): matched = true } } } if matched { matchedAddrs = append(matchedAddrs, ifAddr) } else { excludedAddrs = append(excludedAddrs, ifAddr) } } return matchedAddrs, excludedAddrs, nil } // IfByNetwork returns an IfAddrs that are equal to or included within the // network passed in by selector. func IfByNetwork(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, IfAddrs, error) { var includedIfs, excludedIfs IfAddrs for _, netStr := range strings.Split(selectorParam, "|") { netAddr, err := NewIPAddr(netStr) if err != nil { return nil, nil, fmt.Errorf("unable to create an IP address from %+q: %v", netStr, err) } for _, ifAddr := range inputIfAddrs { if netAddr.Contains(ifAddr.SockAddr) { includedIfs = append(includedIfs, ifAddr) } else { excludedIfs = append(excludedIfs, ifAddr) } } } return includedIfs, excludedIfs, nil } // IfAddrMath will return a new IfAddr struct with a mutated value. func IfAddrMath(operation, value string, inputIfAddr IfAddr) (IfAddr, error) { // Regexp used to enforce the sign being a required part of the grammar for // some values. signRe := signRE.Copy() switch strings.ToLower(operation) { case "address": // "address" operates on the IP address and is allowed to overflow or // underflow networks, however it will wrap along the underlying address's // underlying type. if !signRe.MatchString(value) { return IfAddr{}, fmt.Errorf("sign (+/-) is required for operation %q", operation) } switch sockType := inputIfAddr.SockAddr.Type(); sockType { case TypeIPv4: // 33 == Accept any uint32 value // TODO(seanc@): Add the ability to parse hex i, err := strconv.ParseInt(value, 10, 33) if err != nil { return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) } ipv4 := *ToIPv4Addr(inputIfAddr.SockAddr) ipv4Uint32 := uint32(ipv4.Address) ipv4Uint32 += uint32(i) return IfAddr{ SockAddr: IPv4Addr{ Address: IPv4Address(ipv4Uint32), Mask: ipv4.Mask, }, Interface: inputIfAddr.Interface, }, nil case TypeIPv6: // 64 == Accept any int32 value // TODO(seanc@): Add the ability to parse hex. Also parse a bignum int. i, err := strconv.ParseInt(value, 10, 64) if err != nil { return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) } ipv6 := *ToIPv6Addr(inputIfAddr.SockAddr) ipv6BigIntA := new(big.Int) ipv6BigIntA.Set(ipv6.Address) ipv6BigIntB := big.NewInt(i) ipv6Addr := ipv6BigIntA.Add(ipv6BigIntA, ipv6BigIntB) ipv6Addr.And(ipv6Addr, ipv6HostMask) return IfAddr{ SockAddr: IPv6Addr{ Address: IPv6Address(ipv6Addr), Mask: ipv6.Mask, }, Interface: inputIfAddr.Interface, }, nil default: return IfAddr{}, fmt.Errorf("unsupported type for operation %q: %T", operation, sockType) } case "network": // "network" operates on the network address. Positive values start at the // network address and negative values wrap at the network address, which // means a "-1" value on a network will be the broadcast address after // wrapping is applied. if !signRe.MatchString(value) { return IfAddr{}, fmt.Errorf("sign (+/-) is required for operation %q", operation) } switch sockType := inputIfAddr.SockAddr.Type(); sockType { case TypeIPv4: // 33 == Accept any uint32 value // TODO(seanc@): Add the ability to parse hex i, err := strconv.ParseInt(value, 10, 33) if err != nil { return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) } ipv4 := *ToIPv4Addr(inputIfAddr.SockAddr) ipv4Uint32 := uint32(ipv4.NetworkAddress()) // Wrap along network mask boundaries. EZ-mode wrapping made possible by // use of int64 vs a uint. var wrappedMask int64 if i >= 0 { wrappedMask = i } else { wrappedMask = 1 + i + int64(^uint32(ipv4.Mask)) } ipv4Uint32 = ipv4Uint32 + (uint32(wrappedMask) &^ uint32(ipv4.Mask)) return IfAddr{ SockAddr: IPv4Addr{ Address: IPv4Address(ipv4Uint32), Mask: ipv4.Mask, }, Interface: inputIfAddr.Interface, }, nil case TypeIPv6: // 64 == Accept any int32 value // TODO(seanc@): Add the ability to parse hex. Also parse a bignum int. i, err := strconv.ParseInt(value, 10, 64) if err != nil { return IfAddr{}, fmt.Errorf("unable to convert %q to int for operation %q: %v", value, operation, err) } ipv6 := *ToIPv6Addr(inputIfAddr.SockAddr) ipv6BigInt := new(big.Int) ipv6BigInt.Set(ipv6.NetworkAddress()) mask := new(big.Int) mask.Set(ipv6.Mask) if i > 0 { wrappedMask := new(big.Int) wrappedMask.SetInt64(i) wrappedMask.AndNot(wrappedMask, mask) ipv6BigInt.Add(ipv6BigInt, wrappedMask) } else { // Mask off any bits that exceed the network size. Subtract the // wrappedMask from the last usable - 1 wrappedMask := new(big.Int) wrappedMask.SetInt64(-1 * i) wrappedMask.Sub(wrappedMask, big.NewInt(1)) wrappedMask.AndNot(wrappedMask, mask) lastUsable := new(big.Int) lastUsable.Set(ipv6.LastUsable().(IPv6Addr).Address) ipv6BigInt = lastUsable.Sub(lastUsable, wrappedMask) } return IfAddr{ SockAddr: IPv6Addr{ Address: IPv6Address(ipv6BigInt), Mask: ipv6.Mask, }, Interface: inputIfAddr.Interface, }, nil default: return IfAddr{}, fmt.Errorf("unsupported type for operation %q: %T", operation, sockType) } default: return IfAddr{}, fmt.Errorf("unsupported math operation: %q", operation) } } // IfAddrsMath will apply an IfAddrMath operation each IfAddr struct. Any // failure will result in zero results. func IfAddrsMath(operation, value string, inputIfAddrs IfAddrs) (IfAddrs, error) { outputAddrs := make(IfAddrs, 0, len(inputIfAddrs)) for _, ifAddr := range inputIfAddrs { result, err := IfAddrMath(operation, value, ifAddr) if err != nil { return IfAddrs{}, fmt.Errorf("unable to perform an IPMath operation on %s: %v", ifAddr, err) } outputAddrs = append(outputAddrs, result) } return outputAddrs, nil } // IncludeIfs returns an IfAddrs based on the passed in selector. func IncludeIfs(selectorName, selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) { var includedIfs IfAddrs var err error switch strings.ToLower(selectorName) { case "address": includedIfs, _, err = IfByAddress(selectorParam, inputIfAddrs) case "flag", "flags": includedIfs, _, err = IfByFlag(selectorParam, inputIfAddrs) case "name": includedIfs, _, err = IfByName(selectorParam, inputIfAddrs) case "network": includedIfs, _, err = IfByNetwork(selectorParam, inputIfAddrs) case "port": includedIfs, _, err = IfByPort(selectorParam, inputIfAddrs) case "rfc", "rfcs": includedIfs, _, err = IfByRFCs(selectorParam, inputIfAddrs) case "size": includedIfs, _, err = IfByMaskSize(selectorParam, inputIfAddrs) case "type": includedIfs, _, err = IfByType(selectorParam, inputIfAddrs) default: return IfAddrs{}, fmt.Errorf("invalid include selector %q", selectorName) } if err != nil { return IfAddrs{}, err } return includedIfs, nil } // ExcludeIfs returns an IfAddrs based on the passed in selector. func ExcludeIfs(selectorName, selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) { var excludedIfs IfAddrs var err error switch strings.ToLower(selectorName) { case "address": _, excludedIfs, err = IfByAddress(selectorParam, inputIfAddrs) case "flag", "flags": _, excludedIfs, err = IfByFlag(selectorParam, inputIfAddrs) case "name": _, excludedIfs, err = IfByName(selectorParam, inputIfAddrs) case "network": _, excludedIfs, err = IfByNetwork(selectorParam, inputIfAddrs) case "port": _, excludedIfs, err = IfByPort(selectorParam, inputIfAddrs) case "rfc", "rfcs": _, excludedIfs, err = IfByRFCs(selectorParam, inputIfAddrs) case "size": _, excludedIfs, err = IfByMaskSize(selectorParam, inputIfAddrs) case "type": _, excludedIfs, err = IfByType(selectorParam, inputIfAddrs) default: return IfAddrs{}, fmt.Errorf("invalid exclude selector %q", selectorName) } if err != nil { return IfAddrs{}, err } return excludedIfs, nil } // SortIfBy returns an IfAddrs sorted based on the passed in selector. Multiple // sort clauses can be passed in as a comma delimited list without whitespace. func SortIfBy(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, error) { sortedIfs := append(IfAddrs(nil), inputIfAddrs...) clauses := strings.Split(selectorParam, ",") sortFuncs := make([]CmpIfAddrFunc, len(clauses)) for i, clause := range clauses { switch strings.TrimSpace(strings.ToLower(clause)) { case "+address", "address": // The "address" selector returns an array of IfAddrs // ordered by the network address. IfAddrs that are not // comparable will be at the end of the list and in a // non-deterministic order. sortFuncs[i] = AscIfAddress case "-address": sortFuncs[i] = DescIfAddress case "+default", "default": sortFuncs[i] = AscIfDefault case "-default": sortFuncs[i] = DescIfDefault case "+name", "name": // The "name" selector returns an array of IfAddrs // ordered by the interface name. sortFuncs[i] = AscIfName case "-name": sortFuncs[i] = DescIfName case "+port", "port": // The "port" selector returns an array of IfAddrs // ordered by the port, if included in the IfAddr. // IfAddrs that are not comparable will be at the end of // the list and in a non-deterministic order. sortFuncs[i] = AscIfPort case "-port": sortFuncs[i] = DescIfPort case "+private", "private": // The "private" selector returns an array of IfAddrs // ordered by private addresses first. IfAddrs that are // not comparable will be at the end of the list and in // a non-deterministic order. sortFuncs[i] = AscIfPrivate case "-private": sortFuncs[i] = DescIfPrivate case "+size", "size": // The "size" selector returns an array of IfAddrs // ordered by the size of the network mask, smaller mask // (larger number of hosts per network) to largest // (e.g. a /24 sorts before a /32). sortFuncs[i] = AscIfNetworkSize case "-size": sortFuncs[i] = DescIfNetworkSize case "+type", "type": // The "type" selector returns an array of IfAddrs // ordered by the type of the IfAddr. The sort order is // Unix, IPv4, then IPv6. sortFuncs[i] = AscIfType case "-type": sortFuncs[i] = DescIfType default: // Return an empty list for invalid sort types. return IfAddrs{}, fmt.Errorf("unknown sort type: %q", clause) } } OrderedIfAddrBy(sortFuncs...).Sort(sortedIfs) return sortedIfs, nil } // UniqueIfAddrsBy creates a unique set of IfAddrs based on the matching // selector. UniqueIfAddrsBy assumes the input has already been sorted. func UniqueIfAddrsBy(selectorName string, inputIfAddrs IfAddrs) (IfAddrs, error) { attrName := strings.ToLower(selectorName) ifs := make(IfAddrs, 0, len(inputIfAddrs)) var lastMatch string for _, ifAddr := range inputIfAddrs { var out string switch attrName { case "address": out = ifAddr.SockAddr.String() case "name": out = ifAddr.Name default: return nil, fmt.Errorf("unsupported unique constraint %+q", selectorName) } switch { case lastMatch == "", lastMatch != out: lastMatch = out ifs = append(ifs, ifAddr) case lastMatch == out: continue } } return ifs, nil } // JoinIfAddrs joins an IfAddrs and returns a string func JoinIfAddrs(selectorName string, joinStr string, inputIfAddrs IfAddrs) (string, error) { outputs := make([]string, 0, len(inputIfAddrs)) attrName := AttrName(strings.ToLower(selectorName)) for _, ifAddr := range inputIfAddrs { var attrVal string var err error attrVal, err = ifAddr.Attr(attrName) if err != nil { return "", err } outputs = append(outputs, attrVal) } return strings.Join(outputs, joinStr), nil } // LimitIfAddrs returns a slice of IfAddrs based on the specified limit. func LimitIfAddrs(lim uint, in IfAddrs) (IfAddrs, error) { // Clamp the limit to the length of the array if int(lim) > len(in) { lim = uint(len(in)) } return in[0:lim], nil } // OffsetIfAddrs returns a slice of IfAddrs based on the specified offset. func OffsetIfAddrs(off int, in IfAddrs) (IfAddrs, error) { var end bool if off < 0 { end = true off = off * -1 } if off > len(in) { return IfAddrs{}, fmt.Errorf("unable to seek past the end of the interface array: offset (%d) exceeds the number of interfaces (%d)", off, len(in)) } if end { return in[len(in)-off:], nil } return in[off:], nil } func (ifAddr IfAddr) String() string { return fmt.Sprintf("%s %v", ifAddr.SockAddr, ifAddr.Interface) } // parseDefaultIfNameFromRoute parses standard route(8)'s output for the *BSDs // and Solaris. func parseDefaultIfNameFromRoute(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") for _, line := range lines { kvs := strings.SplitN(line, ":", 2) if len(kvs) != 2 { continue } if strings.TrimSpace(kvs[0]) == "interface" { ifName := strings.TrimSpace(kvs[1]) return ifName, nil } } return "", errors.New("No default interface found") } // parseDefaultIfNameFromIPCmd parses the default interface from ip(8) for // Linux. func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") re := whitespaceRE.Copy() for _, line := range lines { kvs := re.Split(line, -1) if len(kvs) < 5 { continue } if kvs[0] == "default" && kvs[1] == "via" && kvs[3] == "dev" { ifName := strings.TrimSpace(kvs[4]) return ifName, nil } } return "", errors.New("No default interface found") } // parseDefaultIfNameWindows parses the default interface from `netstat -rn` and // `ipconfig` on Windows. func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) { defaultIPAddr, err := parseDefaultIPAddrWindowsRoute(routeOut) if err != nil { return "", err } ifName, err := parseDefaultIfNameWindowsIPConfig(defaultIPAddr, ipconfigOut) if err != nil { return "", err } return ifName, nil } // parseDefaultIPAddrWindowsRoute parses the IP address on the default interface // `netstat -rn`. // // NOTES(sean): Only IPv4 addresses are parsed at this time. If you have an // IPv6 connected host, submit an issue on github.com/hashicorp/go-sockaddr with // the output from `netstat -rn`, `ipconfig`, and version of Windows to see IPv6 // support added. func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") re := whitespaceRE.Copy() for _, line := range lines { kvs := re.Split(strings.TrimSpace(line), -1) if len(kvs) < 3 { continue } if kvs[0] == "0.0.0.0" && kvs[1] == "0.0.0.0" { defaultIPAddr := strings.TrimSpace(kvs[3]) return defaultIPAddr, nil } } return "", errors.New("No IP on default interface found") } // parseDefaultIfNameWindowsIPConfig parses the output of `ipconfig` to find the // interface name forwarding traffic to the default gateway. func parseDefaultIfNameWindowsIPConfig(defaultIPAddr, routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") ifNameRe := ifNameRE.Copy() ipAddrRe := ipAddrRE.Copy() var ifName string for _, line := range lines { switch ifNameMatches := ifNameRe.FindStringSubmatch(line); { case len(ifNameMatches) > 1: ifName = ifNameMatches[1] continue } switch ipAddrMatches := ipAddrRe.FindStringSubmatch(line); { case len(ipAddrMatches) > 1 && ipAddrMatches[1] == defaultIPAddr: return ifName, nil } } return "", errors.New("No default interface found with matching IP") }