statd: avahi: suppress transient mDNS daemon restart errors

When the mdns service is stop-started (e.g. after a config change),
statd's avahi client fires AVAHI_CLIENT_FAILURE momentarily.  With
AVAHI_CLIENT_NO_FAIL the client reconnects automatically, but the
immediate ERROR log is misleading:

  ERROR: avahi: client failure: Daemon connection failed

New behavior:
- On AVAHI_CLIENT_FAILURE: start a 2 s deferred timer (no immediate log)
- Timer fires up to 3 times (~6 s total); on the 3rd attempt, check if
  mDNS is enabled in the running config via a temporary sysrepo session
- Log ERROR only if the daemon is still down AND mDNS is enabled
- On AVAHI_CLIENT_S_RUNNING: cancel the timer, reset the counter, and
  log NOTE "mDNS daemon reconnected" if a failure had been seen

This silences the error entirely when the operator has disabled mDNS
(expected), and defers it by ~6 s for a brief restart (self-heals
before the timer fires).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-03-20 16:18:11 +01:00
parent adee27f923
commit fee552196a
2 changed files with 83 additions and 6 deletions
+80 -6
View File
@@ -16,6 +16,8 @@
* (same thread — no locking required).
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -683,6 +685,62 @@ static void type_browser_cb(AvahiServiceTypeBrowser *b,
}
}
/*
* Check if mDNS is enabled in the running datastore.
* Opens a temporary session to avoid disturbing the operational-DS session.
* Returns true if enabled or if the check cannot be performed (fail-safe).
*/
static bool mdns_is_enabled(struct avahi_ctx *ctx)
{
sr_session_ctx_t *sess = NULL;
sr_data_t *data = NULL;
const char *s;
bool enabled = true; /* fail-safe: assume enabled */
if (!ctx->sr_conn)
return true;
if (sr_session_start(ctx->sr_conn, SR_DS_RUNNING, &sess))
return true;
if (!sr_get_node(sess, "/infix-services:mdns/enabled", 0, &data) && data) {
s = lyd_get_value(data->tree);
if (s && !strcmp(s, "false"))
enabled = false;
sr_release_data(data);
}
sr_session_stop(sess);
return enabled;
}
/*
* Retry timer callback: fires 2 s after AVAHI_CLIENT_FAILURE (and repeats up
* to 3 times). Only logs ERROR once all retries are exhausted AND mDNS is
* enabled in the running config — this avoids noisy errors when the operator
* has simply disabled the mDNS service.
*
* Example log (mDNS enabled, daemon stays down):
* avahi: mDNS daemon not responding (attempt 3/3) — check that the mdns
* service is running
*/
static void avahi_retry_cb(struct ev_loop *loop, ev_timer *w, int revents)
{
struct avahi_ctx *ctx = (struct avahi_ctx *)
((char *)w - offsetof(struct avahi_ctx, retry_timer));
ctx->fail_count++;
if (ctx->fail_count < 3) {
ev_timer_set(w, 2.0, 0.0);
ev_timer_start(loop, w);
return;
}
if (mdns_is_enabled(ctx))
ERROR("avahi: mDNS daemon not responding (attempt %d/3) — "
"check that the mdns service is running", ctx->fail_count);
}
static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
{
struct avahi_ctx *ctx = userdata;
@@ -691,6 +749,11 @@ static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
switch (state) {
case AVAHI_CLIENT_S_RUNNING:
if (ctx->fail_count > 0) {
ev_timer_stop(ctx->loop, &ctx->retry_timer);
NOTE("avahi: mDNS daemon reconnected");
ctx->fail_count = 0;
}
INFO("avahi: client running");
if (ctx->type_browser)
break; /* Already browsing */
@@ -707,13 +770,20 @@ static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
break;
case AVAHI_CLIENT_FAILURE:
ERROR("avahi: client failure: %s",
avahi_strerror(avahi_client_errno(c)));
/*
* Browsers are internally invalidated when the daemon dies.
* Free them explicitly here so they're recreated on reconnect.
* The daemon went away. AVAHI_CLIENT_NO_FAIL means the client
* will reconnect automatically — we just need to clean up the
* now-invalid browsers so they're recreated on reconnect.
*
* Suppress the immediate ERROR; start a 2-second timer that
* will log only if the daemon stays down for 3 attempts (~6 s)
* and mDNS is enabled in the running config.
*/
if (!ev_is_active(&ctx->retry_timer)) {
ev_timer_init(&ctx->retry_timer, avahi_retry_cb, 2.0, 0.0);
ev_timer_start(ctx->loop, &ctx->retry_timer);
}
{
struct avahi_type_entry *te;
@@ -749,7 +819,8 @@ int avahi_ctx_init(struct avahi_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *s
int avahi_err;
memset(ctx, 0, sizeof(*ctx));
ctx->loop = loop;
ctx->loop = loop;
ctx->sr_conn = sr_conn;
LIST_INIT(&ctx->neighbors);
LIST_INIT(&ctx->services);
LIST_INIT(&ctx->type_entries);
@@ -790,6 +861,9 @@ void avahi_ctx_exit(struct avahi_ctx *ctx)
{
struct avahi_type_entry *te;
if (ev_is_active(&ctx->retry_timer))
ev_timer_stop(ctx->loop, &ctx->retry_timer);
/* Free browsers explicitly before freeing the client */
while (!LIST_EMPTY(&ctx->type_entries)) {
te = LIST_FIRST(&ctx->type_entries);
+3
View File
@@ -53,10 +53,13 @@ struct avahi_type_entry {
struct avahi_ctx {
struct ev_loop *loop;
sr_conn_ctx_t *sr_conn; /* Connection for running-DS config queries */
sr_session_ctx_t *sr_ses; /* Dedicated operational DS write session */
AvahiClient *client;
AvahiServiceTypeBrowser *type_browser;
AvahiPoll poll_api; /* libev-backed vtable */
unsigned int fail_count; /* Consecutive avahi-daemon connection failures */
ev_timer retry_timer; /* Deferred error-log timer */
LIST_HEAD(, avahi_neighbor) neighbors;
LIST_HEAD(, avahi_service) services; /* Flat list; keyed by 5-tuple */
LIST_HEAD(, avahi_type_entry) type_entries;