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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package networkinterface
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/errors"
    17  	"github.com/rs/zerolog/log"
    18  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    19  	"go.mondoo.com/cnquery/providers/os/connection"
    20  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    21  	"go.mondoo.com/cnquery/providers/os/resources/powershell"
    22  )
    23  
    24  var errNoSuchInterface = errors.New("no such network interface")
    25  
    26  // mimics https://golang.org/src/net/interface.go to provide a similar api
    27  type Interface struct {
    28  	Index          int              // positive integer that starts at one, zero is never used
    29  	MTU            int              // maximum transmission unit
    30  	Name           string           // e.g., "en0", "lo0", "eth0.100"
    31  	HardwareAddr   net.HardwareAddr // IEEE MAC-48, EUI-48 and EUI-64 form
    32  	Flags          net.Flags        // e.g., FlagUp, FlagLoopback, FlagMulticast
    33  	Addrs          []net.Addr
    34  	MulticastAddrs []net.Addr
    35  }
    36  
    37  func New(conn shared.Connection) *InterfaceResource {
    38  	return &InterfaceResource{
    39  		conn: conn,
    40  	}
    41  }
    42  
    43  type InterfaceResource struct {
    44  	conn shared.Connection
    45  }
    46  
    47  func (r *InterfaceResource) Interfaces() ([]Interface, error) {
    48  	asset := r.conn.Asset()
    49  	if asset == nil || asset.Platform == nil {
    50  		return nil, errors.New("cannot find OS information for interface detection")
    51  	}
    52  
    53  	log.Debug().Strs("families", asset.Platform.Family).Msg("check if platform is supported for network interface")
    54  	if r.conn.Type() == connection.Local {
    55  		handler := &GoNativeInterfaceHandler{}
    56  		return handler.Interfaces()
    57  	} else if asset.Platform.Name == "macos" {
    58  		handler := &MacOSInterfaceHandler{
    59  			conn: r.conn,
    60  		}
    61  		return handler.Interfaces()
    62  	} else if asset.Platform.IsFamily(inventory.FAMILY_LINUX) {
    63  		log.Debug().Msg("detected linux platform")
    64  		handler := &LinuxInterfaceHandler{
    65  			conn: r.conn,
    66  		}
    67  		return handler.Interfaces()
    68  	} else if asset.Platform.Name == "windows" {
    69  		handler := &WindowsInterfaceHandler{
    70  			conn: r.conn,
    71  		}
    72  		return handler.Interfaces()
    73  	}
    74  
    75  	return nil, errors.New("interfaces does not support platform: " + asset.Platform.Name)
    76  }
    77  
    78  func (r *InterfaceResource) InterfaceByName(name string) (*Interface, error) {
    79  	ifaces, err := r.Interfaces()
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	for i := range ifaces {
    85  		if ifaces[i].Name == name {
    86  			return &ifaces[i], nil
    87  		}
    88  	}
    89  	return nil, errNoSuchInterface
    90  }
    91  
    92  type GoNativeInterfaceHandler struct{}
    93  
    94  func (i *GoNativeInterfaceHandler) Interfaces() ([]Interface, error) {
    95  	var goInterfaces []net.Interface
    96  	var err error
    97  	if goInterfaces, err = net.Interfaces(); err != nil {
    98  		return nil, errors.Wrap(err, "failed to load network interfaces")
    99  	}
   100  
   101  	ifaces := make([]Interface, len(goInterfaces))
   102  	for i := range goInterfaces {
   103  
   104  		addrs, err := goInterfaces[i].Addrs()
   105  		if err != nil {
   106  			log.Debug().Err(err).Str("iface", goInterfaces[i].Name).Msg("could not retrieve ip addresses")
   107  		}
   108  		multicastAddrs, err := goInterfaces[i].MulticastAddrs()
   109  		if err != nil {
   110  			log.Debug().Err(err).Str("iface", goInterfaces[i].Name).Msg("could not retrieve multicast addresses")
   111  		}
   112  
   113  		ifaces[i] = Interface{
   114  			Name:           goInterfaces[i].Name,
   115  			Index:          goInterfaces[i].Index,
   116  			MTU:            goInterfaces[i].MTU,
   117  			HardwareAddr:   goInterfaces[i].HardwareAddr,
   118  			Flags:          goInterfaces[i].Flags,
   119  			Addrs:          addrs,
   120  			MulticastAddrs: multicastAddrs,
   121  		}
   122  	}
   123  
   124  	return ifaces, nil
   125  }
   126  
   127  type LinuxInterfaceHandler struct {
   128  	conn shared.Connection
   129  }
   130  
   131  func (i *LinuxInterfaceHandler) Interfaces() ([]Interface, error) {
   132  	// TODO: support extracting the information via /sys/class/net/, /proc/net/fib_trie
   133  	// fetch all network adapter via ip addr show
   134  	cmd, err := i.conn.RunCommand("ip -o addr show")
   135  	if err != nil {
   136  		return nil, errors.Wrap(err, "could not fetch macos network adapter")
   137  	}
   138  
   139  	return i.ParseIpAddr(cmd.Stdout)
   140  }
   141  
   142  func (i *LinuxInterfaceHandler) ParseIpAddr(r io.Reader) ([]Interface, error) {
   143  	interfaces := map[string]Interface{}
   144  
   145  	scanner := bufio.NewScanner(r)
   146  	ipaddrParse := regexp.MustCompile(`^(\d):\s([\w\d\./\:]+)\s*(inet|inet6)\s([\w\d\./\:]+)\s(.*)$`)
   147  
   148  	for scanner.Scan() {
   149  		line := scanner.Text()
   150  
   151  		m := ipaddrParse.FindStringSubmatch(line)
   152  
   153  		// check that we have a match
   154  		if len(m) < 4 {
   155  			return nil, fmt.Errorf("cannot parse ip: %s", line)
   156  		}
   157  
   158  		name := m[2]
   159  
   160  		idx, err := strconv.Atoi(strings.TrimSpace(m[1]))
   161  		if err != nil {
   162  			log.Warn().Err(err).Msg("could not parse ip addr idx")
   163  			continue
   164  		}
   165  
   166  		inet, ok := interfaces[name]
   167  		if !ok {
   168  			inet = Interface{
   169  				Index: idx,
   170  				Name:  name,
   171  			}
   172  		}
   173  
   174  		var ip net.IP
   175  		if m[3] == "inet" {
   176  			ipv4Addr, _, err := net.ParseCIDR(m[4])
   177  			if err != nil {
   178  				log.Error().Err(err).Msg("could not parse ipv4")
   179  			}
   180  
   181  			ip = ipv4Addr
   182  		} else if m[3] == "inet6" {
   183  			ipv6Addr, _, err := net.ParseCIDR(m[4])
   184  			if err != nil {
   185  				log.Error().Err(err).Msg("could not parse ipv6")
   186  			}
   187  			ip = ipv6Addr
   188  		}
   189  
   190  		inet.Addrs = append(inet.Addrs, &ipAddr{IP: ip})
   191  
   192  		var flags net.Flags
   193  		flags |= net.FlagUp
   194  
   195  		if strings.Contains(m[5], "host") {
   196  			flags |= net.FlagLoopback
   197  		} else {
   198  			flags |= net.FlagBroadcast
   199  		}
   200  
   201  		inet.Flags = flags
   202  
   203  		interfaces[name] = inet
   204  	}
   205  
   206  	res := []Interface{}
   207  	for i := range interfaces {
   208  		res = append(res, interfaces[i])
   209  	}
   210  
   211  	return res, nil
   212  }
   213  
   214  type MacOSInterfaceHandler struct {
   215  	conn shared.Connection
   216  }
   217  
   218  func (i *MacOSInterfaceHandler) Interfaces() ([]Interface, error) {
   219  	// fetch all network adapter
   220  	cmd, err := i.conn.RunCommand("ifconfig")
   221  	if err != nil {
   222  		return nil, errors.Wrap(err, "could not fetch macos network adapter")
   223  	}
   224  
   225  	return i.ParseMacOS(cmd.Stdout)
   226  }
   227  
   228  var (
   229  	IfconfigInterfaceLine = regexp.MustCompile(`^(.*):\ flags=(\d*)\<(.*)>\smtu\s(\d*)$`)
   230  	IfconfigInetLine      = regexp.MustCompile(`^\s+inet(\d*)\s([\w\d.:%]+)\s`)
   231  )
   232  
   233  func (i *MacOSInterfaceHandler) ParseMacOS(r io.Reader) ([]Interface, error) {
   234  	interfaces := []Interface{}
   235  	ifIndex := -1
   236  	scanner := bufio.NewScanner(r)
   237  
   238  	for scanner.Scan() {
   239  		line := scanner.Text()
   240  
   241  		// new interface
   242  		m := IfconfigInterfaceLine.FindStringSubmatch(line)
   243  		if len(m) > 0 {
   244  			var mtu int
   245  			var err error
   246  			mtu, err = strconv.Atoi(strings.TrimSpace(m[4]))
   247  			if err != nil {
   248  				return nil, errors.Wrap(err, "cannot parse macos ifconfig mtu")
   249  			}
   250  
   251  			var flags net.Flags
   252  			if len(m[3]) > 0 {
   253  				ifConfigFlags := strings.Split(m[3], ",")
   254  				for i := range ifConfigFlags {
   255  					switch strings.ToLower(ifConfigFlags[i]) {
   256  					case "up":
   257  						flags |= net.FlagUp
   258  					case "broadcast":
   259  						flags |= net.FlagBroadcast
   260  					case "multicast":
   261  						flags |= net.FlagMulticast
   262  					case "loopback":
   263  						flags |= net.FlagLoopback
   264  					case "pointtopoint":
   265  						flags |= net.FlagPointToPoint
   266  					}
   267  				}
   268  			}
   269  
   270  			i := Interface{
   271  				Index: ifIndex + 2,
   272  				Name:  m[1],
   273  				MTU:   mtu,
   274  				Flags: flags,
   275  			}
   276  
   277  			interfaces = append(interfaces, i)
   278  			ifIndex++
   279  		}
   280  
   281  		// parse mac address
   282  		if strings.HasPrefix(line, "	ether") {
   283  			macaddress := strings.TrimSpace(strings.TrimPrefix(line, "	ether"))
   284  			mac, err := net.ParseMAC(macaddress)
   285  			if err != nil {
   286  				return nil, err
   287  			}
   288  			interfaces[ifIndex].HardwareAddr = mac
   289  		}
   290  
   291  		m = IfconfigInetLine.FindStringSubmatch(line)
   292  		if len(m) > 0 {
   293  			ip := parseIpAddr(m[2])
   294  			if ip != nil {
   295  				interfaces[ifIndex].Addrs = append(interfaces[ifIndex].Addrs, &ipAddr{IP: ip})
   296  			}
   297  		}
   298  
   299  	}
   300  	return interfaces, nil
   301  }
   302  
   303  type WindowsInterface struct {
   304  	Name          string `json:"Name"`
   305  	IfIndex       int    `json:"ifIndex"`
   306  	InterfaceType int    `json:"InterfaceType"`
   307  	Status        string `json:"Status"`
   308  	MacAddress    string `json:"MacAddress"`
   309  }
   310  
   311  type WindowsNetIp struct {
   312  	InterfaceAlias string  `json:"InterfaceAlias"`
   313  	IPv4Address    *string `json:"IPv4Address"`
   314  	IPv6Address    *string `json:"IPv6Address"`
   315  }
   316  
   317  const (
   318  	WinGetNetAdapter   = "Get-NetAdapter | Select-Object -Property Name, ifIndex, InterfaceType, InterfaceDescription, Status, State, MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json"
   319  	WinGetNetIPAddress = "Get-NetIPAddress | Select-Object -Property IPv6Address, IPv4Address, InterfaceAlias | ConvertTo-Json"
   320  )
   321  
   322  const (
   323  	IF_TYPE_OTHER              = 1
   324  	IF_TYPE_ETHERNET_CSMACD    = 6
   325  	IF_TYPE_ISO88025_TOKENRING = 9
   326  	IF_TYPE_PPP                = 23
   327  	IF_TYPE_SOFTWARE_LOOPBACK  = 24
   328  	IF_TYPE_ATM                = 37
   329  	IF_TYPE_IEEE80211          = 71
   330  	IF_TYPE_TUNNEL             = 131
   331  	IF_TYPE_IEEE1394           = 144
   332  )
   333  
   334  // derived from https://golang.org/src/net/interface_windows.go
   335  func windowsInterfaceFlags(status string, ifType int) net.Flags {
   336  	var flags net.Flags
   337  	if status == "Up" {
   338  		flags |= net.FlagUp
   339  	}
   340  
   341  	switch ifType {
   342  	case IF_TYPE_ETHERNET_CSMACD, IF_TYPE_ISO88025_TOKENRING, IF_TYPE_IEEE80211, IF_TYPE_IEEE1394:
   343  		flags |= net.FlagBroadcast | net.FlagMulticast
   344  	case IF_TYPE_PPP, IF_TYPE_TUNNEL:
   345  		flags |= net.FlagPointToPoint | net.FlagMulticast
   346  	case IF_TYPE_SOFTWARE_LOOPBACK:
   347  		flags |= net.FlagLoopback | net.FlagMulticast
   348  	case IF_TYPE_ATM:
   349  		flags |= net.FlagBroadcast | net.FlagPointToPoint | net.FlagMulticast
   350  	}
   351  	return flags
   352  }
   353  
   354  type ipAddr struct {
   355  	IP net.IP
   356  }
   357  
   358  // name of the network (for example, "tcp", "udp")
   359  func (a *ipAddr) Network() string {
   360  	return "tcp"
   361  }
   362  
   363  // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80")
   364  func (a *ipAddr) String() string {
   365  	return a.IP.String()
   366  }
   367  
   368  func parseIpAddr(ip string) net.IP {
   369  	// filter network id https://sid-500.com/2017/01/10/cisco-ipv6-link-local-adressen-und-router-advertisements/
   370  	// "fe80::ed94:1267:afb5:bb7%6" becomes "fe80::ed94:1267:afb5:bb7"
   371  	m := strings.Split(ip, "%")
   372  	return net.ParseIP(m[0])
   373  }
   374  
   375  func filterWinIpByInterface(iName string, ips []WindowsNetIp) []net.Addr {
   376  	addrs := []net.Addr{}
   377  
   378  	for i := range ips {
   379  		if ips[i].InterfaceAlias == iName {
   380  			var ip net.IP
   381  			if ips[i].IPv4Address != nil {
   382  				ip = parseIpAddr(*ips[i].IPv4Address)
   383  			} else if ips[i].IPv6Address != nil {
   384  				ip = parseIpAddr(*ips[i].IPv6Address)
   385  			}
   386  			if ip != nil {
   387  				addrs = append(addrs, &ipAddr{IP: ip})
   388  			}
   389  		}
   390  	}
   391  
   392  	return addrs
   393  }
   394  
   395  type WindowsInterfaceHandler struct {
   396  	conn shared.Connection
   397  }
   398  
   399  func (i *WindowsInterfaceHandler) Interfaces() ([]Interface, error) {
   400  	// fetch all network adapter
   401  	cmd, err := i.conn.RunCommand(powershell.Wrap(WinGetNetAdapter))
   402  	if err != nil {
   403  		return nil, errors.Wrap(err, "could not fetch windows network adapter")
   404  	}
   405  	winAdapter, err := i.ParseNetAdapter(cmd.Stdout)
   406  	if err != nil {
   407  		return nil, errors.Wrap(err, "could not parse windows network adapter list")
   408  	}
   409  
   410  	// fetch all ip addresses
   411  	cmd, err = i.conn.RunCommand(powershell.Wrap(WinGetNetIPAddress))
   412  	if err != nil {
   413  		return nil, errors.Wrap(err, "could not fetch windows ip addresses")
   414  	}
   415  	winIpAddresses, err := i.ParseNetIpAdresses(cmd.Stdout)
   416  	if err != nil {
   417  		return nil, errors.Wrap(err, "could not parse windows ip addresses")
   418  	}
   419  
   420  	// map information together
   421  	interfaces := make([]Interface, len(winAdapter))
   422  	for i := range winAdapter {
   423  		mac, err := net.ParseMAC(winAdapter[i].MacAddress)
   424  		if err != nil {
   425  			return nil, err
   426  		}
   427  
   428  		interfaces[i] = Interface{
   429  			Name:         winAdapter[i].Name,
   430  			Index:        winAdapter[i].IfIndex,
   431  			HardwareAddr: mac,
   432  			Flags:        windowsInterfaceFlags(winAdapter[i].Status, winAdapter[i].InterfaceType),
   433  			Addrs:        filterWinIpByInterface(winAdapter[i].Name, winIpAddresses),
   434  		}
   435  	}
   436  
   437  	return interfaces, nil
   438  }
   439  
   440  func (i *WindowsInterfaceHandler) ParseNetAdapter(r io.Reader) ([]WindowsInterface, error) {
   441  	data, err := io.ReadAll(r)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	var winInterfaces []WindowsInterface
   447  	err = json.Unmarshal(data, &winInterfaces)
   448  	if err != nil {
   449  
   450  		// try again without array (powershell returns single values different)
   451  		var winInterface WindowsInterface
   452  		err = json.Unmarshal(data, &winInterface)
   453  		if err != nil {
   454  			return nil, err
   455  		}
   456  
   457  		return []WindowsInterface{winInterface}, nil
   458  	}
   459  	return winInterfaces, nil
   460  }
   461  
   462  func (i *WindowsInterfaceHandler) ParseNetIpAdresses(r io.Reader) ([]WindowsNetIp, error) {
   463  	data, err := io.ReadAll(r)
   464  	if err != nil {
   465  		return nil, err
   466  	}
   467  
   468  	var winNetIps []WindowsNetIp
   469  	err = json.Unmarshal(data, &winNetIps)
   470  	if err != nil {
   471  		return nil, err
   472  	}
   473  	return winNetIps, nil
   474  }