Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Cross-link musl arm64 from x86_64 without a distro-specific musl-gcc package.
# Uses Rust's self-contained crt + rust-lld (rustup target add aarch64-unknown-linux-musl).
#
# CI (rust-musl-ci) overrides via CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc.
# Arch AUR alternative: aarch64-linux-musl-cross → linker = "aarch64-linux-musl-gcc".

[target.aarch64-unknown-linux-musl]
linker = "rust-lld"
rustflags = [
"-C", "link-self-contained=yes",
"-C", "target-feature=+crt-static",
]
23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RUSTUP_TOOLCHAIN ?= stable
export RUSTUP_TOOLCHAIN

.PHONY: all build release release-musl check test test-release-assertions \
clean fmt clippy
clean fmt clippy hub-upload deploy

all: build

Expand Down Expand Up @@ -41,3 +41,24 @@ clippy:
clean:
$(CARGO) clean
rm -rf dist

# --- Hub deploy (RO rootfs: binary lives on /data) -------------------------
# Hub runs Dropbear. Older images lack /usr/libexec/sftp-server; -O uses legacy scp.
# Harmless on images that ship openssh sftp-server (bigfred-os defconfig).
HUB ?= 192.168.0.1
HUB_USER ?= root
HUB_SSH ?= $(HUB_USER)@$(HUB)
SCP ?= scp
SCP_OPTS ?= -O
SSH ?= ssh
DIST_ARM64 ?= dist/microdns-linux-arm64
HUB_BIN_DIR ?= /data/opt/microdns

# Build arm64 musl binary and upload to the hub's writable /data partition.
# Requires /etc/init.d/microdns to prefer $(HUB_BIN_DIR)/microdns (bigfred-os overlay).
deploy: hub-upload

hub-upload: release-musl
@test -f $(DIST_ARM64) || { echo "error: $(DIST_ARM64) missing — run make release-musl" >&2; exit 1; }
$(SCP) $(SCP_OPTS) $(DIST_ARM64) $(HUB_SSH):/tmp/microdns
$(SSH) $(HUB_SSH) 'mkdir -p $(HUB_BIN_DIR) && cp /tmp/microdns $(HUB_BIN_DIR)/microdns && chmod 755 $(HUB_BIN_DIR)/microdns && rm -f /tmp/microdns && microinit stop microdns; microinit start microdns'
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,24 @@ per receiving interface so a WiFi client gets the WiFi address.
- Optional dcc-bus discovery: when `bigfred.enabled` (default true), polls the
loco-server Unix socket (`$DATA_DIR/run/bigfred.sock`) for `dcc_bus_list` and
advertises `_z21._udp` / `_withrottle._tcp` on the ports in that JSON. Missing
socket is retried every `retry.bigfredMs` (default 45s).
socket is retried with exponential backoff (2 s … `retry.bigfredMs`, default
45 s). Last-good dcc-bus ads are kept across a short socket outage and only
withdrawn after `retry.bigfredMs` of consecutive failures.
- Optional microinit watch: when `microinit.enabled` (default true), holds one
connection to `$DATA_DIR/run/microinit.sock` (`{type:watch,label_keys:["microdns-port"]}`)
and advertises running services that have `microdns-port` + `microdns-type`.
`microdns-host` is optional (kernel hostname if omitted). `microdns-txt-*`
labels become TXT pairs. Reconnect backoff is `retry.microinitReconnectMs`
(default 3s). Last-good ads are kept across a dropped socket.
- Periodic unsolicited re-announcements (`announce.periodMs`, default 55 s) plus
a 1/2/4/8 s burst after a real advertisement change. Re-register never sends
a goodbye (TTL 0); `unregister` is only used when a service is actually gone.
- Self-check every `selfcheck.periodMs` (default 60 s): IGMP 224.0.0.251 on
used NICs, mdns-sd thread alive, recent `Announce` events. Escalates
re-announce → daemon recreate.
- Optional Z21 UDP LAN discovery beacon (LAN_GET_SERIAL_NUMBER reply broadcast)
- Unix control socket (`$DATA_DIR/run/microdns.sock`): `microdns services list` queries the live daemon
- Unix control socket (`$DATA_DIR/run/microdns.sock`): `microdns services list`
and `microdns doctor` query the live daemon
- Hot-reload via inotify on the config file
- Static musl builds for linux/arm64 and linux/amd64

Expand Down Expand Up @@ -67,6 +76,8 @@ Default path: `$DATA_DIR/etc/microdns.json`. Created with defaults if missing.
"ifaceMs": 5000,
"microinitReconnectMs": 3000
},
"announce": { "periodMs": 55000, "burstCount": 4 },
"selfcheck": { "periodMs": 60000 },
"skipInterfaces": [],
"interfaces": []
}
Expand All @@ -80,11 +91,18 @@ Default path: `$DATA_DIR/etc/microdns.json`. Created with defaults if missing.
- `dccBus.host` (optional): DNS-SD hostname without `.local` for `_z21._udp` /
`_withrottle._tcp` ads. When omitted, mdns-sd uses the kernel hostname and
`microdns services list` shows `-` in HOST. Product templates set `"bigfred"`.
- `retry.bigfredMs` (default `45000`): wait between probes while the socket is down.
- `retry.bigfredMs` (default `45000`): cap on backoff while the BigFred socket
is down, and grace period before withdrawing last-good dcc-bus ads.
First failures retry at 2 s, 4 s, 8 s, … up to this cap.
`retry.pollMs` (default `25000`) is the poll interval once connected.
Existing files may still use `retry.microinitMs`; that alias still maps to
`pollMs` (BigFred), **not** the microinit watch. Use `retry.microinitReconnectMs`
for watch reconnect backoff.
- `announce.periodMs` (default `55000`): unsolicited re-announce interval, kept
below the 120 s host-record TTL. `announce.burstCount` (default `4`) extra
announcements at 1 s, 2 s, 4 s, 8 s after a real change.
- `selfcheck.periodMs` (default `60000`): how often to verify multicast
membership and recent announcements.
- Retry intervals are configurable; config changes are hot-reloaded.
- `skipInterfaces` (default `[]`): extra interface-name prefixes to skip
(case-insensitive), in addition to the built-in docker/veth/br-*/cni/
Expand All @@ -104,7 +122,12 @@ Default path: `$DATA_DIR/etc/microdns.json`. Created with defaults if missing.
- Hostname A/AAAA answers (`bigfred.local`) are selected **per receiving
interface** (via `IP_PKTINFO`): a client querying on WiFi gets the WiFi
address, not the Ethernet one. Interface add/remove/address changes are
detected via rtnetlink with polling fallback.
detected via rtnetlink with polling fallback. Netlink events on skipped
interfaces (e.g. `wlan0` on the hub) do not re-announce Ethernet mDNS.
IGMP leave+join is reserved for real address-set changes and suspend/resume.
- On a BigFred hub, `micronet` in gateway mode re-probes foreign DHCP every 15 s
and a failed `micronet check` tears the address down. microdns treats that as
a normal address change (re-announce, no goodbye).

## Run

Expand Down Expand Up @@ -132,12 +155,31 @@ plus optional `host` / `txt`). The CLI talks to the live daemon over the ctl
socket — it does not read `microdns.json` on its own. If the socket is missing,
the error is the same shape as `bf` (`is microdns running?`).

Diagnose sockets, IGMP membership, mdns-sd counters, and the last self-check
(works even if the daemon is down for the local kernel half):

```bash
microdns doctor
microdns doctor -o json
```

On a second machine in the same LAN, confirm there are no goodbye packets
(TTL 0) except when a service is actually removed, and that unsolicited
announcements repeat about every 55 s:

```bash
tcpdump -ni <iface> -vv 'udp port 5353 and host <hub-ip>'
# look for: bigfred._http._tcp.local, TTL 0 (bad unless the service went away)
microinit logs microdns --follow # on the hub
cat /proc/net/igmp # 224.0.0.251 on eth0
```

Flags:

- `--config <path>` — config file (default `$DATA_DIR/etc/microdns.json`)
- `--data-dir <path>` — set `DATA_DIR` before start
- `--socket <path>` — ctl socket (default `$DATA_DIR/run/microdns.sock`)
- `-o, --output human|json` — `services list` output (default `human`)
- `-o, --output human|json` — `services list` / `doctor` output (default `human`)
- `--version` / `info` — build and release metadata

## Build
Expand Down
76 changes: 76 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,61 @@ fn default_microinit_reconnect_ms() -> u64 {
3000
}

/// Unsolicited mDNS re-announcement (RFC 6762 §8.3 plus a periodic refresh).
///
/// `mdns-sd` only sends two announcements at register time. Clients that miss
/// those packets (or flush on a later goodbye) would never see the service
/// again without this ticker.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AnnounceConfig {
/// Interval between full re-announcements of the current set (milliseconds).
/// Default 55 s, below the 120 s host-record TTL.
#[serde(default = "default_announce_period_ms")]
pub period_ms: u64,
/// Extra announcements after a real advertisement change, at 1 s, 2 s, 4 s, …
/// `burstCount` 4 → 1 s, 2 s, 4 s, 8 s. Zero disables the burst.
#[serde(default = "default_announce_burst_count")]
pub burst_count: u8,
}

impl Default for AnnounceConfig {
fn default() -> Self {
Self {
period_ms: default_announce_period_ms(),
burst_count: default_announce_burst_count(),
}
}
}

fn default_announce_period_ms() -> u64 {
55_000
}
fn default_announce_burst_count() -> u8 {
4
}

/// Periodic self-verification of multicast membership and announcements.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SelfCheckConfig {
/// How often to verify IGMP membership and recent announcements (milliseconds).
#[serde(default = "default_selfcheck_period_ms")]
pub period_ms: u64,
}

impl Default for SelfCheckConfig {
fn default() -> Self {
Self {
period_ms: default_selfcheck_period_ms(),
}
}
}

fn default_selfcheck_period_ms() -> u64 {
60_000
}

/// Top-level microdns configuration.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
Expand All @@ -213,6 +268,10 @@ pub struct Config {
pub dcc_bus: DccBusConfig,
#[serde(default)]
pub retry: RetryConfig,
#[serde(default)]
pub announce: AnnounceConfig,
#[serde(default)]
pub selfcheck: SelfCheckConfig,
/// Extra interface name prefixes to skip (case-insensitive), in addition
/// to the built-in docker/veth/br-*/... list. Empty by default so mDNS
/// advertises on every usable interface (including `wlan*`) — operators
Expand Down Expand Up @@ -244,6 +303,8 @@ impl Default for Config {
microinit: MicroinitConfig::default(),
dcc_bus: DccBusConfig::default(),
retry: RetryConfig::default(),
announce: AnnounceConfig::default(),
selfcheck: SelfCheckConfig::default(),
skip_interfaces: Vec::new(),
interfaces: Vec::new(),
}
Expand Down Expand Up @@ -274,6 +335,21 @@ impl Config {
}
validate_iface_prefixes("skipInterfaces", &self.skip_interfaces)?;
validate_iface_prefixes("interfaces", &self.interfaces)?;
if self.announce.period_ms < 1000 {
return Err(Error::Config(
"announce.periodMs must be at least 1000".into(),
));
}
if self.announce.burst_count > 8 {
return Err(Error::Config(
"announce.burstCount must be at most 8".into(),
));
}
if self.selfcheck.period_ms < 1000 {
return Err(Error::Config(
"selfcheck.periodMs must be at least 1000".into(),
));
}
Ok(())
}
}
Expand Down
Loading
Loading