macOS Network Metrics Using sysctl()

Published on
Last Updated on

TL;DR

Updates

Network Metrics on macOS

As part of my work on the Buck2 build system, I needed a way to observe the network throughput of the system. After some research, the conclusion was to use sysctl() with NET_RT_IFLIST2: this provided access to 64bit metrics1 which do not suffer from overflowing that affects the 32bit fields of the older APIs.

I wrote up a short sample program to quickly test the API. While the metrics for packets sent/received exactly matched the ones from Activity Monitor, the traffic metrics did not. I noticed two interesting behaviours:

I decided to file a TSI with Apple before digging any further. Thankfully, Quinn resolved the mystery of the inaccurate numbers.

1KiB Units

If you looked at the traffic metrics, they would only ever increase in multiples of 1KiB. The reason for the behaviour is that the kernel applies batching to prevent malicious code from fingerprinting the system. This restriction applies only to 3rd party programs (i.e., not codesigned by Apple).

For example, if you were to copy the netstat binary and re-sign it with an adhoc code signature, you will observe the batching while the Apple-signed binary works without any issues.

Inaccurate Numbers

Testing revealed that sometimes the API returned inaccurate traffic metrics. Upon further investigation, it became clear that the API truncates and wraps around the traffic metrics at the 4GiB mark. Again, this only affects 3rd party programs.

The behaviour is confirmed to be a bug in the kernel as of macOS Ventura 13.2.1 and it’s tracked as rdar://106029568.

Activity Monitor

Activity Monitor and nettop(1) use the private NetworkStatistics.framework to get network metrics. In the *OS Internals, Volume I book, there’s a bonus chapter which covers the details of how the private API works.

Netbottom is a clone of nettop(1) that shows how to use the private APIs.

Alternative APIs

Unfortunately, there’s no alternative public API that can return 64bit metrics on macOS. Using getifaddrs() would only expose a struct if_data which contains 32bit fields that overflow quickly on modern systems: it does not expose struct if_data64.

Rust Crates

If you’re using Rust, the following crates use the NET_RT_IFLIST2 API to get metrics on macOS:

Update (31 Mar, 2023)

Many thanks to Mojo_66 who reached out to note that we can use an additional sysctl() call to get 64bit network metrics which do not suffer from trunctation. Accordingly, I’ve updated the sample code.

Furthermore, using IFMIB_IFDATA does not result in 1KiB batching of the reported metrics. My assumption is that this is a bug and would be fixed in a future version of macOS to prevent fingerprinting.

Thanks

Many thanks to Quinn “The Eskimo!” for investigating and digging into the macOS kernel. Many thanks to Mojo_66 for pointing out a 64bit network metrics API which does not suffer from truncation.


  1. struct if_data64 from if_var.h↩︎

← Back to Writings