Optionally fetch ARP stats via rtnetlink instead of procfs (#2777)

* Optionally fetch ARP stats via rtnetlink instead of procfs

Implement collection of ARP stats via rtnetlink to work around
shortcomings in the output of /proc/net/arp, which truncates InfiniBand
link-layer addresses.

Fixes: #2776

---------

Signed-off-by: Daniel Swarbrick <daniel.swarbrick@gmail.com>
Co-authored-by: Ben Kochie <superq@gmail.com>
This commit is contained in:
Daniel Swarbrick 2023-09-09 16:41:09 +02:00 committed by GitHub
parent cda1d820bb
commit 685b98ec7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 10 deletions

View file

@ -17,16 +17,22 @@
package collector
import (
"errors"
"fmt"
"net"
"github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
"github.com/jsimonetti/rtnetlink"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs"
"golang.org/x/sys/unix"
)
var (
arpDeviceInclude = kingpin.Flag("collector.arp.device-include", "Regexp of arp devices to include (mutually exclusive to device-exclude).").String()
arpDeviceExclude = kingpin.Flag("collector.arp.device-exclude", "Regexp of arp devices to exclude (mutually exclusive to device-include).").String()
arpNetlink = kingpin.Flag("collector.arp.netlink", "Use netlink to gather stats instead of /proc/net/arp.").Default("true").Bool()
)
type arpCollector struct {
@ -69,13 +75,65 @@ func getTotalArpEntries(deviceEntries []procfs.ARPEntry) map[string]uint32 {
return entries
}
func getTotalArpEntriesRTNL() (map[string]uint32, error) {
conn, err := rtnetlink.Dial(nil)
if err != nil {
return nil, err
}
defer conn.Close()
neighbors, err := conn.Neigh.List()
if err != nil {
return nil, err
}
ifIndexEntries := make(map[uint32]uint32)
for _, n := range neighbors {
// Neighbors will also contain IPv6 neighbors, but since this is purely an ARP collector,
// restrict to AF_INET. Also skip entries which have state NUD_NOARP to conform to output
// of /proc/net/arp.
if n.Family == unix.AF_INET && n.State&unix.NUD_NOARP == 0 {
ifIndexEntries[n.Index]++
}
}
enumEntries := make(map[string]uint32)
// Convert interface indexes to names.
for ifIndex, entryCount := range ifIndexEntries {
iface, err := net.InterfaceByIndex(int(ifIndex))
if err != nil {
if errors.Unwrap(err).Error() == "no such network interface" {
continue
}
return nil, err
}
enumEntries[iface.Name] = entryCount
}
return enumEntries, nil
}
func (c *arpCollector) Update(ch chan<- prometheus.Metric) error {
var enumeratedEntry map[string]uint32
if *arpNetlink {
var err error
enumeratedEntry, err = getTotalArpEntriesRTNL()
if err != nil {
return fmt.Errorf("could not get ARP entries: %w", err)
}
} else {
entries, err := c.fs.GatherARPEntries()
if err != nil {
return fmt.Errorf("could not get ARP entries: %w", err)
}
enumeratedEntry := getTotalArpEntries(entries)
enumeratedEntry = getTotalArpEntries(entries)
}
for device, entryCount := range enumeratedEntry {
if c.deviceFilter.ignored(device) {

View file

@ -132,6 +132,7 @@ fi
--collector.qdisc.fixtures="collector/fixtures/qdisc/" \
--collector.qdisc.device-include="(wlan0|eth0)" \
--collector.arp.device-exclude="nope" \
--no-collector.arp.netlink \
--collector.hwmon.chip-include="(applesmc|coretemp|hwmon4|nct6779)" \
--collector.netclass.ignored-devices="(dmz|int)" \
--collector.netclass.ignore-invalid-speed \

4
go.mod
View file

@ -14,7 +14,7 @@ require (
github.com/hodgesds/perf-utils v0.7.0
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973
github.com/josharian/native v1.1.0
github.com/jsimonetti/rtnetlink v1.3.4
github.com/jsimonetti/rtnetlink v1.3.5
github.com/lufia/iostat v1.2.1
github.com/mattn/go-xmlrpc v0.0.3
github.com/mdlayher/ethtool v0.1.0
@ -29,7 +29,7 @@ require (
github.com/prometheus/procfs v0.11.1
github.com/safchain/ethtool v0.3.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/sys v0.10.0
golang.org/x/sys v0.11.0
howett.net/plist v1.0.0
)

9
go.sum
View file

@ -8,7 +8,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -47,8 +47,8 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jsimonetti/rtnetlink v1.3.4 h1:uUcd9SE8sQe/enLBEVaWDkgGYWNsX3EU4eIxbEZYmF0=
github.com/jsimonetti/rtnetlink v1.3.4/go.mod h1:jGCNm5lJdGplEXKCVwqhQ5tRIGV0dNREhLyNWVzBxHc=
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@ -141,8 +141,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=