Skip to content

feat(mdns): configurable skipInterfaces skip-list - #6

Merged
keskad merged 3 commits into
mainfrom
feat/skip-wlan
Aug 10, 2026
Merged

feat(mdns): configurable skipInterfaces skip-list#6
keskad merged 3 commits into
mainfrom
feat/skip-wlan

Conversation

@keskad

@keskad keskad commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a configurable skipInterfaces list to Config (JSON, default []). The built-in docker/veth/br-*/cni/flannel/virbr list is always applied; skipInterfaces adds extra case-insensitive name-prefix matches on top.
  • wlan* is not skipped by default, so mDNS advertises on WiFi on a generic/laptop install. Operators who reserve the WiFi radio for another purpose add ["wlan"] (e.g. the BigFred hub, where wireless-programmer owns the radio, so mDNS must not leak bigfred.local / dcc-bus beacons onto a device config network).
  • Threaded &[String] through should_skip_iface, preferred_ipv4_*, preferred_ipv6_addrs, preferred_iface_indexes, and MdnsPublisher::register, carrying the list via AnswerSet/DesiredAds so it flows from Config through run.rs and legacy_unicast.rs and picks up hot-reloads.

The matching bigfred-os change sets skipInterfaces: ["wlan"] in the hub's microdns.json.

Test plan

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test clean.
  • skip_virtual_ifaces: wlan0/WLAN0 not skipped with &[], skipped with &["wlan"]; built-in virtual ifaces still skipped with &[].

keskad and others added 2 commits August 9, 2026 22:39
On the BigFred hub the on-board WiFi radio is an exclusive, on-demand
resource used only by wireless-programmer to associate to a device config
AP (e.g. a NewHeiko WiFred) for the duration of a programming job. It
never carries hub services, so advertising mDNS on it would leak
bigfred.local / dcc-bus beacons onto a customer's device config network.

Add wlan* to the hardcoded interface skip list (case-insensitive, matching
the existing docker/veth/br-* convention).

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the hardcoded wlan* skip with a configurable skipInterfaces list
on Config (JSON, default empty). The built-in docker/veth/br-*/cni/
flannel/virbr list is always applied; skipInterfaces adds extra
case-insensitive name-prefix matches on top.

By default wlan* is NOT skipped, so mDNS advertises on WiFi on a generic
or laptop install. Operators who reserve the WiFi radio for another
purpose (e.g. the BigFred hub, where wireless-programmer owns the radio)
add ["wlan"] to skipInterfaces so mDNS does not leak bigfred.local /
dcc-bus beacons onto a device config network.

Threaded &[String] through should_skip_iface, preferred_ipv4_*,
preferred_ipv6_addrs, preferred_iface_indexes, MdnsPublisher::register,
and the AnswerSet/DesiredAds so the configured list flows from Config
through run.rs and legacy_unicast.rs (and picks up hot-reloads).

Co-authored-by: Cursor <cursoragent@cursor.com>
@keskad keskad changed the title feat(mdns): skip wlan* interfaces feat(mdns): configurable skipInterfaces skip-list Aug 9, 2026
@keskad

keskad commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Review — configurable skipInterfaces

The design is right: keep the container/virtual-bridge list hard-coded (it's never wrong to skip veth/docker/br-), and make the site-specific part configuration. Defaulting to [] so wlan* is advertised means a laptop/dev install keeps working, and only the hub opts out — which is exactly the shape this needed.

Verified locally:

  • cargo clippy --all-targets clean, full test suite passes.
  • #[serde(rename_all = "camelCase")] on Config makes "skipInterfaces" the correct key for the bigfred-os side (feat(os): skip wlan* in microdns on the hub bigfred-os#30), and #[serde(default)] keeps existing config files loading unchanged.
  • Threading the list through DesiredAds and AnswerSet (both PartialEq) is the subtle part and it's done right: a hot-reloaded change to skipInterfaces now makes desired != last_desired and *w != next, so services are re-registered and multicast memberships refreshed rather than the change only taking effect on restart.
  • The matcher tests cover the interesting cases, including that a blank entry matches nothing and that prefix matching doesn't accidentally catch wlp3s0 under ["wlan"].

1. should_skip_iface allocates on every call, per entry

skip.iter().any(|p| {
    let p = p.trim().to_ascii_lowercase();
    !p.is_empty() && n.starts_with(&p)
})

to_ascii_lowercase() builds a fresh String for every skip entry, for every interface, on every call — and the function is called from four preferred_* loops plus the interface poll every ifaceMs (5 s). It's small in absolute terms, but this repo is deliberately allocation-conscious and the inputs are constant between reloads.

Two ways out. Normalize once at load (trim, lowercase, drop empties in Config::validate), which also makes the stored config self-describing; or compare in place, since n is already lowercased:

skip.iter().any(|p| {
    let p = p.trim();
    !p.is_empty() && n.get(..p.len()).is_some_and(|head| head.eq_ignore_ascii_case(p))
})

get(..) rather than direct slicing avoids a panic if an entry's byte length lands mid-char.

2. run.rs:175 diagnostic no longer tells the whole truth

"no UP non-loopback IPv4 (skipping docker/veth/br-*)"

On the hub with skipInterfaces: ["wlan"] and Ethernet unplugged, this is precisely the message an operator sees, and it actively hides the reason — wlan0 has an address, it's just skipped by policy. Suggest appending the configured list, e.g. ... (skipping docker/veth/br-* + configured ["wlan"]). This is the one place where the new feature's failure mode is user-visible, so it's worth the extra formatting cost.

3. Nothing pins the JSON key

tests/mdns_test.rs covers the matcher thoroughly, but there's no deserialization test, so the wire name skipInterfaces is guarded only by the struct-level rename_all. bigfred-os#30 ships a file containing that exact key, and a future field rename would turn it into a silent no-op (no deny_unknown_fields). A couple of lines in the config test suite would close it:

let c: Config = serde_json::from_str(r#"{"services":[],"skipInterfaces":["wlan"]}"#).unwrap();
assert_eq!(c.skip_interfaces, vec!["wlan".to_string()]);
// and: a config without the key yields an empty list

4. refresh_memberships takes a second, independent read lock

let skip: Vec<String> = state.read().map(|g| g.skip_interfaces.clone()).unwrap_or_default();

This is a separate acquisition from the one that produced want_v4, so in principle the addresses and the skip list can come from different generations of the AnswerSet. Harmless — the next poll converges, and a torn read here only costs one refresh cycle — but folding it into the existing guard would remove the question entirely.

5. has_usable_iface / primary_ip parameter churn

Both gained a skip: &[String] parameter but appear to have no in-tree callers. Fine if they're deliberately part of the public surface; otherwise they're signature churn on dead code and could keep their old shape (or go away).

6. Prefix matching is a foot-gun worth documenting

skipInterfaces: ["e"] silently disables eth0 and enp1s0, and the only symptom is "no UP non-loopback IPv4". The README addition is good on the why, but a one-line caution that entries are prefixes (not names or globs) — or a validate() warning for very short entries — would prevent someone reaching for ["w"] and losing mDNS entirely.

- should_skip_iface no longer allocates: the name is already lowercased, so
  compare its head with eq_ignore_ascii_case instead of building a String per
  skip entry per interface on every poll.
- refresh_memberships takes one read lock, so the addresses and the skip list
  come from the same generation of the AnswerSet.
- The "no UP non-loopback IPv4" diagnostic names the configured skips. On a
  hub with skipInterfaces set and Ethernet unplugged this is the message an
  operator sees, and it previously hid the reason entirely.
- Pin the JSON key: bigfred-os ships "skipInterfaces" and unknown fields are
  ignored, so a rename would silently turn the hub's opt-out into a no-op.
- README: spell out that entries are name prefixes, since a short entry would
  take eth0/enp1s0 with it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@keskad
keskad merged commit a26e2a8 into main Aug 10, 2026
3 checks passed
@keskad

keskad commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Review points applied

Pushed as 27a434a:

  • should_skip_iface no longer allocates. The name is already lowercased, so the head is compared with eq_ignore_ascii_case via get(..len) — no String per skip entry per interface per poll, and no panic risk on a byte length landing mid-char.
  • refresh_memberships takes one read lock, so the addresses and the skip list are guaranteed to come from the same generation of the AnswerSet.
  • The "no UP non-loopback IPv4" diagnostic names the configured skips. This is the message a hub operator sees when Ethernet is unplugged and wlan0 is the only addressed interface, and it previously hid the reason completely.
  • Two serde tests pin the JSON contract: "skipInterfaces": ["wlan"] deserializes into the field, and a config without the key still yields an empty list so existing files keep advertising on wlan*. bigfred-os#30 ships that exact key and unknown fields are ignored, so a rename would otherwise silently turn the hub's opt-out into a no-op.
  • README spells out that entries are prefixes, including the ["e"] foot-gun that would take eth0/enp1s0 with it.

cargo clippy --all-targets clean, 40 tests passing (was 38).

Left alone deliberately: has_usable_iface / primary_ip gained the parameter but have no in-tree callers — they were already unused public functions before this PR, so changing their shape here would be unrelated churn.

keskad added a commit to dcc-bigfred/bigfred-os that referenced this pull request Aug 10, 2026
Add "skipInterfaces": ["wlan"] to the hub's microdns config so mDNS does
not advertise bigfred.local / dcc-bus beacons on the on-board WiFi radio.
The radio is an exclusive, on-demand resource used only by
wireless-programmer to associate to a device config AP; advertising on it
would leak onto a customer's device config network. microdns defaults to
an empty skipInterfaces list (so laptops advertise on WiFi), so the hub
opts in explicitly.

Requires microdns with the configurable skipInterfaces field
(dcc-bigfred/microdns#6).

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant