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
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mdns-sd = "0.20"
nix = { version = "0.29", features = ["signal", "fs"] }
thiserror = "2"
libc = "0.2"
socket2 = "0.6"
notify = "8.2.0"
log = "0.4"
env_logger = "0.11"
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,24 @@ unavailable. Always starts successfully.

- Static DNS-SD services from `$DATA_DIR/etc/microdns.json` (default `/data`)
- Hostname A records for configured `host` values (e.g. `bigfred` → `bigfred.local`)
- Legacy unicast / one-shot mDNS replies (RFC 6762 §6.7) so browsers and OS
resolvers can resolve `.local` names — not only DNS-SD service browsers
- Own legacy unicast / one-shot mDNS responder (RFC 6762 §6.7) so browsers and OS
resolvers (Android `getaddrinfo`) can resolve `.local` names — not only DNS-SD
browsers. Needed because **mdns-sd 0.20.3** answers unicast but hardcodes
transaction ID=`0` (`dns_parser.rs`: `let id = if self.multicast { 0 } else { self.id }`
while `DnsOutgoing.multicast` is never set false). Remove `legacy_unicast` when
upstream fixes that encoding.
- Optional dcc-bus discovery: when enabled, watches microinit for a running
`dcc-bus` process and advertises `_z21._udp` / `_withrottle._tcp` only when
those ports are empirically listening
- Optional Z21 UDP LAN discovery beacon (LAN_GET_SERIAL_NUMBER reply broadcast)
- Hot-reload via inotify on the config file
- Static musl builds for linux/arm64 and linux/amd64

## Tests

Integration-style unit tests live under `tests/` (one file per module), matching
the microinit layout. Run with `cargo test` / `make test`.

## Config

Default path: `$DATA_DIR/etc/microdns.json`. Created with defaults if missing.
Expand Down
25 changes: 0 additions & 25 deletions src/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,28 +108,3 @@ fn run_beacon(port: u16, frame: &[u8], stop: Arc<AtomicBool>) -> Result<()> {
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn default_serial_frame() {
let frame = serial_reply(DEFAULT_VIRTUAL_SERIAL);
// length=8, header=0x0010, serial LE
assert_eq!(frame.len(), 8);
assert_eq!(u16::from_le_bytes([frame[0], frame[1]]), 8);
assert_eq!(u16::from_le_bytes([frame[2], frame[3]]), 0x0010);
assert_eq!(
u32::from_le_bytes([frame[4], frame[5], frame[6], frame[7]]),
258_000_000
);
}

#[test]
fn virtual_serial_matches_go() {
assert_eq!(virtual_serial(0, 0), 258_000_000);
assert_eq!(virtual_serial(2, 1), 258_002_001);
assert_eq!(virtual_serial(1, 2), 258_001_002);
}
}
66 changes: 0 additions & 66 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,69 +271,3 @@ pub fn save(path: &Path, cfg: &Config) -> Result<()> {
fs::write(path, data).map_err(|e| Error::io_at(path, e))?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};

fn tmp_path(name: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
std::env::temp_dir().join(format!("microdns-cfg-{name}-{nanos}.json"))
}

#[test]
fn default_roundtrip() {
let cfg = Config::default();
let json = serde_json::to_string_pretty(&cfg).unwrap();
let back: Config = serde_json::from_str(&json).unwrap();
assert_eq!(cfg, back);
assert!(!back.dcc_bus.enabled);
assert_eq!(back.dcc_bus.z21_port, 21105);
assert_eq!(back.retry.mdns_ms, 3000);
}

#[test]
fn load_or_create_seeds_default() {
let path = tmp_path("seed");
let _ = fs::remove_file(&path);
let cfg = load_or_create(&path).unwrap();
assert_eq!(cfg.services.len(), 1);
assert_eq!(cfg.services[0].name, "bigfred");
assert!(path.exists());
let again = load_or_create(&path).unwrap();
assert_eq!(cfg, again);
let _ = fs::remove_file(&path);
}

#[test]
fn type_field_renames() {
let json = r#"{
"services": [
{"name":"x","type":"_http._tcp","protocol":"tcp","port":80}
]
}"#;
let cfg: Config = serde_json::from_str(json).unwrap();
assert_eq!(cfg.services[0].type_, "_http._tcp");
}

#[test]
fn validate_rejects_bad_dns_sd_type() {
let mut cfg = Config::default();
cfg.services[0].type_ = "_http._sctp".into();
assert!(cfg.validate().is_err());
cfg.services[0].type_ = "_http._tcp".into();
cfg.services[0].protocol = "udp".into();
assert!(cfg.validate().is_err());
}

#[test]
fn validate_rejects_duplicate_names() {
let mut cfg = Config::default();
cfg.services.push(cfg.services[0].clone());
assert!(cfg.validate().is_err());
}
}
25 changes: 0 additions & 25 deletions src/config_watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,28 +141,3 @@ fn watch_loop(
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn relevant_filters() {
assert!(is_relevant_path(
Path::new("/data/etc/microdns.json"),
"microdns.json"
));
assert!(!is_relevant_path(
Path::new("/data/etc/.microdns.json"),
"microdns.json"
));
assert!(!is_relevant_path(
Path::new("/data/etc/microdns.json~"),
"microdns.json"
));
assert!(!is_relevant_path(
Path::new("/data/etc/other.json"),
"microdns.json"
));
}
}
15 changes: 0 additions & 15 deletions src/datadir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,3 @@ where
}
out
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn default_root_is_data() {
// Cannot safely mutate env in parallel tests; just check absolute join.
let p = PathBuf::from(DEFAULT_ROOT)
.join("etc")
.join("microdns.json");
assert!(p.is_absolute());
assert!(p.ends_with("etc/microdns.json"));
}
}
Loading
Loading