Skip to content

Commit

Permalink
Merge pull request #342 from jak3kaj/NicLinkInfo2
Browse files Browse the repository at this point in the history
Add more fields to NIC
  • Loading branch information
jaypipes committed May 12, 2023
2 parents 29cf834 + 413cc04 commit 36ff37e
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 28 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,10 +711,25 @@ Each `ghw.NIC` struct contains the following fields:
device
* `ghw.NIC.Capabilities` is an array of pointers to `ghw.NICCapability` structs
that can describe the things the NIC supports. These capabilities match the
returned values from the `ethtool -k <DEVICE>` call on Linux
returned values from the `ethtool -k <DEVICE>` call on Linux as well as the
AutoNegotiation and PauseFrameUse capabilities from `ethtool`.
* `ghw.NIC.PCIAddress` is the PCI device address of the device backing the NIC.
this is not-nil only if the backing device is indeed a PCI device; more backing
devices (e.g. USB) will be added in future versions.
* `ghw.NIC.Speed` is a string showing the current link speed. On Linux, this
field will be present even if `ethtool` is not available.
* `ghw.NIC.Duplex` is a string showing the current link duplex. On Linux, this
field will be present even if `ethtool` is not available.
* `ghw.NIC.SupportedLinkModes` is a string slice containing a list of
supported link modes
* `ghw.NIC.SupportedPorts` is a string slice containing the list of
supported port types (MII, TP, FIBRE)
* `ghw.NIC.SupportedFECModes` is a string slice containing a list of
supported FEC Modes.
* `ghw.NIC.AdvertisedLinkModes` is a string slice containing the
link modes being advertised during auto negotiation.
* `ghw.NIC.AdvertisedFECModes` is a string slice containing the FEC
modes advertised during auto negotiation.

The `ghw.NICCapability` struct contains the following fields:

Expand Down
17 changes: 12 additions & 5 deletions pkg/net/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ type NICCapability struct {
}

type NIC struct {
Name string `json:"name"`
MacAddress string `json:"mac_address"`
IsVirtual bool `json:"is_virtual"`
Capabilities []*NICCapability `json:"capabilities"`
PCIAddress *string `json:"pci_address,omitempty"`
Name string `json:"name"`
MacAddress string `json:"mac_address"`
IsVirtual bool `json:"is_virtual"`
Capabilities []*NICCapability `json:"capabilities"`
PCIAddress *string `json:"pci_address,omitempty"`
Speed string `json:"speed"`
Duplex string `json:"duplex"`
SupportedLinkModes []string `json:"supported_link_modes,omitempty"`
SupportedPorts []string `json:"supported_ports,omitempty"`
SupportedFECModes []string `json:"supported_fec_modes,omitempty"`
AdvertisedLinkModes []string `json:"advertised_link_modes,omitempty"`
AdvertisedFECModes []string `json:"advertised_fec_modes,omitempty"`
// TODO(fromani): add other hw addresses (USB) when we support them
}

Expand Down
40 changes: 32 additions & 8 deletions pkg/net/net_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ func nics(ctx *context.Context) []*NIC {
mac := netDeviceMacAddress(paths, filename)
nic.MacAddress = mac
if etAvailable {
nic.Capabilities = netDeviceCapabilities(ctx, filename)
nic.netDeviceParseEthtool(ctx, filename)
} else {
nic.Capabilities = []*NICCapability{}
// Sets NIC struct fields from data in SysFs
nic.setNicAttrSysFs(paths, filename)
}

nic.PCIAddress = netDevicePCIAddress(paths.SysClassNet, filename)
Expand Down Expand Up @@ -106,19 +108,29 @@ func ethtoolInstalled() bool {
return err == nil
}

func netDeviceCapabilities(ctx *context.Context, dev string) []*NICCapability {
caps := make([]*NICCapability, 0)
func (n *NIC) netDeviceParseEthtool(ctx *context.Context, dev string) {
var out bytes.Buffer
path, _ := exec.LookPath("ethtool")

// Get auto-negotiation and pause-frame-use capabilities from "ethtool" (with no options)
// Populate Speed, Duplex, SupportedLinkModes, SupportedPorts, SupportedFECModes,
// AdvertisedLinkModes, and AdvertisedFECModes attributes from "ethtool" output.
cmd := exec.Command(path, dev)
cmd.Stdout = &out
err := cmd.Run()
if err == nil {
m := parseNicAttrEthtool(&out)
caps = append(caps, autoNegCap(m))
caps = append(caps, pauseFrameUseCap(m))
n.Capabilities = append(n.Capabilities, autoNegCap(m))
n.Capabilities = append(n.Capabilities, pauseFrameUseCap(m))

// Update NIC Attributes with ethtool output
n.Speed = strings.Join(m["Speed"], "")
n.Duplex = strings.Join(m["Duplex"], "")
n.SupportedLinkModes = m["Supported link modes"]
n.SupportedPorts = m["Supported ports"]
n.SupportedFECModes = m["Supported FEC modes"]
n.AdvertisedLinkModes = m["Advertised link modes"]
n.AdvertisedFECModes = m["Advertised FEC modes"]
} else {
msg := fmt.Sprintf("could not grab NIC link info for %s: %s", dev, err)
ctx.Warn(msg)
Expand Down Expand Up @@ -154,16 +166,14 @@ func netDeviceCapabilities(ctx *context.Context, dev string) []*NICCapability {
scanner.Scan()
for scanner.Scan() {
line := strings.TrimPrefix(scanner.Text(), "\t")
caps = append(caps, netParseEthtoolFeature(line))
n.Capabilities = append(n.Capabilities, netParseEthtoolFeature(line))
}

} else {
msg := fmt.Sprintf("could not grab NIC capabilities for %s: %s", dev, err)
ctx.Warn(msg)
}

return caps

}

// netParseEthtoolFeature parses a line from the ethtool -k output and returns
Expand Down Expand Up @@ -239,6 +249,20 @@ func netDevicePCIAddress(netDevDir, netDevName string) *string {
return &pciAddr
}

func (nic *NIC) setNicAttrSysFs(paths *linuxpath.Paths, dev string) {
// Get speed and duplex from /sys/class/net/$DEVICE/ directory
nic.Speed = readFile(filepath.Join(paths.SysClassNet, dev, "speed"))
nic.Duplex = readFile(filepath.Join(paths.SysClassNet, dev, "duplex"))
}

func readFile(path string) string {
contents, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return strings.TrimSpace(string(contents))
}

func autoNegCap(m map[string][]string) *NICCapability {
autoNegotiation := NICCapability{Name: "auto-negotiation", IsEnabled: false, CanEnable: false}

Expand Down
55 changes: 41 additions & 14 deletions pkg/net/net_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"bytes"
"os"
"reflect"
"strings"
"testing"
)

Expand Down Expand Up @@ -66,7 +67,7 @@ func TestParseNicAttrEthtool(t *testing.T) {

tests := []struct {
input string
expected []*NICCapability
expected *NIC
}{
{
input: `Settings for eth0:
Expand Down Expand Up @@ -96,28 +97,54 @@ func TestParseNicAttrEthtool(t *testing.T) {
drv probe link
Link detected: yes
`,
expected: []*NICCapability{
{
Name: "auto-negotiation",
IsEnabled: true,
CanEnable: true,
expected: &NIC{
Speed: "1000Mb/s",
Duplex: "Full",
SupportedPorts: []string{"TP"},
AdvertisedLinkModes: []string{
"10baseT/Half",
"10baseT/Full",
"100baseT/Half",
"100baseT/Full",
"1000baseT/Full",
},
{
Name: "pause-frame-use",
IsEnabled: false,
CanEnable: false,
SupportedLinkModes: []string{
"10baseT/Half",
"10baseT/Full",
"100baseT/Half",
"100baseT/Full",
"1000baseT/Full",
},
Capabilities: []*NICCapability{
{
Name: "auto-negotiation",
IsEnabled: true,
CanEnable: true,
},
{
Name: "pause-frame-use",
IsEnabled: false,
CanEnable: false,
},
},
},
},
}

for x, test := range tests {
m := parseNicAttrEthtool(bytes.NewBufferString(test.input))
actual := make([]*NICCapability, 0)
actual = append(actual, autoNegCap(m))
actual = append(actual, pauseFrameUseCap(m))
actual := &NIC{}
actual.Speed = strings.Join(m["Speed"], "")
actual.Duplex = strings.Join(m["Duplex"], "")
actual.SupportedLinkModes = m["Supported link modes"]
actual.SupportedPorts = m["Supported ports"]
actual.SupportedFECModes = m["Supported FEC modes"]
actual.AdvertisedLinkModes = m["Advertised link modes"]
actual.AdvertisedFECModes = m["Advertised FEC modes"]
actual.Capabilities = append(actual.Capabilities, autoNegCap(m))
actual.Capabilities = append(actual.Capabilities, pauseFrameUseCap(m))
if !reflect.DeepEqual(test.expected, actual) {
t.Fatalf("In test %d\nExpected:\n%+v\nActual:\n%+v\n", x, test.expected, actual)
t.Fatalf("In test %d\nExpected:\n%+v\nActual:\n%+v\n", x, *test.expected, *actual)
}
}
}
3 changes: 3 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ func ParseBool(str string) (bool, error) {
"off": false,
"yes": true,
"no": false,
// Return false instead of an error on empty strings
// For example from empty files in SysClassNet/Device
"": false,
}
if b, ok := ExtraBools[strings.ToLower(str)]; ok {
return b, nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func TestParseBool(t *testing.T) {
item: "1",
expected: true,
},
{
item: "",
expected: false,
},
{
item: "on",
expected: true,
Expand Down

0 comments on commit 36ff37e

Please sign in to comment.