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 }