]> git.vomp.tv Git - vompclient.git/blob - src/vdpc.cc
Have build.sh deal with cmake
[vompclient.git] / src / vdpc.cc
1 /*
2     Copyright 2020 Chris Tallon
3
4     This file is part of VOMP.
5
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.
10
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.
15
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/>.
18 */
19
20 #ifndef WIN32
21   #include <unistd.h>
22   #include <fcntl.h>
23 #endif
24
25 #include <cstdlib>
26 #include <chrono>
27 #include <algorithm>
28
29 #include "log.h"
30 #include "config.h"
31 #include "wol.h"
32
33 #include "vdpc.h"
34
35 static const char* TAG = "VDPC";
36
37 LogNT* VDPC::logger{};
38
39 bool VDPC::init()
40 {
41   logger = LogNT::getInstance();
42
43   AddServerCallback addFunc( [this] (int ipVersion, const char* ip, const char* name, u2 port, u4 version)
44   {
45     std::lock_guard<std::mutex> lg(serversLock);
46
47     for (auto& s : servers)
48     {
49       if (!s.ip.compare(ip) && (port == s.port)) return;
50     }
51
52     servers.emplace_back(ipVersion, ip, name, port, version);
53   });
54
55 #ifdef IPV4
56   if (!vdpc4.init(addFunc)) return false;
57 #endif
58 #ifdef IPV6
59   if (!vdpc6.init(addFunc)) return false;
60 #endif
61
62   return true;
63 }
64
65 int VDPC::go()
66 {
67   std::unique_lock<std::mutex> ul(waitMutex);
68
69   servers.clear();
70
71 #ifdef IPV4
72   vdpc4.run();
73 #endif
74 #ifdef IPV6
75   vdpc6.run();
76 #endif
77
78   do
79   {
80     logger->debug(TAG, "Sending broadcasts");
81
82 #ifdef IPV4
83     vdpc4.sendRequest();
84 #endif
85 #ifdef IPV6
86     vdpc6.sendRequest();
87 #endif
88
89     waitCond.wait_for(ul, std::chrono::milliseconds(1500), [this]{ return stopNow == true; });
90     logger->debug(TAG, "go() wait finished");
91
92     if (stopNow) break;
93     if (servers.size() == 0) Wol::getInstance()->doWakeUp();
94
95   } while(servers.size() < 1);
96
97 #ifdef IPV4
98     vdpc4.stop();
99 #endif
100 #ifdef IPV6
101     vdpc6.stop();
102 #endif
103
104   filterServers();
105
106   std::sort(servers.begin(), servers.end(), ServerSorter());
107   return servers.size();
108 }
109
110 void VDPC::stop()
111 {
112   std::lock_guard<std::mutex> lg(waitMutex);
113   stopNow = true;
114   waitCond.notify_one();
115 }
116
117 u4 VDPC::numServers() const
118 {
119   return servers.size();
120 }
121
122 const VDRServer& VDPC::operator[](u4 index) const
123 {
124   if (index >= servers.size()) std::abort();
125   return servers[index];
126 }
127
128 void VDPC::filterServers()
129 {
130   // If the same VDR instance has replied on both IPv4 and IPv6,
131   // remove one based on the config option server-discovery.prefer-ipv
132
133   // It is assumed that we can only get a maximum of two responses from
134   // the same server. The only way this doesn't happen is if two different
135   // VDR-vompservers are running with the same server name in the config.
136   // This is not supported. Results will not be as you would expect.
137
138   Config* config = Config::getInstance();
139   int which = 6;
140   config->getInt("server-discovery", "prefer-ipv", which);
141   logger->debug(TAG, "Auto select IPv{}", which);
142
143   bool removed;
144   do
145   {
146     removed = false;
147
148     for(auto i = servers.begin(); i != servers.end(); i++)
149     {
150       for(auto j = i + 1; j != servers.end(); j++)
151       {
152         if (i->name == j->name)
153         {
154           if (i->ipVersion != which)
155           {
156
157             logger->debug(TAG, "{} found twice, removing {}", i->name, i->ipVersion);
158
159             servers.erase(i);
160             removed = true;
161             break;
162           }
163
164           if (j->ipVersion != which)
165           {
166
167             logger->debug(TAG, "{} found twice, removing {}", j->name, j->ipVersion);
168
169             servers.erase(j);
170             removed = true;
171             break;
172           }
173         }
174       }
175
176       if (removed) break;
177     }
178
179   } while (removed);
180 }
181
182
183 #ifdef IPV4
184
185 // ======================================= VDPC4
186
187 bool VDPC::VDPC4::init(AddServerCallback& taddFunc)
188 {
189   LogNT::getInstance()->debug("VDPC4", "init");
190   addFunc = taddFunc;
191
192   if (!udp4.init(0))
193   {
194     LogNT::getInstance()->crit("VDPC4", "Failed to init VDPC4 UDP");
195     return false;
196   }
197
198   return true;
199 }
200
201 void VDPC::VDPC4::run()
202 {
203   LogNT::getInstance()->debug("VDPC4", "run");
204   stopNow = false;
205
206   startProtect.lock();
207   receiveThread = std::thread([this]
208   {
209     startProtect.lock();
210     startProtect.unlock();
211     threadMethod();
212   });
213   startProtect.unlock();
214 }
215
216 void VDPC::VDPC4::stop()
217 {
218   LogNT::getInstance()->debug("VDPC4", "stop");
219
220   stopNow = true;
221
222 #ifdef WIN32
223   CLOSESOCKET(quitPipe);
224 #else
225   write(pfdsUDP4[1], "X", 1);
226 #endif
227
228   receiveThread.join();
229
230 #ifndef WIN32
231   close(pfdsUDP4[0]);
232   close(pfdsUDP4[1]);
233 #endif
234 }
235
236 void VDPC::VDPC4::threadMethod()
237 {
238 #ifdef WIN32
239   quitPipe = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
240   if (quitPipe == INVALID_SOCKET)
241   {
242     logger->error("VDPC4", "socket() fail");
243     return;
244   }
245 #else
246   if (pipe2(pfdsUDP4, O_NONBLOCK) == -1)
247   {
248     logger->error("VDPC4", "pipe2 error B");
249     return;
250   }
251 #endif
252
253   while (!stopNow)
254   {
255     logger->debug("VDPC4", "goto waitformessage");
256
257     #ifdef WIN32
258     u1 retval = udp4.waitforMessage(3, quitPipe);
259     #else
260     u1 retval = udp4.waitforMessage(3, pfdsUDP4[0]);
261     #endif
262     logger->debug("VDPC4", "backfrom waitformessage");
263
264     if (retval == 2) // we got a reply
265     {
266       const char* vdpreply = static_cast<const char*>(udp4.getData());
267       if ((udp4.getDataLength() >= 24) && !strncmp(vdpreply, "VDP-0002", 8))
268       {
269         // FIXME upgrade this to look for a return IP in the reply packet
270
271         u2 newServerPort;
272         memcpy(&newServerPort, &vdpreply[26], 2);
273
274         u4 newServerVersion;
275         memcpy(&newServerVersion, &vdpreply[28], 4);
276
277         // FIXME - packet length > 24 not checked, end NULL not checked!!
278         addFunc(4, udp4.getFromIPA(), &vdpreply[32], ntohs(newServerPort), ntohl(newServerVersion));
279       }
280     }
281
282     logger->debug("VDPC4", "loop stopnow = {}", stopNow);
283   }
284 }
285
286 void VDPC::VDPC4::sendRequest()
287 {
288   LogNT::getInstance()->debug("VDPC4", "broadcast");
289
290   char message[15];
291   memset(message, 0, 15);
292   strcpy(message, "VDP-0001");
293   /*tcp->getMAC(&message[9]); put mac here when TCP modified to do this*/
294
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);
300 }
301
302 #endif
303
304 #ifdef IPV6
305
306 // ======================================= VDPC6
307
308 bool VDPC::VDPC6::init(AddServerCallback& taddFunc)
309 {
310   LogNT::getInstance()->debug("VDPC6", "init");
311   addFunc = taddFunc;
312
313   if (!udp6.init(0))
314   {
315     LogNT::getInstance()->crit("VDPC6", "Failed to init VDPC6 UDP");
316     return false;
317   }
318
319   return true;
320 }
321
322 void VDPC::VDPC6::run()
323 {
324   LogNT::getInstance()->debug("VDPC6", "run");
325   stopNow = false;
326
327   startProtect.lock();
328   receiveThread = std::thread([this]
329   {
330     startProtect.lock();
331     startProtect.unlock();
332     threadMethod();
333   });
334   startProtect.unlock();
335 }
336
337 void VDPC::VDPC6::stop()
338 {
339   LogNT::getInstance()->debug("VDPC6", "stop");
340
341   stopNow = true;
342   write(pfdsUDP6[1], "X", 1);
343   receiveThread.join();
344
345   close(pfdsUDP6[0]);
346   close(pfdsUDP6[1]);
347 }
348
349 void VDPC::VDPC6::threadMethod()
350 {
351   if (pipe2(pfdsUDP6, O_NONBLOCK) == -1)
352   {
353     logger->error("VDPC6", "pipe2 error B");
354     return;
355   }
356
357   while (!stopNow)
358   {
359     logger->debug("VDPC6", "goto waitformessage");
360
361     u1 retval = udp6.waitforMessage(3, pfdsUDP6[0]);
362     logger->debug("VDPC6", "backfrom waitformessage");
363
364     if (retval == 2) // we got a reply
365     {
366       const char* vdpreply = static_cast<const char*>(udp6.getData());
367       if ((udp6.getDataLength() >= 24) && !strncmp(vdpreply, "VDP-0002", 8))
368       {
369         if (udp6.getFromIPisLL())
370         {
371           // Reject responses from link local addresses
372           // To use them the incoming interface name would need to be kept for use in the tcp connect
373           // This would be possible but there doesn't seem to be any point, there
374           // are a few spare real or ULA IPv6 addresses left
375           // Though it would be cool for it to just-work on an unconfigured IPv6 system...
376           // FIXME one day
377
378           logger->debug("VDPC6", "Rejecting response from a link-local address");
379         }
380         else
381         {
382           // FIXME upgrade this to look for a return IP in the reply packet
383
384           u2 newServerPort;
385           memcpy(&newServerPort, &vdpreply[26], 2);
386
387           u4 newServerVersion;
388           memcpy(&newServerVersion, &vdpreply[28], 4);
389
390           // FIXME - packet length > 24 not checked, end NULL not checked!!
391           addFunc(6, udp6.getFromIPA(), &vdpreply[32], ntohs(newServerPort), ntohl(newServerVersion));
392         }
393       }
394     }
395
396     logger->debug("VDPC6", "loop stopnow = {}", stopNow);
397   }
398 }
399
400 void VDPC::VDPC6::sendRequest()
401 {
402   LogNT::getInstance()->debug("VDPC6", "broadcast");
403
404   char message[15];
405   memset(message, 0, 15);
406   strcpy(message, "VDP-0001");
407   /*tcp->getMAC(&message[9]); put mac here when TCP modified to do this*/
408
409   udp6.send("ff15:766f:6d70:2064:6973:636f:7665:7279", 51056U, message, 15, true);
410 }
411
412 #endif