GCC Code Coverage Report


Directory: ./
File: ping.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 259 292 88.7%
Functions: 26 29 89.7%
Branches: 191 360 53.1%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 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 #include "fpp-pch.h"
14 #include <atomic>
15 #include <cstring>
16
17 #include <arpa/inet.h>
18 #include <sys/file.h>
19 #include <sys/param.h>
20 #include <sys/socket.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <iostream>
24 #include <poll.h>
25 #include <unistd.h>
26
27 #include <mutex>
28 #include <algorithm>
29 #include <list>
30 #include <memory>
31 #include <string>
32 #include <utility>
33 #include <vector>
34 #include <thread>
35
36 #include "common.h"
37 #include "ping.h"
38 #include "SysSocket.h"
39 #include <netinet/in.h>
40 #include <netinet/in_systm.h>
41 #include <netinet/ip.h>
42 #include <netinet/ip_icmp.h>
43 #include <ctype.h>
44 #include <netdb.h>
45 #include <stdint.h>
46
47 using namespace std;
48
49 #define DEFDATALEN (64 - ICMP_MINLEN) /* default data length */
50 #define MAXIPLEN 60
51 #define MAXICMPLEN 76
52 #define MAXPACKET (65536 - 60 - ICMP_MINLEN) /* max packet size */
53
54 2001 inline void in_cksum(struct icmp* icp, unsigned len) {
55 2001 icp->icmp_cksum = 0;
56 2001 uint16_t* addr = (uint16_t*)icp;
57 2001 uint16_t answer = 0;
58
59 /*
60 * Our algorithm is simple, using a 32 bit accumulator (sum), we add
61 * sequential 16 bit words to it, and at the end, fold back all the
62 * carry bits from the top 16 bits into the lower 16 bits.
63 */
64 2001 uint32_t sum = 0;
65
2/2
✓ Branch 0 taken 64032 times.
✓ Branch 1 taken 2001 times.
66033 while (len > 1) {
66 64032 sum += *addr++;
67 64032 len -= 2;
68 }
69
70 // mop up an odd byte, if necessary
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2001 times.
2001 if (len == 1) {
72 ✗ *(unsigned char*)&answer = *(unsigned char*)addr;
73 ✗ sum += answer;
74 }
75
76 // add back carry outs from top 16 bits to low 16 bits
77 2001 sum = (sum >> 16) + (sum & 0xffff); // add high 16 to low 16
78 2001 sum += (sum >> 16); // add carry
79 2001 answer = ~sum; // truncate to 16 bits
80 2001 icp->icmp_cksum = answer;
81 2001 }
82
83 ✗ int ping(std::string target, int timeoutMs) {
84 // The result lives in state the callback shares, not in this frame: the
85 // wait below gives up on its own count, and the ping thread still times
86 // the ping out (and calls back) after this has returned. A captured
87 // reference to a local was a write into a dead stack frame.
88 ✗ auto ret = std::make_shared<std::atomic_int>(-2);
89 ✗ PingManager::INSTANCE.ping(target, timeoutMs, [ret](int i) { *ret = i; });
90 ✗ while (*ret == -2 && timeoutMs > 0) {
91 ✗ std::this_thread::sleep_for(std::chrono::milliseconds(1));
92 ✗ --timeoutMs;
93 }
94 ✗ return *ret;
95 ✗ }
96
97 class PingTarget {
98 public:
99 std::string hostname;
100 u_char outpack[MAXPACKET];
101 struct sockaddr_in to;
102 struct icmp* icp;
103 bool valid = true;
104 std::function<void(int)> callback;
105 // GetMonotonicTimeMS(): on the wall clock a step back stopped the periodic
106 // pings (and the timeouts) for the size of the step, so a controller that
107 // had dropped out was not added back to the output when it returned.
108 long long startTime;
109 long long timeoutMS;
110 int period = 0;
111 // in PingInfo::targets awaiting a reply or timeout; guarded by targetLock
112 bool pending = false;
113
114 // Resolves the name on the calling thread, which can take seconds for one
115 // that doesn't exist: never construct one under targetLock. A name that
116 // doesn't resolve leaves the target !valid, and it is the caller's to
117 // report that.
118 1967 PingTarget(const std::string& target, int timeout, std::function<void(int)> cb) :
119
1/2
✓ Branch 0 taken 1967 times.
✗ Branch 1 not taken.
1967 callback(cb) {
120
1/2
✓ Branch 0 taken 1967 times.
✗ Branch 1 not taken.
1967 startTime = GetMonotonicTimeMS();
121 1967 timeoutMS = timeout;
122
1/2
✓ Branch 0 taken 1967 times.
✗ Branch 1 not taken.
1967 hostname = target;
123 1967 memset(outpack, 0, sizeof(outpack));
124 1967 memset(&to, 0, sizeof(to));
125
126 1967 to.sin_family = AF_INET;
127
128 // The header is filled in whether or not the name resolves, so a
129 // target that resolves later only needs its address (setAddress()).
130 1967 icp = (struct icmp*)outpack;
131 1967 icp->icmp_type = ICMP_ECHO;
132 1967 icp->icmp_code = 0;
133 1967 icp->icmp_cksum = 0;
134
1/2
✓ Branch 0 taken 1967 times.
✗ Branch 1 not taken.
1967 icp->icmp_id = FPPrand() & 0xFF;
135
136 struct in_addr addr;
137
1/2
✓ Branch 0 taken 1967 times.
✗ Branch 1 not taken.
1967 valid = resolve(target, addr);
138
2/2
✓ Branch 0 taken 1957 times.
✓ Branch 1 taken 10 times.
1967 if (valid) {
139 1957 setAddress(addr);
140 }
141 1967 }
142
143 1969 static bool resolve(const std::string& target, struct in_addr& addr) {
144 // try to convert as dotted decimal address, else if that fails assume it's a hostname
145 1969 addr.s_addr = inet_addr(target.c_str());
146
2/2
✓ Branch 0 taken 1957 times.
✓ Branch 1 taken 12 times.
1969 if (addr.s_addr != (u_int)-1) {
147 1957 return true;
148 }
149 // gethostbyname() is not thread-safe (shared static hostent); the
150 // ping loop runs on its own thread, so use the reentrant
151 // getaddrinfo() instead.
152 12 struct addrinfo hints{};
153 12 hints.ai_family = AF_INET;
154 12 hints.ai_socktype = SOCK_DGRAM;
155 12 struct addrinfo* res = nullptr;
156
3/8
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
12 if (getaddrinfo(target.c_str(), nullptr, &hints, &res) != 0 || res == nullptr) {
157 11 return false;
158 }
159 ✗ addr = ((struct sockaddr_in*)res->ai_addr)->sin_addr;
160 ✗ freeaddrinfo(res);
161 ✗ return true;
162 }
163
164 1957 void setAddress(const struct in_addr& addr) {
165 1957 to.sin_addr = addr;
166 1957 int ipad = to.sin_addr.s_addr;
167 1957 ipad >>= 16;
168 1957 ipad &= 0xFFFF;
169 1957 icp->icmp_seq = ipad; /* seq and id must be reflected */
170 1957 }
171 };
172
173 class PingManager::PingInfo {
174 public:
175
1/2
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
2346 PingInfo() {
176 2346 }
177 458 ~PingInfo() {}
178
179 1887 void Initialize() {
180 1887 int flags = 0;
181 #ifndef PLATFORM_OSX
182 1887 flags = SOCK_NONBLOCK;
183 #endif
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
1887 if (getuid()) {
185 ✗ pingSocket = OpenSocket(AF_INET, SOCK_DGRAM | flags, IPPROTO_ICMP);
186 } else {
187 1887 pingSocket = OpenSocket(AF_INET, SOCK_RAW | flags, IPPROTO_ICMP);
188 }
189 1887 isRunning = 1;
190
191
2/6
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1887 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1887 pingThread = new std::thread(&PingManager::PingInfo::runPingLoop, this);
192 1887 }
193 1867 void Cleanup() {
194 1867 isRunning = 0;
195
2/4
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
5281 PingTarget t("127.0.0.1", 100, [](int i) {});
196
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 sendPing(&t); // this will trigger a response which will speed up shutdown
197
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 if (pingThread->joinable()) {
198
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 pingThread->join();
199 }
200
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 delete pingThread;
201 1867 pingThread = nullptr;
202
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 if (pingSocket >= 0) {
203
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 close(pingSocket);
204 }
205 1867 isRunning = 0;
206 // a periodic target is in targets only while a ping is pending, and
207 // one whose name never resolved never is
208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1867 times.
1867 for (auto& t : targets) {
209 ✗ if (t->period == 0) {
210 ✗ delete t;
211 }
212 }
213 1867 targets.clear();
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1867 times.
1867 for (auto& t : periodics) {
215 ✗ delete t;
216 }
217 1867 periodics.clear();
218 1867 }
219
220 // caller holds targetLock; true if `target` already has a periodic ping
221 252 bool refreshPeriodic(const std::string& target, int timeout, int period) {
222
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 134 times.
272 for (auto t : periodics) {
223
5/6
✓ Branch 0 taken 118 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 118 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 118 times.
✓ Branch 5 taken 20 times.
138 if (t->hostname == target && t->period) {
224 118 t->timeoutMS = timeout;
225 118 t->period = period;
226 // One ping in flight per target. Callers re-request on every
227 // send error (UDPOutput, ~13/s against a saturated controller,
228 // once per universe), and each re-push used to add another copy
229 // to targets, every one of which fired the callback on timeout
230 // -- hundreds per second, each failure able to start an HTTP
231 // HEAD probe, until fppd ran out of file descriptors. The
232 // pending ping answers this request too. A name that hasn't
233 // resolved yet is left to the ping thread, which retries it
234 // once a period.
235
4/4
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 4 times.
118 if (!t->pending && t->valid) {
236 // a fresh deadline; the stale one timed the ping out at
237 // the next poll, before the host could possibly answer
238
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 t->startTime = GetMonotonicTimeMS();
239
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 queuePing(t);
240 }
241 118 return true;
242 }
243 }
244 134 return false;
245 }
246 185 void addPeriodicPing(const std::string& target, int timeout, int period, std::function<void(int)> callback) {
247 {
248
1/2
✓ Branch 0 taken 185 times.
✗ Branch 1 not taken.
185 std::unique_lock<std::mutex> lock(targetLock);
249
3/4
✓ Branch 0 taken 185 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 118 times.
✓ Branch 3 taken 67 times.
185 if (refreshPeriodic(target, timeout, period)) {
250 118 return;
251 }
252
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 118 times.
185 }
253 // Didn't find it. The new target resolves its name with the lock
254 // released: that can stall for seconds on a name that doesn't exist,
255 // and under the lock it stalled the ping thread and every other
256 // caller with it. A name that doesn't resolve is kept rather than
257 // dropped, so the callers' repeated requests find it above instead of
258 // each paying for the lookup again; the ping thread retries it.
259
4/10
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 67 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 67 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
67 PingTarget* t = new PingTarget(target, timeout, callback);
260 67 t->period = period;
261
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 std::unique_lock<std::mutex> lock(targetLock);
262
2/4
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
67 if (refreshPeriodic(target, timeout, period)) {
263 // another thread added it while this one was resolving
264 ✗ delete t;
265 ✗ return;
266 }
267
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 periodics.push_back(t);
268
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 2 times.
67 if (t->valid) {
269
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65 queuePing(t);
270 65 return;
271 }
272
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 lock.unlock();
273
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 callback(-1);
274
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 65 times.
67 }
275 67 void removePeriodicPing(const std::string& target) {
276 // Waits out the ping thread's current pass: it holds dispatchLock
277 // while it calls callbacks and while it still holds the targets it
278 // collected, so once this returns the removed target's callback is
279 // neither running nor about to run. The caller (an output being
280 // deleted) frees what that callback captured right after this.
281
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 std::lock_guard<std::recursive_mutex> dispatch(dispatchLock);
282
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 std::unique_lock<std::mutex> lock(targetLock);
283
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 for (auto t : periodics) {
284
3/6
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 67 times.
✗ Branch 5 not taken.
67 if (t->hostname == target && t->period) {
285 67 targets.remove(t);
286 67 periodics.remove(t);
287
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 delete t;
288 67 return;
289 }
290 }
291
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
134 }
292 1887 void runPingLoop() {
293
2/4
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
1887 SetThreadName("FPP-PingThread");
294 u_char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
295 struct sockaddr_in from;
296 struct ip* ip;
297 struct icmp* icp;
298
299 1887 memset(packet, 0, sizeof(packet));
300 1887 memset(&from, 0, sizeof(from));
301
302 struct pollfd rfds[2];
303 1887 rfds[0].fd = pingSocket;
304 1887 rfds[0].events = POLLIN;
305
306
2/2
✓ Branch 0 taken 118827 times.
✓ Branch 1 taken 1867 times.
120694 while (isRunning == 1) {
307 118827 std::list<PingTarget*> toRemove;
308
309
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 startDueLookups();
310
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 std::vector<std::pair<std::string, std::pair<bool, struct in_addr>>> resolved = takeLookupResults();
311
312
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118827 int retval = poll(rfds, 1, 250);
313 // held for the rest of the pass, from the callbacks through the
314 // last use of toCall/toRemove; see removePeriodicPing()
315
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 std::lock_guard<std::recursive_mutex> dispatch(dispatchLock);
316
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 long long curTime = GetMonotonicTimeMS();
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118807 times.
118807 if (retval == -1) {
318 ✗ printf("select() -1\n");
319
2/2
✓ Branch 0 taken 81674 times.
✓ Branch 1 taken 37133 times.
118807 } else if (retval) {
320 // Read every reply that is waiting, not just one, before the
321 // timeout sweep below. With one per pass, any stall of this
322 // thread longer than a ping's timeout -- a busy single-core
323 // board -- left answered pings queued here, and the sweep then timed them
324 // out: controllers that were up were dropped from the output.
325 // Bounded, so a flood of ICMP can't hold the sweep off.
326
2/2
✓ Branch 0 taken 192079 times.
✓ Branch 1 taken 4 times.
192083 for (int n = 0; n < 256; n++) {
327 192079 int fromlen = sizeof(sockaddr_in);
328
1/2
✓ Branch 0 taken 192079 times.
✗ Branch 1 not taken.
192079 int ret = recvfrom(pingSocket, (char*)packet, sizeof(packet), MSG_DONTWAIT, (struct sockaddr*)&from, (socklen_t*)&fromlen);
329
2/2
✓ Branch 0 taken 81670 times.
✓ Branch 1 taken 110409 times.
192079 if (ret < 0) {
330 81670 break;
331 }
332 // Check the IP header
333 110409 ip = (struct ip*)((char*)packet);
334 110409 int hlen = sizeof(struct ip);
335
1/2
✓ Branch 0 taken 110409 times.
✗ Branch 1 not taken.
110409 if (ret >= (hlen + ICMP_MINLEN)) {
336 // Now the ICMP part
337 110409 icp = (struct icmp*)(packet + hlen);
338
2/2
✓ Branch 0 taken 1959 times.
✓ Branch 1 taken 108450 times.
110409 if (icp->icmp_type == ICMP_ECHOREPLY) {
339 1959 std::list<PingTarget*> toCall;
340
1/2
✓ Branch 0 taken 1959 times.
✗ Branch 1 not taken.
1959 std::unique_lock<std::mutex> lock(targetLock);
341 1959 int ipadf = from.sin_addr.s_addr;
342
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 1959 times.
2081 for (auto t : targets) {
343 122 int ipad = t->to.sin_addr.s_addr;
344
5/8
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 94 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 94 times.
✗ Branch 7 not taken.
216 if (t->icp->icmp_seq == icp->icmp_seq && ipad == ipadf && icp->icmp_id == t->icp->icmp_id &&
345
3/4
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✓ Branch 3 taken 28 times.
216 std::find(toRemove.begin(), toRemove.end(), t) == toRemove.end()) {
346 // (a duplicate reply read in the same pass
347 // is not counted, or removed, twice)
348
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 toCall.push_back(t);
349
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 toRemove.push_back(t);
350 }
351 }
352
1/2
✓ Branch 0 taken 1959 times.
✗ Branch 1 not taken.
1959 lock.unlock();
353
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 1959 times.
2053 for (auto t : toCall) {
354 94 long long l = curTime - t->startTime;
355
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 26 times.
94 if (l == 0) {
356 68 l = 1;
357 }
358
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 t->callback((int)l);
359 }
360 1959 } else {
361 // printf("Not echo\n");
362 }
363 }
364 }
365 }
366 // Time out on every pass, not only when poll() saw nothing for
367 // 250ms: this raw socket receives every ICMP packet sent to the
368 // box, and a few a second (port-unreachables from a device getting
369 // output on a port it doesn't listen on) used to keep this sweep
370 // from ever running, so a dead controller was never reported down.
371 {
372 // Called after targetLock is released, as replies are: a
373 // callback that pings again (ping(), addPeriodicPing()) takes
374 // targetLock, which is not recursive, and deadlocked here.
375 // dispatchLock, still held, keeps the targets alive.
376 118807 std::list<PingTarget*> timedOut;
377
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 std::unique_lock<std::mutex> lock(targetLock);
378
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 118807 times.
118999 for (auto t : targets) {
379 192 long long tt = t->startTime;
380 192 tt += t->timeoutMS;
381
7/8
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 132 times.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 21 times.
✓ Branch 6 taken 39 times.
✓ Branch 7 taken 153 times.
192 if (curTime > tt && std::find(toRemove.begin(), toRemove.end(), t) == toRemove.end()) {
382
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 timedOut.push_back(t);
383
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 toRemove.push_back(t);
384 }
385 }
386
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 lock.unlock();
387
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 118807 times.
118846 for (auto t : timedOut) {
388
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 t->callback(0);
389 }
390 118807 }
391
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 std::unique_lock<std::mutex> lock(targetLock);
392
2/2
✓ Branch 0 taken 133 times.
✓ Branch 1 taken 118807 times.
118940 for (auto t : toRemove) {
393 133 targets.remove(t);
394 133 t->pending = false;
395
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 108 times.
133 if (t->period == 0) {
396
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 delete t;
397 }
398 }
399 118807 std::list<PingTarget*> unresolved;
400
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118807 times.
118807 for (auto& r : resolved) {
401 ✗ for (auto t : periodics) {
402 ✗ if (t->hostname == r.first && !t->valid) {
403 ✗ if (r.second.first) {
404 ✗ t->setAddress(r.second.second);
405 ✗ t->valid = true;
406 ✗ t->startTime = GetMonotonicTimeMS();
407 ✗ queuePing(t);
408 } else {
409 ✗ unresolved.push_back(t);
410 }
411 }
412 }
413 }
414
2/2
✓ Branch 0 taken 4418 times.
✓ Branch 1 taken 118807 times.
123225 for (auto t : periodics) {
415 4418 long long st = t->startTime;
416 4418 st += t->period;
417
5/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 4397 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 2 times.
4418 if (curTime > st && !t->pending && t->valid) {
418
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 t->startTime = GetMonotonicTimeMS();
419
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 queuePing(t);
420 }
421 }
422
1/2
✓ Branch 0 taken 118807 times.
✗ Branch 1 not taken.
118807 lock.unlock();
423 // still under dispatchLock, like the other callbacks
424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118807 times.
118807 for (auto t : unresolved) {
425 ✗ t->callback(-1);
426 }
427 118807 }
428 1867 isRunning = -1;
429 1867 }
430
431 // Starts a lookup of the names of periodic targets that haven't
432 // resolved, each at most once a period. A lookup can stall for seconds
433 // per name (no DNS server reachable, a .local name through nss-mdns), so
434 // it runs on a short-lived thread of its own: on this one it held off
435 // every send, reply and timeout for that long, and Cleanup()'s join
436 // waited for it. One lookup thread at a time; names that fall due while
437 // it runs wait for the next pass. The results are applied by name in a
438 // later pass, since a target can be removed (and freed) in the meantime.
439 118827 void startDueLookups() {
440 {
441
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 std::lock_guard<std::mutex> l(lookups->lock);
442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118827 times.
118827 if (lookups->running) {
443 ✗ return;
444 }
445
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 }
446 118827 std::vector<std::string> names;
447 {
448
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 std::unique_lock<std::mutex> lock(targetLock);
449
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 long long curTime = GetMonotonicTimeMS();
450
2/2
✓ Branch 0 taken 4418 times.
✓ Branch 1 taken 118827 times.
123245 for (auto t : periodics) {
451
4/4
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 4290 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 126 times.
4418 if (!t->valid && curTime > t->startTime + t->period) {
452 // the next retry is a period from now, whatever the outcome
453 2 t->startTime = curTime;
454
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 names.push_back(t->hostname);
455 }
456 }
457 118827 }
458
2/2
✓ Branch 0 taken 118825 times.
✓ Branch 1 taken 2 times.
118827 if (names.empty()) {
459 118825 return;
460 }
461
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::lock_guard<std::mutex> l(lookups->lock);
462 2 lookups->running = true;
463 try {
464 // Detached, and holding only the shared results: a lookup still
465 // stalled when fppd stops must not hold up Cleanup(), nor write
466 // into a PingInfo that has gone.
467
1/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 std::thread([state = lookups, names]() {
468
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 SetThreadName("FPP-PingLookup");
469 2 std::vector<std::pair<std::string, std::pair<bool, struct in_addr>>> results;
470
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (auto& n : names) {
471 2 struct in_addr addr{};
472
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 bool ok = PingTarget::resolve(n, addr);
473
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 results.emplace_back(n, std::make_pair(ok, addr));
474 }
475
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::lock_guard<std::mutex> l(state->lock);
476
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 state->results.insert(state->results.end(), results.begin(), results.end());
477 1 state->running = false;
478
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
3 }).detach();
479 ✗ } catch (const std::system_error&) {
480 ✗ lookups->running = false; // retried a period from now
481 ✗ }
482
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 118825 times.
118827 }
483 118827 std::vector<std::pair<std::string, std::pair<bool, struct in_addr>>> takeLookupResults() {
484 118827 std::vector<std::pair<std::string, std::pair<bool, struct in_addr>>> r;
485
1/2
✓ Branch 0 taken 118827 times.
✗ Branch 1 not taken.
118827 std::lock_guard<std::mutex> l(lookups->lock);
486 118827 r.swap(lookups->results);
487 118827 return r;
488 118827 }
489
490 // caller holds targetLock
491 134 void queuePing(PingTarget* t) {
492 134 t->pending = true;
493 134 targets.push_back(t);
494 134 sendPing(t);
495 134 }
496 2001 void sendPing(PingTarget* target) {
497 2001 int cc = DEFDATALEN + ICMP_MINLEN;
498 2001 target->icp->icmp_seq++;
499 2001 in_cksum(target->icp, cc);
500 2001 sendto(pingSocket, (char*)target->outpack, cc, 0, (struct sockaddr*)&target->to, (socklen_t)sizeof(struct sockaddr_in));
501 2001 }
502 33 void ping(const std::string& target, int timeout, std::function<void(int)> callback) {
503
3/8
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 33 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
33 PingTarget* t = new PingTarget(target, timeout, callback);
504
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 8 times.
33 if (t->valid) {
505
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 t->callback = callback;
506 // The send has to happen under the same lock that publishes the
507 // target, not after it. A one-shot target (period == 0) is owned
508 // by the ping loop the moment it is in `targets`: the loop matches
509 // the reply -- or simply times it out, which it can do before this
510 // thread has sent anything at all, since startTime is set by the
511 // constructor above -- fires the callback, removes it and deletes
512 // it. Releasing the lock first therefore raced the delete, and
513 // sendPing() then read the freed target (confirmed under ASan:
514 // 64-byte read in sendto of a region freed by the ping thread).
515 // The periodic path below already sends while holding the lock.
516
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 std::unique_lock<std::mutex> lock(targetLock);
517
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 queuePing(t);
518 25 } else {
519
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 delete t;
520 8 callback(-1);
521 }
522 33 }
523
524 int pingSocket = -1;
525 // The ping thread loops on it; Cleanup() clears it from another thread.
526 std::atomic_int isRunning{ 0 };
527 std::thread* pingThread;
528 std::list<PingTarget*> targets;
529 std::list<PingTarget*> periodics;
530 std::mutex targetLock;
531 // taken before targetLock; recursive so a callback could remove a ping
532 // on the ping thread without deadlocking (none does today)
533 std::recursive_mutex dispatchLock;
534 struct Lookups {
535 std::mutex lock;
536 bool running = false;
537 std::vector<std::pair<std::string, std::pair<bool, struct in_addr>>> results;
538 };
539 std::shared_ptr<Lookups> lookups = std::make_shared<Lookups>();
540 };
541
542 PingManager PingManager::INSTANCE;
543
544 2346 PingManager::PingManager() {
545
2/6
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2346 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2346 info = new PingInfo();
546 2346 }
547
548 458 PingManager::~PingManager() {
549
1/2
✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
458 delete info;
550 458 }
551
552 1887 void PingManager::Initialize() {
553 1887 info->Initialize();
554 1887 }
555 1867 void PingManager::Cleanup() {
556 1867 info->Cleanup();
557 1867 }
558
559 185 void PingManager::addPeriodicPing(const std::string& target, int timeout, int period, std::function<void(int)>&& callback) {
560
3/6
✓ Branch 0 taken 185 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 185 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 185 times.
✗ Branch 5 not taken.
295 info->addPeriodicPing(target, timeout, period, [callback](int i) { callback(i); });
561 185 }
562
563 67 void PingManager::removePeriodicPing(const std::string& target) {
564 67 info->removePeriodicPing(target);
565 67 }
566
567 33 void PingManager::ping(const std::string& target, int timeout, std::function<void(int)>&& callback) {
568
2/4
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
33 info->ping(target, timeout, callback);
569 33 }
570