func parseWakeOnLANModes(m *WOLMode) func(*netlink.AttributeDecoder) error {
	return func(ad *netlink.AttributeDecoder) error {
		values, err := newBitset(ad)
		if err != nil {
			return err
		}
		if l := len(values); l > 1 {
			panicf("ethtool: too many Wake-on-LAN mode uint32s in bitset: %d", l)
		}
		*m = WOLMode(values[0])
		return nil
	}
}
parseWakeOnLANModes decodes an ethtool compact bitset into the input WOLMode.
package ethtool
import (
	"errors"
	"fmt"
	"os"
	"github.com/mdlayher/genetlink"
	"github.com/mdlayher/netlink"
	"golang.org/x/sys/unix"
)
var errBadRequest = errors.New("ethtool: Request must have Index and/or Name set when calling Client methods")
type client struct {
	c         *genetlink.Conn
	family    uint16
	monitorID uint32
}
func newClient() (*client, error) {
	conn, err := genetlink.Dial(nil)
	if err != nil {
		return nil, err
	}
	if err := conn.SetOption(netlink.ExtendedAcknowledge, true); err != nil {
		return nil, err
	}
	c, err := initClient(conn)
	if err != nil {
		_ = conn.Close()
		return nil, err
	}
	return c, nil
}
func initClient(c *genetlink.Conn) (*client, error) {
	f, err := c.GetFamily(unix.ETHTOOL_GENL_NAME)
	if err != nil {
		return nil, err
	}
	var monitorID uint32
	for _, g := range f.Groups {
		if g.Name == unix.ETHTOOL_MCGRP_MONITOR_NAME {
			monitorID = g.ID
			break
		}
	}
	if monitorID == 0 {
		return nil, errors.New("ethtool: could not find monitor multicast group ID")
	}
	return &client{
		c:         c,
		family:    f.ID,
		monitorID: monitorID,
	}, nil
}
func (c *client) Close() error { return c.c.Close() }
func (c *client) LinkInfos() ([]*LinkInfo, error) {
	return c.linkInfo(netlink.Dump, Interface{})
}
func (c *client) LinkInfo(ifi Interface) (*LinkInfo, error) {
	lis, err := c.linkInfo(0, ifi)
	if err != nil {
		return nil, err
	}
	if l := len(lis); l != 1 {
		panicf("ethtool: unexpected number of LinkInfo messages for request index: %d, name: %q: %d",
			ifi.Index, ifi.Name, l)
	}
	return lis[0], nil
}
func (c *client) linkInfo(flags netlink.HeaderFlags, ifi Interface) ([]*LinkInfo, error) {
	msgs, err := c.get(
		unix.ETHTOOL_A_LINKINFO_HEADER,
		unix.ETHTOOL_MSG_LINKINFO_GET,
		flags,
		ifi,
		nil,
	)
	if err != nil {
		return nil, err
	}
	return parseLinkInfo(msgs)
}
func (c *client) LinkModes() ([]*LinkMode, error) {
	return c.linkMode(netlink.Dump, Interface{})
}
func (c *client) LinkMode(ifi Interface) (*LinkMode, error) {
	lms, err := c.linkMode(0, ifi)
	if err != nil {
		return nil, err
	}
	if l := len(lms); l != 1 {
		panicf("ethtool: unexpected number of LinkMode messages for request index: %d, name: %q: %d",
			ifi.Index, ifi.Name, l)
	}
	return lms[0], nil
}
func (c *client) linkMode(flags netlink.HeaderFlags, ifi Interface) ([]*LinkMode, error) {
	msgs, err := c.get(
		unix.ETHTOOL_A_LINKMODES_HEADER,
		unix.ETHTOOL_MSG_LINKMODES_GET,
		flags,
		ifi,
		nil,
	)
	if err != nil {
		return nil, err
	}
	return parseLinkModes(msgs)
}
func (c *client) LinkStates() ([]*LinkState, error) {
	return c.linkState(netlink.Dump, Interface{})
}
func (c *client) LinkState(ifi Interface) (*LinkState, error) {
	lss, err := c.linkState(0, ifi)
	if err != nil {
		return nil, err
	}
	if l := len(lss); l != 1 {
		panicf("ethtool: unexpected number of LinkState messages for request index: %d, name: %q: %d",
			ifi.Index, ifi.Name, l)
	}
	return lss[0], nil
}
func (c *client) linkState(flags netlink.HeaderFlags, ifi Interface) ([]*LinkState, error) {
	msgs, err := c.get(
		unix.ETHTOOL_A_LINKSTATE_HEADER,
		unix.ETHTOOL_MSG_LINKSTATE_GET,
		flags,
		ifi,
		nil,
	)
	if err != nil {
		return nil, err
	}
	return parseLinkState(msgs)
}
func (c *client) WakeOnLANs() ([]*WakeOnLAN, error) {
	return c.wakeOnLAN(netlink.Dump, Interface{})
}
func (c *client) WakeOnLAN(ifi Interface) (*WakeOnLAN, error) {
	wols, err := c.wakeOnLAN(0, ifi)
	if err != nil {
		return nil, err
	}
	if l := len(wols); l != 1 {
		panicf("ethtool: unexpected number of WakeOnLAN messages for request index: %d, name: %q: %d",
			ifi.Index, ifi.Name, l)
	}
	return wols[0], nil
}
func (c *client) SetWakeOnLAN(wol WakeOnLAN) error {
	_, err := c.get(
		unix.ETHTOOL_A_WOL_HEADER,
		unix.ETHTOOL_MSG_WOL_SET,
		netlink.Acknowledge,
		wol.Interface,
		wol.encode,
	)
	return err
}
func (c *client) wakeOnLAN(flags netlink.HeaderFlags, ifi Interface) ([]*WakeOnLAN, error) {
	msgs, err := c.get(
		unix.ETHTOOL_A_WOL_HEADER,
		unix.ETHTOOL_MSG_WOL_GET,
		flags,
		ifi,
		nil,
	)
	if err != nil {
		return nil, err
	}
	return parseWakeOnLAN(msgs)
}
func (wol WakeOnLAN) encode(ae *netlink.AttributeEncoder) {
	ae.Nested(unix.ETHTOOL_A_WOL_MODES, func(nae *netlink.AttributeEncoder) error {
		nae.Uint32(unix.ETHTOOL_A_BITSET_SIZE, 8)
		nae.Uint32(unix.ETHTOOL_A_BITSET_VALUE, uint32(wol.Modes))
		nae.Uint32(unix.ETHTOOL_A_BITSET_MASK, uint32(wol.Modes))
		return nil
	})
}
func (c *client) get(
	header uint16,
	cmd uint8,
	flags netlink.HeaderFlags,
	ifi Interface,
	params func(ae *netlink.AttributeEncoder),
) ([]genetlink.Message, error) {
	if flags&netlink.Dump == 0 && ifi.Index == 0 && ifi.Name == "" {
		return nil, errBadRequest
	}
	ae := netlink.NewAttributeEncoder()
	ae.Nested(header, func(nae *netlink.AttributeEncoder) error {
		if ifi.Index > 0 {
			nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, uint32(ifi.Index))
		}
		if ifi.Name != "" {
			nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, ifi.Name)
		}
		nae.Uint32(unix.ETHTOOL_A_HEADER_FLAGS, unix.ETHTOOL_FLAG_COMPACT_BITSETS)
		return nil
	})
	if params != nil {
		params(ae)
	}
	msgs, err := c.execute(cmd, flags, ae)
	if err != nil {
		var msg string
		if oerr, ok := err.(*netlink.OpError); ok {
			msg = oerr.Message
		}
		return nil, &Error{
			Message: msg,
			Err:     err,
		}
	}
	return msgs, nil
}
func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, ae *netlink.AttributeEncoder) ([]genetlink.Message, error) {
	b, err := ae.Encode()
	if err != nil {
		return nil, err
	}
	return c.c.Execute(
		genetlink.Message{
			Header: genetlink.Header{
				Command: cmd,
				Version: unix.ETHTOOL_GENL_VERSION,
			},
			Data: b,
		},
		c.family,
		netlink.Request|flags,
	)
}
func (e *Error) Is(target error) bool {
	switch target {
	case os.ErrNotExist:
		return errors.Is(e.Err, unix.EOPNOTSUPP) || errors.Is(e.Err, unix.ENODEV)
	case os.ErrPermission:
		return errors.Is(e.Err, unix.EPERM)
	default:
		return false
	}
}
func parseLinkInfo(msgs []genetlink.Message) ([]*LinkInfo, error) {
	lis := make([]*LinkInfo, 0, len(msgs))
	for _, m := range msgs {
		ad, err := netlink.NewAttributeDecoder(m.Data)
		if err != nil {
			return nil, err
		}
		var li LinkInfo
		for ad.Next() {
			switch ad.Type() {
			case unix.ETHTOOL_A_LINKINFO_HEADER:
				ad.Nested(parseInterface(&li.Interface))
			case unix.ETHTOOL_A_LINKINFO_PORT:
				li.Port = Port(ad.Uint8())
			}
		}
		if err := ad.Err(); err != nil {
			return nil, err
		}
		lis = append(lis, &li)
	}
	return lis, nil
}
func parseLinkModes(msgs []genetlink.Message) ([]*LinkMode, error) {
	lms := make([]*LinkMode, 0, len(msgs))
	for _, m := range msgs {
		ad, err := netlink.NewAttributeDecoder(m.Data)
		if err != nil {
			return nil, err
		}
		var lm LinkMode
		for ad.Next() {
			switch ad.Type() {
			case unix.ETHTOOL_A_LINKMODES_HEADER:
				ad.Nested(parseInterface(&lm.Interface))
			case unix.ETHTOOL_A_LINKMODES_OURS:
				ad.Nested(parseAdvertisedLinkModes(&lm.Ours))
			case unix.ETHTOOL_A_LINKMODES_PEER:
				ad.Nested(parseAdvertisedLinkModes(&lm.Peer))
			case unix.ETHTOOL_A_LINKMODES_SPEED:
				lm.SpeedMegabits = int(ad.Uint32())
			case unix.ETHTOOL_A_LINKMODES_DUPLEX:
				lm.Duplex = Duplex(ad.Uint8())
			}
		}
		if err := ad.Err(); err != nil {
			return nil, err
		}
		lms = append(lms, &lm)
	}
	return lms, nil
}
func parseAdvertisedLinkModes(alms *[]AdvertisedLinkMode) func(*netlink.AttributeDecoder) error {
	return func(ad *netlink.AttributeDecoder) error {
		values, err := newBitset(ad)
		if err != nil {
			return err
		}
		for i, v := range values {
			if v == 0 {
				continue
			}
			for j := 0; j < 32; j++ {
				if v&(1<<j) != 0 {
					m := linkModes[(32*i)+j]
					*alms = append(*alms, AdvertisedLinkMode{
						Index: int(m.bit),
						Name:  m.str,
					})
				}
			}
		}
		return nil
	}
}
func parseLinkState(msgs []genetlink.Message) ([]*LinkState, error) {
	lss := make([]*LinkState, 0, len(msgs))
	for _, m := range msgs {
		ad, err := netlink.NewAttributeDecoder(m.Data)
		if err != nil {
			return nil, err
		}
		var ls LinkState
		for ad.Next() {
			switch ad.Type() {
			case unix.ETHTOOL_A_LINKSTATE_HEADER:
				ad.Nested(parseInterface(&ls.Interface))
			case unix.ETHTOOL_A_LINKSTATE_LINK:
				ls.Link = ad.Uint8() != 0
			}
		}
		if err := ad.Err(); err != nil {
			return nil, err
		}
		lss = append(lss, &ls)
	}
	return lss, nil
}
func parseWakeOnLAN(msgs []genetlink.Message) ([]*WakeOnLAN, error) {
	wols := make([]*WakeOnLAN, 0, len(msgs))
	for _, m := range msgs {
		ad, err := netlink.NewAttributeDecoder(m.Data)
		if err != nil {
			return nil, err
		}
		var wol WakeOnLAN
		for ad.Next() {
			switch ad.Type() {
			case unix.ETHTOOL_A_WOL_HEADER:
				ad.Nested(parseInterface(&wol.Interface))
			case unix.ETHTOOL_A_WOL_MODES:
				ad.Nested(parseWakeOnLANModes(&wol.Modes))
			case unix.ETHTOOL_A_WOL_SOPASS:
			}
		}
		if err := ad.Err(); err != nil {
			return nil, err
		}
		wols = append(wols, &wol)
	}
	return wols, nil
}