1 /*
2    wxServDisc.cpp: wxServDisc implementation
3 
4    This file is part of wxServDisc, a crossplatform wxWidgets
5    Zeroconf service discovery module.
6 
7    Copyright (C) 2008 Christian Beier <dontmind@freeshell.org>
8 
9    wxServDisc is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13 
14    wxServDisc is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23 
24 
25 #include "wx/object.h"
26 #include "wx/thread.h"
27 #include "wx/intl.h"
28 #include "wx/log.h"
29 
30 #include <fcntl.h>
31 #include <cerrno>
32 #include <csignal>
33 
34 
35 
36 // only used by VC++
37 #ifdef _WIN32
38 #pragma comment(lib, "Ws2_32.lib")
39 #endif
40 
41 #include "wxServDisc.h"
42 
43 
44 // Compatability defines
45 #ifndef IPV6_ADD_MEMBERSHIP
46 #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
47 #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
48 #endif
49 
50 
51 // define our new notify event!
52 #if wxVERSION_NUMBER < 2900
53 DEFINE_EVENT_TYPE(wxServDiscNOTIFY);
54 #else
55 wxDEFINE_EVENT(wxServDiscNOTIFY, wxCommandEvent);
56 #endif
57 
58 
59 
60 
61 /*
62   private member functions
63 
64 */
65 
66 
Entry()67 wxThread::ExitCode wxServDisc::Entry()
68 {
69   mdnsd d;
70   struct message m;
71   unsigned long int ip;
72   unsigned short int port;
73   struct timeval *tv;
74   fd_set fds;
75   bool exit = false;
76 
77   mWallClock.Start();
78 
79   d = mdnsd_new(1,1000);
80 
81 
82   // register query(w,t) at mdnsd d, submit our address for callback ans()
83   mdnsd_query(d, query.char_str(), querytype, ans, this);
84 
85 
86 #ifdef __WXGTK__
87   // this signal is generated when we pop up a file dialog wwith wxGTK
88   // we need to block it here cause it interrupts the select() call
89   sigset_t            newsigs;
90   sigset_t            oldsigs;
91   sigemptyset(&newsigs);
92   sigemptyset(&oldsigs);
93   sigaddset(&newsigs, SIGRTMIN-1);
94 #endif
95 
96 
97   while(!GetThread()->TestDestroy() && !exit)
98     {
99       //printf("TopLoop...\n");
100       tv = mdnsd_sleep(d);
101 
102       long msecs = 100; //tv->tv_sec == 0 ? 100 : tv->tv_sec*1000; // so that the while loop beneath gets executed once
103       //printf("wxServDisc %p: scanthread waiting for data, timeout %i seconds\n", this, (int)tv->tv_sec);
104 
105 
106       // we split the one select() call into several ones every 100ms
107       // to be able to catch TestDestroy()...
108       int datatoread = 0;
109       while(msecs > 0 && !GetThread()->TestDestroy() && !datatoread)
110 	{
111           //printf("loop...\n");
112 	  // the select call leaves tv undefined, so re-set
113 	  tv->tv_sec = 0;
114 	  tv->tv_usec = 100000; // 100 ms
115 
116 	  FD_ZERO(&fds);
117 	  FD_SET(mSock,&fds);
118 
119 
120 #ifdef __WXGTK__
121 	  sigprocmask(SIG_BLOCK, &newsigs, &oldsigs);
122 #endif
123       //wxLogDebug(wxT("wxServDisc %p: select call, timeout %i seconds. %i usec."), this, (int)tv->tv_sec, (int)tv->tv_usec);
124       //printf("wxServDisc %p: select call, timeout %i seconds. %i usec.\n", this, (int)tv->tv_sec, (int)tv->tv_usec);
125 	  datatoread = select(mSock+1,&fds,0,0,tv); // returns 0 if timeout expired
126 
127 #ifdef __WXGTK__
128 	  sigprocmask(SIG_SETMASK, &oldsigs, NULL);
129 #endif
130 
131           //printf("out of select\n");
132 	  if(!datatoread){ // this is a timeout
133             //printf("wxServDisc timeout %d\n", (int)msecs);
134 	    msecs-=100;
135           }
136 	  if(datatoread == -1)
137 	    break;
138 	}
139 
140       //printf("wxServDisc %p: scanthread woke up, reason: incoming data(%i), timeout(%i), error(%i), deletion(%i)\n",
141 	//	 this, datatoread>0, msecs<=0, datatoread==-1, GetThread()->TestDestroy() );
142 
143       // receive
144       if(FD_ISSET(mSock,&fds))
145         {
146 	  while(recvm(&m, mSock, &ip, &port) > 0)
147 	    mdnsd_in(d, &m, ip, port);
148         }
149 
150       // send
151       while(mdnsd_out(d,&m,&ip,&port))
152 	if(!sendm(&m, mSock, ip, port))
153 	  {
154 	    exit = true;
155 	    break;
156 	  }
157     }
158 
159   mdnsd_shutdown(d);
160   mdnsd_free(d);
161 
162 
163   if(mSock != INVALID_SOCKET)
164     closesocket(mSock);
165 
166 
167   //printf("wxServDisc %p: scanthread exiting\n", this);
168 
169   return NULL;
170 }
171 
172 
173 
174 
sendm(struct message * m,SOCKET s,unsigned long int ip,unsigned short int port)175 bool wxServDisc::sendm(struct message* m, SOCKET s, unsigned long int ip, unsigned short int port)
176 {
177   struct sockaddr_in to;
178 
179   memset(&to, '\0', sizeof(to));
180 
181   to.sin_family = AF_INET;
182   to.sin_port = port;
183   to.sin_addr.s_addr = ip;
184 
185   if(sendto(s, (char*)message_packet(m), message_packet_len(m), 0,(struct sockaddr *)&to,sizeof(struct sockaddr_in)) != message_packet_len(m))
186     {
187       err.Printf(_("Can't write to socket: %s\n"),strerror(errno));
188       return false;
189     }
190 
191   return true;
192 }
193 
194 
195 
196 
197 
recvm(struct message * m,SOCKET s,unsigned long int * ip,unsigned short int * port)198 int wxServDisc::recvm(struct message* m, SOCKET s, unsigned long int *ip, unsigned short int *port)
199 {
200   struct sockaddr_in from;
201   int bsize;
202   static unsigned char buf[MAX_PACKET_LEN];
203 #ifdef __WIN32__
204   int ssize  = sizeof(struct sockaddr_in);
205 #else
206   socklen_t ssize  = sizeof(struct sockaddr_in);
207 #endif
208 
209 
210   if((bsize = recvfrom(s, (char*)buf, MAX_PACKET_LEN, 0, (struct sockaddr*)&from, &ssize)) > 0)
211     {
212       memset(m, '\0', sizeof(struct message));
213       message_parse(m,buf);
214       *ip = (unsigned long int)from.sin_addr.s_addr;
215       *port = from.sin_port;
216       return bsize;
217     }
218 
219 #ifdef __WIN32__
220   if(bsize < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
221 #else
222     if(bsize < 0 && errno != EAGAIN)
223 #endif
224       {
225 	err.Printf(_("Can't read from socket %d: %s\n"),
226 		      errno,strerror(errno));
227 	return bsize;
228       }
229 
230   return 0;
231 }
232 
233 
234 
235 
236 
ans(mdnsda a,void * arg)237 int wxServDisc::ans(mdnsda a, void *arg)
238 {
239   wxServDisc *moi = (wxServDisc*)arg;
240 
241   wxString key;
242   switch(a->type)
243     {
244     case QTYPE_PTR:
245       // query result is key
246       key = wxString((char*)a->rdname, wxConvUTF8);
247       break;
248     case QTYPE_A:
249     case QTYPE_SRV:
250       // query name is key
251       key = wxString((char*)a->name, wxConvUTF8);
252       break;
253     default:
254       break;
255     }
256 
257 
258   // insert answer data into result
259   // depending on the query, not all fields have a meaning
260   wxSDEntry result;
261 
262   result.name = wxString((char*)a->rdname, wxConvUTF8);
263 
264   result.time = moi->mWallClock.Time();
265 
266   struct in_addr ip;
267   ip.s_addr =  ntohl(a->ip);
268   result.ip = wxString(inet_ntoa(ip), wxConvUTF8);
269 
270   result.port = a->srv.port;
271 
272 
273   if(a->ttl == 0)
274     // entry was expired
275     moi->results.erase(key);
276   else
277     // entry update
278     moi->results[key] = result;
279 
280   moi->post_notify();
281 
282 
283   wxLogDebug(wxT("wxServDisc %p: got answer:"), moi);
284   wxLogDebug(wxT("wxServDisc %p:    key:  %s"), moi, key.c_str());
285   wxLogDebug(wxT("wxServDisc %p:    ttl:  %i"), moi, (int)a->ttl);
286   wxLogDebug(wxT("wxServDisc %p:    time: %lu"), moi, result.time);
287   if(a->ttl != 0) {
288     wxLogDebug(wxT("wxServDisc %p:    name: %s"), moi, moi->results[key].name.c_str());
289     wxLogDebug(wxT("wxServDisc %p:    ip:   %s"), moi, moi->results[key].ip.c_str());
290     wxLogDebug(wxT("wxServDisc %p:    port: %u"), moi, moi->results[key].port);
291   }
292   wxLogDebug(wxT("wxServDisc %p: answer end"),  moi);
293 
294   return 1;
295 }
296 
297 
298 
299 // create a multicast 224.0.0.251:5353 socket,
300 // aproppriate for receiving and sending,
301 // windows or unix style
msock()302 SOCKET wxServDisc::msock()
303 {
304   SOCKET sock;
305 
306   int multicastTTL = 255; // multicast TTL, must be 255 for zeroconf!
307   const char* mcAddrStr = "224.0.0.251";
308   const char* mcPortStr = "5353";
309 
310 
311   int flag = 1;
312   int status;
313   struct addrinfo hints;   // Hints for name lookup
314   struct addrinfo* multicastAddr;  // Multicast address
315   struct addrinfo* localAddr;      // Local address to bind to
316 
317   unsigned long block=1;
318 
319 
320 #ifdef __WIN32__
321   /*
322     Start up WinSock
323    */
324   WORD		wVersionRequested;
325   WSADATA	wsaData;
326   wVersionRequested = MAKEWORD(2, 2);
327   if(WSAStartup(wVersionRequested, &wsaData) != 0)
328     {
329       WSACleanup();
330       err.Printf(_("Failed to start WinSock!"));
331       return INVALID_SOCKET;
332     }
333 #endif
334 
335 
336   /*
337      Resolve the multicast group address
338   */
339   memset(&hints, 0, sizeof hints); // make sure the struct is empty
340   hints.ai_family = PF_UNSPEC; // IPv4 or IPv6, we don't care
341   hints.ai_flags  = AI_NUMERICHOST;
342   if ((status = getaddrinfo(mcAddrStr, NULL, &hints, &multicastAddr)) != 0) {
343     err.Printf(_("Could not get multicast address: %s\n"), gai_strerror(status));
344     return INVALID_SOCKET;
345   }
346 
347   wxLogDebug(wxT("wxServDisc %p: Using %s"), this, multicastAddr->ai_family == PF_INET6 ? wxT("IPv6") : wxT("IPv4"));
348 
349 
350   /*
351      Resolve a local address with the same family (IPv4 or IPv6) as our multicast group
352   */
353   memset(&hints, 0, sizeof hints); // make sure the struct is empty
354   hints.ai_family   = multicastAddr->ai_family;
355   hints.ai_socktype = SOCK_DGRAM;
356   hints.ai_flags    = AI_NUMERICHOST|AI_PASSIVE; // no name resolving, wildcard address
357   if ((status = getaddrinfo(NULL, mcPortStr, &hints, &localAddr)) != 0 ) {
358     err.Printf(_("Could not get local address: %s\n"), gai_strerror(status));
359     freeaddrinfo(multicastAddr);
360     return INVALID_SOCKET;
361   }
362 
363 
364 
365 
366 
367 
368   /*
369     Create socket
370   */
371   if((sock = socket(localAddr->ai_family, localAddr->ai_socktype, 0)) == INVALID_SOCKET) {
372     err.Printf(_("Could not create socket: %s\n"), strerror(errno));
373     // not yet a complete cleanup!
374     freeaddrinfo(localAddr);
375     freeaddrinfo(multicastAddr);
376     return INVALID_SOCKET;
377   }
378 
379 
380 
381   /*
382     set to reuse address (and maybe port - needed on OSX)
383   */
384   setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag));
385 #if defined (SO_REUSEPORT)
386   setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&flag, sizeof(flag));
387 #endif
388 
389 
390 
391   /*
392     Bind this socket to localAddr
393    */
394   if(bind(sock, localAddr->ai_addr, localAddr->ai_addrlen) != 0) {
395     err.Printf(_("Could not bind socket: %s\n"), strerror(errno));
396     goto CompleteCleanUp;
397   }
398 
399 
400   /*
401     Set the multicast TTL.
402   */
403   if ( setsockopt(sock,
404 		  localAddr->ai_family == PF_INET6 ? IPPROTO_IPV6        : IPPROTO_IP,
405 		  localAddr->ai_family == PF_INET6 ? IPV6_MULTICAST_HOPS : IP_MULTICAST_TTL,
406 		  (char*) &multicastTTL, sizeof(multicastTTL)) != 0 ) {
407     err.Printf(_("Could not set multicast TTL: %s\n"), strerror(errno));
408     goto CompleteCleanUp;
409    }
410 
411 
412 
413    /*
414       Join the multicast group. We do this seperately depending on whether we
415       are using IPv4 or IPv6.
416    */
417   if ( multicastAddr->ai_family  == PF_INET &&
418        multicastAddr->ai_addrlen == sizeof(struct sockaddr_in) ) /* IPv4 */
419     {
420       struct ip_mreq multicastRequest;  // Multicast address join structure
421 
422       /* Specify the multicast group */
423       memcpy(&multicastRequest.imr_multiaddr,
424 	     &((struct sockaddr_in*)(multicastAddr->ai_addr))->sin_addr,
425 	     sizeof(multicastRequest.imr_multiaddr));
426 
427       /* Accept multicast from any interface */
428       multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);
429 
430       /* Join the multicast address */
431       if ( setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &multicastRequest, sizeof(multicastRequest)) != 0 )  {
432 	err.Printf(_("Could not join multicast group: %s\n"), strerror(errno));
433 	goto CompleteCleanUp;
434       }
435 
436     }
437   else if ( multicastAddr->ai_family  == PF_INET6 &&
438 	    multicastAddr->ai_addrlen == sizeof(struct sockaddr_in6) ) /* IPv6 */
439     {
440       struct ipv6_mreq multicastRequest;  /* Multicast address join structure */
441 
442       /* Specify the multicast group */
443       memcpy(&multicastRequest.ipv6mr_multiaddr,
444 	     &((struct sockaddr_in6*)(multicastAddr->ai_addr))->sin6_addr,
445 	     sizeof(multicastRequest.ipv6mr_multiaddr));
446 
447       /* Accept multicast from any interface */
448       multicastRequest.ipv6mr_interface = 0;
449 
450       /* Join the multicast address */
451       if ( setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char*) &multicastRequest, sizeof(multicastRequest)) != 0 ) {
452 	err.Printf(_("Could not join multicast group: %s\n"), strerror(errno));
453 	goto CompleteCleanUp;
454       }
455     }
456   else {
457     err.Printf(_("Neither IPv4 or IPv6"));
458     goto CompleteCleanUp;
459   }
460 
461 
462 
463 
464   /*
465      Set to nonblock
466   */
467 #ifdef _WIN32
468   ioctlsocket(sock, FIONBIO, &block);
469 #else
470   flag =  fcntl(sock, F_GETFL, 0);
471   flag |= O_NONBLOCK;
472   fcntl(sock, F_SETFL, flag);
473 #endif
474 
475 
476 
477   /*
478     whooaa, that's it
479   */
480   freeaddrinfo(localAddr);
481   freeaddrinfo(multicastAddr);
482 
483   return sock;
484 
485 
486 
487  CompleteCleanUp:
488 
489   closesocket(sock);
490   freeaddrinfo(localAddr);
491   freeaddrinfo(multicastAddr);
492   return INVALID_SOCKET;
493 
494 }
495 
496 
497 
498 
499 
500 /*
501   public member functions
502 
503 */
504 
505 
wxServDisc(void * p,const wxString & what,int type)506 wxServDisc::wxServDisc(void* p, const wxString& what, int type)
507 {
508   // save our caller
509   parent = p;
510 
511   // save query
512   query = what;
513   querytype = type;
514 
515   wxLogDebug(wxT(""));
516   wxLogDebug(wxT("wxServDisc %p: about to query '%s'"), this, query.c_str());
517 
518   if((mSock = msock()) == INVALID_SOCKET) {
519     wxLogDebug(wxT("Ouch, error creating socket: ") + err);
520     return;
521   }
522 
523 #if wxVERSION_NUMBER >= 2905 // 2.9.4 still has a bug here: http://trac.wxwidgets.org/ticket/14626
524   if( CreateThread(wxTHREAD_DETACHED) != wxTHREAD_NO_ERROR )
525 #else
526   if( Create() != wxTHREAD_NO_ERROR )
527 #endif
528     err.Printf(_("Could not create scan thread!"));
529   else
530     if( GetThread()->Run() != wxTHREAD_NO_ERROR )
531       err.Printf(_("Could not start scan thread!"));
532 }
533 
534 
535 
~wxServDisc()536 wxServDisc::~wxServDisc()
537 {
538   wxLogDebug(wxT("wxServDisc %p: before scanthread delete"), this);
539   if(GetThread() && GetThread()->IsRunning()){
540     GetThread()->Delete(); // blocks, this makes TestDestroy() return true and cleans up the thread
541     wxMilliSleep(200);
542   }
543 
544   wxLogDebug(wxT("wxServDisc %p: scanthread deleted, wxServDisc destroyed, query was '%s', lifetime was %ld"), this, query.c_str(), mWallClock.Time());
545   wxLogDebug(wxT(""));
546 }
547 
548 
549 
550 
551 
getResults() const552 std::vector<wxSDEntry> wxServDisc::getResults() const
553 {
554   std::vector<wxSDEntry> resvec;
555 
556   wxSDMap::const_iterator it;
557   for(it = results.begin(); it != results.end(); it++)
558     resvec.push_back(it->second);
559 
560   return resvec;
561 }
562 
563 
564 
getResultCount() const565 size_t wxServDisc::getResultCount() const
566 {
567   return results.size();
568 }
569 
570 
571 
572 
573 
post_notify()574 void wxServDisc::post_notify()
575 {
576   if(parent)
577     {
578       // new NOTIFY event, we got no window id
579       wxCommandEvent event(wxServDiscNOTIFY, wxID_ANY);
580       event.SetEventObject(this); // set sender
581 
582       // Send it
583       wxQueueEvent((wxEvtHandler*)parent, event.Clone());
584     }
585 }
586 
587 
588 
589 
590