From 979a34963757b4b9207567c3c8552056f603e64c Mon Sep 17 00:00:00 2001 From: Siavash Safi Date: Sun, 18 May 2025 11:53:48 +0200 Subject: [PATCH] fix: darwin netdev i/o bytes metric (#3336) There is a bug in darwin kernel since macOS Ventura 13.2.1, which results in interface i/o bytes values to be truncated at 4GiB. This change uses a workaround to collect the same metrics, taking advantage of another bug. fixes #3333 Signed-off-by: Siavash Safi --- collector/netdev_darwin.go | 133 ++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 38 deletions(-) diff --git a/collector/netdev_darwin.go b/collector/netdev_darwin.go index 2367ebe5..42835594 100644 --- a/collector/netdev_darwin.go +++ b/collector/netdev_darwin.go @@ -22,6 +22,7 @@ import ( "fmt" "log/slog" "net" + "unsafe" "golang.org/x/sys/unix" ) @@ -71,51 +72,107 @@ func getIfaceData(index int) (*ifMsghdr2, error) { return nil, err } err = binary.Read(bytes.NewReader(rawData), binary.LittleEndian, &data) + if err != nil { + return &data, err + } + + /* + As of macOS Ventura 13.2.1, there’s a kernel bug which truncates traffic values at the 4GiB mark. + This is a workaround to fetch the interface traffic metrics using a sysctl call. + Apple wants to prevent fingerprinting by 3rdparty apps and might fix this bug in future which would break this implementation. + */ + mib := []int32{ + unix.CTL_NET, + unix.AF_LINK, + 0, // NETLINK_GENERIC: functions not specific to a type of iface + 2, //IFMIB_IFDATA: per-interface data table + int32(index), + 1, // IFDATA_GENERAL: generic stats for all kinds of ifaces + } + + var mibData ifMibData + size := unsafe.Sizeof(mibData) + + if _, _, errno := unix.Syscall6( + unix.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(len(mib)), + uintptr(unsafe.Pointer(&mibData)), + uintptr(unsafe.Pointer(&size)), + uintptr(unsafe.Pointer(nil)), + 0, + ); errno != 0 { + return &data, err + } + + var ifdata ifData64 + err = binary.Read(bytes.NewReader(mibData.Data[:]), binary.LittleEndian, &ifdata) + if err != nil { + return &data, err + } + + data.Data.Ibytes = ifdata.Ibytes + data.Data.Obytes = ifdata.Obytes return &data, err } +// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/if.h#L220-L232 type ifMsghdr2 struct { - Msglen uint16 - Version uint8 - Type uint8 - Addrs int32 - Flags int32 - Index uint16 - _ [2]byte - SndLen int32 - SndMaxlen int32 - SndDrops int32 - Timer int32 - Data ifData64 + Msglen uint16 // to skip over non-understood messages + Version uint8 // future binary compatabilit + Type uint8 // message type + Addrs int32 // like rtm_addrs + Flags int32 // value of if_flags + Index uint16 // index for associated ifp + _ [2]byte // padding for alignment + SndLen int32 // instantaneous length of send queue + SndMaxlen int32 // maximum length of send queue + SndDrops int32 // number of drops in send queue + Timer int32 // time until if_watchdog called + Data ifData64 // statistics and other data } -// https://github.com/apple/darwin-xnu/blob/main/bsd/net/if_var.h#L199-L231 +// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/if_var.h#L207-L235 type ifData64 struct { - Type uint8 - Typelen uint8 - Physical uint8 - Addrlen uint8 - Hdrlen uint8 - Recvquota uint8 - Xmitquota uint8 - Unused1 uint8 - Mtu uint32 - Metric uint32 - Baudrate uint64 - Ipackets uint64 - Ierrors uint64 - Opackets uint64 - Oerrors uint64 - Collisions uint64 - Ibytes uint64 - Obytes uint64 - Imcasts uint64 - Omcasts uint64 - Iqdrops uint64 - Noproto uint64 - Recvtiming uint32 - Xmittiming uint32 - Lastchange unix.Timeval32 + Type uint8 // ethernet, tokenring, etc + Typelen uint8 // Length of frame type id + Physical uint8 // e.g., AUI, Thinnet, 10base-T, etc + Addrlen uint8 // media address length + Hdrlen uint8 // media header length + Recvquota uint8 // polling quota for receive intrs + Xmitquota uint8 // polling quota for xmit intrs + Unused1 uint8 // for future use + Mtu uint32 // maximum transmission unit + Metric uint32 // routing metric (external only) + Baudrate uint64 // linespeed + + // volatile statistics + Ipackets uint64 // packets received on interface + Ierrors uint64 // input errors on interface + Opackets uint64 // packets sent on interface + Oerrors uint64 // output errors on interface + Collisions uint64 // collisions on csma interfaces + Ibytes uint64 // total number of octets received + Obytes uint64 // total number of octets sent + Imcasts uint64 // packets received via multicast + Omcasts uint64 // packets sent via multicast + Iqdrops uint64 // dropped on input, this interface + Noproto uint64 // destined for unsupported protocol + Recvtiming uint32 // usec spent receiving when timing + Xmittiming uint32 // usec spent xmitting when timing + Lastchange unix.Timeval32 // time of last administrative change +} + +// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/if_mib.h#L65-L74 +type ifMibData struct { + Name [16]byte // name of interface + PCount uint32 // number of promiscuous listeners + Flags uint32 // interface flags + SendLength uint32 // instantaneous length of send queue + MaxSendLength uint32 // maximum length of send queue + SendDrops uint32 // number of drops in send queue + _ [4]uint32 // for future expansion + Data [128]byte // generic information and statistics } func getNetDevLabels() (map[string]map[string]string, error) {