Skip to content

Commit

Permalink
ethtool: add support for private flags
Browse files Browse the repository at this point in the history
This adds support for geting and setting private flags which are
driver-specific (and sometimes even model-specific) controls not part of
any generic network settings API.

They are used to toggle low-level features in various NICs.
  • Loading branch information
lorenz committed Nov 22, 2023
1 parent 7823329 commit 6e1264d
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 1 deletion.
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)

Check failure on line 322 in client.go

View workflow job for this annotation

GitHub Actions / build (1.19, macos-latest)

cannot use c.c.PrivateFlags(ifi) (value of type []*PrivateFlags) as type *PrivateFlags in return statement

Check failure on line 322 in client.go

View workflow job for this annotation

GitHub Actions / build (1.19, macos-latest)

too many arguments in call to c.c.PrivateFlags

Check failure on line 322 in client.go

View workflow job for this annotation

GitHub Actions / build (1.20, macos-latest)

cannot use c.c.PrivateFlags(ifi) (value of type []*PrivateFlags) as *PrivateFlags value in return statement

Check failure on line 322 in client.go

View workflow job for this annotation

GitHub Actions / build (1.20, macos-latest)

too many arguments in call to c.c.PrivateFlags
}

// 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() }
133 changes: 132 additions & 1 deletion client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,135 @@ 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.Uint32(unix.ETHTOOL_A_BITSET_SIZE, uint32(len(pf.Flags)))
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 +562,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
3 changes: 3 additions & 0 deletions client_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func (c *client) WakeOnLAN(_ Interface) (*WakeOnLAN, error) { return nil, errUns
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() ([]*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

0 comments on commit 6e1264d

Please sign in to comment.