pax_global_header00006660000000000000000000000064146702260700014516gustar00rootroot0000000000000052 comment=48f9949509f36eb4f9431f7f531d57c446691fb0 golang-github-mdlayher-ethtool-0.2.0/000077500000000000000000000000001467022607000175235ustar00rootroot00000000000000golang-github-mdlayher-ethtool-0.2.0/.github/000077500000000000000000000000001467022607000210635ustar00rootroot00000000000000golang-github-mdlayher-ethtool-0.2.0/.github/dependabot.yml000066400000000000000000000001561467022607000237150ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" golang-github-mdlayher-ethtool-0.2.0/.github/workflows/000077500000000000000000000000001467022607000231205ustar00rootroot00000000000000golang-github-mdlayher-ethtool-0.2.0/.github/workflows/static-analysis.yml000066400000000000000000000013431467022607000267540ustar00rootroot00000000000000name: Static Analysis on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: matrix: go-version: ["1.19","1.20"] runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@latest - name: Print staticcheck version run: staticcheck -version - name: Run staticcheck run: staticcheck ./... - name: Run go vet run: go vet ./... golang-github-mdlayher-ethtool-0.2.0/.github/workflows/test.yml000066400000000000000000000010641467022607000246230ustar00rootroot00000000000000name: Test on: push: branches: - "*" pull_request: branches: - "*" jobs: build: strategy: fail-fast: false matrix: go-version: ["1.19","1.20"] os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Run tests run: go test -race ./... golang-github-mdlayher-ethtool-0.2.0/LICENSE.md000066400000000000000000000020631467022607000211300ustar00rootroot00000000000000# MIT License Copyright (C) 2021-2022 Matt Layher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-mdlayher-ethtool-0.2.0/README.md000066400000000000000000000011041467022607000207760ustar00rootroot00000000000000# ethtool [![Test Status](https://github.com/mdlayher/ethtool/workflows/Test/badge.svg)](https://github.com/mdlayher/ethtool/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/ethtool.svg)](https://pkg.go.dev/github.com/mdlayher/ethtool) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/ethtool)](https://goreportcard.com/report/github.com/mdlayher/ethtool) Package `ethtool` allows control of the Linux ethtool generic netlink interface. For more information, see: https://www.kernel.org/doc/html/latest/networking/ethtool-netlink.html. golang-github-mdlayher-ethtool-0.2.0/bitset_linux.go000066400000000000000000000043601467022607000225660ustar00rootroot00000000000000//go:build linux // +build linux package ethtool import ( "fmt" "github.com/mdlayher/netlink" "github.com/mdlayher/netlink/nlenc" "golang.org/x/sys/unix" ) // A bitset is a compact bitset used by ethtool netlink. type bitset []uint32 // newBitset creates a bitset from a netlink attribute decoder by parsing the // various fields and applying a mask to the set if needed. func newBitset(ad *netlink.AttributeDecoder) (bitset, error) { // Bitsets are represented as a slice of contiguous uint32 which each // contain bits. By default, the mask bitset is applied to values unless // we explicitly find the NOMASK flag. var ( values, mask bitset doMask = true ) for ad.Next() { switch ad.Type() { case unix.ETHTOOL_A_BITSET_NOMASK: doMask = false case unix.ETHTOOL_A_BITSET_SIZE: // Convert number of bits to number of bytes, rounded up to the // nearest 32 bits for a uint32 boundary. n := (ad.Uint32() + 31) / 32 values = make(bitset, n) if doMask { mask = make(bitset, n) } case unix.ETHTOOL_A_BITSET_VALUE: ad.Do(values.decode) case unix.ETHTOOL_A_BITSET_MASK: ad.Do(mask.decode) } } // Do a quick check for errors before making use of the bitsets. Normally // this will be called in a nested attribute decoder context and we could // skip this, but we don't want to return an invalid bitset. if err := ad.Err(); err != nil { return nil, err } // Mask by default unless the caller told us not to. if doMask { for i := 0; i < len(values); i++ { values[i] &= mask[i] } } return values, nil } // decode returns a function which parses a compact bitset into a preallocated // bitset. The bitset must be preallocated with the appropriate length and // capacity for the length of the input data. func (bs *bitset) decode(b []byte) error { if len(b)/4 != len(*bs) { return fmt.Errorf("ethtool: cannot store %d bytes in bitset with length %d", len(b), len(*bs)) } for i := 0; i < len(*bs); i++ { (*bs)[i] = nlenc.Uint32(b[i*4 : (i*4)+4]) } return nil } // test is like the ethnl_bitmap32_test_bit() function in the Linux kernel: it // reports whether the bit with the specified index is set in the bitset. func (bs *bitset) test(idx int) bool { return (*bs)[idx/32]&(1<<(idx%32)) != 0 } golang-github-mdlayher-ethtool-0.2.0/client.go000066400000000000000000000232731467022607000213370ustar00rootroot00000000000000package ethtool import ( "fmt" ) //go:generate stringer -type=Duplex,Port -output=string.go //go:generate go run mklinkmodes.go var ( _ error = &Error{} // Ensure compatibility with Go 1.13+ errors package. _ interface{ Unwrap() error } = &Error{} ) // An Error is an error value produced by the kernel due to a bad ethtool // netlink request. Typically the Err will be of type *netlink.OpError. type Error struct { Message string Err error } // Error implements error. func (e *Error) Error() string { // This typically wraps a *netlink.OpError which will contain the error // string anyway, so just return the inner error's string. return e.Err.Error() } // Unwrap unwraps the internal Err field for use with errors.Unwrap. func (e *Error) Unwrap() error { return e.Err } // A Client can manipulate the ethtool netlink interface. type Client struct { // The operating system-specific client. c *client } // New creates a Client which can issue ethtool commands. func New() (*Client, error) { c, err := newClient() if err != nil { return nil, err } return &Client{c: c}, nil } // An Interface is an ethtool netlink Ethernet interface. Interfaces are used to // identify an Ethernet interface being queried by its index and/or name. type Interface struct { // Callers may choose to set either Index, Name, or both fields. Note that // if both are set, the kernel will verify that both Index and Name are // associated with the same interface. If they are not, an error will be // returned. Index int Name string } // LinkInfo contains link settings for an Ethernet interface. type LinkInfo struct { Interface Interface Port Port } // A Port is the port type for a LinkInfo structure. type Port int // Possible Port type values. const ( TwistedPair Port = 0x00 AUI Port = 0x01 MII Port = 0x02 Fibre Port = 0x03 BNC Port = 0x04 DirectAttach Port = 0x05 None Port = 0xef Other Port = 0xff ) // LinkInfos fetches LinkInfo structures for each ethtool-supported interface // on this system. func (c *Client) LinkInfos() ([]*LinkInfo, error) { return c.c.LinkInfos() } // LinkInfo fetches LinkInfo for the specified Interface. // // 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) LinkInfo(ifi Interface) (*LinkInfo, error) { return c.c.LinkInfo(ifi) } // LinkMode contains link mode information for an Ethernet interface. type LinkMode struct { Interface Interface SpeedMegabits int Ours, Peer []AdvertisedLinkMode Duplex Duplex } // A Duplex is the link duplex type for a LinkMode structure. type Duplex int // Possible Duplex type values. const ( Half Duplex = 0x00 Full Duplex = 0x01 Unknown Duplex = 0xff ) // An AdvertisedLinkMode is a link mode that an interface advertises it is // capable of using. type AdvertisedLinkMode struct { Index int Name string } // LinkModes fetches LinkMode structures for each ethtool-supported interface // on this system. func (c *Client) LinkModes() ([]*LinkMode, error) { return c.c.LinkModes() } // LinkMode fetches LinkMode data for the specified Interface. // // 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) LinkMode(ifi Interface) (*LinkMode, error) { return c.c.LinkMode(ifi) } // LinkState contains link state information for an Ethernet interface. type LinkState struct { Interface Interface Link bool } // LinkStates fetches LinkState structures for each ethtool-supported interface // on this system. func (c *Client) LinkStates() ([]*LinkState, error) { return c.c.LinkStates() } // LinkState fetches LinkState data for the specified Interface. // // 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) LinkState(ifi Interface) (*LinkState, error) { return c.c.LinkState(ifi) } // FEC fetches the forward error correction (FEC) setting for the specified // Interface. func (c *Client) FEC(ifi Interface) (*FEC, error) { return c.c.FEC(ifi) } // SetFEC sets the forward error correction (FEC) parameters for the Interface // in fec. // // Setting FEC parameters requires elevated privileges and if the caller // does not have permission, an error compatible with errors.Is(err, // os.ErrPermission) will be returned. // // 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) SetFEC(fec FEC) error { return c.c.SetFEC(fec) } // A FEC contains the forward error correction (FEC) parameters for an // interface. type FEC struct { Interface Interface Modes FECModes Active FECMode Auto bool } // A FECMode is a FEC mode bit value (single element bitmask) specifying the // active mode of an interface. type FECMode int // A FECModes is a FEC mode bitmask of mode(s) supported by an interface. type FECModes FECMode // A WakeOnLAN contains the Wake-on-LAN parameters for an interface. type WakeOnLAN struct { Interface Interface Modes WOLMode } // A WOLMode is a Wake-on-LAN mode bitmask of mode(s) supported by an interface. type WOLMode int // Possible Wake-on-LAN mode bit flags. const ( PHY WOLMode = 1 << 0 Unicast WOLMode = 1 << 1 Multicast WOLMode = 1 << 2 Broadcast WOLMode = 1 << 3 ARP WOLMode = 1 << 4 Magic WOLMode = 1 << 5 MagicSecure WOLMode = 1 << 6 Filter WOLMode = 1 << 7 ) // String returns the string representation of a WOLMode bitmask. func (m WOLMode) String() string { names := []string{ "PHY", "Unicast", "Multicast", "Broadcast", "ARP", "Magic", "MagicSecure", "Filter", } var s string left := uint(m) for i, name := range names { if m&(1< 0 { if s != "" { s += "|" } s += fmt.Sprintf("%#x", left) } return s } // WakeOnLANs fetches WakeOnLAN information for each ethtool-supported interface // on this system. func (c *Client) WakeOnLANs() ([]*WakeOnLAN, error) { return c.c.WakeOnLANs() } // WakeOnLAN fetches WakeOnLAN parameters for the specified Interface. // // Fetching Wake-on-LAN information requires elevated privileges and if the // caller does not have permission, an error compatible with errors.Is(err, // os.ErrPermission) will be returned. // // 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) WakeOnLAN(ifi Interface) (*WakeOnLAN, error) { return c.c.WakeOnLAN(ifi) } // SetWakeOnLAN sets the WakeOnLAN parameters for the Interface in wol. // // Setting Wake-on-LAN parameters requires elevated privileges and if the caller // does not have permission, an error compatible with errors.Is(err, // os.ErrPermission) will be returned. // // 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) 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() } golang-github-mdlayher-ethtool-0.2.0/client_linux.go000066400000000000000000000571611467022607000225610ustar00rootroot00000000000000//go:build linux // +build linux package ethtool import ( "errors" "fmt" "os" "strings" "github.com/mdlayher/genetlink" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) // errBadRequest indicates an invalid Request from the caller. var errBadRequest = errors.New("ethtool: Request must have Index and/or Name set when calling Client methods") // A client is the Linux implementation backing a Client. type client struct { c *genetlink.Conn family uint16 monitorID uint32 } // Note that some Client methods may panic if the kernel returns an unexpected // number of netlink messages when only one is expected. This means that a // fundamental request invariant is broken and we can't provide anything of use // to the caller, so a panic seems reasonable. // newClient opens a generic netlink connection to the ethtool family. func newClient() (*client, error) { // ethtool is a reasonably new genetlink family and anyone using its API // should support the Strict socket options set. conn, err := genetlink.Dial(&netlink.Config{Strict: true}) if err != nil { return nil, err } c, err := initClient(conn) if err != nil { _ = conn.Close() return nil, err } return c, nil } // initClient is the internal client constructor used in some tests. func initClient(c *genetlink.Conn) (*client, error) { f, err := c.GetFamily(unix.ETHTOOL_GENL_NAME) if err != nil { return nil, err } // Find the group ID for the monitor group in case we need it later. 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 } // Close closes the underlying generic netlink connection. func (c *client) Close() error { return c.c.Close() } // LinkInfos fetches information about all ethtool-supported links. func (c *client) LinkInfos() ([]*LinkInfo, error) { return c.linkInfo(netlink.Dump, Interface{}) } // LinkInfo fetches information about a single ethtool-supported link. 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 } // linkInfo is the shared logic for Client.LinkInfo(s). 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) } // LinkModes fetches modes for all ethtool-supported links. func (c *client) LinkModes() ([]*LinkMode, error) { return c.linkMode(netlink.Dump, Interface{}) } // LinkMode fetches information about a single ethtool-supported link's modes. 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 } // linkMode is the shared logic for Client.LinkMode(s). 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) } // LinkStates fetches link state data for all ethtool-supported links. func (c *client) LinkStates() ([]*LinkState, error) { return c.linkState(netlink.Dump, Interface{}) } // LinkState fetches link state data for a single ethtool-supported link. 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 } // linkState is the shared logic for Client.LinkState(s). 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) } // FEC fetches the forward error correction (FEC) setting for a single // ethtool-supported link. func (c *client) FEC(ifi Interface) (*FEC, error) { fecs, err := c.fec(0, ifi) if err != nil { return nil, err } if l := len(fecs); l != 1 { panicf("ethtool: unexpected number of FEC messages for request index: %d, name: %q: %d", ifi.Index, ifi.Name, l) } return fecs[0], nil } // fec is the shared logic for Client.FEC(s). func (c *client) fec(flags netlink.HeaderFlags, ifi Interface) ([]*FEC, error) { msgs, err := c.get( _ETHTOOL_A_FEC_HEADER, unix.ETHTOOL_MSG_FEC_GET, flags, ifi, nil, ) if err != nil { return nil, err } return parseFEC(msgs) } // SetFEC configures forward error correction (FEC) parameters for a single // ethtool-supported interface. func (c *client) SetFEC(fec FEC) error { _, err := c.get( _ETHTOOL_A_FEC_HEADER, unix.ETHTOOL_MSG_FEC_SET, netlink.Acknowledge, fec.Interface, fec.encode, ) return err } // encode packs FEC data into the appropriate netlink attributes for the // encoder. func (fec FEC) encode(ae *netlink.AttributeEncoder) { var bits []string if fec.Modes&unix.ETHTOOL_FEC_OFF != 0 { bits = append(bits, "None") } if fec.Modes&unix.ETHTOOL_FEC_RS != 0 { bits = append(bits, "RS") } if fec.Modes&unix.ETHTOOL_FEC_BASER != 0 { bits = append(bits, "BASER") } if fec.Modes&unix.ETHTOOL_FEC_LLRS != 0 { bits = append(bits, "LLRS") } ae.Nested(_ETHTOOL_A_FEC_MODES, func(nae *netlink.AttributeEncoder) error { // Overwrite the bits instead of merging them. nae.Flag(unix.ETHTOOL_A_BITSET_NOMASK, true) nae.Nested(unix.ETHTOOL_A_BITSET_BITS, func(nae *netlink.AttributeEncoder) error { for _, bit := range bits { nae.Nested(unix.ETHTOOL_A_BITSET_BITS_BIT, func(nae *netlink.AttributeEncoder) error { nae.String(unix.ETHTOOL_A_BITSET_BIT_NAME, bit) return nil }) } return nil }) return nil }) var auto uint8 if fec.Auto { auto = 1 } ae.Uint8(_ETHTOOL_A_FEC_AUTO, auto) } // Supported returns the supported/configured FEC modes. Some drivers report // supported, others configured. See // https://kernel.googlesource.com/pub/scm/network/ethtool/ethtool/+/2b3ddcb35357ae34ed0a6ae2bb006dcdaec353a9 func (f *FEC) Supported() FECModes { result := f.Modes if f.Auto { result |= unix.ETHTOOL_FEC_AUTO } return result } // String implements fmt.Stringer. func (f FECMode) String() string { switch f { case unix.ETHTOOL_FEC_AUTO: return "Auto" case unix.ETHTOOL_FEC_BASER: return "BaseR" case unix.ETHTOOL_FEC_LLRS: return "LLRS" case unix.ETHTOOL_FEC_NONE: return "Off" case unix.ETHTOOL_FEC_OFF: return "Off" case unix.ETHTOOL_FEC_RS: return "RS" default: return "" } } // String implements fmt.Stringer. func (f FECModes) String() string { var modes []string if f&unix.ETHTOOL_FEC_AUTO > 0 { modes = append(modes, "Auto") } if f&unix.ETHTOOL_FEC_BASER > 0 { modes = append(modes, "BaseR") } if f&unix.ETHTOOL_FEC_LLRS > 0 { modes = append(modes, "LLRS") } if f&unix.ETHTOOL_FEC_NONE > 0 { modes = append(modes, "Off") } if f&unix.ETHTOOL_FEC_OFF > 0 { modes = append(modes, "Off") } if f&unix.ETHTOOL_FEC_RS > 0 { modes = append(modes, "RS") } return strings.Join(modes, " ") } // WakeOnLANs fetches Wake-on-LAN information for all ethtool-supported links. func (c *client) WakeOnLANs() ([]*WakeOnLAN, error) { return c.wakeOnLAN(netlink.Dump, Interface{}) } // WakeOnLAN fetches Wake-on-LAN information for a single ethtool-supported // 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 } // SetWakeOnLAN configures Wake-on-LAN parameters for a single ethtool-supported // interface. 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 } // wakeOnLAN is the shared logic for Client.WakeOnLAN(s). 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) } // encode packs WakeOnLAN data into the appropriate netlink attributes for the // encoder. func (wol WakeOnLAN) encode(ae *netlink.AttributeEncoder) { ae.Nested(unix.ETHTOOL_A_WOL_MODES, func(nae *netlink.AttributeEncoder) error { // TODO(mdlayher): ensure this stays in sync if new modes are added! nae.Uint32(unix.ETHTOOL_A_BITSET_SIZE, 8) // Note that we are cheating a bit here by directly passing a // uint32, but this is okay because there are less than 32 bits // for the WOL modes and therefore the bitset is just the native // endian representation of the modes bitmask. nae.Uint32(unix.ETHTOOL_A_BITSET_VALUE, uint32(wol.Modes)) nae.Uint32(unix.ETHTOOL_A_BITSET_MASK, uint32(wol.Modes)) return nil }) } // 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, cmd uint8, flags netlink.HeaderFlags, ifi Interface, // May be nil; used to apply optional parameters. params func(ae *netlink.AttributeEncoder), ) ([]genetlink.Message, error) { if flags&netlink.Dump == 0 && ifi.Index == 0 && ifi.Name == "" { // The caller is not requesting to dump information for multiple // interfaces and thus has to specify some identifier or the kernel will // EINVAL on this path. return nil, errBadRequest } // TODO(mdlayher): make this faster by potentially precomputing the byte // slice of packed netlink attributes and then modifying the index value at // the appropriate byte slice index. ae := netlink.NewAttributeEncoder() ae.Nested(header, func(nae *netlink.AttributeEncoder) error { // When fetching by index or name, one or both will be non-zero. // Otherwise we leave the header empty to dump all the links. // // Note that if the client happens to pass an incompatible non-zero // index/name pair, the kernel will return an error and we'll // immediately send that back. 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) } // Unconditionally add the compact bitsets flag to all query commands // 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_PRIVFLAGS_GET && cmd != unix.ETHTOOL_MSG_PRIVFLAGS_SET { nae.Uint32(unix.ETHTOOL_A_HEADER_FLAGS, unix.ETHTOOL_FLAG_COMPACT_BITSETS) } return nil }) if params != nil { // Optionally apply more parameters to the attribute encoder. params(ae) } // Note: don't send netlink.Acknowledge or we get an extra message back from // the kernel which doesn't seem useful as of now. msgs, err := c.execute(cmd, flags, ae) if err != nil { // Unpack the extended acknowledgement error message if possible so the // caller doesn't have to unpack it themselves. var msg string if oerr, ok := err.(*netlink.OpError); ok { msg = oerr.Message } return nil, &Error{ Message: msg, Err: err, } } return msgs, nil } // execute executes the specified command with additional header flags and input // netlink request attributes. The netlink.Request header flag is automatically // set. 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, }, // Always pass the genetlink family ID and request flag. c.family, netlink.Request|flags, ) } // Is enables Error comparison with sentinel errors that are part of the // Client's API contract such as os.ErrNotExist and os.ErrPermission. func (e *Error) Is(target error) bool { switch target { case os.ErrNotExist: // The queried interface is not supported by the ethtool APIs // (EOPNOTSUPP) or does not exist at all (ENODEV). return errors.Is(e.Err, unix.EOPNOTSUPP) || errors.Is(e.Err, unix.ENODEV) case os.ErrPermission: // The caller lacks permission to perform an operation. return errors.Is(e.Err, unix.EPERM) default: return false } } // parseLinkInfo parses LinkInfo structures from a slice of generic netlink // messages. 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 } // parseLinkModes parses LinkMode structures from a slice of generic netlink // messages. 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 } // parseAdvertisedLinkModes decodes an ethtool compact bitset into the input // slice of AdvertisedLinkModes. 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 { // No bits set, don't bother checking. continue } // Test each bit to find which ones are set, and use that to look up // the proper index in linkModes (accounting for the offset of 32 // for each value in the array) so we can find the correct link mode // to attach. Note that the lookup assumes that there will never be // any skipped bits in the linkModes table. // // Thanks 0x0f10, c_h_lunde, TheCi, and Wacholderbaer from Twitch // chat for saving me from myself! for j := 0; j < 32; j++ { if v&(1< 0 case _ETHTOOL_A_FEC_ACTIVE: switch b := ad.Uint32(); b { case unix.ETHTOOL_LINK_MODE_FEC_NONE_BIT: fec.Active = unix.ETHTOOL_FEC_OFF case unix.ETHTOOL_LINK_MODE_FEC_RS_BIT: fec.Active = unix.ETHTOOL_FEC_RS case unix.ETHTOOL_LINK_MODE_FEC_BASER_BIT: fec.Active = unix.ETHTOOL_FEC_BASER case unix.ETHTOOL_LINK_MODE_FEC_LLRS_BIT: fec.Active = unix.ETHTOOL_FEC_LLRS default: return nil, fmt.Errorf("unsupported FEC link mode bit: %d", b) } } } if err := ad.Err(); err != nil { return nil, err } fecs = append(fecs, &fec) } return fecs, nil } // parseFECModes decodes an ethtool compact bitset into the input FECModes. func parseFECModes(m *FECModes) func(*netlink.AttributeDecoder) error { return func(ad *netlink.AttributeDecoder) error { values, err := newBitset(ad) if err != nil { return err } *m = 0 if values.test(unix.ETHTOOL_LINK_MODE_FEC_NONE_BIT) { *m |= unix.ETHTOOL_FEC_OFF } if values.test(unix.ETHTOOL_LINK_MODE_FEC_RS_BIT) { *m |= unix.ETHTOOL_FEC_RS } if values.test(unix.ETHTOOL_LINK_MODE_FEC_BASER_BIT) { *m |= unix.ETHTOOL_FEC_BASER } if values.test(unix.ETHTOOL_LINK_MODE_FEC_LLRS_BIT) { *m |= unix.ETHTOOL_FEC_LLRS } return nil } } // parseWakeOnLAN parses WakeOnLAN structures from a slice of generic netlink // messages. 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: // TODO(mdlayher): parse the password if we can find a NIC that // supports it, probably using ad.Bytes. } } if err := ad.Err(); err != nil { return nil, err } wols = append(wols, &wol) } return wols, nil } // parseWakeOnLANModes decodes an ethtool compact bitset into the input WOLMode. func parseWakeOnLANModes(m *WOLMode) func(*netlink.AttributeDecoder) error { return func(ad *netlink.AttributeDecoder) error { values, err := newBitset(ad) if err != nil { return err } // Assume the kernel will not sprout 25 more Wake-on-LAN modes and just // inspect the first uint32 so we can populate the WOLMode bitmask for // the caller. 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 } } // parseInterface decodes information from a response header into the input // Interface. func parseInterface(ifi *Interface) func(*netlink.AttributeDecoder) error { return func(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.ETHTOOL_A_HEADER_DEV_INDEX: (*ifi).Index = int(ad.Uint32()) case unix.ETHTOOL_A_HEADER_DEV_NAME: (*ifi).Name = ad.String() } } return nil } } func panicf(format string, a ...interface{}) { panic(fmt.Sprintf(format, a...)) } golang-github-mdlayher-ethtool-0.2.0/client_linux_integration_test.go000066400000000000000000000036241467022607000262160ustar00rootroot00000000000000//go:build linux // +build linux package ethtool_test import ( "errors" "os" "testing" "github.com/google/go-cmp/cmp" "github.com/mdlayher/ethtool" "github.com/mdlayher/netlink" ) func TestIntegrationClientLinkInfos(t *testing.T) { // Make sure the basic netlink plumbing is in place without requiring the // use of any privileged operations. c, err := ethtool.New() if err != nil { if errors.Is(err, os.ErrNotExist) { t.Skip("skipping, ethtool genetlink not found") } t.Fatalf("failed to open client: %v", err) } defer c.Close() lis, err := c.LinkInfos() if err != nil { if errors.Is(err, os.ErrNotExist) { t.Skipf("skipping, operation not supported: %v", err) } t.Fatalf("failed to fetch link infos: %v", err) } for _, li := range lis { t.Logf("%d: %q: %s", li.Interface.Index, li.Interface.Name, li.Port) } } func TestIntegrationClientNetlinkStrict(t *testing.T) { c, err := ethtool.New() if err != nil { if errors.Is(err, os.ErrNotExist) { t.Skip("skipping, ethtool genetlink not found") } t.Fatalf("failed to open client: %v", err) } _, err = c.LinkInfo(ethtool.Interface{Name: "notexist0"}) if err == nil { t.Fatal("expected an error, but none occurred") } var eerr *ethtool.Error if !errors.As(err, &eerr) { t.Fatalf("expected outer wrapped *ethtool.Error, but got: %T", err) } // The underlying error is *netlink.OpError but for the purposes of this // test, all we really care about is that the kernel produced an error // message regarding a non-existent device. This means that the netlink // Strict option plumbing is working. var nerr *netlink.OpError if !errors.As(eerr, &nerr) { t.Fatalf("expected inner wrapped *netlink.OpError, but got: %T", eerr) } eerr.Err = nil want := ðtool.Error{Message: "no device matches name"} if diff := cmp.Diff(want, eerr); diff != "" { t.Fatalf("unexpected *ethtool.Error (-want +got):\n%s", diff) } } golang-github-mdlayher-ethtool-0.2.0/client_linux_test.go000066400000000000000000000630141467022607000236120ustar00rootroot00000000000000//go:build linux // +build linux package ethtool import ( "encoding/binary" "os" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/josharian/native" "github.com/mdlayher/genetlink" "github.com/mdlayher/genetlink/genltest" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" ) func TestLinuxClientErrors(t *testing.T) { tests := []struct { name string ifi Interface errno int err error }{ { name: "empty request", err: errBadRequest, }, { name: "ENODEV", ifi: Interface{Index: 1}, errno: int(unix.ENODEV), err: os.ErrNotExist, }, { name: "EOPNOTSUPP", ifi: Interface{Index: 1}, errno: int(unix.EOPNOTSUPP), err: os.ErrNotExist, }, } fns := []struct { name string call func(c *Client, ifi Interface) error }{ { name: "link info", call: func(c *Client, ifi Interface) error { _, err := c.LinkInfo(ifi) return err }, }, { name: "link mode", call: func(c *Client, ifi Interface) error { _, err := c.LinkMode(ifi) return err }, }, { name: "link state", call: func(c *Client, ifi Interface) error { _, err := c.LinkState(ifi) return err }, }, { name: "wake on lan", call: func(c *Client, ifi Interface) error { _, err := c.WakeOnLAN(ifi) return err }, }, { name: "private flags", call: func(c *Client, ifi Interface) error { _, err := c.PrivateFlags(ifi) return err }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { for _, fn := range fns { t.Run(fn.name, func(t *testing.T) { c := baseClient(t, func(_ genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) { return nil, genltest.Error(tt.errno) }) defer c.Close() err := fn.call(c, tt.ifi) if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" { t.Fatalf("unexpected error (-want +got):\n%s", diff) } }) } }) } } func TestLinuxClientLinkInfos(t *testing.T) { tests := []struct { name string lis []*LinkInfo }{ { name: "OK", lis: []*LinkInfo{ { Interface: Interface{ Index: 1, Name: "eth0", }, Port: TwistedPair, }, { Interface: Interface{ Index: 2, Name: "eth1", }, Port: DirectAttach, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Generate the expected response messages using the wanted list // of LinkInfo structures. var msgs []genetlink.Message for _, li := range tt.lis { msgs = append(msgs, encodeLinkInfo(t, *li)) } c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Dump, Command: unix.ETHTOOL_MSG_LINKINFO_GET, Attributes: requestHeader(unix.ETHTOOL_A_LINKINFO_HEADER), Messages: msgs, }) lis, err := c.LinkInfos() if err != nil { t.Fatalf("failed to get link info: %v", err) } if diff := cmp.Diff(tt.lis, lis); diff != "" { t.Fatalf("unexpected link info (-want +got):\n%s", diff) } }) } } func TestLinuxClientLinkInfo(t *testing.T) { tests := []struct { name string ifi Interface attrs func(ae *netlink.AttributeEncoder) li *LinkInfo }{ { name: "by index", ifi: Interface{Index: 1}, attrs: requestIndex(unix.ETHTOOL_A_LINKINFO_HEADER, true), li: &LinkInfo{ Interface: Interface{ Index: 1, Name: "eth0", }, Port: TwistedPair, }, }, { name: "by name", ifi: Interface{Name: "eth1"}, attrs: requestName(unix.ETHTOOL_A_LINKINFO_HEADER), li: &LinkInfo{ Interface: Interface{ Index: 2, Name: "eth1", }, Port: DirectAttach, }, }, { name: "both", ifi: Interface{ Index: 2, Name: "eth1", }, attrs: requestBoth(unix.ETHTOOL_A_LINKINFO_HEADER, true), li: &LinkInfo{ Interface: Interface{ Index: 2, Name: "eth1", }, Port: DirectAttach, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := testClient(t, clientTest{ HeaderFlags: netlink.Request, Command: unix.ETHTOOL_MSG_LINKINFO_GET, Attributes: tt.attrs, Messages: []genetlink.Message{encodeLinkInfo(t, *tt.li)}, }) li, err := c.LinkInfo(tt.ifi) if err != nil { t.Fatalf("failed to get link info: %v", err) } if diff := cmp.Diff(tt.li, li); diff != "" { t.Fatalf("unexpected link info (-want +got):\n%s", diff) } }) } } func TestLinuxClientLinkModes(t *testing.T) { // See https://github.com/mdlayher/ethtool/issues/12. // // It's likely that the bitset code is incorrect on big endian machines. skipBigEndian(t) tests := []struct { name string lms []*LinkMode }{ { name: "OK", lms: []*LinkMode{ { Interface: Interface{ Index: 1, Name: "eth0", }, SpeedMegabits: 1000, Ours: []AdvertisedLinkMode{ { Index: unix.ETHTOOL_LINK_MODE_1000baseT_Half_BIT, Name: "1000baseT/Half", }, { Index: unix.ETHTOOL_LINK_MODE_1000baseT_Full_BIT, Name: "1000baseT/Full", }, }, Duplex: Half, }, { Interface: Interface{ Index: 2, Name: "eth1", }, SpeedMegabits: 10000, Ours: []AdvertisedLinkMode{ { Index: unix.ETHTOOL_LINK_MODE_FIBRE_BIT, Name: "FIBRE", }, { Index: unix.ETHTOOL_LINK_MODE_10000baseT_Full_BIT, Name: "10000baseT/Full", }, }, Duplex: Full, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Generate the expected response messages using the wanted list // of LinkMode structures. var msgs []genetlink.Message for _, lm := range tt.lms { msgs = append(msgs, encodeLinkMode(t, *lm)) } c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Dump, Command: unix.ETHTOOL_MSG_LINKMODES_GET, Attributes: requestHeader(unix.ETHTOOL_A_LINKMODES_HEADER), Messages: msgs, }) lms, err := c.LinkModes() if err != nil { t.Fatalf("failed to get link mode: %v", err) } if diff := cmp.Diff(tt.lms, lms); diff != "" { t.Fatalf("unexpected link mode (-want +got):\n%s", diff) } }) } } func TestLinuxClientLinkMode(t *testing.T) { tests := []struct { name string ifi Interface attrs func(ae *netlink.AttributeEncoder) li *LinkMode }{ { name: "by index", ifi: Interface{Index: 1}, attrs: requestIndex(unix.ETHTOOL_A_LINKMODES_HEADER, true), li: &LinkMode{ Interface: Interface{ Index: 1, Name: "eth0", }, SpeedMegabits: 1000, Duplex: Half, }, }, { name: "by name", ifi: Interface{Name: "eth1"}, attrs: requestName(unix.ETHTOOL_A_LINKMODES_HEADER), li: &LinkMode{ Interface: Interface{ Index: 2, Name: "eth1", }, SpeedMegabits: 10000, Duplex: Full, }, }, { name: "both", ifi: Interface{ Index: 2, Name: "eth1", }, attrs: requestBoth(unix.ETHTOOL_A_LINKMODES_HEADER, true), li: &LinkMode{ Interface: Interface{ Index: 2, Name: "eth1", }, SpeedMegabits: 10000, Duplex: Full, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := testClient(t, clientTest{ HeaderFlags: netlink.Request, Command: unix.ETHTOOL_MSG_LINKMODES_GET, Attributes: tt.attrs, Messages: []genetlink.Message{encodeLinkMode(t, *tt.li)}, }) li, err := c.LinkMode(tt.ifi) if err != nil { t.Fatalf("failed to get link mode: %v", err) } if diff := cmp.Diff(tt.li, li); diff != "" { t.Fatalf("unexpected link mode (-want +got):\n%s", diff) } }) } } func TestLinuxClientLinkStates(t *testing.T) { tests := []struct { name string lss []*LinkState }{ { name: "OK", lss: []*LinkState{ { Interface: Interface{ Index: 1, Name: "eth0", }, }, { Interface: Interface{ Index: 2, Name: "eth1", }, Link: true, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Generate the expected response messages using the wanted list // of LinkInfo structures. var msgs []genetlink.Message for _, ls := range tt.lss { msgs = append(msgs, encodeLinkState(t, *ls)) } c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Dump, Command: unix.ETHTOOL_MSG_LINKSTATE_GET, Attributes: requestHeader(unix.ETHTOOL_A_LINKSTATE_HEADER), Messages: msgs, }) lss, err := c.LinkStates() if err != nil { t.Fatalf("failed to get link states: %v", err) } if diff := cmp.Diff(tt.lss, lss); diff != "" { t.Fatalf("unexpected link states (-want +got):\n%s", diff) } }) } } func TestLinuxClientLinkState(t *testing.T) { tests := []struct { name string ifi Interface attrs func(ae *netlink.AttributeEncoder) ls *LinkState }{ { name: "by index", ifi: Interface{Index: 1}, attrs: requestIndex(unix.ETHTOOL_A_LINKSTATE_HEADER, true), ls: &LinkState{ Interface: Interface{ Index: 1, Name: "eth0", }, }, }, { name: "by name", ifi: Interface{Name: "eth1"}, attrs: requestName(unix.ETHTOOL_A_LINKSTATE_HEADER), ls: &LinkState{ Interface: Interface{ Index: 2, Name: "eth1", }, Link: true, }, }, { name: "both", ifi: Interface{ Index: 2, Name: "eth1", }, attrs: requestBoth(unix.ETHTOOL_A_LINKSTATE_HEADER, true), ls: &LinkState{ Interface: Interface{ Index: 2, Name: "eth1", }, Link: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := testClient(t, clientTest{ HeaderFlags: netlink.Request, Command: unix.ETHTOOL_MSG_LINKSTATE_GET, Attributes: tt.attrs, Messages: []genetlink.Message{encodeLinkState(t, *tt.ls)}, }) ls, err := c.LinkState(tt.ifi) if err != nil { t.Fatalf("failed to get link state: %v", err) } if diff := cmp.Diff(tt.ls, ls); diff != "" { t.Fatalf("unexpected link state (-want +got):\n%s", diff) } }) } } func TestLinuxClientWakeOnLANs(t *testing.T) { tests := []struct { name string wols []*WakeOnLAN }{ { name: "OK", wols: []*WakeOnLAN{ { Interface: Interface{ Index: 1, Name: "eth0", }, Modes: Magic | MagicSecure, }, { Interface: Interface{ Index: 2, Name: "eth1", }, Modes: Magic, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Generate the expected response messages using the wanted list // of WakeOnLAN structures. var msgs []genetlink.Message for _, wol := range tt.wols { msgs = append(msgs, encodeWOL(t, *wol)) } c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Dump, Command: unix.ETHTOOL_MSG_WOL_GET, Attributes: requestHeader(unix.ETHTOOL_A_WOL_HEADER), Messages: msgs, }) wols, err := c.WakeOnLANs() if err != nil { t.Fatalf("failed to get link mode: %v", err) } if diff := cmp.Diff(tt.wols, wols); diff != "" { t.Fatalf("unexpected link mode (-want +got):\n%s", diff) } }) } } func TestLinuxClientWakeOnLAN(t *testing.T) { tests := []struct { name string ifi Interface attrs func(ae *netlink.AttributeEncoder) wol WakeOnLAN nlErr, err error }{ { name: "EPERM", ifi: Interface{Index: 1}, attrs: requestIndex(unix.ETHTOOL_A_WOL_HEADER, true), nlErr: genltest.Error(int(unix.EPERM)), err: os.ErrPermission, }, { name: "ok by index", ifi: Interface{Index: 1}, attrs: requestIndex(unix.ETHTOOL_A_WOL_HEADER, true), wol: WakeOnLAN{ Interface: Interface{ Index: 1, Name: "eth0", }, }, }, { name: "ok by name", ifi: Interface{Name: "eth1"}, attrs: requestName(unix.ETHTOOL_A_WOL_HEADER), wol: WakeOnLAN{ Interface: Interface{ Index: 2, Name: "eth1", }, }, }, { name: "ok both", ifi: Interface{ Index: 2, Name: "eth1", }, attrs: requestBoth(unix.ETHTOOL_A_WOL_HEADER, true), wol: WakeOnLAN{ Interface: Interface{ Index: 2, Name: "eth1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := testClient(t, clientTest{ HeaderFlags: netlink.Request, Command: unix.ETHTOOL_MSG_WOL_GET, Attributes: tt.attrs, Messages: []genetlink.Message{encodeWOL(t, tt.wol)}, Error: tt.nlErr, }) wol, err := c.WakeOnLAN(tt.ifi) if err != nil { if tt.err != nil { // This test expects an error, check it and skip the rest // of the comparisons. if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" { t.Fatalf("unexpected error(-want +got):\n%s", diff) } return } t.Fatalf("failed to get Wake-on-LAN info: %v", err) } if diff := cmp.Diff(&tt.wol, wol); diff != "" { t.Fatalf("unexpected Wake-on-LAN (-want +got):\n%s", diff) } }) } } func TestLinuxClientSetWakeOnLAN(t *testing.T) { wol := WakeOnLAN{ Interface: Interface{ Index: 2, Name: "eth1", }, Modes: Unicast | Magic, } tests := []struct { name string wol WakeOnLAN attrs func(ae *netlink.AttributeEncoder) nlErr, err error }{ { name: "EPERM", wol: WakeOnLAN{Interface: Interface{Index: 1}}, attrs: requestIndex(unix.ETHTOOL_A_WOL_HEADER, false), nlErr: genltest.Error(int(unix.EPERM)), err: os.ErrPermission, }, { name: "ok", attrs: func(ae *netlink.AttributeEncoder) { // Apply the request header followed immediately by the rest of // the encoded WOL attributes. requestBoth(unix.ETHTOOL_A_WOL_HEADER, false)(ae) wol.encode(ae) }, wol: wol, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Acknowledge, Command: unix.ETHTOOL_MSG_WOL_SET, Attributes: tt.attrs, Messages: []genetlink.Message{encodeWOL(t, tt.wol)}, Error: tt.nlErr, }) err := c.SetWakeOnLAN(tt.wol) if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" { t.Fatalf("unexpected error(-want +got):\n%s", diff) } }) } } func requestHeader(typ uint16) func(*netlink.AttributeEncoder) { return func(ae *netlink.AttributeEncoder) { ae.Nested(typ, func(nae *netlink.AttributeEncoder) error { headerFlags(nae) return nil }) } } func requestIndex(typ uint16, compactBitsets bool) func(*netlink.AttributeEncoder) { return func(ae *netlink.AttributeEncoder) { ae.Nested(typ, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, 1) if compactBitsets { headerFlags(nae) } return nil }) } } func requestName(typ uint16) func(*netlink.AttributeEncoder) { return func(ae *netlink.AttributeEncoder) { ae.Nested(typ, func(nae *netlink.AttributeEncoder) error { nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, "eth1") headerFlags(nae) return nil }) } } func requestBoth(typ uint16, compactBitsets bool) func(*netlink.AttributeEncoder) { return func(ae *netlink.AttributeEncoder) { ae.Nested(typ, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, 2) nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, "eth1") if compactBitsets { headerFlags(nae) } return nil }) } } func headerFlags(ae *netlink.AttributeEncoder) { ae.Uint32(unix.ETHTOOL_A_HEADER_FLAGS, unix.ETHTOOL_FLAG_COMPACT_BITSETS) } func encodeLinkInfo(t *testing.T, li LinkInfo) genetlink.Message { t.Helper() return genetlink.Message{ Data: encode(t, func(ae *netlink.AttributeEncoder) { ae.Nested(unix.ETHTOOL_A_LINKINFO_HEADER, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, uint32(li.Interface.Index)) nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, li.Interface.Name) return nil }) ae.Uint8(unix.ETHTOOL_A_LINKINFO_PORT, uint8(li.Port)) }), } } func encodeLinkMode(t *testing.T, lm LinkMode) genetlink.Message { t.Helper() return genetlink.Message{ Data: encode(t, func(ae *netlink.AttributeEncoder) { ae.Nested(unix.ETHTOOL_A_LINKMODES_HEADER, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, uint32(lm.Interface.Index)) nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, lm.Interface.Name) return nil }) 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.Uint8(unix.ETHTOOL_A_LINKMODES_DUPLEX, uint8(lm.Duplex)) }), } } func encodeLinkState(t *testing.T, ls LinkState) genetlink.Message { t.Helper() return genetlink.Message{ Data: encode(t, func(ae *netlink.AttributeEncoder) { ae.Nested(unix.ETHTOOL_A_LINKSTATE_HEADER, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, uint32(ls.Interface.Index)) nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, ls.Interface.Name) return nil }) // uint8 boolean conversion. var link uint8 if ls.Link { link = 1 } ae.Uint8(unix.ETHTOOL_A_LINKSTATE_LINK, link) }), } } func encodeWOL(t *testing.T, wol WakeOnLAN) genetlink.Message { t.Helper() return genetlink.Message{ Data: encode(t, func(ae *netlink.AttributeEncoder) { ae.Nested(unix.ETHTOOL_A_WOL_HEADER, func(nae *netlink.AttributeEncoder) error { nae.Uint32(unix.ETHTOOL_A_HEADER_DEV_INDEX, uint32(wol.Interface.Index)) nae.String(unix.ETHTOOL_A_HEADER_DEV_NAME, wol.Interface.Name) return nil }) wol.encode(ae) }), } } 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() ae := netlink.NewAttributeEncoder() fn(ae) b, err := ae.Encode() if err != nil { t.Fatalf("failed to encode attributes: %v", err) } return b } // A clientTest is the input to testClient which defines expected request // parameters and canned response messages and/or errors. type clientTest struct { // Expected request parameters. HeaderFlags netlink.HeaderFlags Command uint8 Attributes func(*netlink.AttributeEncoder) EncodedAttributes []byte // Response data. If Error is set, Messages is unused. Messages []genetlink.Message Error error } // testClient produces a Client which handles request/responses by validating // the parameters against those set in the clientTest structure. This is useful // for high-level request/response testing. func testClient(t *testing.T, ct clientTest) *Client { t.Helper() c := baseClient(t, func(greq genetlink.Message, req netlink.Message) ([]genetlink.Message, error) { if ct.Error != nil { // Assume success tests will verify the request and return the error // early to avoid creating excessive test fixtures. return nil, ct.Error } // Verify the base netlink/genetlink headers. if diff := cmp.Diff(ct.HeaderFlags, req.Header.Flags); diff != "" { t.Fatalf("unexpected netlink flags (-want +got):\n%s", diff) } if diff := cmp.Diff(ct.Command, greq.Header.Command); diff != "" { t.Fatalf("unexpected ethtool command (-want +got):\n%s", diff) } // Verify the caller's request data. encoded := ct.EncodedAttributes if encoded == nil { encoded = encode(t, ct.Attributes) } if diff := cmp.Diff(encoded, greq.Data); diff != "" { t.Fatalf("unexpected request header bytes (-want +got):\n%s", diff) } return ct.Messages, nil }) t.Cleanup(func() { _ = c.Close() }) return c } const familyID = 20 // baseClient produces a barebones Client with only the initial genetlink setup // logic performed for low-level tests. func baseClient(t *testing.T, fn genltest.Func) *Client { t.Helper() family := genetlink.Family{ ID: familyID, Version: unix.ETHTOOL_GENL_VERSION, Name: unix.ETHTOOL_GENL_NAME, Groups: []genetlink.MulticastGroup{{ ID: 1, Name: unix.ETHTOOL_MCGRP_MONITOR_NAME, }}, } conn := genltest.Dial(genltest.ServeFamily(family, fn)) c, err := initClient(conn) if err != nil { t.Fatalf("failed to open client: %v", err) } return &Client{c: c} } func TestFEC(t *testing.T) { skipBigEndian(t) // captured from wireshark on the nlmon0 interface when running: // // sudo ethtool --set-fec enp7s0 encoding rs // // corresponding strace: // // sendto(3, [{nlmsg_len=68, nlmsg_type=ethtool, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK, nlmsg_seq=2, nlmsg_pid=0}, // "\x1e\x01\x00\x00\x10\x00\x01\x80" // "\x0b\x00\x02\x00\x65\x6e\x70\x37\x73\x30\x00\x00\x18\x00\x02\x80\x04\x00\x01\x00\x10\x00\x03\x80\x0c\x00\x01\x80\x07\x00\x02\x00\x52\x53\x00\x00\x05\x00\x03\x00\x00\x00\x00\x00"], 68, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 68 c := testClient(t, clientTest{ HeaderFlags: netlink.Request | netlink.Acknowledge, Command: unix.ETHTOOL_MSG_FEC_SET, EncodedAttributes: []byte("\x10\x00\x01\x80\x0b\x00\x02\x00\x65\x6e\x70\x37\x73\x30\x00\x00\x18\x00\x02\x80\x04\x00\x01\x00\x10\x00\x03\x80\x0c\x00\x01\x80\x07\x00\x02\x00\x52\x53\x00\x00\x05\x00\x03\x00\x00\x00\x00\x00"), Messages: []genetlink.Message{{ Data: nil, }}, Error: nil, }) err := c.SetFEC(FEC{ Interface: Interface{Name: "enp7s0"}, Modes: unix.ETHTOOL_FEC_RS, }) if err != nil { t.Fatalf("failed to set FEC: %v", err) } } 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() if binary.ByteOrder(native.Endian) == binary.BigEndian { t.Skip("skipping, this test requires a little endian machine") } } golang-github-mdlayher-ethtool-0.2.0/client_others.go000066400000000000000000000040131467022607000227120ustar00rootroot00000000000000//go:build !linux // +build !linux package ethtool import ( "fmt" "runtime" ) // errUnsupported indicates that this library is not functional on non-Linux // platforms. var errUnsupported = fmt.Errorf("ethtool: this library is not supported on %s/%s", runtime.GOOS, runtime.GOARCH) 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) 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 } func (f FECMode) String() string { return "unsupported" } func (f FECModes) String() string { return "unsupported" } golang-github-mdlayher-ethtool-0.2.0/doc.go000066400000000000000000000003131467022607000206140ustar00rootroot00000000000000// Package ethtool allows control of the Linux ethtool generic netlink // interface. For more information, see: // https://www.kernel.org/doc/html/latest/networking/ethtool-netlink.html. package ethtool golang-github-mdlayher-ethtool-0.2.0/go.mod000066400000000000000000000005511467022607000206320ustar00rootroot00000000000000module github.com/mdlayher/ethtool go 1.19 require ( github.com/google/go-cmp v0.6.0 github.com/josharian/native v1.1.0 github.com/mdlayher/genetlink v1.3.2 github.com/mdlayher/netlink v1.7.2 golang.org/x/sys v0.22.0 ) require ( github.com/mdlayher/socket v0.4.1 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/sync v0.1.0 // indirect ) golang-github-mdlayher-ethtool-0.2.0/go.sum000066400000000000000000000024461467022607000206640ustar00rootroot00000000000000github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang-github-mdlayher-ethtool-0.2.0/linkmodes_linux.go000066400000000000000000000162221467022607000232610ustar00rootroot00000000000000//go:build linux // +build linux // Code generated by "go run mklinkmodes.go"; DO NOT EDIT. package ethtool import "golang.org/x/sys/unix" var linkModes = [...]struct { bit uint32 str string }{ {bit: unix.ETHTOOL_LINK_MODE_10baseT_Half_BIT, str: "10baseT/Half"}, {bit: unix.ETHTOOL_LINK_MODE_10baseT_Full_BIT, str: "10baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100baseT_Half_BIT, str: "100baseT/Half"}, {bit: unix.ETHTOOL_LINK_MODE_100baseT_Full_BIT, str: "100baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_1000baseT_Half_BIT, str: "1000baseT/Half"}, {bit: unix.ETHTOOL_LINK_MODE_1000baseT_Full_BIT, str: "1000baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_Autoneg_BIT, str: "Autoneg"}, {bit: unix.ETHTOOL_LINK_MODE_TP_BIT, str: "TP"}, {bit: unix.ETHTOOL_LINK_MODE_AUI_BIT, str: "AUI"}, {bit: unix.ETHTOOL_LINK_MODE_MII_BIT, str: "MII"}, {bit: unix.ETHTOOL_LINK_MODE_FIBRE_BIT, str: "FIBRE"}, {bit: unix.ETHTOOL_LINK_MODE_BNC_BIT, str: "BNC"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseT_Full_BIT, str: "10000baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_Pause_BIT, str: "Pause"}, {bit: unix.ETHTOOL_LINK_MODE_Asym_Pause_BIT, str: "Asym/Pause"}, {bit: unix.ETHTOOL_LINK_MODE_2500baseX_Full_BIT, str: "2500baseX/Full"}, {bit: unix.ETHTOOL_LINK_MODE_Backplane_BIT, str: "Backplane"}, {bit: unix.ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, str: "1000baseKX/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, str: "10000baseKX4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, str: "10000baseKR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, str: "10000baseR/FEC"}, {bit: unix.ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT, str: "20000baseMLD2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT, str: "20000baseKR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, str: "40000baseKR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, str: "40000baseCR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, str: "40000baseSR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, str: "40000baseLR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT, str: "56000baseKR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT, str: "56000baseCR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT, str: "56000baseSR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT, str: "56000baseLR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, str: "25000baseCR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, str: "25000baseKR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, str: "25000baseSR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, str: "50000baseCR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, str: "50000baseKR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, str: "100000baseKR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, str: "100000baseSR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, str: "100000baseCR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, str: "100000baseLR4/ER4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, str: "50000baseSR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_1000baseX_Full_BIT, str: "1000baseX/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, str: "10000baseCR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, str: "10000baseSR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, str: "10000baseLR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, str: "10000baseLRM/Full"}, {bit: unix.ETHTOOL_LINK_MODE_10000baseER_Full_BIT, str: "10000baseER/Full"}, {bit: unix.ETHTOOL_LINK_MODE_2500baseT_Full_BIT, str: "2500baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_5000baseT_Full_BIT, str: "5000baseT/Full"}, {bit: unix.ETHTOOL_LINK_MODE_FEC_NONE_BIT, str: "FEC/NONE"}, {bit: unix.ETHTOOL_LINK_MODE_FEC_RS_BIT, str: "FEC/RS"}, {bit: unix.ETHTOOL_LINK_MODE_FEC_BASER_BIT, str: "FEC/BASER"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, str: "50000baseKR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, str: "50000baseSR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, str: "50000baseCR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, str: "50000baseLR/ER/FR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, str: "50000baseDR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, str: "100000baseKR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, str: "100000baseSR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, str: "100000baseCR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, str: "100000baseLR2/ER2/FR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, str: "100000baseDR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, str: "200000baseKR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, str: "200000baseSR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT, str: "200000baseLR4/ER4/FR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT, str: "200000baseDR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, str: "200000baseCR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100baseT1_Full_BIT, str: "100baseT1/Full"}, {bit: unix.ETHTOOL_LINK_MODE_1000baseT1_Full_BIT, str: "1000baseT1/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT, str: "400000baseKR8/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT, str: "400000baseSR8/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT, str: "400000baseLR8/ER8/FR8/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT, str: "400000baseDR8/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT, str: "400000baseCR8/Full"}, {bit: unix.ETHTOOL_LINK_MODE_FEC_LLRS_BIT, str: "FEC/LLRS"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, str: "100000baseKR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, str: "100000baseSR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT, str: "100000baseLR/ER/FR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, str: "100000baseCR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100000baseDR_Full_BIT, str: "100000baseDR/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, str: "200000baseKR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, str: "200000baseSR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT, str: "200000baseLR2/ER2/FR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT, str: "200000baseDR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, str: "200000baseCR2/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT, str: "400000baseKR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT, str: "400000baseSR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT, str: "400000baseLR4/ER4/FR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT, str: "400000baseDR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT, str: "400000baseCR4/Full"}, {bit: unix.ETHTOOL_LINK_MODE_100baseFX_Half_BIT, str: "100baseFX/Half"}, {bit: unix.ETHTOOL_LINK_MODE_100baseFX_Full_BIT, str: "100baseFX/Full"}, } golang-github-mdlayher-ethtool-0.2.0/mklinkmodes.go000066400000000000000000000033261467022607000223730ustar00rootroot00000000000000//go:build ignore // +build ignore // mklinkmodes is a Go script which generates the linkModes lookup table using // a ztypes_linux.go from x/sys as input. package main import ( "bufio" "bytes" "fmt" "go/format" "io" "io/ioutil" "log" "os" "path/filepath" "strings" ) func main() { // Assume a local GOPATH checkout of x/sys. f, err := os.Open(filepath.Join(os.Getenv("GOPATH"), "src/golang.org/x/sys/unix/ztypes_linux.go")) if err != nil { log.Fatalf("failed to open ztypes_linux.go: %v", err) } defer f.Close() b := bytes.NewBuffer(nil) pf(b, `//go:build linux // +build linux // Code generated by "go run mklinkmodes.go"; DO NOT EDIT. package ethtool import "golang.org/x/sys/unix" var linkModes = [...]struct{ bit uint32 str string }{`) s := bufio.NewScanner(f) for s.Scan() { if !bytes.HasPrefix(s.Bytes(), []byte("\tETHTOOL_LINK_MODE")) { continue } // Found a link mode, we only care about the name. ss := strings.Fields(s.Text()) if len(ss) != 3 { continue } // The constant and a stringified name like 1000baseT/Full is written // for each line found. bit := ss[0] str := strings.TrimPrefix(ss[0], "ETHTOOL_LINK_MODE_") str = strings.TrimSuffix(str, "_BIT") str = strings.Replace(str, "_", "/", -1) pf(b, `{bit: unix.%s, str: %q},`, bit, str) } pf(b, `}`) if err := s.Err(); err != nil { log.Fatalf("failed to scan: %v", err) } out, err := format.Source(b.Bytes()) if err != nil { log.Fatalf("failed to format: %v", err) } if err := ioutil.WriteFile("linkmodes_linux.go", out, 0o644); err != nil { log.Fatalf("failed to write output file: %v", err) } } func pf(w io.Writer, format string, args ...interface{}) { fmt.Fprintf(w, format+"\n", args...) } golang-github-mdlayher-ethtool-0.2.0/string.go000066400000000000000000000027141467022607000213640ustar00rootroot00000000000000// Code generated by "stringer -type=Duplex,Port -output=string.go"; DO NOT EDIT. package ethtool import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[Half-0] _ = x[Full-1] _ = x[Unknown-255] } const ( _Duplex_name_0 = "HalfFull" _Duplex_name_1 = "Unknown" ) var ( _Duplex_index_0 = [...]uint8{0, 4, 8} ) func (i Duplex) String() string { switch { case 0 <= i && i <= 1: return _Duplex_name_0[_Duplex_index_0[i]:_Duplex_index_0[i+1]] case i == 255: return _Duplex_name_1 default: return "Duplex(" + strconv.FormatInt(int64(i), 10) + ")" } } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[TwistedPair-0] _ = x[AUI-1] _ = x[MII-2] _ = x[Fibre-3] _ = x[BNC-4] _ = x[DirectAttach-5] _ = x[None-239] _ = x[Other-255] } const ( _Port_name_0 = "TwistedPairAUIMIIFibreBNCDirectAttach" _Port_name_1 = "None" _Port_name_2 = "Other" ) var ( _Port_index_0 = [...]uint8{0, 11, 14, 17, 22, 25, 37} ) func (i Port) String() string { switch { case 0 <= i && i <= 5: return _Port_name_0[_Port_index_0[i]:_Port_index_0[i+1]] case i == 239: return _Port_name_1 case i == 255: return _Port_name_2 default: return "Port(" + strconv.FormatInt(int64(i), 10) + ")" } }