2 Copyright 2020 Chris Tallon
4 This file is part of VOMP.
6 VOMP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 VOMP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with VOMP. If not, see <https://www.gnu.org/licenses/>.
35 static const char* TAG = "VDPC";
39 logger = LogNT::getInstance();
41 AddServerCallback addFunc( [this] (int ipVersion, const char* ip, const char* name, u2 port, u4 version)
43 std::lock_guard<std::mutex> lg(serversLock);
45 for (auto& s : servers)
47 if (!s.ip.compare(ip) && (port == s.port)) return;
50 servers.emplace_back(ipVersion, ip, name, port, version);
54 if (!vdpc4.init(addFunc)) return false;
57 if (!vdpc6.init(addFunc)) return false;
65 std::unique_lock<std::mutex> ul(waitMutex);
78 logger->debug(TAG, "Sending broadcasts");
87 waitCond.wait_for(ul, std::chrono::milliseconds(1500), [this]{ return stopNow == true; });
88 logger->debug(TAG, "go() wait finished");
91 if (servers.size() == 0) Wol::getInstance()->doWakeUp();
93 } while(servers.size() < 1);
104 std::sort(servers.begin(), servers.end(), ServerSorter());
105 return servers.size();
110 std::lock_guard<std::mutex> lg(waitMutex);
112 waitCond.notify_one();
115 u4 VDPC::numServers() const
117 return servers.size();
120 const VDRServer& VDPC::operator[](u4 index) const
122 if (index >= servers.size()) std::abort();
123 return servers[index];
126 void VDPC::filterServers()
128 // If the same VDR instance has replied on both IPv4 and IPv6,
129 // remove one based on the config option server-discovery.prefer-ipv
131 // It is assumed that we can only get a maximum of two responses from
132 // the same server. The only way this doesn't happen is if two different
133 // VDR-vompservers are running with the same server name in the config.
134 // This is not supported. Results will not be as you would expect.
136 Config* config = Config::getInstance();
138 config->getInt("server-discovery", "prefer-ipv", which);
139 logger->debug(TAG, "Auto select IPv{}", which);
146 for(auto i = servers.begin(); i != servers.end(); i++)
148 for(auto j = i + 1; j != servers.end(); j++)
150 if (i->name == j->name)
152 if (i->ipVersion != which)
155 logger->debug(TAG, "{} found twice, removing {}", i->name, i->ipVersion);
162 if (j->ipVersion != which)
165 logger->debug(TAG, "{} found twice, removing {}", j->name, j->ipVersion);
183 // ======================================= VDPC4
185 bool VDPC::VDPC4::init(AddServerCallback& taddFunc)
187 LogNT::getInstance()->debug("VDPC4", "init");
192 LogNT::getInstance()->crit("VDPC4", "Failed to init VDPC4 UDP");
199 void VDPC::VDPC4::run()
201 LogNT::getInstance()->debug("VDPC4", "run");
205 receiveThread = std::thread([this]
208 startProtect.unlock();
211 startProtect.unlock();
214 void VDPC::VDPC4::stop()
216 LogNT::getInstance()->debug("VDPC4", "stop");
221 CLOSESOCKET(quitPipe);
223 write(pfdsUDP4[1], "X", 1);
226 receiveThread.join();
234 void VDPC::VDPC4::threadMethod()
236 LogNT* logger = LogNT::getInstance();
239 quitPipe = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
240 if (quitPipe == INVALID_SOCKET)
242 logger->error("VDPC4", "socket() fail");
246 if (pipe2(pfdsUDP4, O_NONBLOCK) == -1)
248 logger->error("VDPC4", "pipe2 error B");
255 logger->debug("VDPC4", "goto waitformessage");
258 u1 retval = udp4.waitforMessage(3, quitPipe);
260 u1 retval = udp4.waitforMessage(3, pfdsUDP4[0]);
262 logger->debug("VDPC4", "backfrom waitformessage");
264 if (retval == 2) // we got a reply
266 const char* vdpreply = static_cast<const char*>(udp4.getData());
267 if ((udp4.getDataLength() >= 24) && !strncmp(vdpreply, "VDP-0002", 8))
269 // FIXME upgrade this to look for a return IP in the reply packet
272 memcpy(&newServerPort, &vdpreply[26], 2);
275 memcpy(&newServerVersion, &vdpreply[28], 4);
277 // FIXME - packet length > 24 not checked, end NULL not checked!!
278 addFunc(4, udp4.getFromIPA(), &vdpreply[32], ntohs(newServerPort), ntohl(newServerVersion));
282 logger->debug("VDPC4", "loop stopnow = {}", stopNow);
286 void VDPC::VDPC4::sendRequest()
288 LogNT::getInstance()->debug("VDPC4", "broadcast");
291 memset(message, 0, 15);
292 strcpy(message, "VDP-0001");
293 /*tcp->getMAC(&message[9]); put mac here when TCP modified to do this*/
295 udp4.send("255.255.255.255", 51051U, message, 15);
296 udp4.send("255.255.255.255", 51052U, message, 15);
297 udp4.send("255.255.255.255", 51053U, message, 15);
298 udp4.send("255.255.255.255", 51054U, message, 15);
299 udp4.send("255.255.255.255", 51055U, message, 15);
306 // ======================================= VDPC6
308 bool VDPC::VDPC6::init(AddServerCallback& taddFunc)
310 LogNT::getInstance()->debug("VDPC6", "init");
315 LogNT::getInstance()->crit("VDPC6", "Failed to init VDPC6 UDP");
322 void VDPC::VDPC6::run()
324 LogNT::getInstance()->debug("VDPC6", "run");
328 receiveThread = std::thread([this]
331 startProtect.unlock();
334 startProtect.unlock();
337 void VDPC::VDPC6::stop()
339 LogNT::getInstance()->debug("VDPC6", "stop");
342 write(pfdsUDP6[1], "X", 1);
343 receiveThread.join();
349 void VDPC::VDPC6::threadMethod()
351 LogNT* logger = LogNT::getInstance();
353 if (pipe2(pfdsUDP6, O_NONBLOCK) == -1)
355 logger->error("VDPC6", "pipe2 error B");
361 logger->debug("VDPC6", "goto waitformessage");
363 u1 retval = udp6.waitforMessage(3, pfdsUDP6[0]);
364 logger->debug("VDPC6", "backfrom waitformessage");
366 if (retval == 2) // we got a reply
368 const char* vdpreply = static_cast<const char*>(udp6.getData());
369 if ((udp6.getDataLength() >= 24) && !strncmp(vdpreply, "VDP-0002", 8))
371 if (udp6.getFromIPisLL())
373 // Reject responses from link local addresses
374 // To use them the incoming interface name would need to be kept for use in the tcp connect
375 // This would be possible but there doesn't seem to be any point, there
376 // are a few spare real or ULA IPv6 addresses left
377 // Though it would be cool for it to just-work on an unconfigured IPv6 system...
380 logger->debug("VDPC6", "Rejecting response from a link-local address");
384 // FIXME upgrade this to look for a return IP in the reply packet
387 memcpy(&newServerPort, &vdpreply[26], 2);
390 memcpy(&newServerVersion, &vdpreply[28], 4);
392 // FIXME - packet length > 24 not checked, end NULL not checked!!
393 addFunc(6, udp6.getFromIPA(), &vdpreply[32], ntohs(newServerPort), ntohl(newServerVersion));
398 logger->debug("VDPC6", "loop stopnow = {}", stopNow);
402 void VDPC::VDPC6::sendRequest()
404 LogNT::getInstance()->debug("VDPC6", "broadcast");
407 memset(message, 0, 15);
408 strcpy(message, "VDP-0001");
409 /*tcp->getMAC(&message[9]); put mac here when TCP modified to do this*/
411 udp6.send("ff15:766f:6d70:2064:6973:636f:7665:7279", 51056U, message, 15, true);