From 0a19628e0cfce8ae07368a7ccb5ec5f1ff2635c9 Mon Sep 17 00:00:00 2001 From: Brandon Ewing Date: Wed, 16 Mar 2022 17:28:59 -0500 Subject: [PATCH] Add structures for interface bonding information Update net_class.go with new structures to handle bonding driver information Add new true/false values to internal.util.parse.ParseBool References: torvalds/linux/drivers/net/bonding/bond_sysfs.c torvalds/linux/drivers/net/bonding/bond_sysfs_slave.c torvalds/linux/include/net/bonding.h torvalds/linux/include/net/bond_options.h torvalds/linux/include/net/bond_3ad.h Signed-off-by: Brandon Ewing --- internal/util/parse.go | 4 +- sysfs/net_class.go | 351 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 320 insertions(+), 35 deletions(-) diff --git a/internal/util/parse.go b/internal/util/parse.go index 22cb07a6b..d7e8c9bf8 100644 --- a/internal/util/parse.go +++ b/internal/util/parse.go @@ -86,9 +86,9 @@ func ReadIntFromFile(path string) (int64, error) { func ParseBool(b string) *bool { var truth bool switch b { - case "enabled": + case "enabled", "up", "1": truth = true - case "disabled": + case "disabled", "down", "0": truth = false default: return nil diff --git a/sysfs/net_class.go b/sysfs/net_class.go index 6dff98e69..2dedbd7de 100644 --- a/sysfs/net_class.go +++ b/sysfs/net_class.go @@ -18,44 +18,103 @@ package sysfs import ( "fmt" "io/ioutil" + "net" "os" "path/filepath" + "strconv" + "strings" "github.com/prometheus/procfs/internal/util" ) const netclassPath = "class/net" +// NetClassBondAttrs contains info from files in /sys/class/net//bonding +// for a bonding controller interface (iface) +type NetClassBondAttrs struct { + ActiveDevice *NetClassIface // /sys/class/net//bonding/active_slave + AdActorKey *uint64 // /sys/class/net//bonding/ad_actor_key (Requires CAP_NET_ADMIN) + AdActorSysPriority *uint64 // /sys/class/net//bonding/ad_actor_sys_prio (Requires CAP_NET_ADMIN) + AdActorSystem *net.HardwareAddr // /sys/class/net//bonding/ad_actor_system (Requires CAP_NET_ADMIN) + AdAggregator *uint64 // /sys/class/net//bonding/ad_aggregator + AdNumPorts *uint64 // /sys/class/net//bonding/ad_num_ports + AdPartnerKey *uint64 // /sys/class/net//bonding/ad_partner_key (Requires CAP_NET_ADMIN) + AdPartnerMac *net.HardwareAddr // /sys/class/net//bonding/ad_partner_mac (Requires CAP_NET_ADMIN) + AdSelect *string // /sys/class/net//bonding/ad_select + AdUserPortKey *uint64 // /sys/class/net//bonding/ad_user_port_key (Requires CAP_NET_ADMIN) + AllDevicesActive *bool // /sys/class/net//bonding/all_slaves_active + ARPAllTargets *string // /sys/class/net//bonding/arp_all_targets + ARPInterval *uint64 // /sys/class/net//bonding/arp_interval + ARPIPTarget *net.IP // /sys/class/net//bonding/arp_ip_target + ARPValidate *string // /sys/class/net//bonding/arp_validate + DownDelay *uint64 // /sys/class/net//bonding/downdelay + FailoverMac *string // /sys/class/net//bonding/failover_mac + LACPRate *string // /sys/class/net//bonding/lacp_rate + LPInterval *uint64 // /sys/class/net//bonding/lp_interval + MIIMon *uint64 // /sys/class/net//bonding/miimon + MIIStatus *bool // /sys/class/net//bonding/mii_status + MinLinks *uint64 // /sys/class/net//bonding/min_links + Mode *string // /sys/class/net//bonding/mode + NumberGratuitousArp *uint64 // /sys/class/net//bonding/num_grat_arp + NumberUnsolicitedNeighborAdvertisement *uint64 // /sys/class/net//bonding/num_unsol_na + PacketsPerDevice *uint64 // /sys/class/net//bonding/packets_per_slave + PrimaryDevice *string // /sys/class/net//bonding/primary + PrimaryReselect *string // /sys/class/net//bonding/primary_reselect + DeviceQueueIDs map[string]uint64 // /sys/class/net//bonding/queue_id + ResendIgmp *uint64 // /sys/class/net//bonding/resend_igmp + Devices []*NetClassIface // /sys/class/net//bonding/slaves + TLBDynamicLB *uint64 // /sys/class/net//bonding/tlb_dynamic_lb + UpDelay *uint64 // /sys/class/net//bonding/updelay + UseCarrier *bool // /sys/class/net//bonding/use_carrier + TransmitHashPolicy *string // /sys/class/net//bonding/xmit_hash_policy +} + +// NetClassBondDeviceAttrs contains info from files in /sys/class/net//bonding_slave +// for a bonding device interface (iface) +type NetClassBondDeviceAttrs struct { + Controller *NetClassIface // /sys/class/net//master + AdActorOperationalPortState *uint64 // /sys/class/net//bonding_slave/ad_actor_oper_port_state + AdAggregatorId *uint64 // /sys/class/net//bonding_slave/ad_aggregator_id + AdPartnerOperationalPortState *uint64 // /sys/class/net//bonding_slave/ad_partner_oper_port_state + LinkFailureCount *uint64 // /sys/class/net//bonding_slave/link_failure_count + MiiStatus *bool // /sys/class/net//bonding_slave/mii_status + PermamentHWAddress *net.HardwareAddr // /sys/class/net//bonding_slave/perm_hwaddr + QueueID *uint64 // /sys/class/net//bonding_slave/queue_id + State *uint64 // /sys/class/net//bonding_slave/state +} + // NetClassIface contains info from files in /sys/class/net/ // for single interface (iface). type NetClassIface struct { - Name string // Interface name - AddrAssignType *int64 // /sys/class/net//addr_assign_type - AddrLen *int64 // /sys/class/net//addr_len - Address string // /sys/class/net//address - Broadcast string // /sys/class/net//broadcast - Carrier *int64 // /sys/class/net//carrier - CarrierChanges *int64 // /sys/class/net//carrier_changes - CarrierUpCount *int64 // /sys/class/net//carrier_up_count - CarrierDownCount *int64 // /sys/class/net//carrier_down_count - DevID *int64 // /sys/class/net//dev_id - Dormant *int64 // /sys/class/net//dormant - Duplex string // /sys/class/net//duplex - Flags *int64 // /sys/class/net//flags - IfAlias string // /sys/class/net//ifalias - IfIndex *int64 // /sys/class/net//ifindex - IfLink *int64 // /sys/class/net//iflink - LinkMode *int64 // /sys/class/net//link_mode - MTU *int64 // /sys/class/net//mtu - NameAssignType *int64 // /sys/class/net//name_assign_type - NetDevGroup *int64 // /sys/class/net//netdev_group - OperState string // /sys/class/net//operstate - PhysPortID string // /sys/class/net//phys_port_id - PhysPortName string // /sys/class/net//phys_port_name - PhysSwitchID string // /sys/class/net//phys_switch_id - Speed *int64 // /sys/class/net//speed - TxQueueLen *int64 // /sys/class/net//tx_queue_len - Type *int64 // /sys/class/net//type + Name string // Interface name + AddrAssignType *int64 // /sys/class/net//addr_assign_type + AddrLen *int64 // /sys/class/net//addr_len + Address string // /sys/class/net//address + Broadcast string // /sys/class/net//broadcast + BondAttrs *NetClassBondAttrs // /sys/class/net//bonding + BondDeviceAttrs *NetClassBondDeviceAttrs // /sys/class/net//bonding_slave + Carrier *int64 // /sys/class/net//carrier + CarrierChanges *int64 // /sys/class/net//carrier_changes + CarrierUpCount *int64 // /sys/class/net//carrier_up_count + CarrierDownCount *int64 // /sys/class/net//carrier_down_count + DevID *int64 // /sys/class/net//dev_id + Dormant *int64 // /sys/class/net//dormant + Duplex string // /sys/class/net//duplex + Flags *int64 // /sys/class/net//flags + IfAlias string // /sys/class/net//ifalias + IfIndex *int64 // /sys/class/net//ifindex + IfLink *int64 // /sys/class/net//iflink + LinkMode *int64 // /sys/class/net//link_mode + MTU *int64 // /sys/class/net//mtu + NameAssignType *int64 // /sys/class/net//name_assign_type + NetDevGroup *int64 // /sys/class/net//netdev_group + OperState string // /sys/class/net//operstate + PhysPortID string // /sys/class/net//phys_port_id + PhysPortName string // /sys/class/net//phys_port_name + PhysSwitchID string // /sys/class/net//phys_switch_id + Speed *int64 // /sys/class/net//speed + TxQueueLen *int64 // /sys/class/net//tx_queue_len + Type *int64 // /sys/class/net//type } // NetClass is collection of info for every interface (iface) in /sys/class/net. The map keys @@ -84,15 +143,14 @@ func (fs FS) NetClassDevices() ([]string, error) { // NetClassByIface returns info for a single net interfaces (iface). func (fs FS) NetClassByIface(devicePath string) (*NetClassIface, error) { - path := fs.sys.Path(netclassPath) - - interfaceClass, err := parseNetClassIface(filepath.Join(path, devicePath)) + devices, err := fs.NetClass() if err != nil { return nil, err } - interfaceClass.Name = devicePath - - return interfaceClass, nil + if device, found := devices[devicePath]; found { + return &device, nil + } + return nil, fmt.Errorf("device %s not found", devicePath) } // NetClass returns info for all net interfaces (iface) read from /sys/class/net/. @@ -112,6 +170,9 @@ func (fs FS) NetClass() (NetClass, error) { interfaceClass.Name = devicePath netClass[devicePath] = *interfaceClass } + if err := fs.resolveBondingRelationships(&netClass); err != nil { + return nil, err + } return netClass, nil } @@ -194,6 +255,230 @@ func parseNetClassIface(devicePath string) (*NetClassIface, error) { interfaceClass.Type = vp.PInt64() } } + bondingPath := filepath.Join(devicePath, "bonding") + if _, err := os.Stat(bondingPath); !os.IsNotExist(err) { + interfaceClass.BondAttrs, err = parseNetClassBondAttrs(bondingPath) + if err != nil { + return nil, err + } + } + bondingDevicesPath := filepath.Join(devicePath, "bonding_slave") + if _, err := os.Stat(bondingDevicesPath); !os.IsNotExist(err) { + interfaceClass.BondDeviceAttrs, err = parseNetClassBondDeviceAttrs(bondingDevicesPath) + if err != nil { + return nil, err + } + } return &interfaceClass, nil } + +func parseNetClassBondAttrs(devicePath string) (*NetClassBondAttrs, error) { + attrs := NetClassBondAttrs{} + + files, err := ioutil.ReadDir(devicePath) + if err != nil { + return nil, err + } + + for _, f := range files { + if !f.Mode().IsRegular() { + continue + } + name := filepath.Join(devicePath, f.Name()) + value, err := util.SysReadFile(name) + if err != nil { + if os.IsNotExist(err) || os.IsPermission(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" { + continue + } + return nil, fmt.Errorf("failed to read file %q: %w", name, err) + } + vp := util.NewValueParser(value) + switch f.Name() { + case "ad_actor_key": + attrs.AdActorKey = vp.PUInt64() + case "ad_actor_sys_prio": + attrs.AdActorSysPriority = vp.PUInt64() + case "ad_actor_system": + mac, err := net.ParseMAC(value) + if err != nil { + return nil, err + } + attrs.AdActorSystem = &mac + case "ad_aggregator": + attrs.AdAggregator = vp.PUInt64() + case "ad_num_ports": + attrs.AdNumPorts = vp.PUInt64() + case "ad_partner_key": + attrs.AdPartnerKey = vp.PUInt64() + case "ad_partner_mac": + mac, err := net.ParseMAC(value) + if err != nil { + return nil, err + } + attrs.AdPartnerMac = &mac + case "ad_select": + attrs.AdSelect = &value + case "ad_user_port_key": + attrs.AdUserPortKey = vp.PUInt64() + case "all_slaves_active": + attrs.AllDevicesActive = util.ParseBool(value) + case "arp_all_targets": + attrs.ARPAllTargets = &value + case "arp_interval": + attrs.ARPInterval = vp.PUInt64() + case "arp_ip_target": + if ip := net.ParseIP(value); ip != nil { + attrs.ARPIPTarget = &ip + } + case "arp_valiate": + attrs.ARPValidate = &value + case "downdelay": + attrs.DownDelay = vp.PUInt64() + case "fail_over_mac": + attrs.FailoverMac = &value + case "lacp_rate": + attrs.LACPRate = &value + case "lp_interval": + attrs.LPInterval = vp.PUInt64() + case "miimon": + attrs.MIIMon = vp.PUInt64() + case "mii_status": + attrs.MIIStatus = util.ParseBool(value) + case "min_links": + attrs.MinLinks = vp.PUInt64() + case "mode": + attrs.Mode = &value + case "num_grat_arp": + attrs.NumberGratuitousArp = vp.PUInt64() + case "num_unsol_na": + attrs.NumberUnsolicitedNeighborAdvertisement = vp.PUInt64() + case "queue_id": + ids, err := parseDeviceQueueIDs(value) + if err != nil { + return nil, err + } + attrs.DeviceQueueIDs = ids + case "packets_per_slave": + attrs.PacketsPerDevice = vp.PUInt64() + case "primary_reselect": + attrs.PrimaryReselect = &value + case "resend_igmp": + attrs.ResendIgmp = vp.PUInt64() + case "tlb_dynamic_lb": + attrs.TLBDynamicLB = vp.PUInt64() + case "updelay": + attrs.UpDelay = vp.PUInt64() + case "use_carrier": + attrs.UseCarrier = util.ParseBool(value) + case "xmit_hash_policy": + attrs.TransmitHashPolicy = &value + } + + } + return &attrs, nil +} + +func parseNetClassBondDeviceAttrs(devicePath string) (*NetClassBondDeviceAttrs, error) { + attrs := NetClassBondDeviceAttrs{} + files, err := ioutil.ReadDir(devicePath) + if err != nil { + return nil, err + } + + for _, f := range files { + if !f.Mode().IsRegular() { + continue + } + name := filepath.Join(devicePath, f.Name()) + value, err := util.SysReadFile(name) + if err != nil { + if os.IsNotExist(err) || os.IsPermission(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" { + continue + } + return nil, fmt.Errorf("failed to read file %q: %w", name, err) + } + vp := util.NewValueParser(value) + switch f.Name() { + case "ad_actor_oper_port_state": + attrs.AdActorOperationalPortState = vp.PUInt64() + case "ad_aggregator_id": + attrs.AdAggregatorId = vp.PUInt64() + case "ad_partner_oper_port_state": + attrs.AdPartnerOperationalPortState = vp.PUInt64() + case "link_failure_count": + attrs.LinkFailureCount = vp.PUInt64() + case "mii_status": + attrs.MiiStatus = util.ParseBool(value) + case "perm_hwaddr": + mac, err := net.ParseMAC(value) + if err != nil { + return nil, err + } + attrs.PermamentHWAddress = &mac + case "queue_id": + attrs.QueueID = vp.PUInt64() + case "state": + attrs.State = vp.PUInt64() + } + } + return &attrs, nil +} + +func (fs FS) resolveBondingRelationships(netClass *NetClass) error { + for _, netClassIface := range *netClass { + if netClassIface.BondAttrs != nil { + path := filepath.Join(fs.sys.Path(netclassPath), netClassIface.Name, "bonding") + if _, err := os.Stat(filepath.Join(path, "active_slave")); !os.IsNotExist(err) { + active_slave, err := util.SysReadFile(filepath.Join(path, "active_slave")) + if err != nil { + return fmt.Errorf("unable to read %s", filepath.Join(path, "active_slave")) + } + if len(active_slave) > 0 { + if intf, exists := (*netClass)[active_slave]; exists { + netClassIface.BondAttrs.ActiveDevice = &intf + } else { + return fmt.Errorf("unable to find device %s", active_slave) + } + } + } + if _, err := os.Stat(filepath.Join(path, "slaves")); !os.IsNotExist(err) { + devices, err := util.SysReadFile(filepath.Join(path, "slaves")) + if err != nil { + return fmt.Errorf("unable to read %s", filepath.Join(path, "slaves")) + } + for _, device := range strings.Split(devices, " ") { + if intf, exists := (*netClass)[device]; exists { + netClassIface.BondAttrs.Devices = append(netClassIface.BondAttrs.Devices, &intf) + } else { + return fmt.Errorf("unable to find device %s", device) + } + } + } + } + if netClassIface.BondDeviceAttrs != nil { + path := filepath.Join(fs.sys.Path(netclassPath), netClassIface.Name, "master") + controller, err := filepath.EvalSymlinks(path) + if err != nil { + return fmt.Errorf("unable to read %s", path) + } + name := filepath.Base(controller) + if intf, exists := (*netClass)[name]; exists { + netClassIface.BondDeviceAttrs.Controller = &intf + } else { + return fmt.Errorf("unable to find device %s", controller) + } + } + } + return nil +} + +func parseDeviceQueueIDs(data string) (queueIDs map[string]uint64, err error) { + for _, line := range strings.Split(data, " ") { + sep := strings.LastIndex(line, ":") + if queueIDs[line[:sep]], err = strconv.ParseUint(line[sep+1:], 10, 64); err != nil { + return nil, err + } + } + return +}