| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2013-2022 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the GPL v2 as described in the | ||
| 11 | * included LICENSE.GPL file. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <set> | ||
| 15 | #include <atomic> | ||
| 16 | #include "fpp-json-fwd.h" | ||
| 17 | #include <condition_variable> | ||
| 18 | #include <list> | ||
| 19 | #include <map> | ||
| 20 | #include <memory> | ||
| 21 | #include <mutex> | ||
| 22 | #include <string> | ||
| 23 | #include <thread> | ||
| 24 | #include <vector> | ||
| 25 | |||
| 26 | #include "../SysSocket.h" | ||
| 27 | #include <netinet/in.h> | ||
| 28 | |||
| 29 | #include "ChannelOutput.h" | ||
| 30 | |||
| 31 | typedef void CURLM; | ||
| 32 | |||
| 33 | #define MULTICAST_MESSAGES_KEY 0x00000001 | ||
| 34 | #define ANY_MESSAGES_KEY 0x00000002 | ||
| 35 | #define ARTNET_MESSAGES_KEY 0x00000003 | ||
| 36 | |||
| 37 | #define LATE_MESSAGES_START 0xFFFFFFF0 | ||
| 38 | #define E131_SYNC_KEY 0xFFFFFFFC | ||
| 39 | #define ARTNET_SYNC_KEY 0xFFFFFFFD | ||
| 40 | #define LATE_MULTICAST_MESSAGES_KEY 0xFFFFFFFE | ||
| 41 | #define BROADCAST_MESSAGES_KEY 0xFFFFFFFF | ||
| 42 | |||
| 43 | class SendSocketInfo; | ||
| 44 | |||
| 45 | class UDPOutputMessages { | ||
| 46 | public: | ||
| 47 | UDPOutputMessages(); | ||
| 48 | ~UDPOutputMessages(); | ||
| 49 | |||
| 50 | void ForceSocket(unsigned int key, int socket, bool preventClose = false); | ||
| 51 | int GetSocket(unsigned int key); | ||
| 52 | |||
| 53 | std::vector<struct mmsghdr>& GetMessages(unsigned int key); | ||
| 54 | 929119 | std::vector<struct mmsghdr>& operator[](unsigned int key) { return GetMessages(key); } | |
| 55 | |||
| 56 | private: | ||
| 57 | std::map<unsigned int, std::vector<struct mmsghdr>> messages; | ||
| 58 | std::map<unsigned int, std::shared_ptr<SendSocketInfo>> sendSockets; | ||
| 59 | // While set, every key an output asks for is noted here (once), so | ||
| 60 | // UDPOutput knows which destinations each output's packets go to. | ||
| 61 | std::vector<unsigned int>* recordKeys = nullptr; | ||
| 62 | |||
| 63 | void clearMessages(); | ||
| 64 | void clearSockets(); | ||
| 65 | |||
| 66 | friend class UDPOutput; | ||
| 67 | }; | ||
| 68 | |||
| 69 | class UDPOutputData { | ||
| 70 | public: | ||
| 71 | UDPOutputData(const Json::Value& config); | ||
| 72 | virtual ~UDPOutputData(); | ||
| 73 | |||
| 74 | virtual bool IsPingable() = 0; | ||
| 75 | 941 | virtual bool Monitor() const { return monitor; } | |
| 76 | virtual void PrepareData(unsigned char* channelData, UDPOutputMessages& msgs) = 0; | ||
| 77 | 95410 | virtual void PostPrepareData(unsigned char* channelData, UDPOutputMessages& msgs) {} | |
| 78 | |||
| 79 | virtual void DumpConfig() = 0; | ||
| 80 | |||
| 81 | // min is the first channel, 0-based; max is one past the last (every | ||
| 82 | // protocol works it out as startChannel + count - 1 from the 1-based | ||
| 83 | // startChannel). UDPOutput converts it for ChannelOutput's inclusive max. | ||
| 84 | 1550 | virtual void GetRequiredChannelRange(int& min, int& max) { | |
| 85 | 1550 | min = startChannel - 1; | |
| 86 | 1550 | max = startChannel + channelCount - 1; | |
| 87 | 1550 | } | |
| 88 | |||
| 89 | 978 | virtual void StartingOutput() {} | |
| 90 | 978 | virtual void StoppingOutput() {} | |
| 91 | |||
| 92 | virtual const std::string& GetOutputTypeString() const; | ||
| 93 | |||
| 94 | // The messages last prepared are being sent. A frame can be prepared | ||
| 95 | // more than once before it goes out (its data changed in between), and | ||
| 96 | // only the last preparation is sent, so a protocol sequence number | ||
| 97 | // assigned in PrepareData() is committed here: advanced as it was | ||
| 98 | // assigned, the discarded preparation skipped one on the wire. | ||
| 99 | 8452 | virtual void FrameSent() {} | |
| 100 | |||
| 101 | static in_addr_t toInetAddr(const std::string& ip, bool& valid); | ||
| 102 | 942 | static in_addr_t toInetAddr(const std::string& ip, std::atomic<bool>& valid) { | |
| 103 | 942 | bool v = false; | |
| 104 |
1/2✓ Branch 0 taken 942 times.
✗ Branch 1 not taken.
|
942 | in_addr_t a = toInetAddr(ip, v); |
| 105 | 942 | valid = v; | |
| 106 | 942 | return a; | |
| 107 | } | ||
| 108 | |||
| 109 | std::string description; | ||
| 110 | bool active; | ||
| 111 | int startChannel; | ||
| 112 | int channelCount; | ||
| 113 | int type; | ||
| 114 | std::string ipAddress; | ||
| 115 | // Flipped by the ping thread (SetHostValid) while the output thread reads | ||
| 116 | // it in PrepData(). | ||
| 117 | std::atomic<bool> valid; | ||
| 118 | bool monitor; | ||
| 119 | |||
| 120 | // per-destination pacing override in Mbps; -1 means "use the global rate". | ||
| 121 | // 0 means this controller is explicitly unpaced (line rate). A mix of a | ||
| 122 | // gigabit FPP instance and a 30Mbps-class ESP32 controller can each get an | ||
| 123 | // appropriate cap instead of one global setting for all of them. | ||
| 124 | int pacingRateMbps = -1; | ||
| 125 | |||
| 126 | // Set while StartingOutput() is owed its StoppingOutput(). valid can change | ||
| 127 | // at runtime (a failed ping clears it), so it cannot say which outputs were | ||
| 128 | // started; UDPOutput pairs the two calls through this instead. | ||
| 129 | bool started = false; | ||
| 130 | |||
| 131 | UDPOutputData(UDPOutputData const&) = delete; | ||
| 132 | void operator=(UDPOutputData const& x) = delete; | ||
| 133 | |||
| 134 | protected: | ||
| 135 | void SaveFrame(unsigned char* channelData, int len); | ||
| 136 | bool NeedToOutputFrame(unsigned char* channelData, int startChannel, int savedIdx, int count); | ||
| 137 | bool deDuplicate = false; | ||
| 138 | int skippedFrames; | ||
| 139 | unsigned char* lastData; | ||
| 140 | }; | ||
| 141 | |||
| 142 | class UDPOutput : public ChannelOutput { | ||
| 143 | public: | ||
| 144 | UDPOutput(unsigned int startChannel, unsigned int channelCount); | ||
| 145 | virtual ~UDPOutput(); | ||
| 146 | |||
| 147 | 1070 | virtual std::string GetOutputType() const override { | |
| 148 |
1/2✓ Branch 0 taken 1070 times.
✗ Branch 1 not taken.
|
2140 | return "UDPOutput"; |
| 149 | } | ||
| 150 | virtual int Init(Json::Value config) override; | ||
| 151 | virtual int Close(void) override; | ||
| 152 | |||
| 153 | virtual void PrepData(unsigned char* channelData) override; | ||
| 154 | virtual int SendData(unsigned char* channelData) override; | ||
| 155 | |||
| 156 | virtual void DumpConfig(void) override; | ||
| 157 | |||
| 158 | virtual void GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) override; | ||
| 159 | |||
| 160 | void addOutput(UDPOutputData*); | ||
| 161 | |||
| 162 | // Outputs another channel output plugin (GenericUDP) sends through this | ||
| 163 | // one. Registered by owner with a factory rather than as data: this | ||
| 164 | // instance is replaced whenever co-universes.json reloads, and the new one | ||
| 165 | // has to rebuild them. Unregistering only retires the data - a worker may | ||
| 166 | // still be sending from its buffers - and it is freed with this instance. | ||
| 167 | static void RegisterExternalOutput(const void* owner, std::function<UDPOutputData*()> create); | ||
| 168 | static void UnregisterExternalOutput(const void* owner); | ||
| 169 | |||
| 170 | int createSocket(int port = 0, bool broadCast = false, bool multiCast = false); | ||
| 171 | |||
| 172 | static UDPOutput* INSTANCE; | ||
| 173 | |||
| 174 | void BackgroundOutputWork(); | ||
| 175 | |||
| 176 | virtual void StartingOutput() override; | ||
| 177 | virtual void StoppingOutput() override; | ||
| 178 | |||
| 179 | private: | ||
| 180 | int SendMessages(unsigned int key, SendSocketInfo* socketInfo, std::vector<struct mmsghdr>& sendmsgs); | ||
| 181 | struct sockaddr_in localAddress; | ||
| 182 | std::string outInterface; | ||
| 183 | bool needsBroadcast = false; | ||
| 184 | bool hasArtNet = false; | ||
| 185 | // between StartingOutput() and StoppingOutput(); output thread only | ||
| 186 | bool outputRunning = false; | ||
| 187 | // tags this output's CurlManager requests so the destructor can cancel them | ||
| 188 | const std::string curlOwner = "UDPOutput-" + std::to_string((uintptr_t)this); | ||
| 189 | bool interfaceUp; | ||
| 190 | // the "Invalid interface" banner InitNetwork() raised, while it is up; | ||
| 191 | // InitNetwork() takes it down when the address arrives, Close() otherwise | ||
| 192 | std::string interfaceWarning; | ||
| 193 | |||
| 194 | // rebuildSockets: the address changed; drop the sockets made on the old one | ||
| 195 | bool InitNetwork(bool rebuildSockets = false); | ||
| 196 | std::shared_ptr<SendSocketInfo> findOrCreateSocket(unsigned int key, int sc = 1); | ||
| 197 | |||
| 198 | // Reachability is a property of the controller, not of each universe sent | ||
| 199 | // to it: one PingHost per address, shared by every output sending there, | ||
| 200 | // so a controller that stops answering takes all its universes out of the | ||
| 201 | // frame, and it is pinged once rather than once per universe. | ||
| 202 | struct PingHost; | ||
| 203 | std::map<std::string, std::shared_ptr<PingHost>> pingHosts; | ||
| 204 | // Guards pingHosts, pingsStarted and pingsStopped. Init() fills the map, | ||
| 205 | // and outputs attached later (GenericUDP's, from the thread loading the | ||
| 206 | // other outputs) add to and take from it while the output thread and the | ||
| 207 | // network callback ping through it. Taken after socketMutex when both are | ||
| 208 | // held, and never with a PingHost's lock held. | ||
| 209 | std::mutex pingHostsLock; | ||
| 210 | // Init() has started the pings: outputs attached after that are pinged as | ||
| 211 | // they attach. | ||
| 212 | bool pingsStarted = false; | ||
| 213 | // Adds a pingable output to its controller's PingHost, creating the host | ||
| 214 | // if it is new (returned, so the caller can ping it). pingHostsLock held. | ||
| 215 | std::shared_ptr<PingHost> addPingTarget(UDPOutputData* o); | ||
| 216 | // Takes an output back off its controller's host, and stops pinging a | ||
| 217 | // controller left with no outputs. pingHostsLock held. | ||
| 218 | void removePingTarget(UDPOutputData* o); | ||
| 219 | void OnPingResult(const std::shared_ptr<PingHost>& host, int ms); | ||
| 220 | void SetHostValid(PingHost& host, bool valid); | ||
| 221 | |||
| 222 | std::mutex socketMutex; | ||
| 223 | UDPOutputMessages messages; | ||
| 224 | bool enabled; | ||
| 225 | |||
| 226 | std::list<UDPOutputData*> outputs; | ||
| 227 | |||
| 228 | static std::mutex externalLock; | ||
| 229 | static std::map<const void*, std::function<UDPOutputData*()>> externalFactories; | ||
| 230 | std::map<const void*, UDPOutputData*> externalOutputs; | ||
| 231 | void attachExternalOutput(const void* owner, UDPOutputData* d); | ||
| 232 | static UDPOutputData* createExternalOutput(const std::function<UDPOutputData*()>& create); | ||
| 233 | |||
| 234 | int networkCallbackId; | ||
| 235 | |||
| 236 | void PingControllers(bool failedOnly); | ||
| 237 | // Takes this output's periodic pings back out of the PingManager (their | ||
| 238 | // callbacks capture this) and its "unreachable" warnings down. Close() | ||
| 239 | // does it; the destructor does it for an output deleted without Close(), | ||
| 240 | // as the loader deletes one whose Init() failed. Once only: after | ||
| 241 | // Close(), a replacement output may hold pings for the same hosts. | ||
| 242 | void StopPinging(); | ||
| 243 | bool pingsStopped = false; | ||
| 244 | // controllers that have not answered yet; Init() waits on it | ||
| 245 | std::atomic_int failedCount; | ||
| 246 | std::string HexToIP(unsigned int hex); | ||
| 247 | |||
| 248 | void CheckLocalDrops(); | ||
| 249 | |||
| 250 | // A WorkItem owns a copy of the mmsghdr list and a reference on the socket | ||
| 251 | // info so a worker that outlives its frame (stuck send to a dead controller) | ||
| 252 | // cannot hit freed memory when PrepData rebuilds the message vectors or | ||
| 253 | // an address change drops the sockets. Note the copy is shallow: the iovecs | ||
| 254 | // still point at per-output buffers, so a late send may transmit the | ||
| 255 | // *current* frame's bytes - acceptable, and no worse than it ever was. | ||
| 256 | // generation identifies which frame queued it. | ||
| 257 | class WorkItem { | ||
| 258 | public: | ||
| 259 | 108214 | WorkItem(unsigned int i, std::shared_ptr<SendSocketInfo> si, const std::vector<struct mmsghdr>& m, unsigned int g) : | |
| 260 | 108214 | id(i), | |
| 261 | 108214 | socketInfo(si), | |
| 262 |
1/2✓ Branch 0 taken 108214 times.
✗ Branch 1 not taken.
|
108214 | msgs(m), |
| 263 | 108214 | generation(g) {} | |
| 264 | unsigned int id; | ||
| 265 | std::shared_ptr<SendSocketInfo> socketInfo; | ||
| 266 | std::vector<struct mmsghdr> msgs; | ||
| 267 | unsigned int generation; | ||
| 268 | }; | ||
| 269 | |||
| 270 | std::mutex workMutex; | ||
| 271 | std::condition_variable workSignal; | ||
| 272 | std::list<WorkItem> workQueue; | ||
| 273 | // Keys a worker is sending to right now (under workMutex). With the keys | ||
| 274 | // still queued, these are the destinations whose packet buffers a worker | ||
| 275 | // may be reading, which PrepData() must not rewrite. | ||
| 276 | std::multiset<unsigned int> sendingKeys; | ||
| 277 | // The destination keys each output's packets went to when it was last | ||
| 278 | // prepared, and the outputs PrepData() left alone this frame (both under | ||
| 279 | // socketMutex). | ||
| 280 | std::map<UDPOutputData*, std::vector<unsigned int>> outputKeys; | ||
| 281 | std::set<UDPOutputData*> skippedOutputs; | ||
| 282 | std::atomic_int doneWorkCount; | ||
| 283 | std::atomic_int numWorkThreads; | ||
| 284 | std::atomic_uint workGeneration; | ||
| 285 | volatile bool runWorkThreads; | ||
| 286 | bool useThreadedOutput; | ||
| 287 | bool blockingOutput; | ||
| 288 | |||
| 289 | // fq/SO_MAX_PACING_RATE based pacing; when active the kernel clocks packets | ||
| 290 | // out at pacingRate per socket and the SIOCOUTQ drain waits are skipped | ||
| 291 | bool pacingEnabled = false; | ||
| 292 | unsigned int pacingRate = 0; // bytes per second (global default) | ||
| 293 | |||
| 294 | // rate (bytes/sec) used only for the pre-sync drain-sleep estimate; the | ||
| 295 | // smallest positive rate among the global rate and all per-destination | ||
| 296 | // overrides, so a mixed set sleeps long enough for the slowest queue and we | ||
| 297 | // never divide by a zero global rate when only overrides are paced. | ||
| 298 | unsigned int pacingDrainRate = 0; | ||
| 299 | |||
| 300 | // per-destination-IP pacing rate in bytes/sec, built in Init() from the | ||
| 301 | // per-output pacingRate overrides. A destination not present here uses the | ||
| 302 | // global pacingRate; a value of ~0U means the controller was explicitly set | ||
| 303 | // to unpaced. The socket is shared per destination IP, so when several | ||
| 304 | // outputs target the same controller with differing overrides the most | ||
| 305 | // conservative (lowest) rate wins. | ||
| 306 | std::map<in_addr_t, unsigned int> perDestPacingRate; | ||
| 307 | |||
| 308 | // local drop diagnostics; the check runs on a short detached thread since | ||
| 309 | // reading qdisc stats forks tc, which is too slow for the frame path | ||
| 310 | int statCheckCounter = 0; | ||
| 311 | std::atomic_bool statCheckRunning{ false }; | ||
| 312 | bool statBaselineValid = false; | ||
| 313 | bool dropWarningActive = false; | ||
| 314 | uint64_t lastSndbufErrors = 0; | ||
| 315 | uint64_t lastTxDropped = 0; | ||
| 316 | uint64_t lastQdiscDropped = 0; | ||
| 317 | }; | ||
| 318 |