GCC Code Coverage Report


Directory: ./
File: NetworkMonitor.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 71 79 89.9%
Functions: 6 6 100.0%
Branches: 57 87 65.5%

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
15 #ifndef PLATFORM_OSX
16 #include <linux/netlink.h>
17 #include <linux/rtnetlink.h>
18 #include <bits/types/struct_iovec.h>
19 #include <linux/if_addr.h>
20 #endif
21 #include <net/if.h>
22 #include <sys/socket.h>
23 #include <cstring>
24 #include <map>
25 #include <string>
26 #include <unistd.h>
27
28 #include "common.h"
29 #include "log.h"
30 #include "commands/Commands.h"
31
32 #include "NetworkMonitor.h"
33 #include "SysSocket.h"
34
35 NetworkMonitor NetworkMonitor::INSTANCE;
36
37 #ifndef PLATFORM_OSX
38 // IFF_UP/IFF_RUNNING of each interface as of its last RTM_NEWLINK, by index.
39 // RTM_NEWLINK is not only a state change: a Wi-Fi driver sends one for every
40 // scan result and statistics update, several a second, and each used to flush
41 // the resolver cache, so a name that doesn't resolve was looked up again
42 // (seconds of stall against a dead DNS server) after every one of them instead
43 // of once per negative TTL. Only the netlink callback touches this, on one thread.
44 static std::map<int, unsigned> s_linkState;
45
46 // True when this link event moves the interface up or down (or is the first
47 // seen for it): the only link events that can change which DNS servers are
48 // reachable.
49 263 static bool linkStateChanged(int index, unsigned flags, bool deleted) {
50
2/2
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 137 times.
263 if (deleted) {
51
1/2
✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
126 s_linkState.erase(index);
52 126 return true;
53 }
54 137 unsigned state = flags & (IFF_UP | IFF_RUNNING);
55
1/2
✓ Branch 0 taken 137 times.
✗ Branch 1 not taken.
137 auto it = s_linkState.find(index);
56
6/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 126 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 133 times.
137 if (it != s_linkState.end() && it->second == state) {
57 4 return false;
58 }
59
1/2
✓ Branch 0 taken 133 times.
✗ Branch 1 not taken.
133 s_linkState[index] = state;
60 133 return true;
61 }
62 #endif
63
64 1885 void NetworkMonitor::Init(std::map<int, std::function<bool(int)>>& callbacks) {
65 #ifndef PLATFORM_OSX
66 1885 int nl_socket = OpenSocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
67
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1884 times.
1885 if (nl_socket < 0) {
68
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_GENERAL, "Could not create NETLINK socket.\n");
69 2 return;
70 }
71
72 struct sockaddr_nl addr;
73 1884 memset(&addr, 0, sizeof(addr));
74 1884 addr.nl_family = AF_NETLINK;
75 1884 addr.nl_pid = getpid();
76 1884 addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
77
78
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1883 times.
1884 if (bind(nl_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
79
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_GENERAL, "Could not bind NETLINK socket.\n");
80
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 close(nl_socket);
81 1 return;
82 }
83
1/2
✓ Branch 0 taken 1883 times.
✗ Branch 1 not taken.
3766 callbacks[nl_socket] = [nl_socket, this](int i) {
84 int status;
85 236 int ret = 0;
86 char buf[4096];
87 236 struct iovec iov = { buf, sizeof buf };
88 struct sockaddr_nl snl;
89 236 struct msghdr msg = { (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0 };
90 struct nlmsghdr* h;
91 char name[IF_NAMESIZE + 1];
92
93
1/2
✓ Branch 0 taken 236 times.
✗ Branch 1 not taken.
236 status = recvmsg(nl_socket, &msg, MSG_DONTWAIT);
94
2/2
✓ Branch 0 taken 1677 times.
✓ Branch 1 taken 236 times.
1913 while (status > 0) {
95 1677 bool OK = true;
96
5/8
✓ Branch 0 taken 3354 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1677 times.
✓ Branch 3 taken 1677 times.
✓ Branch 4 taken 1677 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1677 times.
✗ Branch 7 not taken.
3354 for (h = (struct nlmsghdr*)buf; OK && NLMSG_OK(h, (unsigned int)status); h = NLMSG_NEXT(h, status)) {
97 //Finish reading
98 //LogInfo(VB_GENERAL, "Type: %d Flags: %X\n", h->nlmsg_type, h->nlmsg_flags);
99
2/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 263 times.
✓ Branch 3 taken 1414 times.
✗ Branch 4 not taken.
1677 switch (h->nlmsg_type) {
100 ✗ case NLMSG_DONE:
101 ✗ OK = false;
102 ✗ break;
103 ✗ case NLMSG_ERROR:
104 //error - not sure what to do, just bail
105 ✗ OK = false;
106 ✗ break;
107 263 case RTM_NEWLINK:
108 case RTM_DELLINK: {
109 263 struct ifinfomsg* ifi = (ifinfomsg*)NLMSG_DATA(h);
110 263 std::string strName;
111
2/2
✓ Branch 0 taken 107 times.
✓ Branch 1 taken 156 times.
263 if (if_indextoname(ifi->ifi_index, name)) {
112
1/2
✓ Branch 0 taken 107 times.
✗ Branch 1 not taken.
107 strName = name;
113 }
114 // A link coming up brings reachable DNS servers with it,
115 // so names that failed while it was down have to be
116 // retried now rather than at the end of their negative
117 // TTL. Flushed ahead of the callbacks so none of them
118 // resolves against a cache filled under the old network.
119
3/4
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✓ Branch 3 taken 4 times.
263 if (linkStateChanged(ifi->ifi_index, ifi->ifi_flags, h->nlmsg_type == RTM_DELLINK)) {
120
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 FlushHostResolveCache();
121 }
122 263 callCallbacks(h->nlmsg_type == RTM_NEWLINK ? NetEventType::NEW_LINK : NetEventType::DEL_LINK,
123
1/2
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
263 (ifi->ifi_flags & IFF_RUNNING) ? 1 : 0, strName);
124 263 } break;
125 1414 case RTM_NEWADDR:
126 case RTM_DELADDR: {
127 1414 struct ifaddrmsg* ifi = (ifaddrmsg*)NLMSG_DATA(h);
128
1/2
✓ Branch 0 taken 1414 times.
✗ Branch 1 not taken.
1414 if (ifi->ifa_family == AF_INET) {
129 1414 std::string strName;
130
2/2
✓ Branch 0 taken 463 times.
✓ Branch 1 taken 951 times.
1414 if (if_indextoname(ifi->ifa_index, name)) {
131
1/2
✓ Branch 0 taken 463 times.
✗ Branch 1 not taken.
463 strName = name;
132 }
133 // a new or lost address can change the route to the
134 // DNS servers (or, with DHCP, the servers themselves)
135
1/2
✓ Branch 0 taken 1414 times.
✗ Branch 1 not taken.
1414 FlushHostResolveCache();
136
3/4
✓ Branch 0 taken 673 times.
✓ Branch 1 taken 741 times.
✓ Branch 2 taken 1414 times.
✗ Branch 3 not taken.
1414 callCallbacks(h->nlmsg_type == RTM_NEWADDR ? NetEventType::NEW_ADDR : NetEventType::DEL_ADDR,
137
2/2
✓ Branch 0 taken 673 times.
✓ Branch 1 taken 741 times.
1414 h->nlmsg_type == RTM_NEWADDR ? 1 : 0, strName);
138 1414 }
139 1414 } break;
140 ✗ default:
141 //printf("NETLINK: %d Uknown\n", h->nlmsg_type);
142 ✗ break;
143 }
144 }
145
1/2
✓ Branch 0 taken 1677 times.
✗ Branch 1 not taken.
1677 status = recvmsg(nl_socket, &msg, MSG_DONTWAIT);
146 }
147 236 return false;
148 1883 };
149 #endif
150 }
151 1677 void NetworkMonitor::callCallbacks(NetEventType nl, int up, const std::string& n) {
152 // The resolver cache is flushed by the netlink handler above, before this
153 // runs, and only for the events that can change name resolution.
154
155 // Held for the whole dispatch: this both keeps the map stable while we walk
156 // it and makes removeCallback() block until an in-flight callback returns,
157 // so a caller that removes its callback (e.g. UDPOutput::Close) can then
158 // safely tear down the state the callback captures. Callbacks here never
159 // re-enter register/removeCallback, so the single lock cannot self-deadlock.
160
1/2
✓ Branch 0 taken 1677 times.
✗ Branch 1 not taken.
1677 std::lock_guard<std::mutex> lk(callbackLock);
161
2/2
✓ Branch 0 taken 3015 times.
✓ Branch 1 taken 1677 times.
4692 for (auto& cb : callbacks) {
162
1/2
✓ Branch 0 taken 3015 times.
✗ Branch 1 not taken.
3015 cb.second(nl, up, n);
163 }
164 1677 }
165
166 3011 int NetworkMonitor::registerCallback(std::function<void(NetEventType, int, const std::string&)>& callback) {
167
1/2
✓ Branch 0 taken 3011 times.
✗ Branch 1 not taken.
3011 std::lock_guard<std::mutex> lk(callbackLock);
168 3011 int id = curId++;
169
2/4
✓ Branch 0 taken 3011 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3011 times.
✗ Branch 3 not taken.
3011 callbacks[id] = callback;
170 3011 return id;
171 3011 }
172
173 1917 void NetworkMonitor::removeCallback(int id) {
174
1/2
✓ Branch 0 taken 1917 times.
✗ Branch 1 not taken.
1917 std::lock_guard<std::mutex> lk(callbackLock);
175
1/2
✓ Branch 0 taken 1917 times.
✗ Branch 1 not taken.
1917 callbacks.erase(id);
176 1917 }
177