| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2024 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 <arpa/inet.h> | ||
| 14 | #include <atomic> | ||
| 15 | #include <chrono> | ||
| 16 | #include <cstdio> | ||
| 17 | #include <ctime> | ||
| 18 | #include <filesystem> | ||
| 19 | #include <ifaddrs.h> | ||
| 20 | #include <list> | ||
| 21 | #include <map> | ||
| 22 | #include <net/if.h> | ||
| 23 | #include <netdb.h> | ||
| 24 | #include <netinet/in.h> | ||
| 25 | #include <set> | ||
| 26 | #include <sys/socket.h> | ||
| 27 | #include <sys/stat.h> | ||
| 28 | #include <sys/time.h> | ||
| 29 | #include <systemd/sd-daemon.h> | ||
| 30 | #include <thread> | ||
| 31 | #include <unistd.h> | ||
| 32 | #include <vector> | ||
| 33 | |||
| 34 | #include "FPPINIT.h" | ||
| 35 | |||
| 36 | // One single-quoted shell word: nothing inside single quotes is expanded. | ||
| 37 | // Every name below that reaches a shell goes through this, as well as the | ||
| 38 | // isShellSafeWord() checks the settings among them (TetherInterface, | ||
| 39 | // WifiRegulatoryDomain) already get: this runs as root, and one missed | ||
| 40 | // check should not be all that stands between a setting and the shell. | ||
| 41 | 292 | static std::string ShellSingleQuote(const std::string& v) { | |
| 42 |
1/2✓ Branch 0 taken 292 times.
✗ Branch 1 not taken.
|
292 | std::string out = "'"; |
| 43 |
2/2✓ Branch 0 taken 2080 times.
✓ Branch 1 taken 292 times.
|
2372 | for (char ch : v) { |
| 44 |
5/16✗ Branch 0 not taken.
✓ Branch 1 taken 2080 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2080 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2080 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2080 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 2080 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
6240 | out += (ch == '\'') ? std::string("'\\''") : std::string(1, ch); |
| 45 | } | ||
| 46 |
1/2✓ Branch 0 taken 292 times.
✗ Branch 1 not taken.
|
292 | out += "'"; |
| 47 | 292 | return out; | |
| 48 | ✗ | } | |
| 49 | |||
| 50 | 576 | std::string FindTetherWIFIAdapater() { | |
| 51 | 576 | std::string ret; | |
| 52 |
9/18✓ Branch 0 taken 576 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 576 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 562 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 576 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 576 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 562 times.
✓ Branch 13 taken 14 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
1728 | if (!getRawSetting("TetherInterface", ret) || ret.empty()) { |
| 53 | // see if we can find a non-associated interface | ||
| 54 |
5/8✓ Branch 0 taken 562 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 562 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1124 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1124 times.
✓ Branch 7 taken 562 times.
|
1686 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net/")) { |
| 55 |
2/6✓ Branch 0 taken 1124 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1124 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
1124 | std::string dev = entry.path().filename(); |
| 56 |
4/6✓ Branch 0 taken 1124 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1124 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 205 times.
✓ Branch 5 taken 919 times.
|
2248 | if (startsWith(dev, "wl")) { |
| 57 |
4/8✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 205 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 205 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 205 times.
✗ Branch 7 not taken.
|
205 | std::string output = execAndReturn("/usr/sbin/iw dev " + ShellSingleQuote(dev) + " link"); |
| 58 |
3/6✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 205 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 205 times.
|
410 | if (contains(output, "Not connected")) { |
| 59 | ✗ | return dev; | |
| 60 | } | ||
| 61 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | } |
| 62 |
3/6✓ Branch 0 taken 562 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 562 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 562 times.
✗ Branch 5 not taken.
|
1686 | } |
| 63 | } | ||
| 64 | // Used unquoted in root commands; the UI only offers interface names. | ||
| 65 |
6/8✓ Branch 0 taken 14 times.
✓ Branch 1 taken 562 times.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 562 times.
✓ Branch 7 taken 14 times.
|
576 | if (ret.empty() || !isShellSafeWord(ret)) { |
| 66 |
1/2✓ Branch 0 taken 562 times.
✗ Branch 1 not taken.
|
562 | ret = "wlan0"; |
| 67 | } | ||
| 68 | 576 | return ret; | |
| 69 | 576 | } | |
| 70 | |||
| 71 | 91 | static int getIntFromMap(const std::map<std::string, std::string>& m, const std::string& setting, int def) { | |
| 72 |
3/4✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 83 times.
|
91 | if (m.find(setting) != m.end()) { |
| 73 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | std::string v = m.at(setting); |
| 74 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (v.empty()) { |
| 75 | ✗ | return def; | |
| 76 | } | ||
| 77 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | return std::stoi(v); |
| 78 | 8 | } | |
| 79 | 83 | return def; | |
| 80 | } | ||
| 81 | // Boolean flags in interface.<if> are written by the UI as "1" (checked) or | ||
| 82 | // "" (unchecked); treat an API/hand-written "0"/"false" as off too rather than | ||
| 83 | // as "non-empty, therefore on". | ||
| 84 | 50 | static bool settingEnabled(const std::map<std::string, std::string>& m, const std::string& setting) { | |
| 85 |
1/2✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
|
50 | auto it = m.find(setting); |
| 86 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 17 times.
|
50 | if (it == m.end()) { |
| 87 | 33 | return false; | |
| 88 | } | ||
| 89 |
7/10✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✓ Branch 9 taken 4 times.
|
17 | return !it->second.empty() && it->second != "0" && it->second != "false"; |
| 90 | } | ||
| 91 | 4 | static std::string netmaskToSubnet(const std::string& nm) { | |
| 92 | const std::map<std::string, std::string> NETMASK2SUBNET = { | ||
| 93 | { "255.255.255.252", "30" }, | ||
| 94 | { "255.255.255.248", "29" }, | ||
| 95 | { "255.255.255.240", "28" }, | ||
| 96 | { "255.255.255.224", "27" }, | ||
| 97 | { "255.255.255.192", "26" }, | ||
| 98 | { "255.255.255.128", "25" }, | ||
| 99 | { "255.255.255.0", "24" }, | ||
| 100 | { "255.255.254.0", "23" }, | ||
| 101 | { "255.255.252.0", "22" }, | ||
| 102 | { "255.255.248.0", "21" }, | ||
| 103 | { "255.255.240.0", "20" }, | ||
| 104 | { "255.255.224.0", "19" }, | ||
| 105 | { "255.255.192.0", "18" }, | ||
| 106 | { "255.255.128.0", "17" }, | ||
| 107 | { "255.255.0.0", "16" } | ||
| 108 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
68 | }; |
| 109 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if (NETMASK2SUBNET.contains(nm)) { |
| 110 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | return NETMASK2SUBNET.at(nm); |
| 111 | } | ||
| 112 | ✗ | return "24"; | |
| 113 |
15/34✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 4 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 4 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 4 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 4 times.
✗ Branch 23 not taken.
✓ Branch 24 taken 4 times.
✗ Branch 25 not taken.
✓ Branch 26 taken 4 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 4 times.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
|
8 | } |
| 114 | |||
| 115 | 25 | static bool compareAndCopyFiles(std::map<std::string, std::string> filesNeeded) { | |
| 116 | 25 | bool changed = false; | |
| 117 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 25 times.
|
61 | for (auto& nf : filesNeeded) { |
| 118 |
2/4✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
|
36 | if (FileExists(nf.first)) { |
| 119 | ✗ | std::string c = GetFileContents(nf.first); | |
| 120 | ✗ | if (c != nf.second) { | |
| 121 | ✗ | PutFileContents(nf.first, nf.second); | |
| 122 | ✗ | changed = true; | |
| 123 | } | ||
| 124 | ✗ | } else { | |
| 125 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | PutFileContents(nf.first, nf.second); |
| 126 | 36 | changed = true; | |
| 127 | } | ||
| 128 | } | ||
| 129 | 25 | return changed; | |
| 130 | } | ||
| 131 | // A value for a WIFI: QR code field, where \ ; , : and " are escaped. | ||
| 132 | 16 | static std::string WifiQREscape(const std::string& v) { | |
| 133 | 16 | std::string out; | |
| 134 |
2/2✓ Branch 0 taken 148 times.
✓ Branch 1 taken 16 times.
|
164 | for (char ch : v) { |
| 135 |
5/10✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 148 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 148 times.
|
148 | if (ch == '\\' || ch == ';' || ch == ',' || ch == ':' || ch == '"') { |
| 136 | ✗ | out += '\\'; | |
| 137 | } | ||
| 138 |
1/2✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
|
148 | out += ch; |
| 139 | } | ||
| 140 | 16 | return out; | |
| 141 | ✗ | } | |
| 142 | 8 | static std::string CreateHostAPDConfig(const std::string& interface) { | |
| 143 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
16 | std::string ssid = "FPP"; |
| 144 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
16 | std::string psk = "Christmas"; |
| 145 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
16 | std::string WifiRegulatoryDomain = "US"; |
| 146 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | getRawSetting("WifiRegulatoryDomain", WifiRegulatoryDomain); |
| 147 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | getRawSetting("TetherSSID", ssid); |
| 148 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | getRawSetting("TetherPSK", psk); |
| 149 | |||
| 150 | 8 | std::string content; | |
| 151 |
5/10✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | content.append("interface=").append(interface).append("\ndriver=nl80211\nwpa_passphrase=").append(psk).append("\nssid="); |
| 152 |
3/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | content.append(ssid).append("\ncountry_code=").append(WifiRegulatoryDomain); |
| 153 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | content.append("\nhw_mode=g\nchannel=11\nmacaddr_acl=0\nauth_algs=3\nwpa=2\n"); |
| 154 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | content.append("wpa_key_mgmt=WPA-PSK\nwpa_pairwise=TKIP\nrsn_pairwise=CCMP\nwmm_enabled=0\nignore_broadcast_ssid=0\nwpa_group_rekey=3600\nwpa_gmk_rekey=86400\n"); |
| 155 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | content.append("reassociation_deadline=3000\npmk_r1_push=1\nft_over_ds=0\nbss_transition=1\n"); |
| 156 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | if (FileExists("/usr/bin/qrencode")) { |
| 157 | // The SSID and PSK are single-quoted for the shell: inside the double | ||
| 158 | // quotes they used to sit in, "$(...)" in either setting ran as root. | ||
| 159 |
7/14✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
|
8 | std::string payload = ShellSingleQuote("WIFI:T:WPA;S:" + WifiQREscape(ssid) + ";P:" + WifiQREscape(psk) + ";;"); |
| 160 | // Low error correction MAY result in a QR that can be pixel doubled which generally works better for phones to pick up | ||
| 161 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | std::string cmd = "/usr/bin/qrencode -t ASCII -m 0 -s 6 -l L -o "; |
| 162 |
5/10✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | cmd.append(ShellSingleQuote(FPP_MEDIA_DIR + "/tmp/wifi-L.ascii")).append(" ").append(payload); |
| 163 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | exec(cmd); |
| 164 | // Generate a High error correction as well. If we cannot pixel double, this will give more info to the phone/device | ||
| 165 | |||
| 166 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | cmd = "/usr/bin/qrencode -t ASCII -m 3 -s 6 -l H -o "; |
| 167 |
5/10✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | cmd.append(ShellSingleQuote(FPP_MEDIA_DIR + "/tmp/wifi-H.ascii")).append(" ").append(payload); |
| 168 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | exec(cmd); |
| 169 | 8 | } | |
| 170 | |||
| 171 | 8 | return content; | |
| 172 | 8 | } | |
| 173 | // The [DHCPServer] block for the tethering AP. The pool comes from | ||
| 174 | // config/interface.<iface> (the DHCP Pool Offset / Size fields on the Network | ||
| 175 | // page) whenever that file exists: maybeEnableTethering() rewrites this | ||
| 176 | // .network file *after* setupNetwork() has already generated it from the | ||
| 177 | // interface config, so hardcoding the pool here silently reverted whatever the | ||
| 178 | // user had configured -- a box set to offset 100 kept handing out addresses | ||
| 179 | // from .10 because the second write won. 10/100 remains the default for a | ||
| 180 | // board that has no interface config at all (fresh install, no /config dir). | ||
| 181 | 6 | static void AppendTetherDHCPServerSection(std::string& content, const std::string& interface) { | |
| 182 | 6 | int offset = 10; | |
| 183 | 6 | int poolSize = 100; | |
| 184 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | const std::string interfaceConfigFile = FPP_MEDIA_DIR + "/config/interface." + interface; |
| 185 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
|
6 | if (FileExists(interfaceConfigFile)) { |
| 186 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | auto interfaceSettings = loadSettingsFile(interfaceConfigFile); |
| 187 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | offset = getIntFromMap(interfaceSettings, "DHCPOFFSET", offset); |
| 188 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | poolSize = getIntFromMap(interfaceSettings, "DHCPPOOLSIZE", poolSize); |
| 189 | 3 | } | |
| 190 | // PersistLeases=yes is already the default on Debian 13/trixie (systemd | ||
| 191 | // >= 256, via systemd-networkd-persistent-storage.service); set explicitly | ||
| 192 | // here for clarity and so behavior doesn't silently depend on an OS-level | ||
| 193 | // default we don't control. Harmlessly ignored as an unknown key on older | ||
| 194 | // systemd (verified: an unrecognized [DHCPServer] key logs a warning but | ||
| 195 | // doesn't break parsing). | ||
| 196 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | content.append("[DHCPServer]\n" |
| 197 | "PersistLeases=yes\n" | ||
| 198 | "PoolOffset=") | ||
| 199 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | .append(std::to_string(offset)) |
| 200 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | .append("\nPoolSize=") |
| 201 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | .append(std::to_string(poolSize)) |
| 202 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | .append("\nEmitDNS=no\n\n"); |
| 203 |
5/8✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 5 times.
|
6 | if (FileExists(FPP_MEDIA_DIR + "/config/leases." + interface)) { |
| 204 |
4/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | content.append(GetFileContents(FPP_MEDIA_DIR + "/config/leases." + interface)); |
| 205 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | content.append("\n"); |
| 206 | } | ||
| 207 | 6 | } | |
| 208 | 37 | static void unblockWifi() { | |
| 209 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 35 times.
|
37 | if (FileExists("/usr/sbin/rfkill")) { |
| 210 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | exec("/usr/sbin/rfkill unblock wifi"); |
| 211 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | exec("/usr/sbin/rfkill unblock all"); |
| 212 | } | ||
| 213 | 37 | } | |
| 214 | |||
| 215 | // Consumes the marker resetConfig.php drops for a "network" area reset. Must | ||
| 216 | // only be called from the boot "start" path (FPPINIT.cpp), NOT from | ||
| 217 | // setupNetwork() -- that function is also invoked live via `fppinit | ||
| 218 | // setupNetwork <iface>` (network.php's interface-save path) while | ||
| 219 | // systemd-networkd is already running, which would reintroduce the exact | ||
| 220 | // live-daemon race this is meant to avoid. Boot's "start" action runs before | ||
| 221 | // systemd-networkd.service is (re)started for this boot -- fppinit.service is | ||
| 222 | // ordered Before= it -- so calling this only from there is race-free by | ||
| 223 | // construction: the daemon that owns /var/lib/systemd/network/dhcp-server-lease/ | ||
| 224 | // isn't running yet. | ||
| 225 | // | ||
| 226 | // TODO(FPP11): this is one of several ad-hoc "do this on next boot" marker | ||
| 227 | // files scattered around FPP (see also /fpp_kiosk, fpp_expand_rootfs, | ||
| 228 | // /fppos_upgraded). Standardize these on a single settings-key convention | ||
| 229 | // instead of bare marker files. | ||
| 230 | 3 | void consumePendingDhcpLeaseReset() { | |
| 231 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | std::string marker = FPP_MEDIA_DIR + "/config/dhcpLeaseResetPending"; |
| 232 |
3/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
|
3 | if (!FileExists(marker)) { |
| 233 | 2 | return; | |
| 234 | } | ||
| 235 | // One JSON file per interface (not a per-lease directory), e.g. | ||
| 236 | // /var/lib/systemd/network/dhcp-server-lease/eth0 -- confirmed against a | ||
| 237 | // live device (systemd 257). Wipe all of them. | ||
| 238 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (DirectoryExists("/var/lib/systemd/network/dhcp-server-lease")) { |
| 239 |
5/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 1 times.
|
3 | for (const auto& entry : std::filesystem::directory_iterator("/var/lib/systemd/network/dhcp-server-lease")) { |
| 240 | 2 | std::error_code ec; | |
| 241 | 2 | std::filesystem::remove(entry.path(), ec); | |
| 242 | 1 | } | |
| 243 | } | ||
| 244 | 1 | unlink(marker.c_str()); | |
| 245 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | } |
| 246 | |||
| 247 | // hostapd is up or on its way up or down. `systemctl is-active` prints one | ||
| 248 | // word; "inactive" contains "active", so compare the whole word. | ||
| 249 | 18 | static bool hostapdRunning() { | |
| 250 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | std::string state = execAndReturn("/usr/bin/systemctl is-active hostapd"); |
| 251 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | TrimWhiteSpace(state); |
| 252 |
9/16✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 15 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 15 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 15 times.
|
36 | return state == "active" || state == "activating" || state == "deactivating" || state == "reloading"; |
| 253 | 18 | } | |
| 254 | |||
| 255 | 25 | void setupNetwork(bool fullReload) { | |
| 256 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | unblockWifi(); |
| 257 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | auto dnsSettings = loadSettingsFile(FPP_MEDIA_DIR + "/config/dns"); |
| 258 | |||
| 259 | // Load global gateway configuration with migration support | ||
| 260 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | auto gatewaySettings = loadSettingsFile(FPP_MEDIA_DIR + "/config/gateway"); |
| 261 |
3/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
|
25 | std::string globalGateway = gatewaySettings["GATEWAY"]; |
| 262 | |||
| 263 | // Migration: If no global gateway exists, check for old per-interface gateways | ||
| 264 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | if (globalGateway.empty()) { |
| 265 | 25 | std::error_code cfgEc; | |
| 266 |
6/10✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 25 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 25 times.
✓ Branch 9 taken 25 times.
|
50 | for (const auto& entry : std::filesystem::directory_iterator(FPP_MEDIA_DIR + "/config", cfgEc)) { |
| 267 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | std::string filename = entry.path().filename(); |
| 268 |
4/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 8 times.
|
50 | if (startsWith(filename, "interface.")) { |
| 269 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | auto interfaceSettings = loadSettingsFile(entry.path()); |
| 270 |
3/6✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
|
17 | std::string oldGateway = interfaceSettings["GATEWAY"]; |
| 271 |
2/8✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 17 times.
|
17 | if (!oldGateway.empty() && oldGateway != "\"\"") { |
| 272 | // Found an old gateway setting - migrate it to global | ||
| 273 | ✗ | printf("FPP - Migrating gateway %s from %s to global configuration\n", | |
| 274 | oldGateway.c_str(), filename.c_str()); | ||
| 275 | |||
| 276 | // Save to global gateway file | ||
| 277 | ✗ | std::string gatewayContent = "GATEWAY=\"" + oldGateway + "\"\n"; | |
| 278 | ✗ | PutFileContents(FPP_MEDIA_DIR + "/config/gateway", gatewayContent); | |
| 279 | ✗ | globalGateway = oldGateway; | |
| 280 | |||
| 281 | // Remove old GATEWAY setting from interface file (optional cleanup) | ||
| 282 | ✗ | std::string interfaceContent = GetFileContents(entry.path()); | |
| 283 | ✗ | size_t pos = interfaceContent.find("GATEWAY="); | |
| 284 | ✗ | if (pos != std::string::npos) { | |
| 285 | ✗ | size_t endPos = interfaceContent.find('\n', pos); | |
| 286 | ✗ | if (endPos != std::string::npos) { | |
| 287 | ✗ | interfaceContent.erase(pos, endPos - pos + 1); | |
| 288 | ✗ | PutFileContents(entry.path(), interfaceContent); | |
| 289 | } | ||
| 290 | } | ||
| 291 | ✗ | break; // Use first gateway found | |
| 292 | ✗ | } | |
| 293 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | } |
| 294 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
50 | } |
| 295 | } | ||
| 296 | |||
| 297 |
3/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
|
50 | std::string dns = dnsSettings["DNS1"]; |
| 298 |
4/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 25 times.
|
50 | if (dnsSettings["DNS2"] != "") { |
| 299 | ✗ | dns += ","; | |
| 300 | ✗ | dns += dnsSettings["DNS2"]; | |
| 301 | } | ||
| 302 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
50 | std::string WifiRegulatoryDomain = "US"; |
| 303 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | getRawSetting("WifiRegulatoryDomain", WifiRegulatoryDomain); |
| 304 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | if (isShellSafeWord(WifiRegulatoryDomain)) { |
| 305 |
3/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
|
25 | exec("/usr/sbin/iw reg set " + ShellSingleQuote(WifiRegulatoryDomain)); |
| 306 | } | ||
| 307 | |||
| 308 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | int tetherEnabled = getRawSettingInt("EnableTethering", 0); |
| 309 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | std::string tetherInterface = FindTetherWIFIAdapater(); |
| 310 | |||
| 311 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | const std::string dhcpProxyFile = FPP_MEDIA_DIR + "/config/dhcp-proxy-config.conf"; |
| 312 | 25 | std::string dhcpProxies; | |
| 313 | |||
| 314 | 25 | bool ipForward = false; | |
| 315 | 25 | bool hostapd = false; | |
| 316 | 25 | std::map<std::string, std::string> filesNeeded; | |
| 317 | 25 | std::list<std::string> filesToConsider; | |
| 318 | 25 | std::list<std::string> commandsToRun; | |
| 319 | 25 | std::list<std::string> postCommandsToRun; | |
| 320 | // error_code forms: a directory that isn't there (no wpasupplicant | ||
| 321 | // package, say) is simply empty, not an uncaught exception that ends | ||
| 322 | // fppinit before any of the network is configured. | ||
| 323 | 25 | std::error_code dirEc; | |
| 324 |
5/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 29 times.
✓ Branch 7 taken 25 times.
|
54 | for (const auto& entry : std::filesystem::directory_iterator("/etc/systemd/network", dirEc)) { |
| 325 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | std::string dev = entry.path().filename(); |
| 326 |
8/30✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 29 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 29 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 29 times.
✓ Branch 16 taken 29 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 29 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 29 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
87 | if (startsWith(dev, "10-") && endsWith(dev, ".network")) { |
| 327 | ✗ | filesToConsider.emplace_back("/etc/systemd/network/" + dev); | |
| 328 | } | ||
| 329 | 54 | } | |
| 330 |
5/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 25 times.
✓ Branch 7 taken 25 times.
|
50 | for (const auto& entry : std::filesystem::directory_iterator("/etc/wpa_supplicant", dirEc)) { |
| 331 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | std::string dev = entry.path().filename(); |
| 332 |
8/30✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 25 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 25 times.
✓ Branch 16 taken 25 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 25 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 25 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
75 | if (startsWith(dev, "wpa_supplicant-wl") && endsWith(dev, ".conf")) { |
| 333 | ✗ | filesToConsider.emplace_back("/etc/wpa_supplicant/" + dev); | |
| 334 | } | ||
| 335 | 50 | } | |
| 336 |
3/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 24 times.
|
25 | if (FileExists("/etc/hostapd/hostapd.conf")) { |
| 337 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | filesToConsider.emplace_back("/etc/hostapd/hostapd.conf"); |
| 338 | } | ||
| 339 |
4/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 25 times.
✗ Branch 7 not taken.
|
25 | if (std::filesystem::exists(FPP_MEDIA_DIR + "/config")) { |
| 340 |
6/10✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 25 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 25 times.
✓ Branch 9 taken 25 times.
|
50 | for (const auto& entry : std::filesystem::directory_iterator(FPP_MEDIA_DIR + "/config")) { |
| 341 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | std::string dev = entry.path().filename(); |
| 342 |
4/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 8 times.
|
50 | if (startsWith(dev, "interface.")) { |
| 343 | 17 | bool validConfig = true; | |
| 344 |
3/6✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
|
17 | auto interfaceSettings = loadSettingsFile(FPP_MEDIA_DIR + "/config/" + dev); |
| 345 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | std::string interface = dev.substr(10); |
| 346 | |||
| 347 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | std::string content = "[Match]\nName="; |
| 348 | 17 | std::string addressLines; | |
| 349 | |||
| 350 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
34 | int DHCPSERVER = getIntFromMap(interfaceSettings, "DHCPSERVER", 0); |
| 351 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
34 | int DHCPOFFSET = getIntFromMap(interfaceSettings, "DHCPOFFSET", 100); |
| 352 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
34 | int DHCPPOOLSIZE = getIntFromMap(interfaceSettings, "DHCPPOOLSIZE", 50); |
| 353 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
34 | int ROUTEMETRIC = getIntFromMap(interfaceSettings, "ROUTEMETRIC", 0); |
| 354 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | int IPFORWARDING = getIntFromMap(interfaceSettings, "IPFORWARDING", 0); |
| 355 | |||
| 356 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
|
17 | if (DHCPSERVER == 1) { |
| 357 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | std::string address = interfaceSettings["ADDRESS"]; |
| 358 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | address = address.substr(0, address.find_last_of(".")); |
| 359 |
5/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
|
1 | dhcpProxies += "RewriteRule ^" + address + ".([0-9:]*)$ http://" + address + ".$1/ [P,L]\n"; |
| 360 |
5/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
|
1 | dhcpProxies += "RewriteRule ^" + address + ".([0-9:]*)/(.*)$ http://" + address + ".$1/$2 [P,L]\n\n"; |
| 361 | 1 | } | |
| 362 | |||
| 363 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | content.append(interface).append("\nType="); |
| 364 |
4/6✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✓ Branch 5 taken 2 times.
|
34 | if (startsWith(interface, "wl")) { |
| 365 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | content.append("wlan\n\n"); |
| 366 | } else { | ||
| 367 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | content.append("ether\n\n"); |
| 368 | } | ||
| 369 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append("[Network]\n"); |
| 370 |
4/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 17 times.
|
34 | if (dnsSettings["DNS1"] != "") { |
| 371 | ✗ | content.append("DNS=").append(dnsSettings["DNS1"]).append("\n"); | |
| 372 | } | ||
| 373 |
4/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 17 times.
|
34 | if (dnsSettings["DNS2"] != "") { |
| 374 | ✗ | content.append("DNS=").append(dnsSettings["DNS2"]).append("\n"); | |
| 375 | } | ||
| 376 |
5/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 4 times.
|
34 | if (interfaceSettings["PROTO"] == "dhcp") { |
| 377 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | addressLines = "DHCP=yes\n"; |
| 378 | 13 | DHCPSERVER = 0; | |
| 379 | } else { | ||
| 380 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | addressLines = "Address="; |
| 381 |
9/18✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 4 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
|
16 | addressLines.append(interfaceSettings["ADDRESS"]).append("/").append(netmaskToSubnet(interfaceSettings["NETMASK"])).append("\n"); |
| 382 | } | ||
| 383 |
4/6✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✓ Branch 5 taken 2 times.
|
34 | if (startsWith(interface, "wl")) { |
| 384 | // wifi settings | ||
| 385 |
5/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 13 times.
|
15 | if (tetherEnabled == 1 && interface == tetherInterface) { |
| 386 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
6 | filesNeeded["/etc/hostapd/hostapd.conf"] = CreateHostAPDConfig(interface); |
| 387 | 2 | DHCPSERVER = 1; | |
| 388 | 2 | hostapd = true; | |
| 389 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | addressLines.append("Address=192.168.8.1/24\n"); |
| 390 |
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.
|
26 | } else if (!interfaceSettings["SSID"].empty()) { |
| 391 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
26 | bool wpa3 = settingEnabled(interfaceSettings, "WPA3"); |
| 392 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
26 | bool backupWpa3 = settingEnabled(interfaceSettings, "BACKUPWPA3"); |
| 393 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | std::string wpa = "ctrl_interface=/var/run/wpa_supplicant\nctrl_interface_group=0\nupdate_config=1\ncountry="; |
| 394 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | wpa.append(WifiRegulatoryDomain); |
| 395 | // Protected Management Frames (802.11w) as a GLOBAL "optional" | ||
| 396 | // (pmf=1), never as a per-network ieee80211w=1. The difference | ||
| 397 | // matters: for the global default wpa_supplicant checks whether | ||
| 398 | // the driver advertises a BIP cipher and silently skips PMF if | ||
| 399 | // not, but a per-network ieee80211w is honoured blindly. The | ||
| 400 | // brcmfmac firmware on the Pi Zero W (BCM43430; the Pi 3B shares | ||
| 401 | // the chip) has no MFP support, so with ieee80211w=1 it negotiated | ||
| 402 | // PMF with any PMF-capable AP (most Wi-Fi 6 routers), failed to | ||
| 403 | // install the IGTK ("WPA: Failed to configure IGTK to the driver") | ||
| 404 | // and dropped an otherwise-good association - looked like a bad | ||
| 405 | // password. On boards whose driver does advertise a BIP cipher | ||
| 406 | // (BCM43455, rtw88, ...) nothing changes: SAE still negotiates | ||
| 407 | // PMF and WPA2 APs set to PMF-required still work. | ||
| 408 | // This mirrors what NetworkManager does on stock Raspberry Pi | ||
| 409 | // OS. GitHub issue #2953. | ||
| 410 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | wpa.append("\npmf=1\n"); |
| 411 |
4/4✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 6 times.
|
13 | if (wpa3 || backupWpa3) { |
| 412 | // 2 = hash-to-element AND hunting-and-pecking. With 1 (H2E only) | ||
| 413 | // wpa_supplicant skips every BSS of an AP that lacks H2E ("skip - | ||
| 414 | // SAE H2E required, but not supported by the AP"), so the WPA3 | ||
| 415 | // checkbox could never connect to such an AP on any board. | ||
| 416 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | wpa.append("sae_pwe=2\n"); |
| 417 | } | ||
| 418 |
4/8✓ 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.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
|
39 | wpa.append("\nnetwork={\n ssid=\"").append(interfaceSettings["SSID"]); |
| 419 |
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.
|
26 | if (!interfaceSettings["PSK"].empty()) { |
| 420 |
4/8✓ 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.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
|
39 | wpa.append("\"\n psk=\"").append(interfaceSettings["PSK"]); |
| 421 | } | ||
| 422 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | wpa.append("\"\n key_mgmt="); |
| 423 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 9 times.
|
13 | if (wpa3) { |
| 424 |
5/10✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
|
12 | wpa.append("SAE WPA-PSK\n sae_password=\"").append(interfaceSettings["PSK"]).append("\""); |
| 425 | } else { | ||
| 426 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | wpa.append("WPA-PSK"); |
| 427 | } | ||
| 428 |
3/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
|
26 | if (settingEnabled(interfaceSettings, "HIDDEN")) { |
| 429 | ✗ | wpa.append("\n scan_ssid=1"); | |
| 430 | } | ||
| 431 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | wpa.append("\n priority=100\n}\n\n"); |
| 432 |
16/32✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 11 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 11 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 11 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 11 times.
✓ Branch 15 taken 2 times.
✓ Branch 16 taken 11 times.
✓ Branch 17 taken 2 times.
✓ Branch 18 taken 13 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 13 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 11 times.
✓ Branch 23 taken 2 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
|
61 | if (!interfaceSettings["BACKUPSSID"].empty() && interfaceSettings["BACKUPSSID"] != "\"\"") { |
| 433 |
4/8✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
|
33 | wpa.append("\nnetwork={\n ssid=\"").append(interfaceSettings["BACKUPSSID"]); |
| 434 |
3/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
|
22 | if (!interfaceSettings["BACKUPPSK"].empty()) { |
| 435 |
4/8✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✗ Branch 7 not taken.
|
33 | wpa.append("\"\n psk=\"").append(interfaceSettings["BACKUPPSK"]); |
| 436 | } | ||
| 437 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | wpa.append("\"\n key_mgmt="); |
| 438 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 7 times.
|
11 | if (backupWpa3) { |
| 439 |
5/10✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
|
12 | wpa.append("SAE WPA-PSK\n sae_password=\"").append(interfaceSettings["BACKUPPSK"]).append("\""); |
| 440 | } else { | ||
| 441 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | wpa.append("WPA-PSK"); |
| 442 | } | ||
| 443 |
3/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
|
22 | if (settingEnabled(interfaceSettings, "BACKUPHIDDEN")) { |
| 444 | ✗ | wpa.append("\n scan_ssid=1"); | |
| 445 | } | ||
| 446 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | wpa.append("\n priority=90\n}\n\n"); |
| 447 | } | ||
| 448 |
4/8✓ 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.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
|
13 | filesNeeded["/etc/wpa_supplicant/wpa_supplicant-" + interface + ".conf"] = wpa; |
| 449 | // Queue these unconditionally rather than gating them on | ||
| 450 | // `systemctl is-active`/`is-enabled` queries: during early | ||
| 451 | // boot the systemd manager is saturated (dbus etc. take many | ||
| 452 | // seconds to come up), so those synchronous queries block for | ||
| 453 | // ~9s and stall fppinit -- and this wpa_supplicant@ branch | ||
| 454 | // only runs once a wifi interface exists. The commands run | ||
| 455 | // only if the wpa config actually changed (see commandsToRun | ||
| 456 | // handling below), and `enable` + `reload-or-restart` are both | ||
| 457 | // idempotent, so the pre-checks bought nothing but the stall. | ||
| 458 |
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.
|
13 | commandsToRun.emplace_back("systemctl enable \"wpa_supplicant@" + interface + ".service\" &"); |
| 459 |
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.
|
13 | commandsToRun.emplace_back("systemctl reload-or-restart \"wpa_supplicant@" + interface + ".service\" &"); |
| 460 |
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.
|
13 | postCommandsToRun.emplace_back("systemctl reload-or-restart \"wpa_supplicant@" + interface + ".service\" &"); |
| 461 | 13 | } else { | |
| 462 | ✗ | validConfig = false; | |
| 463 | } | ||
| 464 | } | ||
| 465 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 14 times.
|
17 | if (DHCPSERVER) { |
| 466 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | content.append("DHCPServer=yes\n"); |
| 467 | } | ||
| 468 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (IPFORWARDING == 1) { |
| 469 | ✗ | content.append("IPForward=yes\n"); | |
| 470 | ✗ | ipForward = true; | |
| 471 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
|
17 | } else if (IPFORWARDING == 2) { |
| 472 | // systemd-networkd might not have masquerade support compiled in, we'll just do it manually | ||
| 473 | 1 | ipForward = true; | |
| 474 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | exec("/usr/sbin/nft add table nat"); |
| 475 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | exec("/usr/sbin/nft 'add chain nat postrouting { type nat hook postrouting priority 100 ; }'"); |
| 476 |
4/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | exec("/usr/sbin/nft add rule nat postrouting oif " + ShellSingleQuote(interface) + " masquerade"); |
| 477 | } | ||
| 478 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append(addressLines); |
| 479 | // some of the FPP7 images don't support this setting. They use older gcc | ||
| 480 | // Pi Zero 2 W has issues with IgnoreCarrierLoss causing network visibility problems (Issue #2487) | ||
| 481 | #ifdef PLATFORM_PI | ||
| 482 | ✗ | if (!isPiZero2W()) { | |
| 483 | ✗ | content.append("IgnoreCarrierLoss=5s\n"); | |
| 484 | } | ||
| 485 | #else | ||
| 486 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append("IgnoreCarrierLoss=5s\n"); |
| 487 | #endif | ||
| 488 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append("\n"); |
| 489 | |||
| 490 | // Apply global gateway to static interfaces only | ||
| 491 |
4/20✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 17 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 17 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 17 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
17 | if (!globalGateway.empty() && interfaceSettings["PROTO"] != "dhcp") { |
| 492 | ✗ | content.append("[Route]\nGateway=").append(globalGateway).append("\n"); | |
| 493 | ✗ | if (ROUTEMETRIC != 0) { | |
| 494 | ✗ | content.append("Metric=").append(std::to_string(ROUTEMETRIC)).append("\n"); | |
| 495 | } | ||
| 496 | } | ||
| 497 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append("\n"); |
| 498 |
5/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 4 times.
|
34 | if (interfaceSettings["PROTO"] == "dhcp") { |
| 499 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | content.append("[DHCPv4]\nClientIdentifier=mac\nUseDomains=true\n"); |
| 500 | // FPP owns the hostname (checkHostName writes /etc/hostname), so | ||
| 501 | // there is nothing to learn from the lease. Leaving this at its | ||
| 502 | // default of yes makes networkd call org.freedesktop.hostname1 the | ||
| 503 | // moment the lease lands, which D-Bus-activates systemd-hostnamed, | ||
| 504 | // which in turn activates polkit to authorize the call. Measured on | ||
| 505 | // a single-core AM335x board that chain cost ~21s of boot (4.3s | ||
| 506 | // hostnamed + 16.8s polkitd) for a hostname that was already | ||
| 507 | // correct; nothing else on an FPP box uses polkit at all. | ||
| 508 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | content.append("UseHostname=false\n"); |
| 509 | // Only use NTP from DHCP if explicitly enabled | ||
| 510 | 13 | std::string useNTPFromDHCP; | |
| 511 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | getRawSetting("UseNTPFromDHCP", useNTPFromDHCP); |
| 512 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | if (useNTPFromDHCP != "1") { |
| 513 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | content.append("UseNTP=no\n"); |
| 514 | } | ||
| 515 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
17 | } else if (ROUTEMETRIC != 0) { |
| 516 | ✗ | content.append("[DHCPv4]\n"); | |
| 517 | } | ||
| 518 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (ROUTEMETRIC != 0) { |
| 519 | ✗ | content.append("RouteMetric=").append(interfaceSettings["ROUTEMETRIC"]).append("\n\n"); | |
| 520 | ✗ | content.append("[IPv6AcceptRA]\nRouteMetric=").append(interfaceSettings["ROUTEMETRIC"]).append("\n\n"); | |
| 521 | } else { | ||
| 522 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | content.append("\n"); |
| 523 | } | ||
| 524 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 14 times.
|
17 | if (DHCPSERVER == 1) { |
| 525 | // PersistLeases=yes is already the default on Debian 13/trixie | ||
| 526 | // (systemd >= 256, via systemd-networkd-persistent-storage.service); | ||
| 527 | // set explicitly here for clarity and so behavior doesn't silently | ||
| 528 | // depend on an OS-level default we don't control. Harmlessly ignored | ||
| 529 | // as an unknown key on older systemd (verified: an unrecognized | ||
| 530 | // [DHCPServer] key logs a warning but doesn't break parsing). | ||
| 531 |
5/10✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
|
3 | content.append("[DHCPServer]\nPersistLeases=yes\nDefaultLeaseTimeSec=8h\nMaxLeaseTimeSec=8h\nPoolOffset=").append(std::to_string(DHCPOFFSET)).append("\nPoolSize=").append(std::to_string(DHCPPOOLSIZE)).append("\n"); |
| 532 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
6 | if (!dnsSettings["DNS1"].empty()) { |
| 533 | ✗ | content.append("EmitDNS=yes\nDNS=").append(dnsSettings["DNS1"]).append("\n"); | |
| 534 | } | ||
| 535 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | content.append("\n"); |
| 536 |
5/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 1 times.
|
3 | if (FileExists(FPP_MEDIA_DIR + "/config/leases." + interface)) { |
| 537 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
2 | content.append(GetFileContents(FPP_MEDIA_DIR + "/config/leases." + interface)); |
| 538 | } | ||
| 539 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | content.append("\n"); |
| 540 | } | ||
| 541 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | if (validConfig) { |
| 542 |
4/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 17 times.
✗ Branch 7 not taken.
|
17 | filesNeeded["/etc/systemd/network/10-" + interface + ".network"] = content; |
| 543 | } | ||
| 544 | 17 | } | |
| 545 | 50 | } | |
| 546 | } | ||
| 547 | |||
| 548 | // If tethering is explicitly enabled (==1) but no interface configs set it up, | ||
| 549 | // configure it now (handles fresh installs with no /config directory) | ||
| 550 |
4/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
25 | if (tetherEnabled == 1 && !hostapd) { |
| 551 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
6 | filesNeeded["/etc/hostapd/hostapd.conf"] = CreateHostAPDConfig(tetherInterface); |
| 552 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::string content = "[Match]\nName="; |
| 553 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | content.append(tetherInterface).append("\nType=wlan\n\n" |
| 554 | "[Network]\n" | ||
| 555 | "DHCP=no\n" | ||
| 556 | "Address=192.168.8.1/24\n" | ||
| 557 | "DHCPServer=yes\n\n"); | ||
| 558 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | AppendTetherDHCPServerSection(content, tetherInterface); |
| 559 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
2 | filesNeeded["/etc/systemd/network/10-" + tetherInterface + ".network"] = content; |
| 560 | 2 | hostapd = true; | |
| 561 | 2 | } | |
| 562 | |||
| 563 | 25 | bool reloadApache = false; | |
| 564 |
5/8✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 25 times.
|
25 | if (dhcpProxies.empty() && FileExists(dhcpProxyFile)) { |
| 565 | ✗ | unlink(dhcpProxyFile.c_str()); | |
| 566 | ✗ | reloadApache = true; | |
| 567 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
|
25 | } else if (!dhcpProxies.empty()) { |
| 568 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | PutFileContents(dhcpProxyFile, dhcpProxies); |
| 569 | 1 | reloadApache = true; | |
| 570 | } | ||
| 571 | |||
| 572 | // FPP's NTP daemon is chrony. Large-offset stepping (ntpd's "-g") is handled | ||
| 573 | // by the "makestep" directive in /etc/chrony/chrony.conf, so there is nothing | ||
| 574 | // to inject per-boot. Here we only reconcile DHCP-provided NTP servers with | ||
| 575 | // the UseNTPFromDHCP setting: the networkd-dispatcher chrony hook drops any | ||
| 576 | // DHCP servers into /run/chrony-dhcp (consumed via "sourcedir" in | ||
| 577 | // chrony.conf) when the setting is enabled. When it's disabled, make sure | ||
| 578 | // none linger from a previous configuration. chrony is started after | ||
| 579 | // fpp_postnetwork, so clearing the dir now is enough -- no reload needed. | ||
| 580 | 25 | std::string useNTPFromDHCP; | |
| 581 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | getRawSetting("UseNTPFromDHCP", useNTPFromDHCP); |
| 582 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | if (useNTPFromDHCP != "1") { |
| 583 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
50 | exec("/usr/bin/rm -f /run/chrony-dhcp/*.sources 2>/dev/null"); |
| 584 | } | ||
| 585 | |||
| 586 | // Auto-detect default gateway as a fallback NTP server so that chrony has | ||
| 587 | // a reachable peer when internet pool servers are blocked (e.g. behind | ||
| 588 | // double NAT / a gateway that drops NTP replies -- see issue #2736). LAN | ||
| 589 | // traffic to the gateway does not cross any NAT boundary. chrony gracefully | ||
| 590 | // ignores non-responsive servers, so this is safe even if the gateway does | ||
| 591 | // not run NTP. | ||
| 592 | { | ||
| 593 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
50 | std::string gw = execAndReturn( |
| 594 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | "/usr/bin/ip route show default 2>/dev/null | /usr/bin/awk '{print $3}'"); |
| 595 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | TrimWhiteSpace(gw); |
| 596 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
|
25 | if (!gw.empty()) { |
| 597 | struct sockaddr_in sa; | ||
| 598 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if (inet_pton(AF_INET, gw.c_str(), &(sa.sin_addr)) == 1) { |
| 599 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | std::string chronyConf = GetFileContents("/etc/chrony/chrony.conf"); |
| 600 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | if (chronyConf.find("server " + gw) == std::string::npos) { |
| 601 | chronyConf += "\n# Auto-detected default gateway fallback\nserver " | ||
| 602 |
3/6✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
18 | + gw + " minpoll 10 maxpoll 12 iburst\n"; |
| 603 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
36 | PutFileContents("/etc/chrony/chrony.conf", chronyConf); |
| 604 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | execbg("/usr/bin/systemctl reload-or-restart chrony.service &"); |
| 605 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | printf("FPP - Added default gateway %s as fallback NTP server\n", |
| 606 | gw.c_str()); | ||
| 607 | } | ||
| 608 | 18 | } | |
| 609 | } | ||
| 610 | 25 | } | |
| 611 | 25 | bool changed = false; | |
| 612 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 25 times.
|
26 | for (auto& ftc : filesToConsider) { |
| 613 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (filesNeeded.find(ftc) == filesNeeded.end()) { |
| 614 | // No longer used/needed, remove it | ||
| 615 | 1 | unlink(ftc.c_str()); | |
| 616 | 1 | changed = true; | |
| 617 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
2 | if (startsWith(ftc, "/etc/wpa_supplicant/")) { |
| 618 | ✗ | std::string intf = ftc.substr(ftc.find("-") + 1); | |
| 619 | ✗ | intf = intf.substr(0, intf.find(".")); | |
| 620 | ✗ | execbg("/usr/bin/systemctl disable wpa_supplicant@" + intf + ".service &"); | |
| 621 | ✗ | } | |
| 622 | } | ||
| 623 | } | ||
| 624 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | changed |= compareAndCopyFiles(filesNeeded); |
| 625 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 5 times.
|
25 | if (changed) { |
| 626 | 20 | bool localFullReload = false; | |
| 627 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | printf("Need to restart/reload stuff.\n"); |
| 628 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
|
20 | if (!hostapd) { |
| 629 | // Out of AP mode before the Wi-Fi client starts: while hostapd | ||
| 630 | // still holds the radio, a wpa_supplicant started beside it often | ||
| 631 | // fails to associate, and leaving AP mode from the UI then needed | ||
| 632 | // a power cycle (#2825). | ||
| 633 |
6/10✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 14 times.
|
64 | if (contains(execAndReturn("/usr/bin/systemctl is-enabled hostapd"), "enabled")) { |
| 634 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | execbg("/usr/bin/systemctl disable hostapd.service &"); |
| 635 | 2 | localFullReload = true; | |
| 636 | } | ||
| 637 |
3/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 14 times.
|
16 | if (hostapdRunning()) { |
| 638 | // Bounded: an unresponsive hostapd (driver/dbus hiccup) must not be | ||
| 639 | // able to wedge fppinit indefinitely - this runs synchronously in | ||
| 640 | // the interactive "Restart Network" request (network_apply_interface), | ||
| 641 | // so a hang here blocks that whole HTTP request and can make the | ||
| 642 | // rest of the appliance look unresponsive until it clears. | ||
| 643 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | exec("/usr/bin/timeout 10 /usr/bin/systemctl stop hostapd.service"); |
| 644 |
3/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
2 | if (hostapdRunning()) { |
| 645 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - hostapd did not stop within 10s, killing it\n"); |
| 646 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | exec("/usr/bin/systemctl kill --signal=SIGKILL hostapd.service"); |
| 647 | } | ||
| 648 | 2 | localFullReload = true; | |
| 649 | } | ||
| 650 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
32 | exec("rm -f /home/fpp/media/tmp/wifi-*.ascii"); |
| 651 | } | ||
| 652 | // These end in "&" and must not be waited for: through exec()'s popen the | ||
| 653 | // backgrounded systemctl keeps the pipe open, so exec() blocks until it | ||
| 654 | // exits. A start job for an adapter that is not plugged in then held | ||
| 655 | // fppinit -- and systemd-networkd, ordered after it -- until the job | ||
| 656 | // timed out. | ||
| 657 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 20 times.
|
46 | for (auto& c : commandsToRun) { |
| 658 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | printf(" %s\n", c.c_str()); |
| 659 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | execbg(c); |
| 660 | } | ||
| 661 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (fullReload) { |
| 662 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
40 | exec("/usr/bin/systemctl daemon-reload"); |
| 663 | } | ||
| 664 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | execbg("/usr/bin/systemctl reload-or-restart systemd-networkd.service &"); |
| 665 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 16 times.
|
20 | if (hostapd) { |
| 666 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | unblockWifi(); |
| 667 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | execbg("/usr/bin/systemctl reload-or-restart hostapd.service &"); |
| 668 | } | ||
| 669 |
3/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 18 times.
|
20 | if (fullReload && localFullReload) { |
| 670 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | exec("/usr/bin/systemctl daemon-reload"); |
| 671 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | exec("/usr/bin/systemctl reload-or-restart systemd-networkd.service"); |
| 672 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | for (auto& c : postCommandsToRun) { |
| 673 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf(" %s\n", c.c_str()); |
| 674 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | execbg(c); |
| 675 | } | ||
| 676 | } | ||
| 677 | } | ||
| 678 | |||
| 679 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | printf("FPP - Setting max IGMP memberships\n"); |
| 680 | // rmem_max is the ceiling a plain SO_RCVBUF request is clamped to. The e131 | ||
| 681 | // bridge input sockets ask for far more than the old 384KB when many | ||
| 682 | // universes are configured (and use SO_RCVBUFFORCE where possible), so raise | ||
| 683 | // the ceiling to 8MB to cover the burst of a several-hundred-universe frame. | ||
| 684 |
2/4✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
|
25 | exec("/usr/sbin/sysctl -w net/core/rmem_max=8388608 net/core/wmem_max=393216 net/ipv4/igmp_max_memberships=512 > /dev/null 2>&1"); |
| 685 | |||
| 686 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | printf("FPP - Configuring ethernet interfaces for show traffic\n"); |
| 687 |
5/8✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 49 times.
✓ Branch 7 taken 25 times.
|
74 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net/")) { |
| 688 |
2/4✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
|
49 | std::string dev = entry.path().filename(); |
| 689 |
3/4✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 27 times.
|
49 | if (IsShowEthernetInterface(dev)) { |
| 690 |
3/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 4 times.
|
22 | if (InterfaceHasRoutableIPv4(dev)) { |
| 691 | // fq is required for the per-socket pacing the UDP channel | ||
| 692 | // outputs use, and is a good general-purpose default even | ||
| 693 | // when pacing is off (whether to pace is decided per | ||
| 694 | // destination at runtime). Only useful on interfaces that | ||
| 695 | // can carry routed UDP, so an address-less interface | ||
| 696 | // dedicated to a raw layer-2 output (ColorLight) keeps its | ||
| 697 | // default qdisc. Not applied to wifi (mac80211 has its own | ||
| 698 | // internal queueing), and the helper refuses to replace an | ||
| 699 | // admin-installed custom qdisc. | ||
| 700 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | InstallShowTrafficQdisc(dev); |
| 701 | } | ||
| 702 | // Energy Efficient Ethernet causes latency spikes and drops with | ||
| 703 | // bursty traffic on several NICs (including the Pi's) | ||
| 704 |
4/8✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
|
22 | exec("/usr/sbin/ethtool --set-eee " + ShellSingleQuote(dev) + " eee off > /dev/null 2>&1"); |
| 705 | } | ||
| 706 | 74 | } | |
| 707 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
|
25 | if (ipForward) { |
| 708 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | exec("/usr/sbin/sysctl net.ipv4.ip_forward=1"); |
| 709 | } else { | ||
| 710 |
2/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
48 | exec("/usr/sbin/sysctl net.ipv4.ip_forward=0"); |
| 711 | } | ||
| 712 |
18/34✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 24 times.
✓ Branch 14 taken 1 times.
✓ Branch 15 taken 24 times.
✓ Branch 16 taken 1 times.
✓ Branch 17 taken 24 times.
✓ Branch 18 taken 1 times.
✓ Branch 19 taken 24 times.
✓ Branch 20 taken 1 times.
✓ Branch 21 taken 24 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 25 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
|
29 | if (reloadApache && (!contains(execAndReturn("/usr/bin/systemctl is-active apache2"), "inactive"))) { |
| 713 | ✗ | exec("/usr/bin/systemctl reload-or-restart apache2"); | |
| 714 | } | ||
| 715 | 25 | } | |
| 716 | |||
| 717 | // Whether an IPv4 address is one that reaches a real network. Excludes the | ||
| 718 | // board's own USB gadget and tether addresses, and 169.254/16: networkd falls | ||
| 719 | // back to link-local while DHCP is still pending (LinkLocalAddressing=yes), and | ||
| 720 | // the probe can finish before a slow DHCP server answers, so treating it as | ||
| 721 | // "the address" started fppd on it ahead of the real lease. | ||
| 722 | 102 | static bool isUpstreamAddress(const std::string& addr) { | |
| 723 |
11/26✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 102 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 102 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 102 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 102 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 102 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 102 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 102 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 102 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 102 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
|
306 | return !startsWith(addr, "169.254.") && addr != "192.168.6.2" && addr != "192.168.7.2" && addr != "192.168.8.1"; |
| 724 | } | ||
| 725 | |||
| 726 | // Check if there are any network interfaces that could potentially receive NTP time | ||
| 727 | // Returns true if there's at least one interface with a "real" IP address that could reach NTP servers | ||
| 728 | // Excludes loopback, USB gadget, and tethering interfaces | ||
| 729 | 711 | static bool hasNetworkInterfaceForNTP() { | |
| 730 | // If EnableTethering is explicitly set to 1, user expects to use tethering | ||
| 731 | // which means no external network - skip NTP wait | ||
| 732 |
2/4✓ Branch 0 taken 711 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 711 times.
✗ Branch 3 not taken.
|
711 | int tetheringEnabled = getRawSettingInt("EnableTethering", 0); |
| 733 |
2/2✓ Branch 0 taken 165 times.
✓ Branch 1 taken 546 times.
|
711 | if (tetheringEnabled == 1) { |
| 734 |
1/2✓ Branch 0 taken 165 times.
✗ Branch 1 not taken.
|
165 | printf("FPP - Tethering is enabled, skipping NTP time wait\n"); |
| 735 | 165 | return false; | |
| 736 | } | ||
| 737 | |||
| 738 | 546 | struct ifaddrs* ifAddrStruct = NULL; | |
| 739 | 546 | struct ifaddrs* ifa = NULL; | |
| 740 | 546 | void* tmpAddrPtr = NULL; | |
| 741 | 546 | bool hasValidIP = false; | |
| 742 | |||
| 743 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 546 times.
|
546 | if (getifaddrs(&ifAddrStruct) != 0) { |
| 744 | // If we can't get interface info, assume we might have network | ||
| 745 | ✗ | return true; | |
| 746 | } | ||
| 747 | |||
| 748 |
2/2✓ Branch 0 taken 781 times.
✓ Branch 1 taken 503 times.
|
1284 | for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { |
| 749 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 781 times.
|
781 | if (!ifa->ifa_addr) { |
| 750 | 589 | continue; | |
| 751 | } | ||
| 752 | |||
| 753 |
1/2✓ Branch 0 taken 781 times.
✗ Branch 1 not taken.
|
781 | std::string nm = ifa->ifa_name; |
| 754 | // Skip loopback and USB gadget interfaces (usb0, usb1, etc.) | ||
| 755 |
12/20✓ Branch 0 taken 781 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 192 times.
✓ Branch 3 taken 589 times.
✓ Branch 4 taken 192 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 192 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 192 times.
✓ Branch 10 taken 192 times.
✓ Branch 11 taken 589 times.
✓ Branch 12 taken 192 times.
✓ Branch 13 taken 589 times.
✓ Branch 14 taken 589 times.
✓ Branch 15 taken 192 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
1165 | if (nm == "lo" || startsWith(nm, "usb")) { |
| 756 | 589 | continue; | |
| 757 | } | ||
| 758 | |||
| 759 | // Only check IPv4 addresses | ||
| 760 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 149 times.
|
192 | if (ifa->ifa_addr->sa_family == AF_INET) { |
| 761 | 43 | tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; | |
| 762 | char addressBuffer[INET_ADDRSTRLEN]; | ||
| 763 | 43 | inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); | |
| 764 |
1/2✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
|
43 | std::string addr = addressBuffer; |
| 765 | |||
| 766 | // Skip USB gadget, tethering and link-local addresses | ||
| 767 |
2/4✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 43 times.
|
43 | if (!isUpstreamAddress(addr)) { |
| 768 | ✗ | continue; | |
| 769 | } | ||
| 770 | |||
| 771 | // Found a valid IP that could potentially reach NTP | ||
| 772 | 43 | hasValidIP = true; | |
| 773 | 43 | break; | |
| 774 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 43 times.
|
43 | } |
| 775 |
3/3✓ Branch 0 taken 149 times.
✓ Branch 1 taken 589 times.
✓ Branch 2 taken 43 times.
|
781 | } |
| 776 | |||
| 777 |
1/2✓ Branch 0 taken 546 times.
✗ Branch 1 not taken.
|
546 | if (ifAddrStruct != NULL) { |
| 778 | 546 | freeifaddrs(ifAddrStruct); | |
| 779 | } | ||
| 780 | |||
| 781 | // If no valid IP found, check if any "real" interfaces exist with link up | ||
| 782 | // An interface must have carrier (link) to potentially get DHCP | ||
| 783 |
2/2✓ Branch 0 taken 503 times.
✓ Branch 1 taken 43 times.
|
546 | if (!hasValidIP) { |
| 784 | // Re-scan to check if interfaces exist with carrier | ||
| 785 |
1/2✓ Branch 0 taken 503 times.
✗ Branch 1 not taken.
|
503 | if (getifaddrs(&ifAddrStruct) == 0) { |
| 786 |
1/2✓ Branch 0 taken 503 times.
✗ Branch 1 not taken.
|
503 | std::string tetherInterface = FindTetherWIFIAdapater(); |
| 787 |
2/2✓ Branch 0 taken 607 times.
✓ Branch 1 taken 503 times.
|
1110 | for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { |
| 788 |
1/2✓ Branch 0 taken 607 times.
✗ Branch 1 not taken.
|
607 | std::string nm = ifa->ifa_name; |
| 789 | // Skip loopback and USB gadget interfaces | ||
| 790 |
12/20✓ Branch 0 taken 607 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✓ Branch 3 taken 503 times.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 104 times.
✓ Branch 10 taken 104 times.
✓ Branch 11 taken 503 times.
✓ Branch 12 taken 104 times.
✓ Branch 13 taken 503 times.
✓ Branch 14 taken 503 times.
✓ Branch 15 taken 104 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
815 | if (nm == "lo" || startsWith(nm, "usb")) { |
| 791 | 503 | continue; | |
| 792 | } | ||
| 793 | // Skip the interface designated for tethering | ||
| 794 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
|
104 | if (nm == tetherInterface) { |
| 795 | ✗ | continue; | |
| 796 | } | ||
| 797 | // Check for "real" network interface names | ||
| 798 |
5/24✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 104 times.
✓ Branch 10 taken 104 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 104 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 104 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
312 | if (startsWith(nm, "eth") || startsWith(nm, "wlan") || |
| 799 |
4/28✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 104 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 104 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 104 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 104 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
|
104 | startsWith(nm, "en") || startsWith(nm, "wl") || |
| 800 |
7/34✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✓ Branch 17 taken 104 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 104 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 104 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 104 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 104 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
|
208 | startsWith(nm, "br") || startsWith(nm, "bond")) { |
| 801 | // Check carrier state via sysfs - most reliable across all drivers | ||
| 802 | // /sys/class/net/<iface>/carrier returns 1 if link, 0 if no link | ||
| 803 |
2/4✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
|
104 | std::string carrierPath = "/sys/class/net/" + nm + "/carrier"; |
| 804 |
1/2✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
|
104 | std::string carrier = GetFileContents(carrierPath); |
| 805 |
1/2✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
|
104 | TrimWhiteSpace(carrier); |
| 806 |
2/4✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
|
104 | if (carrier == "1") { |
| 807 | ✗ | printf("FPP - Interface %s has carrier, will wait for NTP\n", nm.c_str()); | |
| 808 | ✗ | hasValidIP = true; | |
| 809 | ✗ | break; | |
| 810 | } | ||
| 811 |
2/4✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
|
104 | } |
| 812 |
2/3✓ Branch 0 taken 104 times.
✓ Branch 1 taken 503 times.
✗ Branch 2 not taken.
|
607 | } |
| 813 |
1/2✓ Branch 0 taken 503 times.
✗ Branch 1 not taken.
|
503 | if (ifAddrStruct != NULL) { |
| 814 | 503 | freeifaddrs(ifAddrStruct); | |
| 815 | } | ||
| 816 | 503 | } | |
| 817 | } | ||
| 818 | 546 | return hasValidIP; | |
| 819 | } | ||
| 820 | |||
| 821 | 23 | void handleBootDelay() { | |
| 822 |
2/4✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
23 | int i = getRawSettingInt("bootDelay", -1); |
| 823 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | const std::string delayFile = FPP_MEDIA_DIR + "/tmp/boot_delay"; |
| 824 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | const std::string skipFile = FPP_MEDIA_DIR + "/tmp/boot_delay_skip"; |
| 825 | |||
| 826 | // bootDelay=0 means no delay at all - clean up any flag file and return immediately | ||
| 827 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 17 times.
|
23 | if (i == 0) { |
| 828 | 6 | unlink(delayFile.c_str()); | |
| 829 | 6 | unlink(skipFile.c_str()); | |
| 830 | 6 | return; | |
| 831 | } | ||
| 832 | |||
| 833 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
|
17 | if (i > 0) { |
| 834 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - Sleeping for %d seconds\n", i); |
| 835 | // Create flag file with start time and duration for UI countdown | ||
| 836 | 1 | time_t startTime = time(nullptr); | |
| 837 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | std::string flagContent = std::to_string(startTime) + "," + std::to_string(i); |
| 838 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | PutFileContents(delayFile, flagContent); |
| 839 | // Notify systemd we're starting the delay and extend timeout | ||
| 840 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | sd_notify(0, "STATUS=Boot delay in progress"); |
| 841 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | sd_notifyf(0, "EXTEND_TIMEOUT_USEC=%llu", (unsigned long long)(i + 30) * 1000000); |
| 842 | |||
| 843 | // Sleep in 100ms increments to allow UI to show countdown and respond to skip | ||
| 844 | 1 | int remainingMs = i * 1000; | |
| 845 | 1 | int watchdogCounter = 0; | |
| 846 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | while (remainingMs > 0) { |
| 847 | // Check for skip request from UI every iteration | ||
| 848 |
3/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 11 times.
|
12 | if (FileExists(skipFile)) { |
| 849 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - Boot delay skip requested by user\n"); |
| 850 | 1 | unlink(skipFile.c_str()); | |
| 851 | 1 | break; | |
| 852 | } | ||
| 853 | |||
| 854 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| 855 | 11 | remainingMs -= 100; | |
| 856 | 11 | watchdogCounter++; | |
| 857 | |||
| 858 | // Notify systemd watchdog every 10 seconds (100 iterations) | ||
| 859 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | if (watchdogCounter >= 100) { |
| 860 | ✗ | sd_notify(0, "WATCHDOG=1"); | |
| 861 | ✗ | watchdogCounter = 0; | |
| 862 | } | ||
| 863 | } | ||
| 864 | // Remove flag files when delay completes | ||
| 865 | 1 | unlink(delayFile.c_str()); | |
| 866 | 1 | unlink(skipFile.c_str()); | |
| 867 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
17 | } else if (i == -1) { |
| 868 | // Auto mode: nothing to wait on here. The is-there-an-NTP-capable-network | ||
| 869 | // decision and the quick SNTP set both happen in handleTimeSyncWait(), | ||
| 870 | // which runs on the network thread after waitForInterfacesUp() has | ||
| 871 | // already waited for link/IP properly. This used to pre-poll up to 5s | ||
| 872 | // for an interface, but that only serialized boot ahead of the | ||
| 873 | // concurrent audio/network threads and its verdict was recomputed | ||
| 874 | // later anyway. Just clean up any stale flag files. | ||
| 875 | 16 | unlink(delayFile.c_str()); | |
| 876 | 16 | unlink(skipFile.c_str()); | |
| 877 | } | ||
| 878 |
4/4✓ Branch 0 taken 17 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 6 times.
|
29 | } |
| 879 | |||
| 880 | // Minimal SNTP (RFC 4330) client: query an NTP server over UDP and step the | ||
| 881 | // system clock to its transmit timestamp (second precision). This one-shot set | ||
| 882 | // runs during boot so we never block ~20s waiting for the chrony daemon to step | ||
| 883 | // the clock; chrony refines it to sub-millisecond afterwards. Note the socket is | ||
| 884 | // left unbound, so the query sources from a random ephemeral port -- which (like | ||
| 885 | // chrony itself) dodges routers/ISP gateways that drop NTP replies to source | ||
| 886 | // port 123. Must run as root (clock_settime needs CAP_SYS_TIME). Returns true if | ||
| 887 | // the clock was set. | ||
| 888 | 28 | static bool quickSntpSet(const std::string& host, int timeoutSecs) { | |
| 889 | 28 | struct addrinfo hints {}; | |
| 890 | 28 | hints.ai_family = AF_INET; | |
| 891 | 28 | hints.ai_socktype = SOCK_DGRAM; | |
| 892 | 28 | struct addrinfo* res = nullptr; | |
| 893 |
3/8✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 28 times.
✗ Branch 7 not taken.
|
28 | if (getaddrinfo(host.c_str(), "123", &hints, &res) != 0 || res == nullptr) { |
| 894 | 28 | return false; | |
| 895 | } | ||
| 896 | ✗ | bool ok = false; | |
| 897 | ✗ | int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); | |
| 898 | ✗ | if (sock >= 0) { | |
| 899 | ✗ | struct timeval tv { timeoutSecs, 0 }; | |
| 900 | ✗ | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | |
| 901 | ✗ | setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); | |
| 902 | ✗ | unsigned char pkt[48] = { 0 }; | |
| 903 | ✗ | pkt[0] = 0x1b; // LI=0, VN=3, Mode=3 (client) | |
| 904 | ✗ | if (sendto(sock, pkt, sizeof(pkt), 0, res->ai_addr, res->ai_addrlen) == (ssize_t)sizeof(pkt) | |
| 905 | ✗ | && recv(sock, pkt, sizeof(pkt), 0) == (ssize_t)sizeof(pkt)) { | |
| 906 | ✗ | int stratum = pkt[1]; | |
| 907 | // Transmit timestamp: 32-bit seconds since 1900, big-endian, at offset 40. | ||
| 908 | ✗ | uint32_t ntpSecs = ((uint32_t)pkt[40] << 24) | ((uint32_t)pkt[41] << 16) | ((uint32_t)pkt[42] << 8) | (uint32_t)pkt[43]; | |
| 909 | // stratum 0 == "kiss o' death"/invalid; 16+ == unsynchronized. | ||
| 910 | // 2208988800 = seconds between the 1900 (NTP) and 1970 (Unix) epochs. | ||
| 911 | ✗ | if (stratum > 0 && stratum < 16 && ntpSecs > 2208988800U) { | |
| 912 | ✗ | struct timespec ts { (time_t)(ntpSecs - 2208988800U), 0 }; | |
| 913 | ✗ | ok = (clock_settime(CLOCK_REALTIME, &ts) == 0); | |
| 914 | } | ||
| 915 | } | ||
| 916 | ✗ | close(sock); | |
| 917 | } | ||
| 918 | ✗ | freeaddrinfo(res); | |
| 919 | ✗ | return ok; | |
| 920 | } | ||
| 921 | |||
| 922 | // Ensure the clock is sane before fppd starts - called AFTER waitForInterfacesUp | ||
| 923 | // so we know network is available. On RTC-less boards the clock boots stale (at | ||
| 924 | // the image build date), which would make schedules and logs wrong. Rather than | ||
| 925 | // block boot for ~20s waiting for the chrony daemon to step the clock, do a | ||
| 926 | // quick one-shot SNTP set now and continue; the daemon refines it afterwards. | ||
| 927 | 21 | void handleTimeSyncWait() { | |
| 928 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | int i = getRawSettingInt("bootDelay", -1); |
| 929 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 16 times.
|
21 | if (i != -1) { |
| 930 | // Only do time sync wait in auto mode | ||
| 931 | 7 | return; | |
| 932 | } | ||
| 933 | |||
| 934 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | const std::string delayFile = FPP_MEDIA_DIR + "/tmp/boot_delay"; |
| 935 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | const std::string skipFile = FPP_MEDIA_DIR + "/tmp/boot_delay_skip"; |
| 936 | |||
| 937 | // Check if there are any network interfaces that could get NTP time | ||
| 938 | // If not, skip - no point trying NTP on a device with no network | ||
| 939 |
3/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 14 times.
|
16 | if (!hasNetworkInterfaceForNTP()) { |
| 940 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | printf("FPP - No network interface found, skipping NTP time set\n"); |
| 941 | 2 | unlink(delayFile.c_str()); | |
| 942 | 2 | unlink(skipFile.c_str()); | |
| 943 | 2 | return; | |
| 944 | } | ||
| 945 | |||
| 946 | struct stat attr; | ||
| 947 | 14 | stat("/etc/fpp/rfs_version", &attr); | |
| 948 | 14 | time_t fileTime = attr.st_ctime; | |
| 949 | 14 | time_t currentTime = time(nullptr); | |
| 950 | |||
| 951 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (difftime(fileTime, currentTime) <= 0) { |
| 952 | // Clock is already at or past the image build date - nothing to do. | ||
| 953 | ✗ | unlink(delayFile.c_str()); | |
| 954 | ✗ | unlink(skipFile.c_str()); | |
| 955 | ✗ | return; | |
| 956 | } | ||
| 957 | |||
| 958 | // Clock is stale (no RTC). Do a fast SNTP set against the configured server | ||
| 959 | // and move on -- do NOT block on full NTP convergence. Use the first | ||
| 960 | // pool/server from chrony.conf so a site-local time server is honoured; fall | ||
| 961 | // back to FPP's pool. | ||
| 962 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
28 | std::string ntpHost = execAndReturn( |
| 963 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | "/usr/bin/awk '/^(pool|server)[[:space:]]/ { print $2; exit }' /etc/chrony/chrony.conf 2>/dev/null"); |
| 964 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | TrimWhiteSpace(ntpHost); |
| 965 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (ntpHost.empty()) { |
| 966 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | ntpHost = "falconplayer.pool.ntp.org"; |
| 967 | } | ||
| 968 | |||
| 969 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | printf("FPP - System clock is stale; doing a quick SNTP set from %s\n", ntpHost.c_str()); |
| 970 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | sd_notify(0, "STATUS=Setting system clock via SNTP"); |
| 971 | 14 | bool set = false; | |
| 972 |
3/4✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
42 | for (int attempt = 0; attempt < 2 && !set; ++attempt) { |
| 973 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | set = quickSntpSet(ntpHost, 2); |
| 974 | } | ||
| 975 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (set) { |
| 976 | ✗ | currentTime = time(nullptr); | |
| 977 | struct tm tmNow; | ||
| 978 | ✗ | localtime_r(¤tTime, &tmNow); | |
| 979 | char buffer[26]; | ||
| 980 | ✗ | strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", &tmNow); | |
| 981 | ✗ | printf("FPP - Clock set via SNTP to %s; chrony will refine it in the background\n", buffer); | |
| 982 | } else { | ||
| 983 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | printf("FPP - Quick SNTP set failed (no time server reachable yet); continuing, chrony will sync when available\n"); |
| 984 | } | ||
| 985 | 14 | unlink(delayFile.c_str()); | |
| 986 | 14 | unlink(skipFile.c_str()); | |
| 987 |
4/4✓ Branch 0 taken 14 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 2 times.
|
18 | } |
| 988 | |||
| 989 | 23 | void checkWLANInterface() { | |
| 990 |
16/62✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 23 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 23 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 23 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 23 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 23 times.
✗ Branch 26 not taken.
✓ Branch 27 taken 23 times.
✗ Branch 28 not taken.
✓ Branch 29 taken 23 times.
✓ Branch 30 taken 23 times.
✗ Branch 31 not taken.
✓ Branch 32 taken 23 times.
✗ Branch 33 not taken.
✓ Branch 34 taken 23 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 23 times.
✗ Branch 37 not taken.
✓ Branch 38 taken 23 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✓ Branch 41 taken 23 times.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✗ Branch 52 not taken.
✗ Branch 53 not taken.
✗ Branch 54 not taken.
✗ Branch 55 not taken.
✗ Branch 56 not taken.
✗ Branch 57 not taken.
✗ Branch 58 not taken.
✗ Branch 59 not taken.
✗ Branch 60 not taken.
✗ Branch 61 not taken.
|
115 | if (contains(execAndReturn("/usr/bin/systemctl is-enabled wpa_supplicant@wlan0"), "enabled") && contains(execAndReturn("/usr/bin/systemctl is-active wpa_supplicant@wlan0"), "inactive")) { |
| 991 | ✗ | printf("Need to restart wpa_supplicant@wlan0\n"); | |
| 992 | ✗ | execbg("systemctl restart \"wpa_supplicant@wlan0.service\" &"); | |
| 993 | } | ||
| 994 | 23 | } | |
| 995 | |||
| 996 | // Build the spoken "I Have Found The Following I P Addresses, ..." string from | ||
| 997 | // the current non-tether IPv4 addresses. Returns "" if none are present yet. | ||
| 998 | 71 | static std::string buildIPAnnounceString() { | |
| 999 |
1/2✓ Branch 0 taken 71 times.
✗ Branch 1 not taken.
|
71 | std::string announce = "I Have Found The Following I P Addresses"; |
| 1000 | 71 | bool found = false; | |
| 1001 | 71 | struct ifaddrs* ifAddrStruct = NULL; | |
| 1002 | 71 | struct ifaddrs* ifa = NULL; | |
| 1003 | 71 | void* tmpAddrPtr = NULL; | |
| 1004 | 71 | getifaddrs(&ifAddrStruct); | |
| 1005 |
2/2✓ Branch 0 taken 195 times.
✓ Branch 1 taken 71 times.
|
266 | for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { |
| 1006 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 195 times.
|
195 | if (!ifa->ifa_addr) { |
| 1007 | 129 | continue; | |
| 1008 | } | ||
| 1009 |
1/2✓ Branch 0 taken 195 times.
✗ Branch 1 not taken.
|
195 | std::string nm = ifa->ifa_name; |
| 1010 |
13/30✓ Branch 0 taken 195 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 195 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 195 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 129 times.
✓ Branch 11 taken 66 times.
✓ Branch 12 taken 195 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 195 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 195 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 195 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 129 times.
✓ Branch 21 taken 66 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
975 | if (startsWith(nm, "usb") || startsWith(nm, "lo")) { |
| 1011 | 129 | continue; | |
| 1012 | } | ||
| 1013 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 35 times.
|
66 | if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4 |
| 1014 | // is a valid IP4 Address | ||
| 1015 | 31 | tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; | |
| 1016 | char addressBuffer[INET_ADDRSTRLEN]; | ||
| 1017 | 31 | inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); | |
| 1018 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | std::string addr = addressBuffer; |
| 1019 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
31 | if (isUpstreamAddress(addr)) { |
| 1020 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | printf("FPP - Found %s IP Address %s\n", ifa->ifa_name, addressBuffer); |
| 1021 |
3/6✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
|
31 | announce += ", " + std::string(addressBuffer); |
| 1022 | 31 | found = true; | |
| 1023 | } | ||
| 1024 | 31 | } | |
| 1025 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 129 times.
|
195 | } |
| 1026 |
1/2✓ Branch 0 taken 71 times.
✗ Branch 1 not taken.
|
71 | if (ifAddrStruct != NULL) { |
| 1027 | 71 | freeifaddrs(ifAddrStruct); | |
| 1028 | } | ||
| 1029 |
6/10✓ Branch 0 taken 29 times.
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 42 times.
✓ Branch 7 taken 29 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
184 | return found ? announce : ""; |
| 1030 | 71 | } | |
| 1031 | |||
| 1032 | // A USB network adapter can enumerate, bind, and bring up its netdev, and then | ||
| 1033 | // have the USB link itself fail at the protocol layer: every register access | ||
| 1034 | // returns -EPROTO, the radio never associates, and the board runs perfectly | ||
| 1035 | // while being completely unreachable. Measured at roughly 5% of boots on one | ||
| 1036 | // adapter/controller pairing, and the box stays dark until someone walks out | ||
| 1037 | // to it. | ||
| 1038 | // | ||
| 1039 | // Nothing recovers this in software. Tried on hardware and rejected: unbind and | ||
| 1040 | // rebind on the usb driver (re-enumerates, then fails its descriptor reads and | ||
| 1041 | // never returns), disabling and re-enabling the root-hub port (leaves it "not | ||
| 1042 | // attached"), and unbinding the host controller (brings the controller back but | ||
| 1043 | // not the device). Even a physical replug does not reliably fix it. A reboot | ||
| 1044 | // does, so that is what this does. | ||
| 1045 | // | ||
| 1046 | // The whole difficulty is firing only for this failure and nothing else. All of | ||
| 1047 | // the following must hold: | ||
| 1048 | // - a USB-attached network interface exists and has no carrier | ||
| 1049 | // - no other interface has an address, so we are not already reachable | ||
| 1050 | // - tethering is not the configured intent | ||
| 1051 | // - this boot's kernel log shows USB protocol errors | ||
| 1052 | // That last condition is what separates a wedged adapter from an ordinary "no | ||
| 1053 | // access point in range", "wrong passphrase", or "AP is down" -- none of which a | ||
| 1054 | // reboot would fix, and all of which it would hide. | ||
| 1055 | static constexpr int USB_WEDGE_MAX_REBOOTS = 2; | ||
| 1056 | |||
| 1057 | 33 | static std::string usbWedgeStateFile() { | |
| 1058 | // /var/tmp, not media/tmp (fppinit.service wipes that on stop, and this has | ||
| 1059 | // to survive exactly the reboot it is counting) and not media/config (this | ||
| 1060 | // is transient state, and a restored config backup must not carry a stale | ||
| 1061 | // count into an unrelated box). | ||
| 1062 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
66 | return "/var/tmp/fpp-usbnet-wedge-reboots"; |
| 1063 | } | ||
| 1064 | |||
| 1065 | // Attempts made recently. Deliberately expires: a box power-cycled hours later | ||
| 1066 | // should get a fresh pair of tries rather than inheriting a stale count. | ||
| 1067 | 2 | static int usbWedgeRebootCount() { | |
| 1068 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | std::string c = GetFileContents(usbWedgeStateFile()); |
| 1069 | 2 | int count = 0; | |
| 1070 | 2 | long long when = 0; | |
| 1071 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | if (c.empty() || sscanf(c.c_str(), "%d %lld", &count, &when) != 2) { |
| 1072 | 2 | return 0; | |
| 1073 | } | ||
| 1074 | ✗ | if ((((long long)time(nullptr)) - when) > 3600) { | |
| 1075 | ✗ | return 0; | |
| 1076 | } | ||
| 1077 | ✗ | return count; | |
| 1078 | 2 | } | |
| 1079 | |||
| 1080 | 29 | static void clearUsbWedgeRebootCount() { | |
| 1081 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | unlink(usbWedgeStateFile().c_str()); |
| 1082 | 29 | } | |
| 1083 | |||
| 1084 | // USB port ids (e.g. "1-1.2") of USB-attached network interfaces that have no | ||
| 1085 | // carrier. An interface that has carrier is working even if DHCP has not | ||
| 1086 | // finished, and is none of our business. | ||
| 1087 | 7 | static std::set<std::string> carrierlessUsbNetDeviceIds() { | |
| 1088 | 7 | std::set<std::string> ids; | |
| 1089 | 7 | std::error_code dirEc; | |
| 1090 |
5/8✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 7 times.
|
21 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net", dirEc)) { |
| 1091 |
2/4✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
14 | std::string ifName = entry.path().filename(); |
| 1092 |
12/20✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 7 times.
✓ Branch 10 taken 7 times.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 7 times.
✓ Branch 13 taken 7 times.
✓ Branch 14 taken 7 times.
✓ Branch 15 taken 7 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
28 | if (ifName == "lo" || startsWith(ifName, "usb")) { |
| 1093 | 7 | continue; // usb0/usb1 are the board's own gadget interfaces, not adapters | |
| 1094 | } | ||
| 1095 |
3/6✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | std::string carrier = GetFileContents(entry.path().string() + "/carrier"); |
| 1096 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | TrimWhiteSpace(carrier); |
| 1097 |
3/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
|
7 | if (carrier == "1") { |
| 1098 | 3 | continue; | |
| 1099 | } | ||
| 1100 | 4 | std::error_code ec; | |
| 1101 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | std::filesystem::path dev = std::filesystem::canonical(entry.path() / "device", ec); |
| 1102 |
8/12✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
4 | if (ec || dev.string().find("/usb") == std::string::npos) { |
| 1103 | 1 | continue; // onboard MAC; a reboot is not indicated for it | |
| 1104 | } | ||
| 1105 | // .../usb1/1-1/1-1.2/1-1.2:1.0 -- the interface node's parent is the USB | ||
| 1106 | // device, and its name is the port id the kernel logs errors against. | ||
| 1107 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | std::string usbId = dev.parent_path().filename(); |
| 1108 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | if (!usbId.empty() && usbId.find(':') == std::string::npos) { |
| 1109 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | ids.insert(usbId); |
| 1110 | } | ||
| 1111 |
6/6✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 11 times.
|
26 | } |
| 1112 | 7 | return ids; | |
| 1113 | ✗ | } | |
| 1114 | |||
| 1115 | 3 | static bool anyInterfaceHasAddress() { | |
| 1116 | 3 | struct ifaddrs* addrs = nullptr; | |
| 1117 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (getifaddrs(&addrs) != 0) { |
| 1118 | ✗ | return true; // cannot tell: assume reachable and do not reboot | |
| 1119 | } | ||
| 1120 | 3 | bool found = false; | |
| 1121 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | for (struct ifaddrs* ifa = addrs; ifa && !found; ifa = ifa->ifa_next) { |
| 1122 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) { |
| 1123 | 3 | continue; | |
| 1124 | } | ||
| 1125 | ✗ | std::string nm = ifa->ifa_name; | |
| 1126 | ✗ | if (nm == "lo" || startsWith(nm, "usb")) { | |
| 1127 | ✗ | continue; | |
| 1128 | } | ||
| 1129 | ✗ | uint32_t a = ntohl(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr); | |
| 1130 | ✗ | if ((a >> 16) == 0xA9FE) { | |
| 1131 | ✗ | continue; // 169.254/16 link-local is not a usable address | |
| 1132 | } | ||
| 1133 | ✗ | found = true; | |
| 1134 | ✗ | } | |
| 1135 | 3 | freeifaddrs(addrs); | |
| 1136 | 3 | return found; | |
| 1137 | } | ||
| 1138 | |||
| 1139 | // The signature of a link that has failed below the driver: -EPROTO/-ETIMEDOUT | ||
| 1140 | // against the adapter's own USB port id. | ||
| 1141 | // | ||
| 1142 | // Anchoring on the port id is what keeps a flaky USB stick from rebooting a box | ||
| 1143 | // whose WiFi is merely out of range -- both conditions would otherwise be true | ||
| 1144 | // at once. Every line the kernel and the driver emit for this failure carries | ||
| 1145 | // the id, in both forms: | ||
| 1146 | // usb 1-1.2: device descriptor read/64, error -71 | ||
| 1147 | // rtw88_8822bu 1-1.2:1.0: write register 0x5 failed with -71 | ||
| 1148 | // Thresholded because one retried transfer is normal; a real wedge produces | ||
| 1149 | // thousands. | ||
| 1150 | 3 | static bool kernelLogShowsUsbProtocolFailure(const std::set<std::string>& usbIds) { | |
| 1151 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (usbIds.empty()) { |
| 1152 | ✗ | return false; | |
| 1153 | } | ||
| 1154 | 3 | std::string ids; | |
| 1155 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (const auto& id : usbIds) { |
| 1156 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!ids.empty()) { |
| 1157 | ✗ | ids += "|"; | |
| 1158 | } | ||
| 1159 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
|
12 | for (char c : id) { // the ids contain '.', which must not be a wildcard |
| 1160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (c == '.') { |
| 1161 | ✗ | ids += "\\."; | |
| 1162 | } else { | ||
| 1163 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | ids += c; |
| 1164 | } | ||
| 1165 | } | ||
| 1166 | } | ||
| 1167 | std::string out = execAndReturn( | ||
| 1168 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | "/bin/dmesg 2>/dev/null | /bin/grep -cE '(" + ids + "):.*(error -(71|110)" + |
| 1169 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | "|failed with -(71|110)|fail, status: -(71|110))'"); |
| 1170 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | TrimWhiteSpace(out); |
| 1171 | 3 | return atoi(out.c_str()) >= 4; | |
| 1172 | 3 | } | |
| 1173 | |||
| 1174 | // Set once a reboot has been asked for, so the rest of postNetwork can stop | ||
| 1175 | // doing work for a boot that is about to be thrown away. | ||
| 1176 | static std::atomic<bool> s_usbWedgeRebootPending{false}; | ||
| 1177 | |||
| 1178 | 23 | bool usbWedgeRebootPending() { | |
| 1179 | 23 | return s_usbWedgeRebootPending.load(); | |
| 1180 | } | ||
| 1181 | |||
| 1182 | // Returns true if a reboot was requested. Does NOT block waiting for it. | ||
| 1183 | // | ||
| 1184 | // fppd's start depends on fpp_postnetwork, so anything that stalls this service | ||
| 1185 | // past its TimeoutStartSec takes fppd down with it. /usr/sbin/reboot is a | ||
| 1186 | // symlink to systemctl, which asks logind over D-Bus to set a wall message; | ||
| 1187 | // on an early-boot box that activation can time out. Calling it synchronously | ||
| 1188 | // hung this service for 37+ seconds until systemd killed it -- the reboot never | ||
| 1189 | // happened AND fppd never started, which is worse than the wedge being fixed. | ||
| 1190 | // So: go straight to the system manager (no logind), don't wait for the job, | ||
| 1191 | // and background the call so it cannot stall this thread no matter what. | ||
| 1192 | 8 | static bool rebootIfUsbNetWedged() { | |
| 1193 |
3/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
|
16 | if (getRawSettingInt("AutoRebootOnUSBNetworkFailure", 1) == 0) { |
| 1194 | ✗ | return false; | |
| 1195 | } | ||
| 1196 |
4/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 7 times.
|
16 | if (getRawSettingInt("EnableTethering", 0) == 1) { |
| 1197 | 1 | return false; // no upstream network is expected; being without one is the plan | |
| 1198 | } | ||
| 1199 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::set<std::string> stalled = carrierlessUsbNetDeviceIds(); |
| 1200 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
|
7 | if (stalled.empty()) { |
| 1201 | 4 | return false; // no USB adapter in trouble, so a reboot cannot be the answer | |
| 1202 | } | ||
| 1203 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | if (anyInterfaceHasAddress()) { |
| 1204 | ✗ | return false; // reachable some other way; never reboot a box that is fine | |
| 1205 | } | ||
| 1206 |
3/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
3 | if (!kernelLogShowsUsbProtocolFailure(stalled)) { |
| 1207 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - USB network adapter has no carrier, but its USB link is healthy " |
| 1208 | "(no access point in range?); not rebooting\n"); | ||
| 1209 | 1 | return false; | |
| 1210 | } | ||
| 1211 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | int count = usbWedgeRebootCount(); |
| 1212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (count >= USB_WEDGE_MAX_REBOOTS) { |
| 1213 | ✗ | printf("FPP - USB network adapter is wedged, but %d reboots have not cleared it; " | |
| 1214 | "staying up rather than looping\n", | ||
| 1215 | count); | ||
| 1216 | ✗ | return false; | |
| 1217 | } | ||
| 1218 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | printf("FPP - USB network adapter is wedged: USB protocol errors, no carrier, and no " |
| 1219 | "other network. Rebooting (attempt %d of %d).\n", | ||
| 1220 | count + 1, USB_WEDGE_MAX_REBOOTS); | ||
| 1221 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | PutFileContents(usbWedgeStateFile(), |
| 1222 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
4 | std::to_string(count + 1) + " " + std::to_string((long long)time(nullptr))); |
| 1223 | 2 | sync(); | |
| 1224 | 2 | s_usbWedgeRebootPending = true; | |
| 1225 | // Forced reboot, not a graceful one. The condition being recovered from is a | ||
| 1226 | // wedged USB stack, which is precisely what stops a graceful shutdown from | ||
| 1227 | // finishing: tasks block in uninterruptible sleep on the dead device, so | ||
| 1228 | // neither SIGTERM nor SIGKILL clears them. Measured on hardware, an ordinary | ||
| 1229 | // "systemctl start reboot.target" began stopping units 8s after this point | ||
| 1230 | // and then sat there for 37 minutes, the controller logging | ||
| 1231 | // "musb-hdrc: VBUS_ERROR in a_wait_bcon" while it churned -- the board was | ||
| 1232 | // unreachable that whole time, which is the outage this is supposed to end. | ||
| 1233 | // sync() above is what makes skipping the unmounts acceptable; the sysrq | ||
| 1234 | // fallback covers systemctl itself being unable to run. | ||
| 1235 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | execbg("( /bin/systemctl --force --force reboot" |
| 1236 | " || { echo 1 > /proc/sys/kernel/sysrq; echo b > /proc/sysrq-trigger; } ) &"); | ||
| 1237 | 2 | return true; | |
| 1238 | 7 | } | |
| 1239 | |||
| 1240 | // WiFi interfaces with client settings, which may still associate and get an | ||
| 1241 | // address. Read from the config, so it includes adapters that are not present. | ||
| 1242 | 47 | static std::set<std::string> wifiClientInterfaces() { | |
| 1243 | 47 | std::set<std::string> ret; | |
| 1244 |
2/4✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
|
47 | int tetherEnabled = getRawSettingInt("EnableTethering", 0); |
| 1245 | 47 | std::string tetherInterface; | |
| 1246 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 42 times.
|
47 | if (tetherEnabled == 1) { |
| 1247 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | tetherInterface = FindTetherWIFIAdapater(); // its settings describe the AP, not a client |
| 1248 | } | ||
| 1249 | 47 | std::error_code ec; | |
| 1250 |
6/10✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 34 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 34 times.
✓ Branch 9 taken 47 times.
|
81 | for (const auto& entry : std::filesystem::directory_iterator(FPP_MEDIA_DIR + "/config", ec)) { |
| 1251 |
2/4✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
|
34 | std::string name = entry.path().filename(); |
| 1252 |
13/24✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 21 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 18 times.
✓ Branch 10 taken 21 times.
✓ Branch 11 taken 13 times.
✓ Branch 12 taken 34 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 34 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 16 times.
✓ Branch 17 taken 18 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
102 | if (!startsWith(name, "interface.wl") || name.substr(10) == tetherInterface) { |
| 1253 | 16 | continue; | |
| 1254 | } | ||
| 1255 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | auto settings = loadSettingsFile(entry.path()); |
| 1256 |
12/32✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 18 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 18 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 18 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 18 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 18 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 18 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 18 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 18 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
|
90 | if (!settings["SSID"].empty() && settings["SSID"] != "\"\"") { |
| 1257 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
18 | ret.insert(name.substr(10)); |
| 1258 | } | ||
| 1259 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 16 times.
|
81 | } |
| 1260 | // A hand-written supplicant config counts too | ||
| 1261 |
5/8✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✓ Branch 7 taken 47 times.
|
77 | for (const auto& entry : std::filesystem::directory_iterator("/etc/wpa_supplicant", ec)) { |
| 1262 |
2/4✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | std::string name = entry.path().filename(); |
| 1263 |
15/30✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 12 times.
✓ Branch 13 taken 18 times.
✓ Branch 14 taken 12 times.
✓ Branch 15 taken 18 times.
✓ Branch 16 taken 30 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 30 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 12 times.
✓ Branch 21 taken 18 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
114 | if (startsWith(name, "wpa_supplicant-wl") && endsWith(name, ".conf")) { |
| 1264 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | ret.insert(name.substr(15, name.size() - 15 - 5)); |
| 1265 | } | ||
| 1266 | 77 | } | |
| 1267 | 47 | return ret; | |
| 1268 | 47 | } | |
| 1269 | |||
| 1270 | // An address can only come from a wired interface once it has carrier (static | ||
| 1271 | // configs are not applied without it), or from a present WiFi adapter with | ||
| 1272 | // client settings. With neither, a first boot would sit out the whole wait | ||
| 1273 | // before tethering comes up, so for a board with no wired port and no | ||
| 1274 | // configured WiFi adapter, the wait ends once the set of interfaces has held | ||
| 1275 | // still for a moment -- enough for USB adapters enumerating one after another | ||
| 1276 | // behind a hub (seen ~1s apart). | ||
| 1277 | // | ||
| 1278 | // A wired port without carrier is never written off early: carrier cannot tell | ||
| 1279 | // "no cable" from "still autonegotiating", which took 5.1s on a Pi 5 against a | ||
| 1280 | // gigabit switch. Giving up there started tethering, then tore it down when | ||
| 1281 | // DHCP landed a few seconds later. | ||
| 1282 | // | ||
| 1283 | // A configured WiFi adapter that is absent is not worth waiting for: postNetwork | ||
| 1284 | // runs after network.target, which wpa_supplicant@<if> orders itself before, so | ||
| 1285 | // by now its start job has either seen the adapter appear or given up on it | ||
| 1286 | // (JobTimeoutSec in etc/systemd/wpa_supplicant@.service.d). If it turns up | ||
| 1287 | // later, udev starts its supplicant then. | ||
| 1288 | class UpstreamLinkWatch { | ||
| 1289 | public: | ||
| 1290 | static constexpr int SETTLE_ITERATIONS = 20; // x 200ms | ||
| 1291 | |||
| 1292 | enum class State { | ||
| 1293 | NONE, // nothing can get an address | ||
| 1294 | POSSIBLE, // something may still get link | ||
| 1295 | LINK // something has link; its address may be about to land | ||
| 1296 | }; | ||
| 1297 | |||
| 1298 | 47 | explicit UpstreamLinkWatch(std::set<std::string> wifiClients) : | |
| 1299 | 47 | wifiClients(std::move(wifiClients)) {} | |
| 1300 | |||
| 1301 | 666 | State check(int count) { | |
| 1302 | 666 | std::set<std::string> state; | |
| 1303 | 666 | bool possible = false; | |
| 1304 | 666 | std::error_code ec; | |
| 1305 |
5/8✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1366 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1367 times.
✓ Branch 7 taken 665 times.
|
2032 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net", ec)) { |
| 1306 |
2/4✓ Branch 0 taken 1367 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1367 times.
✗ Branch 3 not taken.
|
1367 | std::string dev = entry.path().filename(); |
| 1307 |
21/36✓ Branch 0 taken 1367 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 702 times.
✓ Branch 3 taken 665 times.
✓ Branch 4 taken 702 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 702 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 702 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 702 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 702 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 702 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 456 times.
✓ Branch 17 taken 246 times.
✓ Branch 18 taken 702 times.
✓ Branch 19 taken 665 times.
✓ Branch 20 taken 702 times.
✓ Branch 21 taken 665 times.
✓ Branch 22 taken 702 times.
✓ Branch 23 taken 665 times.
✓ Branch 24 taken 702 times.
✓ Branch 25 taken 665 times.
✓ Branch 26 taken 1121 times.
✓ Branch 27 taken 246 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
|
2771 | if (dev == "lo" || startsWith(dev, "usb") || !FileExists(entry.path().string() + "/device")) { |
| 1308 | 1121 | continue; // loopback, the board's own gadget links, virtual devices | |
| 1309 | } | ||
| 1310 |
3/6✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 246 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 246 times.
✗ Branch 5 not taken.
|
246 | std::string flags = GetFileContents(entry.path().string() + "/flags"); |
| 1311 | 246 | bool up = strtol(flags.c_str(), nullptr, 16) & IFF_UP; | |
| 1312 |
3/6✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 246 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 246 times.
✗ Branch 5 not taken.
|
246 | state.insert(dev + (up ? ":up" : ":down")); |
| 1313 |
12/22✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 246 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 246 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 217 times.
✓ Branch 7 taken 29 times.
✓ Branch 8 taken 217 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 42 times.
✓ Branch 11 taken 175 times.
✓ Branch 12 taken 246 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 246 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 42 times.
✓ Branch 17 taken 204 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
246 | if (FileExists(entry.path().string() + "/wireless") && !wifiClients.count(dev)) { |
| 1314 | 42 | continue; // no client config, so it can only ever be the tether AP | |
| 1315 | } | ||
| 1316 |
3/6✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 204 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 204 times.
✗ Branch 5 not taken.
|
204 | std::string carrier = GetFileContents(entry.path().string() + "/carrier"); |
| 1317 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | TrimWhiteSpace(carrier); |
| 1318 |
3/4✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 203 times.
|
204 | if (carrier == "1") { |
| 1319 | 1 | return State::LINK; | |
| 1320 | } | ||
| 1321 | 203 | possible = true; | |
| 1322 |
14/14✓ Branch 0 taken 203 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 203 times.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 203 times.
✓ Branch 6 taken 1163 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 665 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 665 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 665 times.
✓ Branch 13 taken 1 times.
|
2079 | } |
| 1323 |
3/4✓ Branch 0 taken 665 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 657 times.
|
665 | if (state != lastState) { |
| 1324 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | lastState = state; |
| 1325 | 8 | lastChange = count; | |
| 1326 | } | ||
| 1327 |
8/8✓ Branch 0 taken 462 times.
✓ Branch 1 taken 203 times.
✓ Branch 2 taken 42 times.
✓ Branch 3 taken 420 times.
✓ Branch 4 taken 40 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 663 times.
✓ Branch 7 taken 2 times.
|
665 | if (possible || lastState.empty() || (count - lastChange) < SETTLE_ITERATIONS) { |
| 1328 | 663 | return State::POSSIBLE; | |
| 1329 | } | ||
| 1330 | 2 | return State::NONE; | |
| 1331 | 666 | } | |
| 1332 | |||
| 1333 | private: | ||
| 1334 | std::set<std::string> wifiClients; | ||
| 1335 | std::set<std::string> lastState; | ||
| 1336 | int lastChange = 0; | ||
| 1337 | }; | ||
| 1338 | |||
| 1339 | 47 | bool waitForInterfacesUp(int timeOut, bool allowUsbRecovery) { | |
| 1340 | 47 | int count = 0; | |
| 1341 |
1/2✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
|
47 | UpstreamLinkWatch linkWatch(wifiClientInterfaces()); |
| 1342 | // If no network interfaces have carrier/link, don't wait for IP address - likely no network available and no point waiting for DHCP/NTP | ||
| 1343 |
3/4✓ Branch 0 taken 695 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✓ Branch 3 taken 29 times.
|
695 | while (!hasNetworkInterfaceForNTP()) { |
| 1344 |
1/2✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
|
666 | UpstreamLinkWatch::State link = linkWatch.check(count); |
| 1345 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 665 times.
|
666 | if (link == UpstreamLinkWatch::State::LINK) { |
| 1346 | // Has link but no address yet: DHCP gets the rest of the timeOut | ||
| 1347 | // below rather than being cut off at the halfway mark. | ||
| 1348 | 1 | break; | |
| 1349 | } | ||
| 1350 | 665 | bool giveUp = count >= (timeOut / 2); // spend half the timeOut waiting for interfaces to have link, then give up | |
| 1351 |
4/4✓ Branch 0 taken 650 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 648 times.
|
665 | if (!giveUp && link == UpstreamLinkWatch::State::NONE) { |
| 1352 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | printf("FPP - No wired port and no configured WiFi adapter present; nothing can get an address\n"); |
| 1353 | 2 | giveUp = true; | |
| 1354 | } | ||
| 1355 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 648 times.
|
665 | if (giveUp) { |
| 1356 | // Before writing the network off, check for a USB adapter whose USB | ||
| 1357 | // link has died, which only a reboot clears. | ||
| 1358 | // | ||
| 1359 | // Opt-in, and only the boot path opts in. The tether callers below | ||
| 1360 | // are driven by networkd-dispatcher on carrier changes, and rebooting | ||
| 1361 | // from a carrier-change handler is a boot loop with extra steps. | ||
| 1362 |
7/8✓ Branch 0 taken 8 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 15 times.
|
17 | if (allowUsbRecovery && rebootIfUsbNetWedged()) { |
| 1363 | // Reboot is queued. Return at once and let the caller skip the | ||
| 1364 | // rest of postNetwork: this service is on fppd's dependency | ||
| 1365 | // chain, so overrunning its start timeout here would fail fppd | ||
| 1366 | // for the few seconds of life this boot has left. | ||
| 1367 | 2 | return false; | |
| 1368 | } | ||
| 1369 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | printf("FPP - No network interfaces with link detected after waiting for %d ms, skipping IP wait\n", count * 200); |
| 1370 | 15 | return false; | |
| 1371 | } | ||
| 1372 |
1/2✓ Branch 0 taken 648 times.
✗ Branch 1 not taken.
|
648 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); |
| 1373 | 648 | ++count; | |
| 1374 | } | ||
| 1375 |
1/2✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
|
30 | printf("FPP - Waited for %0.1f seconds for network interfaces to have link...\n", (((float)count) * 0.2)); |
| 1376 | 30 | std::string announce; | |
| 1377 | do { | ||
| 1378 |
1/2✓ Branch 0 taken 71 times.
✗ Branch 1 not taken.
|
71 | announce = buildIPAnnounceString(); |
| 1379 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 29 times.
|
71 | if (announce.empty()) { |
| 1380 |
1/2✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
|
42 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); |
| 1381 | 42 | ++count; | |
| 1382 | } | ||
| 1383 |
6/6✓ Branch 0 taken 42 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 41 times.
✓ Branch 5 taken 30 times.
|
71 | } while (announce.empty() && (count < timeOut)); |
| 1384 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
|
30 | if (announce.empty()) { |
| 1385 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - Could not get a valid IP address\n"); |
| 1386 | 1 | return false; | |
| 1387 | } | ||
| 1388 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | printf("FPP - Waited for %0.1f seconds for IP address\n", (((float)count) * 0.2f)); |
| 1389 | // Network came up, so whatever the last wedge was, it is over. Clearing here | ||
| 1390 | // rather than letting the count expire keeps the next occurrence entitled to | ||
| 1391 | // its full pair of attempts. | ||
| 1392 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | clearUsbWedgeRebootCount(); |
| 1393 | 29 | return true; | |
| 1394 | 47 | } | |
| 1395 | |||
| 1396 | // Announce the device's IP address(es) over audio with flite + pw-play. Invoked | ||
| 1397 | // from fpp-announce-ip.service (ordered after fpp_postnetwork and the PipeWire | ||
| 1398 | // services) rather than inline in postNetwork, so the CPU-heavy flite synthesis | ||
| 1399 | // doesn't compete with the tail of postNetwork / fppd startup on single-core | ||
| 1400 | // boards. Network is already up by the time the service runs. | ||
| 1401 | ✗ | void announceIPAddresses() { | |
| 1402 | ✗ | if (getRawSettingInt("disableIPAnnouncement", 0) || !FileExists("/usr/bin/flite")) { | |
| 1403 | ✗ | return; | |
| 1404 | } | ||
| 1405 | ✗ | std::string announce = buildIPAnnounceString(); | |
| 1406 | // postNetwork's IP wait can time out with the DHCP lease landing moments | ||
| 1407 | // later; this service is off fppd's critical path, so give the address a | ||
| 1408 | // bounded chance to appear rather than silently skipping the announcement. | ||
| 1409 | ✗ | for (int i = 0; announce.empty() && i < 75; ++i) { | |
| 1410 | ✗ | std::this_thread::sleep_for(std::chrono::milliseconds(200)); | |
| 1411 | ✗ | announce = buildIPAnnounceString(); | |
| 1412 | } | ||
| 1413 | ✗ | if (announce.empty()) { | |
| 1414 | ✗ | printf("FPP - announceIP: no usable IP address to announce\n"); | |
| 1415 | ✗ | return; | |
| 1416 | } | ||
| 1417 | // flite is an ALSA client, so playing directly (flite -t) would route | ||
| 1418 | // through the pipewire-alsa plugin + /root/.asoundrc -- the fragile ALSA | ||
| 1419 | // chain we're retiring. Instead render to a WAV and play it through PipeWire | ||
| 1420 | // natively with pw-play. pw-play needs the runtime env vars to find FPP's | ||
| 1421 | // PipeWire instance at /run/pipewire-fpp. /run/fppd is created by setupAudio. | ||
| 1422 | // Both halves are hard-capped. A PipeWire graph that cannot reach a working | ||
| 1423 | // sink does not fail pw-play -- the node parks in error and the stream never | ||
| 1424 | // starts *or* ends, so pw-play blocks in poll() indefinitely. Unbounded, that | ||
| 1425 | // turns a cosmetic audio problem into an unbootable box. The real fix is | ||
| 1426 | // upstream (FPP now stops WirePlumber from creating a second node fighting | ||
| 1427 | // for the same PCM), but nothing about announcing an IP address is worth | ||
| 1428 | // risking a hung boot service over, so cap it here too. | ||
| 1429 | ✗ | const std::string announceWav = "/run/fppd/ip-announce.wav"; | |
| 1430 | std::string fliteCmd = | ||
| 1431 | ✗ | "/usr/bin/timeout 60 /usr/bin/flite -voice awb -t \"" + announce + "\" -o " + announceWav + | |
| 1432 | " && PIPEWIRE_RUNTIME_DIR=/run/pipewire-fpp XDG_RUNTIME_DIR=/run/pipewire-fpp" | ||
| 1433 | ✗ | " /usr/bin/timeout 60 /usr/bin/pw-play " + | |
| 1434 | ✗ | announceWav; | |
| 1435 | ✗ | exec(fliteCmd); | |
| 1436 | ✗ | unlink(announceWav.c_str()); // don't leave the rendered WAV behind in /run | |
| 1437 | ✗ | } | |
| 1438 | // Turn off 802.11 power saving on every wireless interface. A show controller | ||
| 1439 | // wants its network responsive, not its radio dozing between beacons; the power | ||
| 1440 | // saved is irrelevant on a mains-powered board. | ||
| 1441 | // | ||
| 1442 | // Was dead code until now -- written but never called, from the commit that | ||
| 1443 | // introduced it -- so this had simply never run. The drivers in use happen to | ||
| 1444 | // default power_save off, which is why nobody noticed; this makes it true by | ||
| 1445 | // intent rather than by luck, and covers an adapter whose driver defaults the | ||
| 1446 | // other way. | ||
| 1447 | 21 | void disableWLANPowerManagement() { | |
| 1448 | 21 | struct ifaddrs* ifAddrStruct = NULL; | |
| 1449 | 21 | getifaddrs(&ifAddrStruct); | |
| 1450 | // getifaddrs yields one entry per address family, so a single interface shows | ||
| 1451 | // up as AF_PACKET plus AF_INET plus AF_INET6. Collect names first: otherwise | ||
| 1452 | // this forks iw two or three times per adapter for no reason. | ||
| 1453 | 21 | std::set<std::string> wireless; | |
| 1454 |
2/2✓ Branch 0 taken 91 times.
✓ Branch 1 taken 21 times.
|
112 | for (struct ifaddrs* ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { |
| 1455 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
|
91 | if (!ifa->ifa_addr) { |
| 1456 | ✗ | continue; | |
| 1457 | } | ||
| 1458 |
1/2✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
|
182 | std::string nm = ifa->ifa_name; |
| 1459 |
4/6✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 91 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 83 times.
|
182 | if (startsWith(nm, "wl")) { |
| 1460 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | wireless.insert(nm); |
| 1461 | } | ||
| 1462 | 91 | } | |
| 1463 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | if (ifAddrStruct != NULL) { |
| 1464 | 21 | freeifaddrs(ifAddrStruct); | |
| 1465 | } | ||
| 1466 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
|
23 | for (const auto& nm : wireless) { |
| 1467 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | printf("FPP - Disabling power saving for %s\n", nm.c_str()); |
| 1468 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
2 | exec("/usr/sbin/iw dev " + ShellSingleQuote(nm) + " set power_save off 2>/dev/null > /dev/null"); |
| 1469 | } | ||
| 1470 | 21 | } | |
| 1471 | |||
| 1472 | 45 | void maybeEnableTethering() { | |
| 1473 |
2/4✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
|
45 | int te = getRawSettingInt("EnableTethering", 0); |
| 1474 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 9 times.
|
45 | if (te == 0) { |
| 1475 | 36 | bool found = false; | |
| 1476 | 36 | struct ifaddrs* ifAddrStruct = NULL; | |
| 1477 | 36 | struct ifaddrs* ifa = NULL; | |
| 1478 | 36 | void* tmpAddrPtr = NULL; | |
| 1479 | 36 | getifaddrs(&ifAddrStruct); | |
| 1480 |
2/2✓ Branch 0 taken 150 times.
✓ Branch 1 taken 36 times.
|
186 | for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { |
| 1481 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
|
150 | if (!ifa->ifa_addr) { |
| 1482 | 92 | continue; | |
| 1483 | } | ||
| 1484 |
1/2✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
|
150 | std::string nm = ifa->ifa_name; |
| 1485 |
13/30✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 150 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 150 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 150 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 92 times.
✓ Branch 11 taken 58 times.
✓ Branch 12 taken 150 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 150 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 150 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 150 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 92 times.
✓ Branch 21 taken 58 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
750 | if (startsWith(nm, "usb") || startsWith(nm, "lo")) { |
| 1486 | 92 | continue; | |
| 1487 | } | ||
| 1488 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 30 times.
|
58 | if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4 |
| 1489 | // is a valid IP4 Address | ||
| 1490 | 28 | tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; | |
| 1491 | char addressBuffer[INET_ADDRSTRLEN]; | ||
| 1492 | 28 | inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); | |
| 1493 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | std::string addr = addressBuffer; |
| 1494 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
28 | if (isUpstreamAddress(addr)) { |
| 1495 | 28 | found = true; | |
| 1496 | } | ||
| 1497 | 28 | } | |
| 1498 |
2/2✓ Branch 0 taken 58 times.
✓ Branch 1 taken 92 times.
|
150 | } |
| 1499 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | if (ifAddrStruct != NULL) { |
| 1500 | 36 | freeifaddrs(ifAddrStruct); | |
| 1501 | } | ||
| 1502 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 28 times.
|
36 | if (!found) { |
| 1503 | // Before enabling tethering, check if any wired interface has carrier/link. | ||
| 1504 | // If a cable is connected, DHCP will likely succeed shortly - don't enable tethering. | ||
| 1505 | // This matches the original bash MaybeEnableTethering logic that checked ethtool link. | ||
| 1506 | 8 | bool hasWiredLink = false; | |
| 1507 |
5/8✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✓ Branch 7 taken 3 times.
|
18 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net/")) { |
| 1508 |
2/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
|
15 | std::string dev = entry.path().filename(); |
| 1509 |
29/58✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 9 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 9 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 9 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 9 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 9 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 9 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 9 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 23 taken 9 times.
✓ Branch 24 taken 9 times.
✓ Branch 25 taken 6 times.
✓ Branch 26 taken 9 times.
✓ Branch 27 taken 6 times.
✓ Branch 28 taken 9 times.
✓ Branch 29 taken 6 times.
✓ Branch 30 taken 9 times.
✓ Branch 31 taken 6 times.
✓ Branch 32 taken 9 times.
✓ Branch 33 taken 6 times.
✓ Branch 34 taken 9 times.
✓ Branch 35 taken 6 times.
✓ Branch 36 taken 15 times.
✗ Branch 37 not taken.
✓ Branch 38 taken 15 times.
✗ Branch 39 not taken.
✓ Branch 40 taken 6 times.
✓ Branch 41 taken 9 times.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✗ Branch 52 not taken.
✗ Branch 53 not taken.
✗ Branch 54 not taken.
✗ Branch 55 not taken.
✗ Branch 56 not taken.
✗ Branch 57 not taken.
|
99 | if (startsWith(dev, "eth") || startsWith(dev, "en") || startsWith(dev, "br") || startsWith(dev, "bond")) { |
| 1510 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | std::string carrierPath = "/sys/class/net/" + dev + "/carrier"; |
| 1511 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | if (FileExists(carrierPath)) { |
| 1512 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::string carrier = GetFileContents(carrierPath); |
| 1513 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | TrimWhiteSpace(carrier); |
| 1514 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
|
6 | if (carrier == "1") { |
| 1515 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | printf("FPP - Wired interface %s has link, not enabling tethering\n", dev.c_str()); |
| 1516 | 5 | hasWiredLink = true; | |
| 1517 | 5 | break; | |
| 1518 | } | ||
| 1519 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | } |
| 1520 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | } |
| 1521 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
|
23 | } |
| 1522 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
|
8 | if (hasWiredLink) { |
| 1523 | // A wired interface has link - DHCP should complete soon. | ||
| 1524 | // Don't enable tethering; the routable.d/check_tether dispatcher | ||
| 1525 | // will clean up tethering if it was enabled from a previous boot. | ||
| 1526 | 5 | return; | |
| 1527 | } | ||
| 1528 | // Check if the tether interface has a wifi client configuration | ||
| 1529 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | std::string tetherInterface = FindTetherWIFIAdapater(); |
| 1530 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | std::string interfaceConfigFile = FPP_MEDIA_DIR + "/config/interface." + tetherInterface; |
| 1531 |
3/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
|
3 | if (FileExists(interfaceConfigFile)) { |
| 1532 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | auto interfaceSettings = loadSettingsFile(interfaceConfigFile); |
| 1533 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
2 | if (!interfaceSettings["SSID"].empty()) { |
| 1534 | // WiFi client config exists - give it time to connect and get DHCP | ||
| 1535 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - %s has WiFi SSID configured, waiting up to 12s for connection and DHCP...\n", tetherInterface.c_str()); |
| 1536 | 1 | int waitCount = 0; | |
| 1537 | 1 | bool gotIP = false; | |
| 1538 | 1 | bool connected = false; | |
| 1539 |
4/6✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
|
25 | while (waitCount < 24 && !gotIP && !connected) { // 24 * 500ms = 12 seconds |
| 1540 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); |
| 1541 | // Check for IP address | ||
| 1542 | 24 | struct ifaddrs* ifAddrCheck = NULL; | |
| 1543 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | if (getifaddrs(&ifAddrCheck) == 0) { |
| 1544 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 24 times.
|
48 | for (struct ifaddrs* ifa = ifAddrCheck; ifa != NULL; ifa = ifa->ifa_next) { |
| 1545 |
2/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
|
24 | if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { |
| 1546 | ✗ | std::string ifname = ifa->ifa_name; | |
| 1547 | ✗ | if (ifname == tetherInterface) { | |
| 1548 | ✗ | void* tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; | |
| 1549 | char addressBuffer[INET_ADDRSTRLEN]; | ||
| 1550 | ✗ | inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); | |
| 1551 | ✗ | std::string addr = addressBuffer; | |
| 1552 | ✗ | if (!contains(addr, "169.254.")) { // Ignore link-local | |
| 1553 | ✗ | printf("FPP - %s got IP address %s\n", tetherInterface.c_str(), addr.c_str()); | |
| 1554 | ✗ | gotIP = true; | |
| 1555 | ✗ | break; | |
| 1556 | } | ||
| 1557 | ✗ | } | |
| 1558 | ✗ | } | |
| 1559 | } | ||
| 1560 | 24 | freeifaddrs(ifAddrCheck); | |
| 1561 | } | ||
| 1562 | // Also check if connected to AP (even without IP yet) | ||
| 1563 |
3/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 11 times.
|
24 | if (!gotIP && waitCount > 10) { // After 5 seconds, start checking connection status |
| 1564 |
4/8✓ 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.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
|
13 | std::string iwOutput = execAndReturn("/usr/sbin/iw " + ShellSingleQuote(tetherInterface) + " link"); |
| 1565 |
3/6✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
|
26 | if (contains(iwOutput, "Connected to")) { |
| 1566 | ✗ | printf("FPP - %s connected to WiFi, keeping WiFi config active\n", tetherInterface.c_str()); | |
| 1567 | ✗ | connected = true; | |
| 1568 | } | ||
| 1569 | 13 | } | |
| 1570 |
2/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | if (gotIP || connected) |
| 1571 | break; | ||
| 1572 | 24 | waitCount++; | |
| 1573 | } | ||
| 1574 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (!gotIP && !connected) { |
| 1575 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | printf("FPP - %s no IP or connection after 12s, enabling tethering\n", tetherInterface.c_str()); |
| 1576 | 1 | te = 1; | |
| 1577 | } | ||
| 1578 | } else { | ||
| 1579 | // Interface config exists but no SSID - enable tethering | ||
| 1580 | ✗ | te = 1; | |
| 1581 | } | ||
| 1582 | 1 | } else { | |
| 1583 | // did not find an ip address and no wifi client config exists | ||
| 1584 | 2 | te = 1; | |
| 1585 | } | ||
| 1586 | 3 | } | |
| 1587 | } | ||
| 1588 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | std::string tetherInterface = FindTetherWIFIAdapater(); |
| 1589 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 32 times.
|
40 | if (te == 1) { |
| 1590 |
4/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
|
8 | if (!FileExists("/sys/class/net/" + tetherInterface)) { |
| 1591 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | printf("No interface for tethering.\n"); |
| 1592 | 4 | te = 2; | |
| 1593 | } | ||
| 1594 | } | ||
| 1595 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 36 times.
|
40 | if (te == 1) { |
| 1596 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string c = CreateHostAPDConfig(tetherInterface); |
| 1597 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | PutFileContents("/etc/hostapd/hostapd.conf", c); |
| 1598 | |||
| 1599 | // Remove wpa_supplicant config if it exists (switching from client to AP mode) | ||
| 1600 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | std::string wpaConfig = "/etc/wpa_supplicant/wpa_supplicant-" + tetherInterface + ".conf"; |
| 1601 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (FileExists(wpaConfig)) { |
| 1602 | ✗ | printf("FPP - Removing wpa_supplicant config for %s to enable tethering\n", tetherInterface.c_str()); | |
| 1603 | ✗ | unlink(wpaConfig.c_str()); | |
| 1604 | ✗ | exec("/usr/bin/systemctl stop " + ShellSingleQuote("wpa_supplicant@" + tetherInterface + ".service")); | |
| 1605 | ✗ | exec("/usr/bin/systemctl disable " + ShellSingleQuote("wpa_supplicant@" + tetherInterface + ".service")); | |
| 1606 | } | ||
| 1607 | |||
| 1608 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string content = "[Match]\nName="; |
| 1609 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | content.append(tetherInterface).append("\nType=wlan\n\n" |
| 1610 | "[Network]\n" | ||
| 1611 | "DHCP=no\n" | ||
| 1612 | "Address=192.168.8.1/24\n" | ||
| 1613 | "DHCPServer=yes\n\n"); | ||
| 1614 | |||
| 1615 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | AppendTetherDHCPServerSection(content, tetherInterface); |
| 1616 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | PutFileContents("/etc/systemd/network/10-" + tetherInterface + ".network", content); |
| 1617 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | unblockWifi(); |
| 1618 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | exec("/usr/bin/systemctl reload-or-restart systemd-networkd.service"); |
| 1619 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | unblockWifi(); |
| 1620 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | exec("/usr/bin/systemctl reload-or-restart hostapd.service"); |
| 1621 | // Only the boot-time symlink is wanted here. Plain "enable" also syncs | ||
| 1622 | // hostapd's SysV init script through update-rc.d and then reloads the | ||
| 1623 | // manager: measured 4.75s on a PocketBeagle2 even when already enabled, | ||
| 1624 | // on fppd's startup path. systemd ignores the init script since a native | ||
| 1625 | // unit exists, and nothing needs a reload to see a new wants link. | ||
| 1626 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | exec("SYSTEMCTL_SKIP_SYSV=1 /usr/bin/systemctl enable --no-reload hostapd.service"); |
| 1627 | 4 | } | |
| 1628 | 40 | } | |
| 1629 | 45 | void detectNetworkModules() { | |
| 1630 | 45 | std::string content; | |
| 1631 |
5/8✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 93 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 93 times.
✓ Branch 7 taken 45 times.
|
138 | for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net/")) { |
| 1632 |
2/4✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 93 times.
✗ Branch 3 not taken.
|
93 | const std::string filename = entry.path().filename(); |
| 1633 |
2/4✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 93 times.
✗ Branch 3 not taken.
|
93 | std::string modName = "/sys/class/net/" + filename + "/device/driver/module"; |
| 1634 |
2/4✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 93 times.
|
93 | if (FileExists(modName)) { |
| 1635 | char buf[256]; | ||
| 1636 | ✗ | size_t l = readlink(modName.c_str(), buf, sizeof(buf)); | |
| 1637 | ✗ | buf[l] = 0; | |
| 1638 | ✗ | std::string mod = buf; | |
| 1639 | ✗ | mod = mod.substr(mod.rfind("/") + 1); | |
| 1640 | ✗ | content += mod; | |
| 1641 | ✗ | content += "\n"; | |
| 1642 | ✗ | } | |
| 1643 | 138 | } | |
| 1644 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
|
45 | if (!content.empty()) { |
| 1645 | ✗ | PutFileContents("/etc/modules-load.d/fpp-network.conf", content); | |
| 1646 | // PutFileContents("/home/fpp/media/config/fpp-network-modules.conf", content); | ||
| 1647 | } | ||
| 1648 | 45 | } | |
| 1649 | 46 | void removeDummyInterface() { | |
| 1650 |
2/4✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
|
46 | exec("ip link delete dummy0 > /dev/null 2>&1"); |
| 1651 | 46 | } | |
| 1652 |