| // Copyright The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //go:build linux |
| |
| package sysfs |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "syscall" |
| |
| "github.com/prometheus/procfs/internal/util" |
| ) |
| |
| const netclassPath = "class/net" |
| |
| // NetClassIface contains info from files in /sys/class/net/<iface> |
| // for single interface (iface). |
| type NetClassIface struct { |
| Name string // Interface name |
| AddrAssignType *int64 // /sys/class/net/<iface>/addr_assign_type |
| AddrLen *int64 // /sys/class/net/<iface>/addr_len |
| Address string // /sys/class/net/<iface>/address |
| Broadcast string // /sys/class/net/<iface>/broadcast |
| Carrier *int64 // /sys/class/net/<iface>/carrier |
| CarrierChanges *int64 // /sys/class/net/<iface>/carrier_changes |
| CarrierUpCount *int64 // /sys/class/net/<iface>/carrier_up_count |
| CarrierDownCount *int64 // /sys/class/net/<iface>/carrier_down_count |
| DevID *int64 // /sys/class/net/<iface>/dev_id |
| Dormant *int64 // /sys/class/net/<iface>/dormant |
| Duplex string // /sys/class/net/<iface>/duplex |
| Flags *int64 // /sys/class/net/<iface>/flags |
| IfAlias string // /sys/class/net/<iface>/ifalias |
| IfIndex *int64 // /sys/class/net/<iface>/ifindex |
| IfLink *int64 // /sys/class/net/<iface>/iflink |
| LinkMode *int64 // /sys/class/net/<iface>/link_mode |
| MTU *int64 // /sys/class/net/<iface>/mtu |
| NameAssignType *int64 // /sys/class/net/<iface>/name_assign_type |
| NetDevGroup *int64 // /sys/class/net/<iface>/netdev_group |
| OperState string // /sys/class/net/<iface>/operstate |
| PhysPortID string // /sys/class/net/<iface>/phys_port_id |
| PhysPortName string // /sys/class/net/<iface>/phys_port_name |
| PhysSwitchID string // /sys/class/net/<iface>/phys_switch_id |
| Speed *int64 // /sys/class/net/<iface>/speed |
| TxQueueLen *int64 // /sys/class/net/<iface>/tx_queue_len |
| Type *int64 // /sys/class/net/<iface>/type |
| } |
| |
| // NetClass is collection of info for every interface (iface) in /sys/class/net. The map keys |
| // are interface (iface) names. |
| type NetClass map[string]NetClassIface |
| |
| // NetClassDevices scans /sys/class/net for devices and returns them as a list of names. |
| func (fs FS) NetClassDevices() ([]string, error) { |
| var res []string |
| path := fs.sys.Path(netclassPath) |
| |
| devices, err := os.ReadDir(path) |
| if err != nil { |
| return res, fmt.Errorf("cannot access dir %q: %w", path, err) |
| } |
| |
| for _, deviceDir := range devices { |
| if deviceDir.Type().IsRegular() { |
| continue |
| } |
| res = append(res, deviceDir.Name()) |
| } |
| |
| return res, nil |
| } |
| |
| // 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)) |
| if err != nil { |
| return nil, err |
| } |
| interfaceClass.Name = devicePath |
| |
| return interfaceClass, nil |
| } |
| |
| // NetClass returns info for all net interfaces (iface) read from /sys/class/net/<iface>. |
| func (fs FS) NetClass() (NetClass, error) { |
| devices, err := fs.NetClassDevices() |
| if err != nil { |
| return nil, err |
| } |
| |
| path := fs.sys.Path(netclassPath) |
| netClass := NetClass{} |
| for _, devicePath := range devices { |
| interfaceClass, err := parseNetClassIface(filepath.Join(path, devicePath)) |
| if err != nil { |
| return nil, err |
| } |
| interfaceClass.Name = devicePath |
| netClass[devicePath] = *interfaceClass |
| } |
| |
| return netClass, nil |
| } |
| |
| // canIgnoreError returns true if the error is non-fatal and can be ignored. |
| // Some kernels and some devices don't expose specific attributes or return |
| // errors when reading those attributes; we can ignore these errors and the |
| // attribute that caused them. |
| func canIgnoreError(err error) bool { |
| var errno syscall.Errno |
| |
| switch { |
| case os.IsNotExist(err): |
| return true |
| case os.IsPermission(err): |
| return true |
| case err.Error() == "operation not supported": |
| return true |
| case errors.Is(err, os.ErrInvalid): |
| return true |
| case errors.As(err, &errno) && (errno == syscall.EINVAL): |
| return true |
| } |
| // all other errors are fatal |
| return false |
| } |
| |
| // ParseNetClassAttribute parses a given file in /sys/class/net/<iface> |
| // and sets the value in a given NetClassIface object if the value was readable. |
| // It returns an error if the file cannot be read and the error is fatal. |
| func ParseNetClassAttribute(devicePath, attrName string, interfaceClass *NetClassIface) error { |
| attrPath := filepath.Join(devicePath, attrName) |
| value, err := util.SysReadFile(attrPath) |
| if err != nil { |
| if canIgnoreError(err) { |
| return nil |
| } |
| return fmt.Errorf("failed to read file %q: %w", attrPath, err) |
| } |
| |
| vp := util.NewValueParser(value) |
| switch attrName { |
| case "addr_assign_type": |
| interfaceClass.AddrAssignType = vp.PInt64() |
| case "addr_len": |
| interfaceClass.AddrLen = vp.PInt64() |
| case "address": |
| interfaceClass.Address = value |
| case "broadcast": |
| interfaceClass.Broadcast = value |
| case "carrier": |
| interfaceClass.Carrier = vp.PInt64() |
| case "carrier_changes": |
| interfaceClass.CarrierChanges = vp.PInt64() |
| case "carrier_up_count": |
| interfaceClass.CarrierUpCount = vp.PInt64() |
| case "carrier_down_count": |
| interfaceClass.CarrierDownCount = vp.PInt64() |
| case "dev_id": |
| interfaceClass.DevID = vp.PInt64() |
| case "dormant": |
| interfaceClass.Dormant = vp.PInt64() |
| case "duplex": |
| interfaceClass.Duplex = value |
| case "flags": |
| interfaceClass.Flags = vp.PInt64() |
| case "ifalias": |
| interfaceClass.IfAlias = value |
| case "ifindex": |
| interfaceClass.IfIndex = vp.PInt64() |
| case "iflink": |
| interfaceClass.IfLink = vp.PInt64() |
| case "link_mode": |
| interfaceClass.LinkMode = vp.PInt64() |
| case "mtu": |
| interfaceClass.MTU = vp.PInt64() |
| case "name_assign_type": |
| interfaceClass.NameAssignType = vp.PInt64() |
| case "netdev_group": |
| interfaceClass.NetDevGroup = vp.PInt64() |
| case "operstate": |
| interfaceClass.OperState = value |
| case "phys_port_id": |
| interfaceClass.PhysPortID = value |
| case "phys_port_name": |
| interfaceClass.PhysPortName = value |
| case "phys_switch_id": |
| interfaceClass.PhysSwitchID = value |
| case "speed": |
| interfaceClass.Speed = vp.PInt64() |
| case "tx_queue_len": |
| interfaceClass.TxQueueLen = vp.PInt64() |
| case "type": |
| interfaceClass.Type = vp.PInt64() |
| default: |
| return nil |
| } |
| |
| return nil |
| } |
| |
| // parseNetClassIface scans predefined files in /sys/class/net/<iface> |
| // directory and gets their contents. |
| func parseNetClassIface(devicePath string) (*NetClassIface, error) { |
| interfaceClass := NetClassIface{} |
| |
| files, err := os.ReadDir(devicePath) |
| if err != nil { |
| return nil, err |
| } |
| |
| for _, f := range files { |
| if !f.Type().IsRegular() { |
| continue |
| } |
| if err := ParseNetClassAttribute(devicePath, f.Name(), &interfaceClass); err != nil { |
| return nil, err |
| } |
| } |
| |
| return &interfaceClass, nil |
| } |