diff --git a/ethtool.go b/ethtool.go index 251c7d5..2555e31 100644 --- a/ethtool.go +++ b/ethtool.go @@ -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 @@ -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) diff --git a/ethtool_test.go b/ethtool_test.go index 53f774c..15fcee4 100644 --- a/ethtool_test.go +++ b/ethtool_test.go @@ -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") + } +}