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
- 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.
- Verify the kernel supports CONFIG_ETHTOOL_NETLINK (kernel >= 5.6) with `ls /sys/kernel/debug` or check `genl ctrl list | grep -i ethtool`.
- Check container seccomp/AppArmor policies allow socket(AF_NETLINK) with NETLINK_GENERIC.
- 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.
- 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
- Pin kernel >= 5.6 where CONFIG_ETHTOOL_NETLINK exists
- Keep node_exporter current so missing-ethtool degrades to an Info log
- Ensure container seccomp profiles allow AF_NETLINK sockets
- Alert on the log line 'ETHTOOL netlink interface unavailable' rather than paging on it
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
- failed to initialize ethtool library
- could not get net class info
- couldn't connect rtnetlink
- couldn't get links
- couldn't get routes
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)