diff --git a/client.go b/client.go index 73c9c4b..6129d12 100644 --- a/client.go +++ b/client.go @@ -99,6 +99,14 @@ type LinkMode struct { Duplex Duplex } +// LinkModeUpdate contains an update to the link mode information +// for an Ethernet interface. +type LinkModeUpdate struct { + SpeedMegabits *int + Ours, Peer []AdvertisedLinkMode + Duplex *Duplex +} + // A Duplex is the link duplex type for a LinkMode structure. type Duplex int @@ -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 diff --git a/client_linux.go b/client_linux.go index cd2f75e..84e5cd5 100644 --- a/client_linux.go +++ b/client_linux.go @@ -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{}) @@ -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...)) } diff --git a/client_linux_test.go b/client_linux_test.go index 0b57227..4282315 100644 --- a/client_linux_test.go +++ b/client_linux_test.go @@ -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)) }), @@ -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()