Skip to content

netmon: macos support - #396

Merged
npry merged 1 commit into
mainfrom
npry/netmon.mac
Sep 16, 2026
Merged

npry merged 1 commit into
mainfrom
npry/netmon.mac

Conversation

@npry

@npry npry commented Sep 3, 2026

Copy link
Copy Markdown
Member

Best place to start is ts_netmon/src/bsd/mod.rs: the module comment has a general overview of how things work, and then net_table below it is almost entirely nom parsing logic and zerocopy struct defs.

In short: we can get messages from either a PF_ROUTE socket (which don't include enough info to actually calculate route/interface diffs) or a sysctl, which does include everything. The strategy is to listen to the socket for changes which will trigger us calling the sysctl to dump the new state, reconcile the diff against our current state, and emit the deltas as netmon events. The debounce stream combinator in the parent commit is used to ratelimit this so that 3 addresses changing in quick succession don't cause 3 sysctl invocations.

The parsing is annoying, but it's worth pointing out that everything is just nested TLVs; there is always a length byte or u16 somewhere in every message header, which lets us be pretty confident about where we are in the stream. The first tests I wrote e.g. just deframe the stream into individual messages because that's easy to do without needing to know what the internal structure of each message looks like. Each top-level message has a header and a body, where the header is type-specific and the body is a bunch of back-to-back possibly-truncated, 4-byte-aligned-up sockaddrs (semantics indicated by a flags field in the header). Note that the headers have a type field (MessageType) which indicates what the message means — these superset the actual message header structure, which is set up as an enum in MessageHeader.

Closes #238

@npry
npry force-pushed the npry/netmon.mac branch 2 times, most recently from e079f75 to 90c889d Compare September 9, 2026 22:08
@npry
npry force-pushed the npry/netmon.mac branch 2 times, most recently from 9844d2f to ece33d8 Compare September 10, 2026 00:58
@npry
npry marked this pull request as ready for review September 10, 2026 01:17
@danderson danderson self-assigned this Sep 10, 2026

@danderson danderson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta love a good old fashioned BSD API with organic evolution over decades. No wait, not "love", what's the word I'm looking for...

Kudos for figuring this mess out! I have a bunch of nits, suggestions and questions, but nothing blocking I don't think.

Comment thread ts_netmon/src/bsd/mod.rs Outdated
Comment thread ts_netmon/src/bsd/mod.rs Outdated
Comment thread ts_netmon/src/bsd/mod.rs Outdated
Comment thread ts_netmon/src/bsd/mod.rs
Comment thread ts_netmon/src/bsd/mod.rs Outdated
Comment thread ts_netmon/src/bsd/message.rs Outdated
Comment thread ts_netmon/src/bsd/message.rs
Comment thread ts_netmon/src/bsd/message.rs
Comment thread ts_netmon/src/bsd/message.rs
Comment thread ts_netmon/src/bsd/message.rs Outdated
Route {
dst: self.dest_addr()?,
gateway: self.gateway().into_iter().collect(),
metric: metrics.hopcount.get() as _, // TODO: is this the correct field?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. If this is the route metric as in its priority relative to other equivalent routes on other interfaces, I don't think hopcount is the right one. Looking at rt_metrics in darwin's manpages, I don't see any field that's obviously a route metric value.

A quick interweb search says that macOS doesn't assign a metric to routes, but instead has a per-interface metric that is applies to all routes using that interface (this also feels vaguely familiar from when I implemented some routing stuff on windows, which shares BSD heritage for the socket APIs moreso than linux). So, I think for a route message in isolation, the metric would want to be an Option and be set to None on macOS, with higher level netmon logic propagating interface metrics onto routes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, I did start to look into this, got distracted — I believe that's right.

(also, windows has both per-route and per-interface metrics, and you have to add them, or you get the wrong answer :))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow that's a memory I'd suppressed (re: windows). Thanks :P

I guess the Windows semantics point at a lowest common denominator to target: give both interfaces and routes a metric value, and sum them once both route and interface metrics are known (beyond this logic, after change events are delivered, I think). Systems which don't use one or the other flavor of metric can set them to 0, and the sum works out on all platforms.

(until someone shows up with a platform where routes and interfaces both have metrics, but they combine in some other way like 0 on the route means use the interface default, sigh)

@npry
npry force-pushed the npry/netmon.mac branch 2 times, most recently from 9fb1d15 to 9f20569 Compare September 15, 2026 20:17
@npry
npry force-pushed the npry/netmon.mac branch 2 times, most recently from 40b192f to 5e861f6 Compare September 16, 2026 13:05
Base automatically changed from npry/futures.debounce to main September 16, 2026 13:22
Signed-off-by: Nathan Perry <nathan@tailscale.com>
Change-Id: Iba1ce303e8606274550f0ee838511a7c6a6a6964
@npry
npry merged commit b682c1a into main Sep 16, 2026
23 checks passed
@npry
npry deleted the npry/netmon.mac branch September 16, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

netmon: macOS support

2 participants