Commit 319e85f
feat(net): native OS reachability backends with automatic fallback (#138)
Adds native per-OS network reachability backends for the DESIGN s5.8.2
probe-1
(OS connectivity) check in `driven-net`, tried before the existing
generic TCP
probe with transparent runtime fallback. Refs #34.
Until now `Backend::os_online` was a plain TCP connect to anycast DNS,
with the
native OS APIs explicitly deferred (`crates/driven-net/src/lib.rs`).
This wires
them in, mirroring `driven-power`'s cfg-gated per-OS module +
pure-classifier
precedent. No public API change: `ReqwestBackend::new()` and the
`driven_core::network::Backend` trait are unchanged, so no caller was
touched.
## Per-OS backends (new `crates/driven-net/src/reachability.rs`)
- **Windows:** `INetworkListManager::GetConnectivity` via the `windows`
crate
COM (`CoCreateInstance` after `CoInitializeEx`, balanced by an RAII
`CoUninitialize` guard, `RPC_E_CHANGED_MODE` tolerated) - same COM
discipline
as `driven-power`'s metered read. The `NLM_CONNECTIVITY` bitmask is
classified
by the pure `classify_nlm_connectivity`.
- **Linux:** NetworkManager's aggregate `Connectivity` property on
`org.freedesktop.NetworkManager` over the system bus, read via zbus's
BLOCKING
API on a `spawn_blocking` thread. NetworkManager / bus absent -> read
fails ->
`Unknown` -> TCP fallback (handled gracefully). Classified by
`classify_nm_connectivity`.
- **macOS:** a process-lifetime `NWPathMonitor` whose update handler
caches
`nw_path_get_status` in an `AtomicU8` (declared via `extern "C"` +
`#[link(name = "Network", kind = "framework")]`, block from `block2`).
Classified by `classify_nw_path_status`.
The native result feeds the SAME transport-agnostic `classify.rs`
pipeline
unchanged - only probe 1 (`os_online`) gains a native source; probes 2-3
(captive + service) stay reqwest-based.
## Selection + fallback semantics
`os_online` consults `detect_reachability()` (compile-time-selected per
OS)
FIRST, resolved by the pure `resolve_native`:
- **Online** (any active connection - internet, link-local, limited,
portal) ->
`true`, proceed to captive + service probes.
- **Offline** (confident no active connection - airplane mode / rfkill /
no
interface) -> `false`, short-circuit to `NetworkState::Offline`.
- **Unknown** (API unavailable, ambiguous verdict, not-yet-delivered
NWPath, or
a target with no native backend) -> transparent fallback to the existing
TCP
probe. Logged once at debug level, never per-call.
**Deliberate direction (never Offline on a connected link):** only a
confident
no-connection verdict maps to `Offline`; every ambiguity collapses to
`Unknown`
-> TCP fallback, never a wrong `Offline`. This protects the distinct
CaptivePortal
(drives the "Sign in to network" tray action) and NoInternet (30s
re-probe)
states from being hidden behind Offline's "re-probe only on
interface-up" path.
**Behavior note (Windows, positive change):** a
connected-but-no-Internet or
captive-portal machine reports `IPV4_LOCALNETWORK` with no internet bit
(NOT
`DISCONNECTED`). The old TCP probe rendered that as Offline; the native
read now
maps it to `Online`, so the HTTP probes classify it precisely as
NoInternet /
CaptivePortal. Verified locally on this Windows host: normal network ->
Online.
## Dependency additions (per-OS, minimal - justification)
- Windows: `windows = "0.62"` with `Win32_Foundation`,
`Win32_System_Com`,
`Win32_Networking_NetworkListManager`. Same crate + version already used
by
`driven-power`; only the NLM + COM surface is enabled.
- Linux: `zbus = "5"` (`tokio` feature). Same crate + version + feature
set
`driven-power` already resolves, so the workspace keeps ONE zbus version
and no
new transitive tree; chosen over a raw D-Bus binding because it is
already in
the tree and gives a typed `#[proxy]` for the one property read.
- macOS: `block2 = "0.6"` only (the Network.framework + libdispatch C
functions
are hand-declared `extern "C"`, so no `core-foundation`/`objc2` needed
for this
read).
## Tests
- Pure selection/fallback + classifier logic (`resolve_native`,
`classify_nlm_connectivity`, `classify_nm_connectivity`,
`classify_nw_path_status`, `Reachability::from_u8`) - compiled and run
on
EVERY OS, including the Windows-regression guard that a local-only NLM
mask
maps to Online, not Offline.
- Per-OS native-read smoke tests gated like `driven-power` (compile
everywhere,
run natively): `windows_read_connectivity_is_total` exercises the live
NLM COM
read; Linux/macOS equivalents run on their CI legs.
- `os_online_is_total` exercises the whole probe-1 path (native read +
fallback)
end to end on this Windows host.
- Existing `classify.rs` + DNS/topology tests unchanged and still green.
## Gates
Run locally on this Windows host, all green: `cargo fmt --check`,
`cargo clippy --workspace --all-targets -- -D warnings` (the
`--all-targets`
superset also type-checks every target, covering `cargo check
--workspace`),
`cargo check -p driven-net --locked` (lockfile consistent post-rebase),
`cargo test -p driven-net` (23 passed). The Windows leg exercises the
live NLM
COM read; the Linux + macOS backends are compile-everywhere and verified
on
their CI legs (they cannot run on this Windows host). LF, no em-dashes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QZQVP2tUuTLh8oL31D8heC
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent dbd4809 commit 319e85f
4 files changed
Lines changed: 741 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
29 | 60 | | |
30 | 61 | | |
31 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
16 | 19 | | |
17 | 20 | | |
18 | 21 | | |
| |||
35 | 38 | | |
36 | 39 | | |
37 | 40 | | |
| 41 | + | |
38 | 42 | | |
39 | 43 | | |
| 44 | + | |
40 | 45 | | |
41 | 46 | | |
42 | 47 | | |
| |||
157 | 162 | | |
158 | 163 | | |
159 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
160 | 170 | | |
161 | 171 | | |
162 | 172 | | |
| |||
183 | 193 | | |
184 | 194 | | |
185 | 195 | | |
| 196 | + | |
186 | 197 | | |
187 | 198 | | |
188 | 199 | | |
| |||
212 | 223 | | |
213 | 224 | | |
214 | 225 | | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
215 | 268 | | |
216 | 269 | | |
217 | 270 | | |
| |||
289 | 342 | | |
290 | 343 | | |
291 | 344 | | |
292 | | - | |
| 345 | + | |
| 346 | + | |
293 | 347 | | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
301 | | - | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
302 | 359 | | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
311 | 365 | | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
319 | | - | |
320 | | - | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
327 | | - | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
332 | | - | |
333 | | - | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
334 | 377 | | |
| 378 | + | |
335 | 379 | | |
336 | 380 | | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | 381 | | |
343 | 382 | | |
344 | 383 | | |
| |||
570 | 609 | | |
571 | 610 | | |
572 | 611 | | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
573 | 627 | | |
574 | 628 | | |
575 | 629 | | |
| |||
0 commit comments