]> git.vomp.tv Git - vompclient.git/blob - src/vdpc.cc
Type updates:
[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 bool VDPC::init()
38 {
39   logger = LogNT::getInstance();
40
41   AddServerCallback addFunc( [this] (int ipVersion, const char* ip, const char* name, USHORT port, u4 version)
42   {
43     std::lock_guard<std::mutex> lg(serversLock);
44
45     for (auto& s : servers)
46     {
47       if (!s.ip.compare(ip) && (port == s.port)) return;
48     }
49
50     servers.emplace_back(ipVersion, ip, name, port, version);
51   });
52
53 #ifdef IPV4
54   if (!vdpc4.init(addFunc)) return false;
55 #endif
56 #ifdef IPV6
57   if (!vdpc6.init(addFunc)) return false;
58 #endif
59
60   return true;
61 }
62
63 int VDPC::go()
64 {
65   std::unique_lock<std::mutex> ul(waitMutex);
66
67   servers.clear();
68
69 #ifdef IPV4
70   vdpc4.run();
71 #endif
72 #ifdef IPV6
73   vdpc6.run();
74 #endif
75
76   do
77   {
78     logger->debug(TAG, "Sending broadcasts");
79
80 #ifdef IPV4
81     vdpc4.sendRequest();
82 #endif
83 #ifdef IPV6
84     vdpc6.sendRequest();
85 #endif
86
87     waitCond.wait_for(ul, std::chrono::milliseconds(1500), [this]{ return stopNow == true; });
88     logger->debug(TAG, "go() wait finished");
89
90     if (stopNow) break;
91     if (servers.size() == 0) Wol::getInstance()->doWakeUp();
92
93   } while(servers.size() < 1);
94
95 #ifdef IPV4
96     vdpc4.stop();
97 #endif
98 #ifdef IPV6
99     vdpc6.stop();
100 #endif
101
102   filterServers();
103
104   std::sort(servers.begin(), servers.end(), ServerSorter());
105   return servers.size();
106 }
107
108 void VDPC::stop()
109 {
110   std::lock_guard<std::mutex> lg(waitMutex);
111   stopNow = true;
112   waitCond.notify_one();
113 }
114
115 u4 VDPC::numServers() const
116 {
117   return servers.size();
118 }
119
120 const VDRServer& VDPC::operator[](u4 index) const
121 {
122   if (index >= servers.size()) std::abort();
123   return servers[index];
124 }
125
126 void VDPC::filterServers()
127 {
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
130
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.
135
136   Config* config = Config::getInstance();
137   int which = 6;
138   config->getInt("server-discovery", "prefer-ipv", which);
139   logger->debug(TAG, "Auto select IPv{}", which);
140
141   bool removed;
142   do
143   {
144     removed = false;
145
146     for(auto i = servers.begin(); i != servers.end(); i++)
147     {
148       for(auto j = i + 1; j != servers.end(); j++)
149       {
150         if (i->name == j->name)
151         {
152           if (i->ipVersion != which)
153           {
154
155             logger->debug(TAG, "{} found twice, removing {}", i->name, i->ipVersion);
156
157             servers.erase(i);
158             removed = true;
159             break;
160           }
161
162           if (j->ipVersion != which)
163           {
164
165             logger->debug(TAG, "{} found twice, removing {}", j->name, j->ipVersion);
166
167             servers.erase(j);
168             removed = true;
169             break;
170           }
171         }
172       }
173
174       if (removed) break;
175     }
176
177   } while (removed);
178 }
179
180
181 #ifdef IPV4
182
183 // ======================================= VDPC4
184
185 bool VDPC::VDPC4::init(AddServerCallback& taddFunc)
186 {
187   LogNT::getInstance()->debug("VDPC4", "init");
188   addFunc = taddFunc;
189
190   if (!udp4.init(0))
191   {
192     LogNT::getInstance()->crit("VDPC4", "Failed to init VDPC4 UDP");
193     return false;
194   }
195
196   return true;
197 }
198
199 void VDPC::VDPC4::run()
200 {
201   LogNT::getInstance()->debug("VDPC4", "run");
202   stopNow = false;
203
204   startProtect.lock();
205   receiveThread = std::thread([this]
206   {
207     startProtect.lock();
208     startProtect.unlock();
209     threadMethod();
210   });
211   startProtect.unlock();
212 }
213
214 void VDPC::VDPC4::stop()
215 {
216   LogNT::getInstance()->debug("VDPC4", "stop");
217
218   stopNow = true;
219
220 #ifdef WIN32
221   CLOSESOCKET(quitPipe);
222 #else
223   write(pfdsUDP4[1], "X", 1);
224 #endif
225
226   receiveThread.join();
227
228 #ifndef WIN32
229   close(pfdsUDP4[0]);
230   close(pfdsUDP4[1]);
231 #endif
232 }
233
234 void VDPC::VDPC4::threadMethod()
235 {
236   LogNT* logger = LogNT::getInstance();
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     UCHAR retval = udp4.waitforMessage(3, quitPipe);
259     #else
260     UCHAR 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         USHORT 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   LogNT* logger = LogNT::getInstance();
352
353   if (pipe2(pfdsUDP6, O_NONBLOCK) == -1)
354   {
355     logger->error("VDPC6", "pipe2 error B");
356     return;
357   }
358
359   while (!stopNow)
360   {
361     logger->debug("VDPC6", "goto waitformessage");
362
363     UCHAR retval = udp6.waitforMessage(3, pfdsUDP6[0]);
364     logger->debug("VDPC6", "backfrom waitformessage");
365
366     if (retval == 2) // we got a reply
367     {
368       const char* vdpreply = static_cast<const char*>(udp6.getData());
369       if ((udp6.getDataLength() >= 24) && !strncmp(vdpreply, "VDP-0002", 8))
370       {
371         if (udp6.getFromIPisLL())
372         {
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...
378           // FIXME one day
379
380           logger->debug("VDPC6", "Rejecting response from a link-local address");
381         }
382         else
383         {
384           // FIXME upgrade this to look for a return IP in the reply packet
385
386           USHORT newServerPort;
387           memcpy(&newServerPort, &vdpreply[26], 2);
388
389           u4 newServerVersion;
390           memcpy(&newServerVersion, &vdpreply[28], 4);
391
392           // FIXME - packet length > 24 not checked, end NULL not checked!!
393           addFunc(6, udp6.getFromIPA(), &vdpreply[32], ntohs(newServerPort), ntohl(newServerVersion));
394         }
395       }
396     }
397
398     logger->debug("VDPC6", "loop stopnow = {}", stopNow);
399   }
400 }
401
402 void VDPC::VDPC6::sendRequest()
403 {
404   LogNT::getInstance()->debug("VDPC6", "broadcast");
405
406   char message[15];
407   memset(message, 0, 15);
408   strcpy(message, "VDP-0001");
409   /*tcp->getMAC(&message[9]); put mac here when TCP modified to do this*/
410
411   udp6.send("ff15:766f:6d70:2064:6973:636f:7665:7279", 51056U, message, 15, true);
412 }
413
414 #endif