| 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 |