| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2026 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | // The Avahi mDNS backend. Everything here is the Avahi half of MDNSManager; | ||
| 14 | // the shared half -- the service name, what counts as a newly discovered host, | ||
| 15 | // and what is done with one -- stays in MDNSManager.cpp. MDNSBackend.h says | ||
| 16 | // how one backend gets chosen. | ||
| 17 | |||
| 18 | #include "fpp-pch.h" | ||
| 19 | |||
| 20 | #include "Warnings.h" // WarningHolder -- needed directly for NOPCH builds | ||
| 21 | |||
| 22 | #include "MDNSBackend.h" | ||
| 23 | #include "MDNSManager.h" | ||
| 24 | |||
| 25 | #include "common.h" | ||
| 26 | |||
| 27 | #include <atomic> | ||
| 28 | #include "EPollManager.h" | ||
| 29 | #include "log.h" | ||
| 30 | |||
| 31 | #include <chrono> | ||
| 32 | #include <cerrno> | ||
| 33 | #include <cstdio> | ||
| 34 | #include <cstring> | ||
| 35 | #include <net/if.h> | ||
| 36 | #include <unistd.h> | ||
| 37 | #include <vector> | ||
| 38 | |||
| 39 | #if HAVE_AVAHI | ||
| 40 | |||
| 41 | #include <avahi-client/client.h> | ||
| 42 | #include <avahi-client/lookup.h> | ||
| 43 | #include <avahi-client/publish.h> | ||
| 44 | #include <avahi-common/error.h> | ||
| 45 | #include <avahi-common/malloc.h> | ||
| 46 | #include <avahi-common/strlst.h> | ||
| 47 | #include <avahi-common/timeval.h> | ||
| 48 | #include <avahi-common/alternative.h> | ||
| 49 | #include <sys/timerfd.h> | ||
| 50 | |||
| 51 | // ── Custom AvahiPoll adapter ──────────────────────────────────────────── | ||
| 52 | // Avahi forward-declares AvahiWatch and AvahiTimeout as opaque types. | ||
| 53 | // The poll adapter provides the concrete definitions and wires them into | ||
| 54 | // the main FPP event loop via EPollManager. | ||
| 55 | // ───────────────────────────────────────────────────────────────────────── | ||
| 56 | |||
| 57 | |||
| 58 | struct AvahiWatch { | ||
| 59 | int fd; | ||
| 60 | AvahiWatchEvent requested; | ||
| 61 | AvahiWatchEvent last; | ||
| 62 | AvahiWatchCallback callback; | ||
| 63 | void* userdata; | ||
| 64 | std::function<bool(int)> epollCb; | ||
| 65 | }; | ||
| 66 | |||
| 67 | struct AvahiTimeout { | ||
| 68 | int timerFd; | ||
| 69 | AvahiTimeoutCallback callback; | ||
| 70 | void* userdata; | ||
| 71 | std::function<bool(int)> epollCb; | ||
| 72 | bool registered; | ||
| 73 | }; | ||
| 74 | |||
| 75 | 12 | static uint32_t avahiToEpoll(AvahiWatchEvent e) { | |
| 76 | 12 | uint32_t ep = 0; | |
| 77 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (e & AVAHI_WATCH_IN) ep |= EPOLLIN; |
| 78 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (e & AVAHI_WATCH_OUT) ep |= EPOLLOUT; |
| 79 | // EPOLLERR and EPOLLHUP are always reported by epoll regardless of mask | ||
| 80 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | return ep ? ep : EPOLLIN; |
| 81 | } | ||
| 82 | |||
| 83 | 1087 | static void armTimerFd(int tfd, const struct timeval* tv) { | |
| 84 | 1087 | struct itimerspec its = {}; | |
| 85 |
2/2✓ Branch 0 taken 859 times.
✓ Branch 1 taken 228 times.
|
1087 | if (tv) { |
| 86 | struct timeval now; | ||
| 87 | 859 | gettimeofday(&now, NULL); | |
| 88 | 859 | int64_t delayUs = ((int64_t)tv->tv_sec - now.tv_sec) * 1000000 | |
| 89 | 859 | + (tv->tv_usec - now.tv_usec); | |
| 90 |
2/2✓ Branch 0 taken 445 times.
✓ Branch 1 taken 414 times.
|
859 | if (delayUs < 1) |
| 91 | 445 | delayUs = 1; | |
| 92 | 859 | its.it_value.tv_sec = delayUs / 1000000; | |
| 93 | 859 | its.it_value.tv_nsec = (delayUs % 1000000) * 1000; | |
| 94 | } | ||
| 95 | // tv == NULL → its is all-zero → disarms the timer | ||
| 96 | 1087 | timerfd_settime(tfd, 0, &its, NULL); | |
| 97 | 1087 | } | |
| 98 | |||
| 99 | // ── AvahiPoll function table ── | ||
| 100 | |||
| 101 | 12 | static AvahiWatch* fpp_watch_new(const AvahiPoll* api, int fd, | |
| 102 | AvahiWatchEvent events, | ||
| 103 | AvahiWatchCallback callback, void* userdata) { | ||
| 104 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | auto* w = new AvahiWatch{fd, events, (AvahiWatchEvent)0, callback, userdata, {}}; |
| 105 | |||
| 106 | 283 | w->epollCb = [w](int) -> bool { | |
| 107 | 259 | w->last = w->requested; | |
| 108 | 259 | w->callback(w, w->fd, w->last, w->userdata); | |
| 109 | 259 | return false; | |
| 110 | 12 | }; | |
| 111 | |||
| 112 | 12 | EPollManager::INSTANCE.addFileDescriptor(fd, w->epollCb); | |
| 113 | 12 | uint32_t ep = avahiToEpoll(events); | |
| 114 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (ep != EPOLLIN) { |
| 115 | ✗ | EPollManager::INSTANCE.updateFileDescriptorEvents(fd, ep); | |
| 116 | } | ||
| 117 | 12 | return w; | |
| 118 | } | ||
| 119 | |||
| 120 | ✗ | static void fpp_watch_update(AvahiWatch* w, AvahiWatchEvent events) { | |
| 121 | ✗ | w->requested = events; | |
| 122 | ✗ | EPollManager::INSTANCE.updateFileDescriptorEvents(w->fd, avahiToEpoll(events)); | |
| 123 | ✗ | } | |
| 124 | |||
| 125 | ✗ | static AvahiWatchEvent fpp_watch_get_events(AvahiWatch* w) { | |
| 126 | ✗ | return w->last; | |
| 127 | } | ||
| 128 | |||
| 129 | 12 | static void fpp_watch_free(AvahiWatch* w) { | |
| 130 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (!w) |
| 131 | ✗ | return; | |
| 132 | 12 | EPollManager::INSTANCE.removeFileDescriptor(w->fd); | |
| 133 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | delete w; |
| 134 | } | ||
| 135 | |||
| 136 | 425 | static AvahiTimeout* fpp_timeout_new(const AvahiPoll* api, | |
| 137 | const struct timeval* tv, | ||
| 138 | AvahiTimeoutCallback callback, | ||
| 139 | void* userdata) { | ||
| 140 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | auto* t = new AvahiTimeout{-1, callback, userdata, {}, false}; |
| 141 | 425 | t->timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); | |
| 142 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | if (t->timerFd < 0) { |
| 143 | ✗ | LogWarn(VB_SYNC, "Failed to create timerfd for Avahi timeout\n"); | |
| 144 | ✗ | delete t; | |
| 145 | ✗ | return nullptr; | |
| 146 | } | ||
| 147 | |||
| 148 | 1273 | t->epollCb = [t](int) -> bool { | |
| 149 | uint64_t expirations; | ||
| 150 |
1/2✓ Branch 0 taken 423 times.
✗ Branch 1 not taken.
|
423 | ssize_t n = read(t->timerFd, &expirations, sizeof(expirations)); |
| 151 | (void)n; | ||
| 152 |
1/2✓ Branch 0 taken 423 times.
✗ Branch 1 not taken.
|
423 | t->callback(t, t->userdata); |
| 153 | 423 | return false; | |
| 154 | 425 | }; | |
| 155 | |||
| 156 | 425 | EPollManager::INSTANCE.addFileDescriptor(t->timerFd, t->epollCb); | |
| 157 | 425 | t->registered = true; | |
| 158 | 425 | armTimerFd(t->timerFd, tv); | |
| 159 | 425 | return t; | |
| 160 | } | ||
| 161 | |||
| 162 | 662 | static void fpp_timeout_update(AvahiTimeout* t, const struct timeval* tv) { | |
| 163 |
2/4✓ Branch 0 taken 662 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 662 times.
|
662 | if (!t || t->timerFd < 0) |
| 164 | ✗ | return; | |
| 165 | 662 | armTimerFd(t->timerFd, tv); | |
| 166 | } | ||
| 167 | |||
| 168 | 425 | static void fpp_timeout_free(AvahiTimeout* t) { | |
| 169 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 425 times.
|
425 | if (!t) |
| 170 | ✗ | return; | |
| 171 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | if (t->registered) { |
| 172 | 425 | EPollManager::INSTANCE.removeFileDescriptor(t->timerFd); | |
| 173 | } | ||
| 174 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | if (t->timerFd >= 0) { |
| 175 | 425 | close(t->timerFd); | |
| 176 | } | ||
| 177 |
1/2✓ Branch 0 taken 425 times.
✗ Branch 1 not taken.
|
425 | delete t; |
| 178 | } | ||
| 179 | |||
| 180 | |||
| 181 | // ── Avahi service callbacks ───────────────────────────────────────────── | ||
| 182 | |||
| 183 | 75 | static void resolve_callback(AvahiServiceResolver* r, | |
| 184 | AvahiIfIndex interface, | ||
| 185 | AvahiProtocol protocol, | ||
| 186 | AvahiResolverEvent event, | ||
| 187 | const char* name, | ||
| 188 | const char* type, | ||
| 189 | const char* domain, | ||
| 190 | const char* host_name, | ||
| 191 | const AvahiAddress* a, | ||
| 192 | uint16_t port, | ||
| 193 | AvahiStringList* txt, | ||
| 194 | AvahiLookupResultFlags flags, | ||
| 195 | void* userdata) { | ||
| 196 | 75 | MDNSManager* mgr = static_cast<MDNSManager*>(userdata); | |
| 197 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
|
75 | if (!mgr) |
| 198 | ✗ | return; | |
| 199 | |||
| 200 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | if (event == AVAHI_RESOLVER_FOUND) { |
| 201 | char addr_buf[AVAHI_ADDRESS_STR_MAX]; | ||
| 202 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | avahi_address_snprint(addr_buf, sizeof(addr_buf), a); |
| 203 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | std::string ip(addr_buf); |
| 204 | // An IPv6 link-local address (fe80::/10) is only usable together with the | ||
| 205 | // interface it was discovered on, so carry the zone id ("%eth0") along. | ||
| 206 | // Without it curl can't pick an interface and the address can't be reached | ||
| 207 | // on IPv6-only networks. The zone is harmless to the IPv4-only ping paths | ||
| 208 | // (they already can't parse a v6 address). | ||
| 209 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | if (a->proto == AVAHI_PROTO_INET6 && interface != AVAHI_IF_UNSPEC && |
| 210 |
8/10✓ Branch 0 taken 13 times.
✓ Branch 1 taken 62 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 66 times.
|
88 | ip.rfind("fe80", 0) == 0 && ip.find('%') == std::string::npos) { |
| 211 | 9 | char ifname[IF_NAMESIZE] = {0}; | |
| 212 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (if_indextoname(interface, ifname)) { |
| 213 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | ip += "%"; |
| 214 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | ip += ifname; |
| 215 | } | ||
| 216 | } | ||
| 217 | // WLED nodes (and FPP's own _wled._tcp advertisement) are probed over | ||
| 218 | // HTTP rather than the FPP ping protocol. | ||
| 219 |
3/4✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 38 times.
|
75 | bool isWled = (type != nullptr && strstr(type, "_wled._tcp") != nullptr); |
| 220 | // delegate to instance method to avoid accessing private members from C callback | ||
| 221 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | mgr->HandleResolveIP(ip, isWled); |
| 222 | 75 | } | |
| 223 | |||
| 224 | 75 | avahi_service_resolver_free(r); | |
| 225 | } | ||
| 226 | |||
| 227 | 142 | static void browse_callback(AvahiServiceBrowser* b, | |
| 228 | AvahiIfIndex interface, | ||
| 229 | AvahiProtocol protocol, | ||
| 230 | AvahiBrowserEvent event, | ||
| 231 | const char* name, | ||
| 232 | const char* type, | ||
| 233 | const char* domain, | ||
| 234 | AvahiLookupResultFlags flags, | ||
| 235 | void* userdata) { | ||
| 236 | 142 | AvahiClient* client = avahi_service_browser_get_client(b); | |
| 237 | 142 | MDNSManager* mgr = static_cast<MDNSManager*>(userdata); | |
| 238 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
|
142 | if (!mgr) |
| 239 | ✗ | return; | |
| 240 | |||
| 241 |
3/3✓ Branch 0 taken 88 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 44 times.
|
142 | switch (event) { |
| 242 | 88 | case AVAHI_BROWSER_NEW: | |
| 243 | // resolve service to get address | ||
| 244 | 88 | avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, mgr); | |
| 245 | 88 | break; | |
| 246 | 10 | case AVAHI_BROWSER_REMOVE: { | |
| 247 | // callers will remove entries when services disappear via monitor | ||
| 248 | 10 | } break; | |
| 249 | 44 | default: | |
| 250 | 44 | break; | |
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | 54 | static void entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, void* userdata) { | |
| 255 | 54 | MDNSManager* mgr = static_cast<MDNSManager*>(userdata); | |
| 256 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (!mgr) |
| 257 | ✗ | return; | |
| 258 | |||
| 259 |
3/4✓ Branch 0 taken 11 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 39 times.
|
54 | switch (state) { |
| 260 | 11 | case AVAHI_ENTRY_GROUP_ESTABLISHED: | |
| 261 | 11 | LogInfo(VB_SYNC, "MDNS service registered successfully\n"); | |
| 262 | 11 | break; | |
| 263 | 4 | case AVAHI_ENTRY_GROUP_COLLISION: { | |
| 264 | 4 | AvahiClient* client = avahi_entry_group_get_client(g); | |
| 265 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (client) { |
| 266 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string was = mgr->ServiceName(); |
| 267 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | avahi_entry_group_reset(g); |
| 268 | // Pick an alternative name to avoid re-colliding | ||
| 269 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | mgr->PickAlternativeServiceName(); |
| 270 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | LogWarn(VB_SYNC, "MDNS service name collision on '%s', publishing as '%s'\n", |
| 271 | was.c_str(), mgr->ServiceName().c_str()); | ||
| 272 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | mgr->RegisterService(client); |
| 273 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | mgr->ScheduleServiceNameRetry(); |
| 274 | 4 | } | |
| 275 | |||
| 276 | 4 | break; | |
| 277 | } | ||
| 278 | ✗ | case AVAHI_ENTRY_GROUP_FAILURE: | |
| 279 | ✗ | LogErr(VB_SYNC, "MDNS entry group failure\n"); | |
| 280 | ✗ | break; | |
| 281 | 39 | default: | |
| 282 | 39 | break; | |
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | // Defined in fppd.cpp; cleared when FPP begins shutting down. Declared the same way | ||
| 287 | // httpAPI.cpp does rather than adding a header for one flag. | ||
| 288 | extern std::atomic_int runMainFPPDLoop; | ||
| 289 | |||
| 290 | // WarningHolder matches on the id AND the exact message text, so the add and the | ||
| 291 | // remove have to use the same string. Naming them once is what keeps a warning from | ||
| 292 | // becoming permanent because the two copies drifted apart. | ||
| 293 | static constexpr int MDNS_WARNING_ID = 43; | ||
| 294 | static constexpr const char* MDNS_WARN_CLIENT = "Network discovery: mDNS/Avahi client failure (is avahi-daemon running?)"; | ||
| 295 | static constexpr const char* MDNS_WARN_FPPD = "Network discovery: could not browse for FPP devices (_fppd._udp)"; | ||
| 296 | static constexpr const char* MDNS_WARN_WLED = "Network discovery: could not browse for WLED devices (_wled._tcp)"; | ||
| 297 | |||
| 298 | // Reconnect backoff, and how long the daemon has to stay gone before it counts as an | ||
| 299 | // abnormal condition. A restart of avahi-daemon is back inside a second or two, and | ||
| 300 | // on the way to a reboot it goes down some seconds before fppd is told to stop - | ||
| 301 | // warning on either of those puts a red banner on screen for something that is not | ||
| 302 | // wrong. Retries keep running for as long as fppd does, so a real outage is still | ||
| 303 | // picked up the moment it ends. | ||
| 304 | static constexpr int MDNS_RECONNECT_MIN_MS = 2000; | ||
| 305 | static constexpr int MDNS_RECONNECT_MAX_MS = 30000; | ||
| 306 | static constexpr long long MDNS_WARN_AFTER_MS = 30000; | ||
| 307 | |||
| 308 | // Deliberately not the wall clock: these boxes have no RTC, so the NTP step early in | ||
| 309 | // a boot would otherwise read as a half-hour outage. | ||
| 310 | 3270 | static long long MDNSMonotonicMS() { | |
| 311 |
1/2✓ Branch 0 taken 3270 times.
✗ Branch 1 not taken.
|
3270 | return std::chrono::duration_cast<std::chrono::milliseconds>( |
| 312 | 6540 | std::chrono::steady_clock::now().time_since_epoch()) | |
| 313 | 6540 | .count(); | |
| 314 | } | ||
| 315 | |||
| 316 | // -1 is set during teardown, after the main loop is already gone, so it is not | ||
| 317 | // "running" for our purposes even though it is truthy. | ||
| 318 | 142 | static bool fppdIsRunning() { | |
| 319 | 142 | return runMainFPPDLoop > 0; | |
| 320 | } | ||
| 321 | |||
| 322 | 16 | static void client_callback(AvahiClient* c, AvahiClientState state, void* userdata) { | |
| 323 | 16 | MDNSManager* mgr = static_cast<MDNSManager*>(userdata); | |
| 324 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!mgr) |
| 325 | ✗ | return; | |
| 326 | |||
| 327 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
|
16 | if (state == AVAHI_CLIENT_S_RUNNING) { |
| 328 | // The client is up, so whatever we said about it being down is no longer | ||
| 329 | // true. Without this the warning stayed on screen for the rest of the | ||
| 330 | // session. | ||
| 331 | 13 | mgr->NoteClientRunning(); | |
| 332 | |||
| 333 | // Register local service. Back under the host name: this is also where we | ||
| 334 | // land after avahi re-establishes its host records, which is exactly when a | ||
| 335 | // stale echo can have pushed us onto "<host> #2". | ||
| 336 | 13 | mgr->ResetServiceName(); | |
| 337 | 13 | mgr->RegisterService(c); | |
| 338 | |||
| 339 | // Create a service browser for _fppd._udp | ||
| 340 | 13 | void* sb = avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_fppd._udp", NULL, (AvahiLookupFlags)0, browse_callback, mgr); | |
| 341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (!sb) { |
| 342 | ✗ | LogErr(VB_SYNC, "Failed to create Avahi service browser for _fppd._udp: %s\n", | |
| 343 | avahi_strerror(avahi_client_errno(c))); | ||
| 344 | ✗ | WarningHolder::AddWarning(MDNS_WARNING_ID, MDNS_WARN_FPPD); | |
| 345 | } else { | ||
| 346 | 13 | mgr->SetServiceBrowser(sb); | |
| 347 |
3/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
|
52 | WarningHolder::RemoveWarning(MDNS_WARNING_ID, MDNS_WARN_FPPD); |
| 348 | } | ||
| 349 | |||
| 350 | // Also browse for _wled._tcp so WLED nodes show up in the systems | ||
| 351 | // list. FPP nodes also advertise _wled._tcp; per-IP dedup in | ||
| 352 | // HandleResolveIP keeps them from being processed twice. | ||
| 353 | 13 | void* wsb = avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_wled._tcp", NULL, (AvahiLookupFlags)0, browse_callback, mgr); | |
| 354 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (!wsb) { |
| 355 | ✗ | LogErr(VB_SYNC, "Failed to create Avahi service browser for _wled._tcp: %s\n", | |
| 356 | avahi_strerror(avahi_client_errno(c))); | ||
| 357 | ✗ | WarningHolder::AddWarning(MDNS_WARNING_ID, MDNS_WARN_WLED); | |
| 358 | } else { | ||
| 359 | 13 | mgr->SetWLEDServiceBrowser(wsb); | |
| 360 |
3/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
|
52 | WarningHolder::RemoveWarning(MDNS_WARNING_ID, MDNS_WARN_WLED); |
| 361 | } | ||
| 362 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
3 | } else if (state == AVAHI_CLIENT_S_REGISTERING || state == AVAHI_CLIENT_S_COLLISION) { |
| 363 | // The daemon is (re)registering its host records, e.g. after its host name | ||
| 364 | // changed. Our services point at the old host name, so withdraw them; | ||
| 365 | // S_RUNNING registers them again. | ||
| 366 | 1 | mgr->WithdrawService(); | |
| 367 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | } else if (state == AVAHI_CLIENT_FAILURE) { |
| 368 | 2 | LogWarn(VB_SYNC, "Avahi client failure: %s - will rebuild the client\n", | |
| 369 | avahi_strerror(avahi_client_errno(c))); | ||
| 370 | // This client is finished; everything it owns (browsers, entry group) died | ||
| 371 | // with it. Freeing it here would be freeing the object whose callback we are | ||
| 372 | // in, so hand the rebuild to the reconnect timer, which runs on the main loop | ||
| 373 | // once Avahi is off the stack. | ||
| 374 | 2 | mgr->NoteClientDown(); | |
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | // Build the connection to avahi-daemon. Returns false (and schedules a retry) when | ||
| 379 | // the daemon isn't there to talk to. | ||
| 380 | 3279 | bool MDNSManager::StartAvahiClient() { | |
| 381 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3279 times.
|
3279 | if (m_client) |
| 382 | ✗ | return true; | |
| 383 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3279 times.
|
3279 | if (!m_pollAdapter) |
| 384 | ✗ | return false; | |
| 385 | |||
| 386 | 3279 | int error = 0; | |
| 387 |
1/2✓ Branch 0 taken 3279 times.
✗ Branch 1 not taken.
|
3279 | AvahiClient* client = avahi_client_new(static_cast<AvahiPoll*>(m_pollAdapter), |
| 388 | (AvahiClientFlags)0, client_callback, this, &error); | ||
| 389 |
2/2✓ Branch 0 taken 3267 times.
✓ Branch 1 taken 12 times.
|
3279 | if (!client) { |
| 390 |
2/4✓ Branch 0 taken 3267 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3267 times.
✗ Branch 3 not taken.
|
3267 | LogWarn(VB_SYNC, "Failed to create Avahi client: %s\n", avahi_strerror(error)); |
| 391 |
1/2✓ Branch 0 taken 3267 times.
✗ Branch 1 not taken.
|
3267 | NoteClientDown(); |
| 392 | 3267 | return false; | |
| 393 | } | ||
| 394 | 12 | m_client = client; | |
| 395 | 12 | return true; | |
| 396 | } | ||
| 397 | |||
| 398 | 13 | void MDNSManager::NoteClientRunning() { | |
| 399 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
|
13 | if (m_clientDownSinceMS) { |
| 400 | 1 | LogInfo(VB_SYNC, "Avahi client connected after %lld ms down\n", | |
| 401 | MDNSMonotonicMS() - m_clientDownSinceMS); | ||
| 402 | } | ||
| 403 | 13 | m_clientDownSinceMS = 0; | |
| 404 | 13 | m_reconnectDelayMS = MDNS_RECONNECT_MIN_MS; | |
| 405 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
|
13 | if (m_reconnectTimerFd >= 0) { |
| 406 | 1 | struct itimerspec its = {}; // all-zero disarms | |
| 407 | 1 | timerfd_settime(m_reconnectTimerFd, 0, &its, NULL); | |
| 408 | } | ||
| 409 |
3/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
|
39 | WarningHolder::RemoveWarning(MDNS_WARNING_ID, MDNS_WARN_CLIENT); |
| 410 | 13 | } | |
| 411 | |||
| 412 | 3269 | void MDNSManager::NoteClientDown() { | |
| 413 | 3269 | long long now = MDNSMonotonicMS(); | |
| 414 |
2/2✓ Branch 0 taken 1876 times.
✓ Branch 1 taken 1393 times.
|
3269 | if (m_clientDownSinceMS == 0) { |
| 415 | 1876 | m_clientDownSinceMS = now; | |
| 416 | 1876 | m_reconnectDelayMS = MDNS_RECONNECT_MIN_MS; | |
| 417 | } else { | ||
| 418 | 1393 | m_reconnectDelayMS *= 2; | |
| 419 |
2/2✓ Branch 0 taken 142 times.
✓ Branch 1 taken 1251 times.
|
1393 | if (m_reconnectDelayMS > MDNS_RECONNECT_MAX_MS) |
| 420 | 142 | m_reconnectDelayMS = MDNS_RECONNECT_MAX_MS; | |
| 421 | |||
| 422 | // Only once the daemon has stayed gone, and never while fppd is on its way | ||
| 423 | // down - a shutdown takes avahi-daemon with it, and that is not a fault. | ||
| 424 |
5/6✓ Branch 0 taken 142 times.
✓ Branch 1 taken 1251 times.
✓ Branch 2 taken 142 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 142 times.
✓ Branch 5 taken 1251 times.
|
1393 | if ((now - m_clientDownSinceMS) >= MDNS_WARN_AFTER_MS && fppdIsRunning()) { |
| 425 |
2/4✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 142 times.
✗ Branch 3 not taken.
|
371 | WarningHolder::AddWarning(MDNS_WARNING_ID, MDNS_WARN_CLIENT); |
| 426 | } | ||
| 427 | } | ||
| 428 | 3269 | ScheduleClientReconnect(); | |
| 429 | 3269 | } | |
| 430 | |||
| 431 | 3269 | void MDNSManager::ScheduleClientReconnect() { | |
| 432 |
2/2✓ Branch 0 taken 1875 times.
✓ Branch 1 taken 1394 times.
|
3269 | if (m_reconnectTimerFd < 0) { |
| 433 | 1875 | m_reconnectTimerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); | |
| 434 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1875 times.
|
1875 | if (m_reconnectTimerFd < 0) { |
| 435 | ✗ | LogWarn(VB_SYNC, "Failed to create timerfd for Avahi reconnect: %s\n", | |
| 436 | FPPstrerror(errno)); | ||
| 437 | ✗ | return; | |
| 438 | } | ||
| 439 | 5144 | m_reconnectCallback = [this](int) -> bool { | |
| 440 | 1394 | uint64_t expirations = 0; | |
| 441 |
1/2✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
|
1394 | ssize_t n = read(m_reconnectTimerFd, &expirations, sizeof(expirations)); |
| 442 | (void)n; | ||
| 443 |
1/2✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
|
1394 | ReconnectAvahiClient(); |
| 444 | 1394 | return false; | |
| 445 | 1875 | }; | |
| 446 |
1/2✓ Branch 0 taken 1875 times.
✗ Branch 1 not taken.
|
1875 | EPollManager::INSTANCE.addFileDescriptor(m_reconnectTimerFd, m_reconnectCallback); |
| 447 | } | ||
| 448 | |||
| 449 | 3269 | struct itimerspec its = {}; | |
| 450 | 3269 | its.it_value.tv_sec = m_reconnectDelayMS / 1000; | |
| 451 | 3269 | its.it_value.tv_nsec = (m_reconnectDelayMS % 1000) * 1000000L; | |
| 452 | 3269 | timerfd_settime(m_reconnectTimerFd, 0, &its, NULL); | |
| 453 | } | ||
| 454 | |||
| 455 | // Runs on the main loop, not from inside an Avahi callback, so the dead client can | ||
| 456 | // safely be freed here. | ||
| 457 | 1394 | void MDNSManager::ReconnectAvahiClient() { | |
| 458 | 1394 | CancelServiceNameRetry(); | |
| 459 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1393 times.
|
1394 | if (m_client) { |
| 460 | // avahi_client_free() frees every object the client owns, so drop our handles | ||
| 461 | // to the browsers and the entry group first rather than free them twice. | ||
| 462 | 1 | m_serviceBrowser = nullptr; | |
| 463 | 1 | m_wledServiceBrowser = nullptr; | |
| 464 | 1 | m_entryGroup = nullptr; | |
| 465 | 1 | avahi_client_free(static_cast<AvahiClient*>(m_client)); | |
| 466 | 1 | m_client = nullptr; | |
| 467 | } | ||
| 468 | 1394 | StartAvahiClient(); | |
| 469 | 1394 | } | |
| 470 | |||
| 471 | 13 | void MDNSManager::SetServiceBrowser(void* sb) { | |
| 472 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | std::unique_lock<std::recursive_mutex> lk(m_callbackLock); |
| 473 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
|
13 | if (m_serviceBrowser) { |
| 474 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | avahi_service_browser_free(static_cast<AvahiServiceBrowser*>(m_serviceBrowser)); |
| 475 | 1 | m_serviceBrowser = nullptr; | |
| 476 | } | ||
| 477 | 13 | m_serviceBrowser = sb; | |
| 478 | 13 | } | |
| 479 | |||
| 480 | 13 | void MDNSManager::SetWLEDServiceBrowser(void* sb) { | |
| 481 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | std::unique_lock<std::recursive_mutex> lk(m_callbackLock); |
| 482 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
|
13 | if (m_wledServiceBrowser) { |
| 483 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | avahi_service_browser_free(static_cast<AvahiServiceBrowser*>(m_wledServiceBrowser)); |
| 484 | 1 | m_wledServiceBrowser = nullptr; | |
| 485 | } | ||
| 486 | 13 | m_wledServiceBrowser = sb; | |
| 487 | 13 | } | |
| 488 | |||
| 489 | 4 | void MDNSManager::PickAlternativeServiceName() { | |
| 490 | 4 | char* alt = avahi_alternative_service_name(m_serviceName.c_str()); | |
| 491 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (alt) { |
| 492 | 4 | m_serviceName = alt; | |
| 493 | 4 | avahi_free(alt); | |
| 494 | } | ||
| 495 | 4 | } | |
| 496 | |||
| 497 | // How long to wait before each retry of the host name after a collision. An echo | ||
| 498 | // of our own records is gone by the first one; a real second box with this name | ||
| 499 | // is still there after the last, and then the alternative name stays. | ||
| 500 | static constexpr int MDNS_NAME_RETRY_DELAYS_SEC[] = { 30, 120, 600 }; | ||
| 501 | |||
| 502 | 2 | static void name_retry_callback(AvahiTimeout* t, void* userdata) { | |
| 503 | 2 | static_cast<MDNSManager*>(userdata)->RetryBaseServiceName(); | |
| 504 | 2 | } | |
| 505 | |||
| 506 | 1 | void MDNSManager::WithdrawService() { | |
| 507 | 1 | CancelServiceNameRetry(); | |
| 508 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (m_entryGroup) { |
| 509 | 1 | avahi_entry_group_reset(static_cast<AvahiEntryGroup*>(m_entryGroup)); | |
| 510 | } | ||
| 511 | 1 | } | |
| 512 | |||
| 513 | 13 | void MDNSManager::ResetServiceName() { | |
| 514 | 13 | CancelServiceNameRetry(); | |
| 515 | 13 | m_nameRetryAttempts = 0; | |
| 516 | 13 | m_serviceName = m_baseServiceName; | |
| 517 | 13 | } | |
| 518 | |||
| 519 | 4 | void MDNSManager::ScheduleServiceNameRetry() { | |
| 520 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
|
4 | if (m_serviceName == m_baseServiceName || !m_pollAdapter) { |
| 521 | ✗ | return; | |
| 522 | } | ||
| 523 | 4 | constexpr int maxAttempts = sizeof(MDNS_NAME_RETRY_DELAYS_SEC) / sizeof(MDNS_NAME_RETRY_DELAYS_SEC[0]); | |
| 524 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (m_nameRetryAttempts >= maxAttempts) { |
| 525 | ✗ | LogWarn(VB_SYNC, "MDNS service name '%s' is in use by another host, keeping '%s'\n", | |
| 526 | m_baseServiceName.c_str(), m_serviceName.c_str()); | ||
| 527 | ✗ | return; | |
| 528 | } | ||
| 529 | struct timeval tv; | ||
| 530 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | avahi_elapse_time(&tv, MDNS_NAME_RETRY_DELAYS_SEC[m_nameRetryAttempts] * 1000, 0); |
| 531 | 4 | m_nameRetryAttempts++; | |
| 532 | |||
| 533 | 4 | const AvahiPoll* poll = static_cast<const AvahiPoll*>(m_pollAdapter); | |
| 534 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | if (m_nameRetryTimeout) { |
| 535 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | poll->timeout_update(static_cast<AvahiTimeout*>(m_nameRetryTimeout), &tv); |
| 536 | } else { | ||
| 537 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | m_nameRetryTimeout = poll->timeout_new(poll, &tv, name_retry_callback, this); |
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 541 | 2 | void MDNSManager::RetryBaseServiceName() { | |
| 542 | 2 | AvahiClient* c = static_cast<AvahiClient*>(m_client); | |
| 543 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
|
2 | if (!c || avahi_client_get_state(c) != AVAHI_CLIENT_S_RUNNING || m_serviceName == m_baseServiceName) { |
| 544 | ✗ | return; | |
| 545 | } | ||
| 546 | 2 | LogInfo(VB_SYNC, "MDNS retrying service name '%s' (attempt %d)\n", m_baseServiceName.c_str(), m_nameRetryAttempts); | |
| 547 | 2 | m_serviceName = m_baseServiceName; | |
| 548 | 2 | RegisterService(c); | |
| 549 | } | ||
| 550 | |||
| 551 | 3733 | void MDNSManager::CancelServiceNameRetry() { | |
| 552 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3730 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3733 | if (m_nameRetryTimeout && m_pollAdapter) { |
| 553 | 3 | const AvahiPoll* poll = static_cast<const AvahiPoll*>(m_pollAdapter); | |
| 554 | 3 | poll->timeout_free(static_cast<AvahiTimeout*>(m_nameRetryTimeout)); | |
| 555 | } | ||
| 556 | 3733 | m_nameRetryTimeout = nullptr; | |
| 557 | 3733 | } | |
| 558 | |||
| 559 | 19 | void MDNSManager::RegisterService(void* client) { | |
| 560 | 19 | AvahiClient* c = static_cast<AvahiClient*>(client); | |
| 561 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (!c) |
| 562 | ✗ | return; | |
| 563 | |||
| 564 | // Free any existing entry group before creating a new one | ||
| 565 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 12 times.
|
19 | if (m_entryGroup) { |
| 566 | 7 | avahi_entry_group_free(static_cast<AvahiEntryGroup*>(m_entryGroup)); | |
| 567 | 7 | m_entryGroup = nullptr; | |
| 568 | } | ||
| 569 | |||
| 570 | int error; | ||
| 571 | 19 | AvahiEntryGroup* group = avahi_entry_group_new(c, entry_group_callback, this); | |
| 572 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (!group) { |
| 573 | ✗ | LogWarn(VB_SYNC, "Failed to create Avahi entry group\n"); | |
| 574 | ✗ | return; | |
| 575 | } | ||
| 576 | |||
| 577 | // Register _fppd._udp service on port 32320 (MultiSync port). | ||
| 578 | 19 | error = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, | |
| 579 | m_serviceName.c_str(), "_fppd._udp", NULL, NULL, 32320, NULL); | ||
| 580 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (error < 0) { |
| 581 | ✗ | LogWarn(VB_SYNC, "Failed to add service: %s\n", avahi_strerror(error)); | |
| 582 | ✗ | avahi_entry_group_free(group); | |
| 583 | ✗ | return; | |
| 584 | } | ||
| 585 | |||
| 586 | // Also register _wled._tcp on the HTTP port so WLED-compatible apps | ||
| 587 | // (notably WLED-iOS / WLED-native) discover the FPP node and treat | ||
| 588 | // it as a WLED device. The mac= TXT record is what those apps key | ||
| 589 | // off for device identity. | ||
| 590 | { | ||
| 591 | // Both backends advertise the same record, and finding the MAC is a | ||
| 592 | // per-platform job rather than a per-library one, so it lives in | ||
| 593 | // MDNSManager.cpp. Already lowercase hex with no separators, which is | ||
| 594 | // the form WLED expects. | ||
| 595 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | std::string macFlat = MDNSPrimaryMacAddress(); |
| 596 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | std::string macTxt = "mac=" + macFlat; |
| 597 | |||
| 598 | // Restrict _wled._tcp to IPv4 (AVAHI_PROTO_INET) even though | ||
| 599 | // Apache is dual-stack. The WLED iOS UI renders the device's | ||
| 600 | // address inline and a full IPv6 string wraps awkwardly; the | ||
| 601 | // shorter v4 dotted-quad fits the layout cleanly. | ||
| 602 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | AvahiStringList* txt = avahi_string_list_new(macTxt.c_str(), NULL); |
| 603 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | int werror = avahi_entry_group_add_service_strlst(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, |
| 604 | (AvahiPublishFlags)0, m_serviceName.c_str(), | ||
| 605 | "_wled._tcp", NULL, NULL, 80, txt); | ||
| 606 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | avahi_string_list_free(txt); |
| 607 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (werror < 0) { |
| 608 | ✗ | LogWarn(VB_SYNC, "Failed to add _wled._tcp service: %s\n", avahi_strerror(werror)); | |
| 609 | } else { | ||
| 610 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | LogDebug(VB_SYNC, "MDNS _wled._tcp on port 80 (mac=%s)\n", macFlat.c_str()); |
| 611 | } | ||
| 612 | 19 | } | |
| 613 | |||
| 614 | // Commit the entry group to make the service visible | ||
| 615 | 19 | error = avahi_entry_group_commit(group); | |
| 616 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (error < 0) { |
| 617 | ✗ | LogWarn(VB_SYNC, "Failed to commit entry group: %s\n", avahi_strerror(error)); | |
| 618 | ✗ | avahi_entry_group_free(group); | |
| 619 | ✗ | return; | |
| 620 | } | ||
| 621 | |||
| 622 | 19 | m_entryGroup = group; | |
| 623 | 19 | LogInfo(VB_SYNC, "MDNS service registration initiated for %s._fppd._udp on port 32320\n", m_serviceName.c_str()); | |
| 624 | } | ||
| 625 | |||
| 626 | // ── Backend entry points ──────────────────────────────────────────────── | ||
| 627 | |||
| 628 | 1885 | void MDNSManager::StartBackend() { | |
| 629 | // Build a custom AvahiPoll that delegates to EPollManager | ||
| 630 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1885 times.
|
1885 | auto* poll = new AvahiPoll{}; |
| 631 | 1885 | poll->watch_new = fpp_watch_new; | |
| 632 | 1885 | poll->watch_update = fpp_watch_update; | |
| 633 | 1885 | poll->watch_get_events = fpp_watch_get_events; | |
| 634 | 1885 | poll->watch_free = fpp_watch_free; | |
| 635 | 1885 | poll->timeout_new = fpp_timeout_new; | |
| 636 | 1885 | poll->timeout_update = fpp_timeout_update; | |
| 637 | 1885 | poll->timeout_free = fpp_timeout_free; | |
| 638 | 1885 | poll->userdata = this; | |
| 639 | 1885 | m_pollAdapter = poll; | |
| 640 | 1885 | m_running = true; | |
| 641 | |||
| 642 | // Failing here is not fatal and not permanent: fppd starts before avahi-daemon | ||
| 643 | // on a cold boot often enough that giving up would leave mDNS off for the whole | ||
| 644 | // session. StartAvahiClient() arms the retry. | ||
| 645 | 1885 | StartAvahiClient(); | |
| 646 | 1885 | } | |
| 647 | |||
| 648 | 2325 | void MDNSManager::StopBackend() { | |
| 649 | // Stop the reconnect retries first: it must not fire (and rebuild the client we | ||
| 650 | // are about to free) part way through this. | ||
| 651 |
2/2✓ Branch 0 taken 1857 times.
✓ Branch 1 taken 468 times.
|
2325 | if (m_reconnectTimerFd >= 0) { |
| 652 | 1857 | EPollManager::INSTANCE.removeFileDescriptor(m_reconnectTimerFd); | |
| 653 | 1857 | close(m_reconnectTimerFd); | |
| 654 | 1857 | m_reconnectTimerFd = -1; | |
| 655 | } | ||
| 656 | 2325 | m_clientDownSinceMS = 0; | |
| 657 | // Lives on the poll adapter, not the client, so freeing the client won't take it. | ||
| 658 | 2325 | CancelServiceNameRetry(); | |
| 659 | |||
| 660 | // Free Avahi objects in dependency order. | ||
| 661 | // Freeing the client triggers watch_free / timeout_free for its internal | ||
| 662 | // watches and timeouts, which removes them from EPollManager automatically. | ||
| 663 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2314 times.
|
2325 | if (m_entryGroup) { |
| 664 | // Reset the group first, which forces the daemon to send mDNS | ||
| 665 | // withdraw packets *before* the group is destroyed. Without this, | ||
| 666 | // peers can keep our advertised services cached until the record | ||
| 667 | // TTL expires (often >1 hour) — and on a force-killed fppd they'd | ||
| 668 | // miss the goodbye entirely. | ||
| 669 | 11 | avahi_entry_group_reset(static_cast<AvahiEntryGroup*>(m_entryGroup)); | |
| 670 | 11 | avahi_entry_group_free(static_cast<AvahiEntryGroup*>(m_entryGroup)); | |
| 671 | 11 | m_entryGroup = nullptr; | |
| 672 | } | ||
| 673 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2314 times.
|
2325 | if (m_serviceBrowser) { |
| 674 | 11 | avahi_service_browser_free(static_cast<AvahiServiceBrowser*>(m_serviceBrowser)); | |
| 675 | 11 | m_serviceBrowser = nullptr; | |
| 676 | } | ||
| 677 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2314 times.
|
2325 | if (m_wledServiceBrowser) { |
| 678 | 11 | avahi_service_browser_free(static_cast<AvahiServiceBrowser*>(m_wledServiceBrowser)); | |
| 679 | 11 | m_wledServiceBrowser = nullptr; | |
| 680 | } | ||
| 681 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2314 times.
|
2325 | if (m_client) { |
| 682 | 11 | avahi_client_free(static_cast<AvahiClient*>(m_client)); | |
| 683 | 11 | m_client = nullptr; | |
| 684 | } | ||
| 685 |
2/2✓ Branch 0 taken 1867 times.
✓ Branch 1 taken 458 times.
|
2325 | if (m_pollAdapter) { |
| 686 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | delete static_cast<AvahiPoll*>(m_pollAdapter); |
| 687 | 1867 | m_pollAdapter = nullptr; | |
| 688 | } | ||
| 689 | 2325 | m_running = false; | |
| 690 | 2325 | } | |
| 691 | |||
| 692 | #endif // HAVE_AVAHI | ||
| 693 |