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