prometheus/node_exporter · error

could not get link modes

Error message

could not get link modes: %w

What it means

node_exporter's netclass collector wraps any failure from getLinkModes(), which queries the ETHTOOL netlink socket to learn duplex and link-speed per interface. If the error is not fs.ErrNotExist (i.e. it is not simply 'the netlink interface is unavailable'), the collector aborts its whole Update with this wrapped error. It exists so duplex/linkspeed data loss is distinguishable from total netclass failure.

Solutions

  1. Upgrade node_exporter (and its ethtool dependency) so the missing-ETHTOOL-netlink case is detected via errors.Is(err, fs.ErrNotExist) and degrades gracefully to Info log.
  2. Verify the kernel supports CONFIG_ETHTOOL_NETLINK (kernel >= 5.6) with `ls /sys/kernel/debug` or check `genl ctrl list | grep -i ethtool`.
  3. Check container seccomp/AppArmor policies allow socket(AF_NETLINK) with NETLINK_GENERIC.
  4. If duplex/linkspeed are not needed, the error itself does not block other netclass metrics only in newer versions — otherwise run with --collector.netclass and confirm which sub-metrics fail; file an issue if the error surfaces on a supported kernel.
  5. Reproduce with strace -f -e trace=socket,sendto,recvfrom to see whether the netlink socket creation or the dump fails.

Example fix

// before (older detection logic that misses the degraded case)
if err != nil {
    return fmt.Errorf("could not get link modes: %w", err)
}
// after (graceful degradation when ethtool netlink is unavailable)
if err != nil {
    if !errors.Is(errors.Unwrap(err), fs.ErrNotExist) {
        return fmt.Errorf("could not get link modes: %w", err)
    }
    c.logger.Info("ETHTOOL netlink interface unavailable, duplex and linkspeed are not scraped.")
}
Defensive patterns

Strategy: fallback

Validate before calling

// check ETHTOOL netlink family availability before scraping
genl, err := genetlink.Dial(nil)
if err != nil { /* log: ethtool netlink unavailable */ }
else if _, err := genl.GetFamily("ethtool"); err != nil {
    // fall back: duplex/linkspeed will not be scraped
}

Type guard

func isEthtoolUnavailable(err error) bool {
	return errors.Is(errors.Unwrap(err), fs.ErrNotExist)
}

Try / catch

if err := collector.Update(ch); err != nil {
	var wrapped *fmt.wrapError
	if errors.As(err, &wrapped) && strings.Contains(err.Error(), "could not get link modes") {
		logger.Info("ethtool netlink unavailable; continuing without duplex/linkspeed")
	} else {
		return err
	}
}

Prevention

When it happens

Trigger: getLinkModes() returns a non-ErrNotExist error: the ethtool netlink socket cannot be created/opened, the netlink query to the kernel fails (e.g. socket permission issue, kernel without CONFIG_ETHTOOL_NETLINK support returning an unexpected error rather than a missing file), or a syscall error while dumping link modes.

Common situations: Running on kernels older than 5.6 where ETHTOOL netlink genetlink family is absent but the ethtool library surfaces the absence as something other than fs.ErrNotExist; running in restricted containers/seccomp environments that block NETLINK_GENERIC socket creation; older node_exporter/ethtool-go library versions whose error wrapping no longer matches errors.Unwrap(err)==fs.ErrNotExist so the graceful-degradation path is missed.

Related errors


AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07). Data as JSON: /api/errors/435093165d1e6ee7. Report an issue: GitHub.

Appendix: source

Thrown at collector/netclass_rtnl_linux.go:45

	"github.com/mdlayher/ethtool"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/procfs/sysfs"
)

var (
	netclassRTNLWithStats = kingpin.Flag("collector.netclass_rtnl.with-stats", "Expose the statistics for each network device, replacing netdev collector.").Bool()
	operstateStr          = []string{
		"unknown", "notpresent", "down", "lowerlayerdown", "testing",
		"dormant", "up",
	}
)

func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) error {
	linkModes := make(map[string]*ethtool.LinkMode)
	lms, err := c.getLinkModes()
	if err != nil {
		if !errors.Is(errors.Unwrap(err), fs.ErrNotExist) {
			return fmt.Errorf("could not get link modes: %w", err)
		}
		c.logger.Info("ETHTOOL netlink interface unavailable, duplex and linkspeed are not scraped.")
	} else {
		for _, lm := range lms {
			if c.ignoredDevicesPattern.MatchString(lm.Interface.Name) {
				continue
			}
			if lm.SpeedMegabits >= 0 {
				speedBytes := uint64(lm.SpeedMegabits * 1000 * 1000 / 8)
				pushMetric(ch, c.getFieldDesc("speed_bytes"), speedBytes, prometheus.GaugeValue, lm.Interface.Name)
			}
			linkModes[lm.Interface.Name] = lm
		}
	}

	// Get most attributes from Netlink
	lMsgs, err := c.getNetClassInfoRTNL()
	if err != nil {

View on GitHub (pinned to 17ddd77c59)