Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ethtool: add support for private flags #22

Merged
merged 1 commit into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,5 +284,61 @@ func (c *Client) SetWakeOnLAN(wol WakeOnLAN) error {
return c.c.SetWakeOnLAN(wol)
}

// PrivateFlags is a list of driver-specific flags which are either on or off.
// These are used to control behavior specific to a specific driver or device
// for which no generic API exists.
//
// The flags which go in here are mostly undocumented other than in kernel
// source code, you can get the list of supported flags by calling
// PrivateFlags() and then searching for the returned names in Linux kernel
// sources.
//
// This is technically a bitset but as the bit positions are not stable across
// kernel versions there is no reason to use that functionality, thus it is not
// exposed.
//
// Note that these flags are in practice not fully covered by Linux's userspace
// ABI guarantees, it should be expected that a flag can go away.
type PrivateFlags struct {
Interface Interface
// Flags is a map of flag names to their active state, i.e. if the flag
// is on or off.
Flags map[string]bool
}

// AllPrivateFlags returns Private Flags for each ethtool-supported interface
// on this system.
func (c *Client) AllPrivateFlags() ([]*PrivateFlags, error) {
return c.c.AllPrivateFlags()
}

// PrivateFlags returns Private Flags for a single interface. See the type for
// a more in-depth explanation.
//
// If the requested device does not exist or is not supported by the ethtool
// interface, an error compatible with errors.Is(err, os.ErrNotExist) will be
// returned.
func (c *Client) PrivateFlags(ifi Interface) (*PrivateFlags, error) {
return c.c.PrivateFlags(ifi)
}

// SetPrivateFlags attempts to set the given private flags on the given
// interface. Flags does not need to contain the all flags, those not
// in it are left as-is.
//
// Setting Private Flags requires elevated privileges and if the caller
// does not have permission, an error compatible with errors.Is(err,
// os.ErrPermission) will be returned.
//
// Note that not all flags can be changed in all interface states, some might
// only be settable if the interface is down or are only settable once.
//
// If the requested device does not exist or is not supported by the ethtool
// interface, an error compatible with errors.Is(err, os.ErrNotExist) will be
// returned.
func (c *Client) SetPrivateFlags(p PrivateFlags) error {
return c.c.SetPrivateFlags(p)
}

// Close cleans up the Client's resources.
func (c *Client) Close() error { return c.c.Close() }
132 changes: 131 additions & 1 deletion client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,134 @@ func (wol WakeOnLAN) encode(ae *netlink.AttributeEncoder) {
})
}

// AllPrivateFlags fetches Private Flags for all ethtool-supported links.
func (c *client) AllPrivateFlags() ([]*PrivateFlags, error) {
return c.privateFlags(netlink.Dump, Interface{})
}

// PrivateFlags fetches Private Flags for a single interface.
func (c *client) PrivateFlags(ifi Interface) (*PrivateFlags, error) {
fs, err := c.privateFlags(0, ifi)
if err != nil {
return nil, err
}
if f := len(fs); f != 1 {
panicf("ethtool: unexpected number of PrivateFlags messages for request index: %d, name: %q: %d",
ifi.Index, ifi.Name, f)
}

return fs[0], nil
}

func (c *client) privateFlags(flags netlink.HeaderFlags, ifi Interface) ([]*PrivateFlags, error) {
msgs, err := c.get(
unix.ETHTOOL_A_PRIVFLAGS_HEADER,
unix.ETHTOOL_MSG_PRIVFLAGS_GET,
flags,
ifi,
nil,
)
if err != nil {
return nil, err
}

return parsePrivateFlags(msgs)
}

func (c *client) SetPrivateFlags(pf PrivateFlags) error {
_, err := c.get(
unix.ETHTOOL_A_WOL_HEADER,
unix.ETHTOOL_MSG_PRIVFLAGS_SET,
netlink.Acknowledge,
pf.Interface,
pf.encode,
)
return err
}

// parsePrivateFlags parses PrivateFlag structures from a slice of generic netlink
// messages.
func parsePrivateFlags(msgs []genetlink.Message) ([]*PrivateFlags, error) {
wols := make([]*PrivateFlags, 0, len(msgs))
for _, m := range msgs {
ad, err := netlink.NewAttributeDecoder(m.Data)
if err != nil {
return nil, err
}

var privFlags PrivateFlags
for ad.Next() {
switch ad.Type() {
case unix.ETHTOOL_A_PRIVFLAGS_HEADER:
ad.Nested(parseInterface(&privFlags.Interface))
case unix.ETHTOOL_A_PRIVFLAGS_FLAGS:
ad.Nested(parsePrivateFlagBitset(&privFlags.Flags))
}
}

if err := ad.Err(); err != nil {
return nil, err
}

wols = append(wols, &privFlags)
}

return wols, nil
}

func parsePrivateFlagBitset(p *map[string]bool) func(*netlink.AttributeDecoder) error {
return func(ad *netlink.AttributeDecoder) error {
flags := make(map[string]bool)
for ad.Next() {
switch ad.Type() {
case unix.ETHTOOL_A_BITSET_BITS:
ad.Nested(func(nad *netlink.AttributeDecoder) error {
for nad.Next() {
switch nad.Type() {
case unix.ETHTOOL_A_BITSET_BITS_BIT:
nad.Nested(func(nnad *netlink.AttributeDecoder) error {
var name string
var active bool
for nnad.Next() {
switch nnad.Type() {
case unix.ETHTOOL_A_BITSET_BIT_NAME:
name = nnad.String()
case unix.ETHTOOL_A_BITSET_BIT_VALUE:
active = true
}
}
flags[name] = active
return nnad.Err()
})
}
}
return nad.Err()
})
}
}
*p = flags
return ad.Err()
}
}

// encode packs PrivateFlags data into the appropriate netlink attributes for the
// encoder.
func (pf *PrivateFlags) encode(ae *netlink.AttributeEncoder) {
ae.Nested(unix.ETHTOOL_A_PRIVFLAGS_FLAGS, func(nae *netlink.AttributeEncoder) error {
nae.Nested(unix.ETHTOOL_A_BITSET_BITS, func(nnae *netlink.AttributeEncoder) error {
for name, active := range pf.Flags {
nnae.Nested(unix.ETHTOOL_A_BITSET_BITS_BIT, func(nnnae *netlink.AttributeEncoder) error {
nnnae.String(unix.ETHTOOL_A_BITSET_BIT_NAME, name)
nnnae.Flag(unix.ETHTOOL_A_BITSET_BIT_VALUE, active)
return nil
})
}
return nil
})
return nil
})
}

// get performs a request/response interaction with ethtool netlink.
func (c *client) get(
header uint16,
Expand Down Expand Up @@ -433,7 +561,9 @@ func (c *client) get(
// since the ethtool multicast group notifications require the compact
// format, so we might as well always use it.
if cmd != unix.ETHTOOL_MSG_FEC_SET &&
cmd != unix.ETHTOOL_MSG_WOL_SET {
cmd != unix.ETHTOOL_MSG_WOL_SET &&
cmd != unix.ETHTOOL_MSG_PRIVFLAGS_GET &&
cmd != unix.ETHTOOL_MSG_PRIVFLAGS_SET {
nae.Uint32(unix.ETHTOOL_A_HEADER_FLAGS, unix.ETHTOOL_FLAG_COMPACT_BITSETS)
}

Expand Down
55 changes: 55 additions & 0 deletions client_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ func TestLinuxClientErrors(t *testing.T) {
return err
},
},
{
name: "private flags",
call: func(c *Client, ifi Interface) error {
_, err := c.PrivateFlags(ifi)
return err
},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -968,6 +975,54 @@ func TestFEC(t *testing.T) {
}
}

func TestPrivateFlags(t *testing.T) {
// Reference value captured from ethtool --show-priv-flags eth0
c := testClient(t, clientTest{
HeaderFlags: netlink.Request,
Command: unix.ETHTOOL_MSG_PRIVFLAGS_GET,
EncodedAttributes: []byte("\x10\x00\x01\x80\x09\x00\x02\x00\x65\x74\x68\x30\x00\x00\x00\x00"),
Messages: []genetlink.Message{{
Data: []byte("\x18\x00\x01\x80\x08\x00\x01\x00\x02\x00\x00\x00\x09\x00\x02\x00\x65\x74\x68\x30\x00\x00\x00\x00\xb4\x01\x02\x80\x08\x00\x02\x00\x0d\x00\x00\x00\xa8\x01\x03\x80\x14\x00\x01\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\x4d\x46\x50\x00\x24\x00\x01\x80\x08\x00\x01\x00\x01\x00\x00\x00\x18\x00\x02\x00\x74\x6f\x74\x61\x6c\x2d\x70\x6f\x72\x74\x2d\x73\x68\x75\x74\x64\x6f\x77\x6e\x00\x1c\x00\x01\x80\x08\x00\x01\x00\x02\x00\x00\x00\x10\x00\x02\x00\x4c\x69\x6e\x6b\x50\x6f\x6c\x6c\x69\x6e\x67\x00\x28\x00\x01\x80\x08\x00\x01\x00\x03\x00\x00\x00\x16\x00\x02\x00\x66\x6c\x6f\x77\x2d\x64\x69\x72\x65\x63\x74\x6f\x72\x2d\x61\x74\x72\x00\x00\x00\x04\x00\x03\x00\x1c\x00\x01\x80\x08\x00\x01\x00\x04\x00\x00\x00\x0e\x00\x02\x00\x76\x65\x62\x2d\x73\x74\x61\x74\x73\x00\x00\x00\x20\x00\x01\x80\x08\x00\x01\x00\x05\x00\x00\x00\x14\x00\x02\x00\x68\x77\x2d\x61\x74\x72\x2d\x65\x76\x69\x63\x74\x69\x6f\x6e\x00\x24\x00\x01\x80\x08\x00\x01\x00\x06\x00\x00\x00\x17\x00\x02\x00\x6c\x69\x6e\x6b\x2d\x64\x6f\x77\x6e\x2d\x6f\x6e\x2d\x63\x6c\x6f\x73\x65\x00\x00\x1c\x00\x01\x80\x08\x00\x01\x00\x07\x00\x00\x00\x0e\x00\x02\x00\x6c\x65\x67\x61\x63\x79\x2d\x72\x78\x00\x00\x00\x28\x00\x01\x80\x08\x00\x01\x00\x08\x00\x00\x00\x1b\x00\x02\x00\x64\x69\x73\x61\x62\x6c\x65\x2d\x73\x6f\x75\x72\x63\x65\x2d\x70\x72\x75\x6e\x69\x6e\x67\x00\x00\x20\x00\x01\x80\x08\x00\x01\x00\x09\x00\x00\x00\x14\x00\x02\x00\x64\x69\x73\x61\x62\x6c\x65\x2d\x66\x77\x2d\x6c\x6c\x64\x70\x00\x1c\x00\x01\x80\x08\x00\x01\x00\x0a\x00\x00\x00\x0b\x00\x02\x00\x72\x73\x2d\x66\x65\x63\x00\x00\x04\x00\x03\x00\x20\x00\x01\x80\x08\x00\x01\x00\x0b\x00\x00\x00\x0f\x00\x02\x00\x62\x61\x73\x65\x2d\x72\x2d\x66\x65\x63\x00\x00\x04\x00\x03\x00\x28\x00\x01\x80\x08\x00\x01\x00\x0c\x00\x00\x00\x1c\x00\x02\x00\x76\x66\x2d\x74\x72\x75\x65\x2d\x70\x72\x6f\x6d\x69\x73\x63\x2d\x73\x75\x70\x70\x6f\x72\x74\x00"),
}},
Error: nil,
})
f, err := c.PrivateFlags(Interface{Name: "eth0"})
if err != nil {
t.Fatalf("failed to get private flags: %v", err)
}
if len(f.Flags) != 13 {
t.Errorf("expected 13 flags, got %d", len(f.Flags))
}
if _, ok := f.Flags["disable-fw-lldp"]; !ok {
t.Errorf("expected flag disable-fw-lldp to be present, but it is not")
}
if !f.Flags["rs-fec"] {
t.Error("expected rs-fec flag to be active")
}
}

func TestSetPrivateFlags(t *testing.T) {
// Reference value captured from ethtool --set-priv-flags eth0 disable-fw-lldp on
c := testClient(t, clientTest{
HeaderFlags: netlink.Request | netlink.Acknowledge,
Command: unix.ETHTOOL_MSG_PRIVFLAGS_SET,
EncodedAttributes: []byte("\x10\x00\x01\x80\x09\x00\x02\x00\x65\x74\x68\x30\x00\x00\x00\x00\x24\x00\x02\x80\x20\x00\x03\x80\x1c\x00\x01\x80\x14\x00\x02\x00\x64\x69\x73\x61\x62\x6c\x65\x2d\x66\x77\x2d\x6c\x6c\x64\x70\x00\x04\x00\x03\x00"),
Messages: []genetlink.Message{{
Data: nil,
}},
Error: nil,
})
err := c.SetPrivateFlags(PrivateFlags{
Interface: Interface{Name: "eth0"},
Flags: map[string]bool{
"disable-fw-lldp": true,
},
})
if err != nil {
t.Fatalf("failed to set private flags: %v", err)
}
}

func skipBigEndian(t *testing.T) {
t.Helper()

Expand Down
29 changes: 16 additions & 13 deletions client_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ func (*Error) Is(_ error) bool { return false }

type client struct{}

func newClient() (*client, error) { return nil, errUnsupported }
func (c *client) LinkInfos() ([]*LinkInfo, error) { return nil, errUnsupported }
func (c *client) LinkInfo(_ Interface) (*LinkInfo, error) { return nil, errUnsupported }
func (c *client) LinkModes() ([]*LinkMode, error) { return nil, errUnsupported }
func (c *client) LinkMode(_ Interface) (*LinkMode, error) { return nil, errUnsupported }
func (c *client) LinkStates() ([]*LinkState, error) { return nil, errUnsupported }
func (c *client) LinkState(_ Interface) (*LinkState, error) { return nil, errUnsupported }
func (c *client) WakeOnLANs() ([]*WakeOnLAN, error) { return nil, errUnsupported }
func (c *client) WakeOnLAN(_ Interface) (*WakeOnLAN, error) { return nil, errUnsupported }
func (c *client) SetWakeOnLAN(_ WakeOnLAN) error { return errUnsupported }
func (c *client) FEC(_ Interface) (*FEC, error) { return nil, errUnsupported }
func (c *client) SetFEC(_ FEC) error { return errUnsupported }
func (c *client) Close() error { return errUnsupported }
func newClient() (*client, error) { return nil, errUnsupported }
func (c *client) LinkInfos() ([]*LinkInfo, error) { return nil, errUnsupported }
func (c *client) LinkInfo(_ Interface) (*LinkInfo, error) { return nil, errUnsupported }
func (c *client) LinkModes() ([]*LinkMode, error) { return nil, errUnsupported }
func (c *client) LinkMode(_ Interface) (*LinkMode, error) { return nil, errUnsupported }
func (c *client) LinkStates() ([]*LinkState, error) { return nil, errUnsupported }
func (c *client) LinkState(_ Interface) (*LinkState, error) { return nil, errUnsupported }
func (c *client) WakeOnLANs() ([]*WakeOnLAN, error) { return nil, errUnsupported }
func (c *client) WakeOnLAN(_ Interface) (*WakeOnLAN, error) { return nil, errUnsupported }
func (c *client) SetWakeOnLAN(_ WakeOnLAN) error { return errUnsupported }
func (c *client) FEC(_ Interface) (*FEC, error) { return nil, errUnsupported }
func (c *client) SetFEC(_ FEC) error { return errUnsupported }
func (c *client) AllPrivateFlags() ([]*PrivateFlags, error) { return nil, errUnsupported }
func (c *client) PrivateFlags(_ Interface) (*PrivateFlags, error) { return nil, errUnsupported }
func (c *client) SetPrivateFlags(_ PrivateFlags) error { return errUnsupported }
func (c *client) Close() error { return errUnsupported }

func (f *FEC) Supported() FECModes { return 0 }

Expand Down