@@ -20,13 +20,16 @@ import (
2020 "fmt"
2121 "io/fs"
2222 "path/filepath"
23+ "strconv"
2324 "strings"
2425
2526 "github.com/alecthomas/kingpin/v2"
2627 "github.com/jsimonetti/rtnetlink/v2"
2728 "github.com/mdlayher/ethtool"
29+ "github.com/mdlayher/netlink"
2830 "github.com/prometheus/client_golang/prometheus"
2931 "github.com/prometheus/procfs/sysfs"
32+ "golang.org/x/sys/unix"
3033)
3134
3235var (
3538 "unknown" , "notpresent" , "down" , "lowerlayerdown" , "testing" ,
3639 "dormant" , "up" ,
3740 }
41+
42+ netclassMasterDesc = prometheus .NewDesc (
43+ prometheus .BuildFQName (namespace , "network" , "master_info" ),
44+ "Direct master device of <iface> and the master's kind (bridge, bond, vrf), value is always 1." ,
45+ []string {"device" , "master" , "master_kind" },
46+ nil ,
47+ )
48+ netclassVRFDesc = prometheus .NewDesc (
49+ prometheus .BuildFQName (namespace , "network" , "vrf_info" ),
50+ "Routing table ID of a VRF device, value is always 1." ,
51+ []string {"device" , "table" },
52+ nil ,
53+ )
3854)
3955
4056func (c * netClassCollector ) netClassRTNLUpdate (ch chan <- prometheus.Metric ) error {
@@ -64,6 +80,15 @@ func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) erro
6480 return fmt .Errorf ("could not get net class info: %w" , err )
6581 }
6682
83+ // Map every interface index to its name so that master devices can be
84+ // resolved by name. This is built from all links, not just the relevant
85+ // ones, because a master may itself be an ignored device while the
86+ // interfaces enslaved to it are not.
87+ ifNames := make (map [uint32 ]string , len (lMsgs ))
88+ for _ , msg := range lMsgs {
89+ ifNames [msg .Index ] = msg .Attributes .Name
90+ }
91+
6792 relevantLinks := make ([]rtnetlink.LinkMessage , 0 , len (lMsgs ))
6893 for _ , msg := range lMsgs {
6994 if ! c .ignoredDevicesPattern .MatchString (msg .Attributes .Name ) {
@@ -128,6 +153,32 @@ func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) erro
128153 ch <- prometheus .MustNewConstMetric (altnameDesc , prometheus .GaugeValue , infoValue , strings .ToValidUTF8 (altname , "\uFFFD " ), msg .Attributes .Name )
129154 }
130155 }
156+
157+ // Only the direct master is reported. An interface enslaved to a
158+ // bridge that is itself enslaved to a VRF has master_kind="bridge".
159+ if msg .Attributes .Master != nil {
160+ if master , ok := ifNames [* msg .Attributes .Master ]; ok {
161+ // IFLA_INFO_SLAVE_KIND holds the master's kind as
162+ // reported by the kernel. It may be absent, in which
163+ // case the master is still reported with an empty kind.
164+ masterKind := ""
165+ if msg .Attributes .Info != nil {
166+ masterKind = msg .Attributes .Info .SlaveKind
167+ }
168+ ch <- prometheus .MustNewConstMetric (netclassMasterDesc , prometheus .GaugeValue , infoValue , msg .Attributes .Name , master , masterKind )
169+ }
170+ }
171+
172+ if msg .Attributes .Info != nil && msg .Attributes .Info .Kind == "vrf" {
173+ // rtnetlink has no vrf driver, so IFLA_INFO_DATA is left as
174+ // raw nested attributes.
175+ if data , ok := msg .Attributes .Info .Data .(* rtnetlink.LinkData ); ok {
176+ if table , ok := vrfTable (data .Data ); ok {
177+ ch <- prometheus .MustNewConstMetric (netclassVRFDesc , prometheus .GaugeValue , infoValue , msg .Attributes .Name , strconv .FormatUint (uint64 (table ), 10 ))
178+ }
179+ }
180+ }
181+
131182 pushMetric (ch , c .getFieldDesc ("address_assign_type" ), ifaceInfo .AddrAssignType , prometheus .GaugeValue , msg .Attributes .Name )
132183 pushMetric (ch , c .getFieldDesc ("carrier" ), msg .Attributes .Carrier , prometheus .GaugeValue , msg .Attributes .Name )
133184 pushMetric (ch , c .getFieldDesc ("carrier_changes_total" ), msg .Attributes .CarrierChanges , prometheus .CounterValue , msg .Attributes .Name )
@@ -220,6 +271,24 @@ func (c *netClassCollector) getLinkModes() ([]*ethtool.LinkMode, error) {
220271 return lms , err
221272}
222273
274+ // vrfTable decodes the routing table ID from the raw IFLA_INFO_DATA payload of
275+ // a vrf link. It reports false if the attribute is absent or malformed.
276+ func vrfTable (data []byte ) (uint32 , bool ) {
277+ // The attribute decoder defaults to native byte order, which is what
278+ // rtnetlink uses.
279+ ad , err := netlink .NewAttributeDecoder (data )
280+ if err != nil {
281+ return 0 , false
282+ }
283+ for ad .Next () {
284+ if ad .Type () == unix .IFLA_VRF_TABLE {
285+ table := ad .Uint32 ()
286+ return table , ad .Err () == nil
287+ }
288+ }
289+ return 0 , false
290+ }
291+
223292// getSysfsAttributes reads attributes that are absent from netlink but provided
224293// by sysfs.
225294func getSysfsAttributes (links []rtnetlink.LinkMessage ) (sysfs.NetClass , error ) {
0 commit comments