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 LinkModeUpdate and Client.UpdateLinkMode #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ type LinkMode struct {
Duplex Duplex
}

// LinkModeUpdate contains an update to the link mode information
// for an Ethernet interface.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note something like "Zero-value fields are not sent to the kernel as part of the update.", and that covers the nil and empty slice cases. I'm good with empty slices here; *[]AdvertisedLinkMode would be pretty awkward. I can't think of any valid reason you would try to clear all the advertised link modes via an empty slice anyway.

type LinkModeUpdate struct {
SpeedMegabits *int
Ours, Peer []AdvertisedLinkMode
Duplex *Duplex
}

// A Duplex is the link duplex type for a LinkMode structure.
type Duplex int

Expand Down Expand Up @@ -131,6 +139,13 @@ func (c *Client) LinkMode(ifi Interface) (*LinkMode, error) {
return c.c.LinkMode(ifi)
}

// UpdateLinkMode updates link mode information for the specified Interface.
//
// Specifically, the interface is brought to agreement with the non-nil fields of lmu.
func (c *Client) UpdateLinkMode(ifi Interface, lmu *LinkModeUpdate) error {
return c.c.UpdateLinkMode(ifi, lmu)
}

// LinkState contains link state information for an Ethernet interface.
type LinkState struct {
Interface Interface
Expand Down
57 changes: 57 additions & 0 deletions client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,35 @@ func (c *client) linkMode(flags netlink.HeaderFlags, ifi Interface) ([]*LinkMode
return parseLinkModes(msgs)
}

// UpdateLinkMode updates link mode information for a single ethtool-supported interface.
func (c *client) UpdateLinkMode(ifi Interface, lmu *LinkModeUpdate) error {
_, err := c.get(
unix.ETHTOOL_A_LINKMODES_HEADER,
unix.ETHTOOL_MSG_LINKMODES_SET,
netlink.Acknowledge,
ifi,
lmu.encode,
)
return err
}

// encode packs LinkModeUpdate data into the appropriate netlink attributes for the
// encoder.
func (lmu *LinkModeUpdate) encode(ae *netlink.AttributeEncoder) {
if lmu.SpeedMegabits != nil {
ae.Uint32(unix.ETHTOOL_A_LINKMODES_SPEED, uint32(*lmu.SpeedMegabits))
}
if lmu.Ours != nil {
ae.Nested(unix.ETHTOOL_A_LINKMODES_OURS, packALMs(lmu.Ours))
}
if lmu.Peer != nil {
ae.Nested(unix.ETHTOOL_A_LINKMODES_PEER, packALMs(lmu.Peer))
}
if lmu.Duplex != nil {
ae.Uint8(unix.ETHTOOL_A_LINKMODES_DUPLEX, uint8(*lmu.Duplex))
}
}

// LinkStates fetches link state data for all ethtool-supported links.
func (c *client) LinkStates() ([]*LinkState, error) {
return c.linkState(netlink.Dump, Interface{})
Expand Down Expand Up @@ -562,6 +591,34 @@ func parseInterface(ifi *Interface) func(*netlink.AttributeDecoder) error {
}
}

// packALMBitset encodes given AdvertisedLinkModes as a compact bitset.
func packALMBitset(alms []AdvertisedLinkMode) func() ([]byte, error) {
return func() ([]byte, error) {
// Calculate the number of words necessary for the bitset, then
// multiply by 4 for bytes.
b := make([]byte, ((len(linkModes)+31)/32)*4)

for _, alm := range alms {
byteIndex := alm.Index / 8
bitIndex := alm.Index % 8
b[byteIndex] |= 1 << bitIndex
}

return b, nil
}
}

// packALMs encodes given AdvertisedLinkModes.
func packALMs(alms []AdvertisedLinkMode) func(*netlink.AttributeEncoder) error {
return func(nae *netlink.AttributeEncoder) error {
fn := packALMBitset(alms)
nae.Uint32(unix.ETHTOOL_A_BITSET_SIZE, uint32(len(alms)))
nae.Do(unix.ETHTOOL_A_BITSET_VALUE, fn)
nae.Do(unix.ETHTOOL_A_BITSET_MASK, fn)
return nil
}
}

func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}
30 changes: 2 additions & 28 deletions client_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,18 +758,8 @@ func encodeLinkMode(t *testing.T, lm LinkMode) genetlink.Message {

ae.Uint32(unix.ETHTOOL_A_LINKMODES_SPEED, uint32(lm.SpeedMegabits))

packALMs := func(typ uint16, alms []AdvertisedLinkMode) {
ae.Nested(typ, func(nae *netlink.AttributeEncoder) error {
fn := packALMBitset(alms)
nae.Uint32(unix.ETHTOOL_A_BITSET_SIZE, uint32(len(linkModes)))
nae.Do(unix.ETHTOOL_A_BITSET_VALUE, fn)
nae.Do(unix.ETHTOOL_A_BITSET_MASK, fn)
return nil
})
}

packALMs(unix.ETHTOOL_A_LINKMODES_OURS, lm.Ours)
packALMs(unix.ETHTOOL_A_LINKMODES_PEER, lm.Peer)
ae.Nested(unix.ETHTOOL_A_LINKMODES_OURS, packALMs(lm.Ours))
ae.Nested(unix.ETHTOOL_A_LINKMODES_PEER, packALMs(lm.Peer))

ae.Uint8(unix.ETHTOOL_A_LINKMODES_DUPLEX, uint8(lm.Duplex))
}),
Expand Down Expand Up @@ -814,22 +804,6 @@ func encodeWOL(t *testing.T, wol WakeOnLAN) genetlink.Message {
}
}

func packALMBitset(alms []AdvertisedLinkMode) func() ([]byte, error) {
return func() ([]byte, error) {
// Calculate the number of words necessary for the bitset, then
// multiply by 4 for bytes.
b := make([]byte, ((len(linkModes)+31)/32)*4)

for _, alm := range alms {
byteIndex := alm.Index / 8
bitIndex := alm.Index % 8
b[byteIndex] |= 1 << bitIndex
}

return b, nil
}
}

func encode(t *testing.T, fn func(ae *netlink.AttributeEncoder)) []byte {
t.Helper()

Expand Down