Skip to content

Commit

Permalink
add a new API to report report feature states
Browse files Browse the repository at this point in the history
When inspecting features, the current API reports only
if each feature is active.
Client applications, especially those consuming the output
of `ethtool -k [iface]` also want to know if a feature can
actually be enabled or not. Examples:
- https://github.com/jaypipes/ghw/blob/main/README.md#network
-https://github.com/jaypipes/ghw/blob/main/pkg/net/net_linux.go#L161

This information is already available but is not exposed to the API.
So, add a new API to export all the flags. We add a new API instead
of changing the existing one to avoid breaking the backward
compatibility.

Signed-off-by: Francesco Romani <[email protected]>
  • Loading branch information
ffromani committed Jun 10, 2024
1 parent 46753c7 commit 10f3ba2
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
46 changes: 46 additions & 0 deletions ethtool.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,22 @@ func isFeatureBitSet(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index u
return (blocks)[index/32].active&(1<<(index%32)) != 0
}

type FeatureState struct {
Available bool
Requested bool
Active bool
NeverChanged bool
}

func getFeatureStateBits(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index uint) FeatureState {
return FeatureState{
Available: (blocks)[index/32].available&(1<<(index%32)) != 0,
Requested: (blocks)[index/32].requested&(1<<(index%32)) != 0,
Active: (blocks)[index/32].active&(1<<(index%32)) != 0,
NeverChanged: (blocks)[index/32].never_changed&(1<<(index%32)) != 0,
}
}

func setFeatureBit(blocks *[MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock, index uint, value bool) {
blockIndex, bitIndex := index/32, index%32

Expand Down Expand Up @@ -790,6 +806,36 @@ func (e *Ethtool) Features(intf string) (map[string]bool, error) {
return result, nil
}

// FeaturesWithState retrieves features of the given interface name,
// with extra flags to explain if they can be enabled
func (e *Ethtool) FeaturesWithState(intf string) (map[string]FeatureState, error) {
names, err := e.FeatureNames(intf)
if err != nil {
return nil, err
}

length := uint32(len(names))
if length == 0 {
return map[string]FeatureState{}, nil
}

features := ethtoolGfeatures{
cmd: ETHTOOL_GFEATURES,
size: (length + 32 - 1) / 32,
}

if err := e.ioctl(intf, uintptr(unsafe.Pointer(&features))); err != nil {
return nil, err
}

var result = make(map[string]FeatureState, length)
for key, index := range names {
result[key] = getFeatureStateBits(features.blocks, index)
}

return result, nil
}

// Change requests a change in the given device's features.
func (e *Ethtool) Change(intf string, config map[string]bool) error {
names, err := e.FeatureNames(intf)
Expand Down
43 changes: 43 additions & 0 deletions ethtool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,46 @@ func TestSupportedLinkModes(t *testing.T) {
}
}
}

func TestFeatures(t *testing.T) {
et, err := NewEthtool()
if err != nil {
t.Fatal(err)
}
defer et.Close()

feats, err := et.Features("lo")
if err != nil {
t.Fatal(err)
}

if len(feats) == 0 {
// TOOD: do we have a sane subset of features we should check?
t.Fatalf("expected features for loopback interface")
}

featsWithState, err := et.FeaturesWithState("lo")
if err != nil {
t.Fatal(err)
}

if len(feats) != len(featsWithState) {
t.Fatalf("features mismatch: %d with state %d", len(feats), len(featsWithState))
}

fixed := 0
for key, val := range feats {
state, ok := featsWithState[key]
if !ok || val != state.Active {
t.Errorf("inconsistent feature: %q reported %v active %v", key, val, state.Active)
}
if !state.Available {
fixed++
}
}

if fixed == 0 {
// the lo interface MUST have some non-available features, by design
t.Fatalf("loopback interface reported all features available")
}
}

0 comments on commit 10f3ba2

Please sign in to comment.