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" "fmt"
"log/slog" "log/slog"
"net" "net"
"unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -71,51 +72,107 @@ func getIfaceData(index int) (*ifMsghdr2, error) {
return nil, err return nil, err
} }
err = binary.Read(bytes.NewReader(rawData), binary.LittleEndian, &data) err = binary.Read(bytes.NewReader(rawData), binary.LittleEndian, &data)
if err != nil {
return &data, err
}
/*
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
}
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 return &data, err
} }
// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/if.h#L220-L232
type ifMsghdr2 struct { type ifMsghdr2 struct {
Msglen uint16 Msglen uint16 // to skip over non-understood messages
Version uint8 Version uint8 // future binary compatabilit
Type uint8 Type uint8 // message type
Addrs int32 Addrs int32 // like rtm_addrs
Flags int32 Flags int32 // value of if_flags
Index uint16 Index uint16 // index for associated ifp
_ [2]byte _ [2]byte // padding for alignment
SndLen int32 SndLen int32 // instantaneous length of send queue
SndMaxlen int32 SndMaxlen int32 // maximum length of send queue
SndDrops int32 SndDrops int32 // number of drops in send queue
Timer int32 Timer int32 // time until if_watchdog called
Data ifData64 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 ifData64 struct {
Type uint8 Type uint8 // ethernet, tokenring, etc
Typelen uint8 Typelen uint8 // Length of frame type id
Physical uint8 Physical uint8 // e.g., AUI, Thinnet, 10base-T, etc
Addrlen uint8 Addrlen uint8 // media address length
Hdrlen uint8 Hdrlen uint8 // media header length
Recvquota uint8 Recvquota uint8 // polling quota for receive intrs
Xmitquota uint8 Xmitquota uint8 // polling quota for xmit intrs
Unused1 uint8 Unused1 uint8 // for future use
Mtu uint32 Mtu uint32 // maximum transmission unit
Metric uint32 Metric uint32 // routing metric (external only)
Baudrate uint64 Baudrate uint64 // linespeed
Ipackets uint64
Ierrors uint64 // volatile statistics
Opackets uint64 Ipackets uint64 // packets received on interface
Oerrors uint64 Ierrors uint64 // input errors on interface
Collisions uint64 Opackets uint64 // packets sent on interface
Ibytes uint64 Oerrors uint64 // output errors on interface
Obytes uint64 Collisions uint64 // collisions on csma interfaces
Imcasts uint64 Ibytes uint64 // total number of octets received
Omcasts uint64 Obytes uint64 // total number of octets sent
Iqdrops uint64 Imcasts uint64 // packets received via multicast
Noproto uint64 Omcasts uint64 // packets sent via multicast
Recvtiming uint32 Iqdrops uint64 // dropped on input, this interface
Xmittiming uint32 Noproto uint64 // destined for unsupported protocol
Lastchange unix.Timeval32 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) { func getNetDevLabels() (map[string]map[string]string, error) {