From 26acec29ca53d967b64504e46c1f09ee64e0200e Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Sat, 25 Jul 2026 23:48:12 +0200 Subject: [PATCH] rename the host on a probe conflict instead of giving up When probing finds another responder already using our host name, umdns prints a message on stderr and returns from the announce state machine without ever reaching STATE_ANNOUNCE. It then never announces its host records or its services, never retries, and stays that way until it is restarted. The init script does not capture stderr, so nothing reaches the log either: the daemon is running, the services are still listed by `ubus call umdns announcements`, port 5353 is still bound, and there is simply nothing on the wire. RFC 6762 section 9 asks a responder to pick a new name and probe again. Do that: append a -N suffix to the host name and restart probing, up to HOSTNAME_MAX_SUFFIX attempts, keeping the old behaviour of giving up once those are exhausted. The suffix is derived from the unsuffixed system host name, so repeated conflicts produce host-2, host-3 and not host-2-2. Services that did not carry an explicit hostname or instance follow the rename automatically, as they point at the globals. Since the host name is global while the announce state machine is per interface, all interfaces probe again for the new name rather than announce the old one. get_hostname() is called again on every service reload, so it now only resets the suffix when the system host name itself changed, letting a name picked to resolve a conflict survive an unrelated reload. Tested against avahi-daemon holding testrouter.local: umdns logs the host testrouter.local already exists, renaming to testrouter-2.local and goes on to announce testrouter-2.local, where before it announced nothing. Note this only changes what happens once a conflict is detected. The detection itself is unchanged and remains cache based, so a responder that answers the probe but never announces unsolicited can still be missed. Assisted-By: Claude Opus 4.8 --- announce.c | 30 +++++++++++++++++++++++++++++- util.c | 42 ++++++++++++++++++++++++++++++++++++++++-- util.h | 11 +++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/announce.c b/announce.c index a562277..c33f014 100644 --- a/announce.c +++ b/announce.c @@ -37,6 +37,8 @@ enum { int announce_ttl = 75 * 60; +static void announce_restart(void); + static void announce_timer(struct uloop_timeout *timeout) { @@ -58,7 +60,24 @@ announce_timer(struct uloop_timeout *timeout) case STATE_PROBE_END: if (cache_host_is_known(mdns_hostname_local)) { - fprintf(stderr, "the host %s already exists. stopping announce service\n", mdns_hostname_local); + char conflict[sizeof(mdns_hostname_local)]; + + snprintf(conflict, sizeof(conflict), "%s", mdns_hostname_local); + + if (!rename_hostname()) { + fprintf(stderr, "the host %s already exists. stopping announce service\n", + conflict); + return; + } + + fprintf(stderr, "the host %s already exists, renaming to %s\n", + conflict, mdns_hostname_local); + + /* + * The host name is global, so every interface has to probe + * again for the new one rather than announce the old one. + */ + announce_restart(); return; } iface->announce_state++; @@ -81,6 +100,15 @@ announce_init(struct interface *iface) uloop_timeout_set(&iface->announce_timer, 100); } +static void +announce_restart(void) +{ + struct interface *iface; + + vlist_for_each_element(&interfaces, iface, node) + announce_init(iface); +} + void announce_free(struct interface *iface) { diff --git a/util.c b/util.c index f5cfdb8..54bc6f4 100644 --- a/util.c +++ b/util.c @@ -38,6 +38,9 @@ int debug = 0; char umdns_host_label[HOSTNAME_LEN]; char mdns_hostname_local[HOSTNAME_LEN + 6]; +static char umdns_host_base[HOSTNAME_LEN]; +static unsigned int umdns_host_suffix; + uint32_t rand_time_delta(uint32_t t) { @@ -61,6 +64,21 @@ rand_time_delta(uint32_t t) return val; } +/* "-" plus the widest suffix an unsigned int can print, plus the terminator */ +#define HOSTNAME_SUFFIX_LEN 12 + +static void apply_hostname(void) +{ + if (umdns_host_suffix) + snprintf(umdns_host_label, sizeof(umdns_host_label), "%.*s-%u", + (int)sizeof(umdns_host_label) - HOSTNAME_SUFFIX_LEN, + umdns_host_base, umdns_host_suffix + 1); + else + snprintf(umdns_host_label, sizeof(umdns_host_label), "%s", umdns_host_base); + + snprintf(mdns_hostname_local, sizeof(mdns_hostname_local), "%s.local", umdns_host_label); +} + void get_hostname(void) { struct utsname utsname; @@ -71,8 +89,28 @@ void get_hostname(void) if (uname(&utsname) < 0) return; - snprintf(umdns_host_label, sizeof(umdns_host_label), "%s", utsname.nodename); - snprintf(mdns_hostname_local, sizeof(mdns_hostname_local), "%s.local", utsname.nodename); + /* + * Only start over from the unsuffixed name when the system host name + * itself changed, so that a name picked to resolve a conflict survives + * an unrelated reload. + */ + if (strcmp(umdns_host_base, utsname.nodename)) { + snprintf(umdns_host_base, sizeof(umdns_host_base), "%s", utsname.nodename); + umdns_host_suffix = 0; + } + + apply_hostname(); +} + +bool rename_hostname(void) +{ + if (umdns_host_suffix >= HOSTNAME_MAX_SUFFIX) + return false; + + umdns_host_suffix++; + apply_hostname(); + + return true; } time_t monotonic_time(void) diff --git a/util.h b/util.h index 57afcd3..5e2de4e 100644 --- a/util.h +++ b/util.h @@ -26,6 +26,9 @@ #define MDNS_BUF_LEN (8 * 1024) #define HOSTNAME_LEN 256 +/* Highest -N suffix appended to the host name to resolve a conflict. */ +#define HOSTNAME_MAX_SUFFIX 9 + extern int debug; extern uint8_t mdns_buf[MDNS_BUF_LEN]; @@ -39,6 +42,14 @@ extern char umdns_host_label[HOSTNAME_LEN]; extern char mdns_hostname_local[HOSTNAME_LEN + 6]; extern void get_hostname(void); + +/** + * Append or bump the -N suffix on the host name after a probe conflict + * + * Returns false once HOSTNAME_MAX_SUFFIX is reached, leaving the name as is. + */ +extern bool rename_hostname(void); + extern uint32_t rand_time_delta(uint32_t t); extern time_t monotonic_time(void);