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 <git@hosted.run>
This commit is contained in:
Siavash Safi 2025-05-18 11:53:48 +02:00 committed by GitHub
parent 43fb05c81d
commit 979a349637
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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
}
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
/*
As of macOS Ventura 13.2.1, theres 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
}
// https://github.com/apple/darwin-xnu/blob/main/bsd/net/if_var.h#L199-L231
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 // 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-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) {