go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/networkinterface/hostip.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package networkinterface
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"sort"
    10  
    11  	"github.com/cockroachdb/errors"
    12  	"github.com/rs/zerolog/log"
    13  )
    14  
    15  func filterNetworkInterface(interfaces []Interface, flagFilter func(flags net.Flags) bool) []Interface {
    16  	i := []Interface{}
    17  	for _, v := range interfaces {
    18  		if flagFilter(v.Flags) {
    19  			i = append(i, v)
    20  		}
    21  	}
    22  	return i
    23  }
    24  
    25  // byIfaceIndex Interface by its index
    26  type byIfaceIndex []Interface
    27  
    28  func (iface byIfaceIndex) Len() int           { return len(iface) }
    29  func (iface byIfaceIndex) Less(i, j int) bool { return iface[i].Index < iface[j].Index }
    30  func (iface byIfaceIndex) Swap(i, j int)      { iface[i], iface[j] = iface[j], iface[i] }
    31  
    32  // HostIP extracts the best-guess for the IP of the host
    33  // It will search ip v4 first and fallback to v6
    34  func HostIP(interfaces []Interface) (ip string, err error) {
    35  	log.Debug().Int("interfaces", len(interfaces)).Msg("search ip")
    36  	// filter interfaces that are not up or a loopback/p2p interface
    37  	interfaces = filterNetworkInterface(interfaces, func(flags net.Flags) bool {
    38  		if (flags&net.FlagUp != 0) &&
    39  			(flags&net.FlagLoopback == 0) &&
    40  			(flags&net.FlagPointToPoint == 0) {
    41  			return true
    42  		}
    43  		return false
    44  	})
    45  
    46  	// sort interfaces by its index
    47  	sort.Sort(byIfaceIndex(interfaces))
    48  
    49  	var foundIPv4 net.IP
    50  	foundIPsv6 := []net.IP{}
    51  
    52  	// search for IPv4
    53  	for _, i := range interfaces {
    54  		addrs := i.Addrs
    55  		for _, addr := range addrs {
    56  			var foundIPv6 net.IP
    57  			switch v := addr.(type) {
    58  			case *net.IPAddr:
    59  				foundIPv4 = v.IP.To4()
    60  				foundIPv6 = v.IP.To16()
    61  			case *net.IPNet:
    62  				foundIPv4 = v.IP.To4()
    63  				foundIPv6 = v.IP.To16()
    64  			case *ipAddr:
    65  				foundIPv4 = v.IP.To4()
    66  				foundIPv6 = v.IP.To16()
    67  			}
    68  
    69  			if foundIPv4 != nil {
    70  				return foundIPv4.String(), nil
    71  			}
    72  			if foundIPv6 != nil {
    73  				foundIPsv6 = append(foundIPsv6, foundIPv6)
    74  			}
    75  		}
    76  	}
    77  
    78  	// search for IPv6
    79  	if len(foundIPsv6) > 0 {
    80  		return foundIPsv6[0].String(), nil
    81  	}
    82  
    83  	return "", fmt.Errorf("no IP address found")
    84  }
    85  
    86  // GetOutboundIP returns the local IP that is used for outbound connections
    87  // It does not establish a real connection and the destination does not need to valid.
    88  // Since its using udp protocol (unlike TCP) a handshake nor connection is required,
    89  // / then it gets the local up address if it would connect to that target
    90  // conn.LocalAddr().String() returns the local ip and port
    91  //
    92  // # NOTE be aware that this code does not work on remote targets
    93  //
    94  // @see this approach is derived from https://stackoverflow.com/a/37382208
    95  func GetOutboundIP() (net.IP, error) {
    96  	conn, err := net.Dial("udp", "1.1.1.1:80")
    97  	if err != nil {
    98  		return nil, errors.Wrap(err, "could not determine outbound ip")
    99  	}
   100  	defer conn.Close()
   101  
   102  	localAddr := conn.LocalAddr().(*net.UDPAddr)
   103  
   104  	if localAddr == nil {
   105  		return nil, errors.New("could not determine outbound ip")
   106  	}
   107  
   108  	return localAddr.IP, nil
   109  }