]> git.saurik.com Git - apple/mdnsresponder.git/blob - Clients/DNSServiceBrowser.m
mDNSResponder-878.50.17.tar.gz
[apple/mdnsresponder.git] / Clients / DNSServiceBrowser.m
1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #import <Cocoa/Cocoa.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <net/if.h>
22 #include <arpa/inet.h>
23 #include <netdb.h>
24 #include <sys/select.h>
25 #include <netinet/in.h>
26 #include <unistd.h>
27 #include <dns_sd.h>
28
29 @class ServiceController; // holds state corresponding to outstanding DNSServiceRef
30
31 @interface BrowserController : NSObject
32 {
33 IBOutlet id nameField;
34 IBOutlet id typeField;
35
36 IBOutlet id serviceDisplayTable;
37 IBOutlet id typeColumn;
38 IBOutlet id nameColumn;
39 IBOutlet id serviceTypeField;
40 IBOutlet id serviceNameField;
41
42 IBOutlet id hostField;
43 IBOutlet id ipAddressField;
44 IBOutlet id ip6AddressField;
45 IBOutlet id portField;
46 IBOutlet id interfaceField;
47 IBOutlet id textField;
48
49 NSMutableArray *_srvtypeKeys;
50 NSMutableArray *_srvnameKeys;
51 NSMutableArray *_sortedServices;
52 NSMutableDictionary *_servicesDict;
53
54 ServiceController *_serviceBrowser;
55 ServiceController *_serviceResolver;
56 ServiceController *_ipv4AddressResolver;
57 ServiceController *_ipv6AddressResolver;
58 }
59
60 - (void)notifyTypeSelectionChange:(NSNotification*)note;
61 - (void)notifyNameSelectionChange:(NSNotification*)note;
62
63 - (IBAction)connect:(id)sender;
64
65 - (IBAction)handleTableClick:(id)sender;
66 - (IBAction)removeSelected:(id)sender;
67 - (IBAction)addNewService:(id)sender;
68
69 - (IBAction)update:(NSString *)Type;
70
71 - (void)updateBrowseWithName:(const char *)name type:(const char *)resulttype domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags;
72 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen;
73 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen host:(const char*)host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome;
74
75 - (void)_cancelPendingResolve;
76 - (void)_clearResolvedInfo;
77
78 @end
79
80 // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
81 @interface ServiceController : NSObject
82 {
83 DNSServiceRef fServiceRef;
84 CFSocketRef fSocketRef;
85 CFRunLoopSourceRef fRunloopSrc;
86 }
87
88 - (id)initWithServiceRef:(DNSServiceRef)ref;
89 - (void)addToCurrentRunLoop;
90 - (DNSServiceRef)serviceRef;
91 - (void)dealloc;
92
93 @end // interface ServiceController
94
95
96 static void
97 ProcessSockData(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
98 {
99 DNSServiceRef serviceRef = (DNSServiceRef)info;
100 DNSServiceErrorType err = DNSServiceProcessResult(serviceRef);
101 if (err != kDNSServiceErr_NoError) {
102 printf("DNSServiceProcessResult() returned an error! %d\n", err);
103 }
104 }
105
106
107 static void
108 ServiceBrowseReply(DNSServiceRef sdRef, DNSServiceFlags servFlags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
109 const char *serviceName, const char *regtype, const char *replyDomain, void *context)
110 {
111 if (errorCode == kDNSServiceErr_NoError) {
112 [(BrowserController*)context updateBrowseWithName:serviceName type:regtype domain:replyDomain interface:interfaceIndex flags:servFlags];
113 } else {
114 printf("ServiceBrowseReply got an error! %d\n", errorCode);
115 }
116 }
117
118
119 static void
120 ServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
121 const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
122 {
123 if (errorCode == kDNSServiceErr_NoError) {
124 [(BrowserController*)context resolveClientWitHost:[NSString stringWithUTF8String:hosttarget] port:port interfaceIndex:interfaceIndex txtRecord:txtRecord txtLen:txtLen];
125 } else {
126 printf("ServiceResolveReply got an error! %d\n", errorCode);
127 }
128 }
129
130
131 static void
132 QueryRecordReply(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
133 const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
134 {
135 if (errorCode == kDNSServiceErr_NoError) {
136 [(BrowserController*)context updateAddress:rrtype addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex more:(flags & kDNSServiceFlagsMoreComing)];
137 } else {
138 printf("QueryRecordReply got an error! %d\n", errorCode);
139 }
140 }
141
142
143 static void
144 InterfaceIndexToName(uint32_t interface, char *interfaceName)
145 {
146 assert(interfaceName);
147
148 if (interface == kDNSServiceInterfaceIndexAny) {
149 // All active network interfaces.
150 strlcpy(interfaceName, "all", IF_NAMESIZE);
151 } else if (interface == kDNSServiceInterfaceIndexLocalOnly) {
152 // Only available locally on this machine.
153 strlcpy(interfaceName, "local", IF_NAMESIZE);
154 } else if (interface == kDNSServiceInterfaceIndexP2P) {
155 // Peer-to-peer.
156 strlcpy(interfaceName, "p2p", IF_NAMESIZE);
157 } else {
158 // Converts interface index to interface name.
159 if_indextoname(interface, interfaceName);
160 }
161 }
162
163
164 @implementation BrowserController //Begin implementation of BrowserController methods
165
166 - (void)registerDefaults
167 {
168 NSMutableDictionary *regDict = [NSMutableDictionary dictionary];
169
170 NSArray *typeArray = [NSArray arrayWithObjects:@"_afpovertcp._tcp",
171 @"_smb._tcp",
172 @"_rfb._tcp",
173 @"_ssh._tcp",
174 @"_ftp._tcp",
175 @"_http._tcp",
176 @"_printer._tcp",
177 @"_ipp._tcp",
178 @"_airport._tcp",
179 @"_presence._tcp",
180 @"_daap._tcp",
181 @"_dpap._tcp",
182 nil];
183
184 NSArray *nameArray = [NSArray arrayWithObjects:@"AppleShare Servers",
185 @"Windows Sharing",
186 @"Screen Sharing",
187 @"Secure Shell",
188 @"FTP Servers",
189 @"Web Servers",
190 @"LPR Printers",
191 @"IPP Printers",
192 @"AirPort Base Stations",
193 @"iChat Buddies",
194 @"iTunes Libraries",
195 @"iPhoto Libraries",
196 nil];
197
198 [regDict setObject:typeArray forKey:@"SrvTypeKeys"];
199 [regDict setObject:nameArray forKey:@"SrvNameKeys"];
200
201 [[NSUserDefaults standardUserDefaults] registerDefaults:regDict];
202 }
203
204
205 - (id)init
206 {
207 self = [super init];
208 if (self) {
209 _srvtypeKeys = nil;
210 _srvnameKeys = nil;
211 _serviceBrowser = nil;
212 _serviceResolver = nil;
213 _ipv4AddressResolver = nil;
214 _ipv6AddressResolver = nil;
215 _sortedServices = [[NSMutableArray alloc] init];
216 _servicesDict = [[NSMutableDictionary alloc] init];
217 }
218 return self;
219 }
220
221
222 - (void)awakeFromNib
223 {
224 [typeField sizeLastColumnToFit];
225 [nameField sizeLastColumnToFit];
226 [nameField setDoubleAction:@selector(connect:)];
227
228 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyTypeSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:typeField];
229 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyNameSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:nameField];
230
231 _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
232 _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
233
234 if (!_srvtypeKeys || !_srvnameKeys) {
235 [_srvtypeKeys release];
236 [_srvnameKeys release];
237 [self registerDefaults];
238 _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
239 _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
240 }
241
242 [typeField reloadData];
243 }
244
245
246 - (void)dealloc
247 {
248 [_srvtypeKeys release];
249 [_srvnameKeys release];
250 [_servicesDict release];
251 [_sortedServices release];
252 [super dealloc];
253 }
254
255
256 -(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
257 {
258 if (row < 0) return;
259 }
260
261
262 - (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods
263 {
264 if (theTableView == typeField) {
265 return [_srvnameKeys count];
266 }
267 if (theTableView == nameField) {
268 return [_servicesDict count];
269 }
270 if (theTableView == serviceDisplayTable) {
271 return [_srvnameKeys count];
272 }
273 return 0;
274 }
275
276
277 - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex
278 {
279 if (theTableView == typeField) {
280 return [_srvnameKeys objectAtIndex:rowIndex];
281 }
282 if (theTableView == nameField) {
283 return [[_servicesDict objectForKey:[_sortedServices objectAtIndex:rowIndex]] name];
284 }
285 if (theTableView == serviceDisplayTable) {
286 if (theColumn == typeColumn) {
287 return [_srvtypeKeys objectAtIndex:rowIndex];
288 }
289 if (theColumn == nameColumn) {
290 return [_srvnameKeys objectAtIndex:rowIndex];
291 }
292 return nil;
293 }
294
295 return nil;
296 }
297
298
299 - (void)notifyTypeSelectionChange:(NSNotification*)note
300 {
301 [self _cancelPendingResolve];
302
303 int index = [[note object] selectedRow];
304 if (index != -1) {
305 [self update:[_srvtypeKeys objectAtIndex:index]];
306 } else {
307 [self update:nil];
308 }
309 }
310
311
312 - (void)notifyNameSelectionChange:(NSNotification*)note
313 {
314 [self _cancelPendingResolve];
315
316 int index = [[note object] selectedRow];
317 if (index == -1) {
318 return;
319 }
320
321 // Get the currently selected service
322 NSNetService *service = [_servicesDict objectForKey:[_sortedServices objectAtIndex:index]];
323
324 DNSServiceRef serviceRef;
325 DNSServiceErrorType err = DNSServiceResolve(&serviceRef,
326 (DNSServiceFlags)0,
327 kDNSServiceInterfaceIndexAny,
328 (const char *)[[service name] UTF8String],
329 (const char *)[[service type] UTF8String],
330 (const char *)[[service domain] UTF8String],
331 (DNSServiceResolveReply)ServiceResolveReply,
332 self);
333
334 if (kDNSServiceErr_NoError == err) {
335 _serviceResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
336 [_serviceResolver addToCurrentRunLoop];
337 }
338 }
339
340
341 - (IBAction)update:(NSString *)theType
342 {
343 [_servicesDict removeAllObjects];
344 [_sortedServices removeAllObjects];
345 [nameField reloadData];
346
347 // get rid of the previous browser if one exists
348 if (_serviceBrowser != nil) {
349 [_serviceBrowser release];
350 _serviceBrowser = nil;
351 }
352
353 if (theType) {
354 DNSServiceRef serviceRef;
355 DNSServiceErrorType err = DNSServiceBrowse(&serviceRef, (DNSServiceFlags)0, 0, [theType UTF8String], NULL, ServiceBrowseReply, self);
356 if (kDNSServiceErr_NoError == err) {
357 _serviceBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef];
358 [_serviceBrowser addToCurrentRunLoop];
359 }
360 }
361 }
362
363
364 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
365 {
366 return YES;
367 }
368
369
370 - (void)updateBrowseWithName:(const char *)name type:(const char *)type domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags
371 {
372 NSString *key = [NSString stringWithFormat:@"%s.%s%s%d", name, type, domain, interface];
373 NSNetService *service = [[NSNetService alloc] initWithDomain:[NSString stringWithUTF8String:domain] type:[NSString stringWithUTF8String:type] name:[NSString stringWithUTF8String:name]];
374
375 if (flags & kDNSServiceFlagsAdd) {
376 [_servicesDict setObject:service forKey:key];
377 } else {
378 [_servicesDict removeObjectForKey:key];
379 }
380
381 // If not expecting any more data, then reload (redraw) TableView with newly found data
382 if (!(flags & kDNSServiceFlagsMoreComing)) {
383
384 // Save the current TableView selection
385 int index = [nameField selectedRow];
386 NSString *selected = (index != -1) ? [[_sortedServices objectAtIndex:index] copy] : nil;
387
388 [_sortedServices release];
389 _sortedServices = [[_servicesDict allKeys] mutableCopy];
390 [_sortedServices sortUsingSelector:@selector(caseInsensitiveCompare:)];
391 [nameField reloadData];
392
393 // Restore the previous TableView selection
394 index = selected ? [_sortedServices indexOfObject:selected] : NSNotFound;
395 if (index != NSNotFound) {
396 [nameField selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
397 [nameField scrollRowToVisible:index];
398 }
399
400 [selected release];
401 }
402
403 [service release];
404
405 return;
406 }
407
408
409 - (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen
410 {
411 DNSServiceRef serviceRef;
412
413 if (_ipv4AddressResolver) {
414 [_ipv4AddressResolver release];
415 _ipv4AddressResolver = nil;
416 }
417
418 if (_ipv6AddressResolver) {
419 [_ipv6AddressResolver release];
420 _ipv6AddressResolver = nil;
421 }
422
423 // Start an async lookup for IPv4 addresses
424 DNSServiceErrorType err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_A, kDNSServiceClass_IN, QueryRecordReply, self);
425 if (err == kDNSServiceErr_NoError) {
426 _ipv4AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
427 [_ipv4AddressResolver addToCurrentRunLoop];
428 }
429
430 // Start an async lookup for IPv6 addresses
431 err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_AAAA, kDNSServiceClass_IN, QueryRecordReply, self);
432 if (err == kDNSServiceErr_NoError) {
433 _ipv6AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
434 [_ipv6AddressResolver addToCurrentRunLoop];
435 }
436
437 char interfaceName[IF_NAMESIZE];
438 InterfaceIndexToName(interface, interfaceName);
439
440 [hostField setStringValue:host];
441 [interfaceField setStringValue:[NSString stringWithUTF8String:interfaceName]];
442 [portField setIntValue:ntohs(port)];
443
444 // kind of a hack: munge txtRecord so it's human-readable
445 if (txtLen > 0) {
446 char *readableText = (char*) malloc(txtLen);
447 if (readableText != nil) {
448 ByteCount index, subStrLen;
449 memcpy(readableText, txtRecord, txtLen);
450 for (index=0; index < txtLen - 1; index += subStrLen + 1) {
451 subStrLen = readableText[index];
452 readableText[index] = ' ';
453 }
454 [textField setStringValue:[NSString stringWithCString:&readableText[1] length:txtLen - 1]];
455 free(readableText);
456 }
457 }
458 }
459
460
461 - (void)updateAddress:(uint16_t)rrtype addr:(const void *)buff addrLen:(uint16_t)addrLen host:(const char*) host interfaceIndex:(uint32_t)interface more:(boolean_t)moreToCome
462 {
463 char addrBuff[256];
464
465 if (rrtype == kDNSServiceType_A) {
466 inet_ntop(AF_INET, buff, addrBuff, sizeof(addrBuff));
467 if ([[ipAddressField stringValue] length] > 0) {
468 [ipAddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ipAddressField stringValue]]];
469 }
470 [ipAddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ipAddressField stringValue], addrBuff]];
471
472 if (!moreToCome) {
473 [_ipv4AddressResolver release];
474 _ipv4AddressResolver = nil;
475 }
476 } else if (rrtype == kDNSServiceType_AAAA) {
477 inet_ntop(AF_INET6, buff, addrBuff, sizeof(addrBuff));
478 if ([[ip6AddressField stringValue] length] > 0) {
479 [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ip6AddressField stringValue]]];
480 }
481 [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ip6AddressField stringValue], addrBuff]];
482
483 if (!moreToCome) {
484 [_ipv6AddressResolver release];
485 _ipv6AddressResolver = nil;
486 }
487 }
488 }
489
490
491 - (void)connect:(id)sender
492 {
493 NSString *host = [hostField stringValue];
494 NSString *txtRecord = [textField stringValue];
495 int port = [portField intValue];
496
497 int index = [nameField selectedRow];
498 NSString *selected = (index >= 0) ? [_sortedServices objectAtIndex:index] : nil;
499 NSString *type = [[_servicesDict objectForKey:selected] type];
500
501 if ([type isEqual:@"_http._tcp."]) {
502 NSString *pathDelim = @"path=";
503 NSRange where;
504
505 // If the TXT record specifies a path, extract it.
506 where = [txtRecord rangeOfString:pathDelim options:NSCaseInsensitiveSearch];
507 if (where.length) {
508 NSRange targetRange = { where.location + where.length, [txtRecord length] - where.location - where.length };
509 NSRange endDelim = [txtRecord rangeOfString:@"\n" options:kNilOptions range:targetRange];
510
511 if (endDelim.length) // if a delimiter was found, truncate the target range
512 targetRange.length = endDelim.location - targetRange.location;
513
514 NSString *path = [txtRecord substringWithRange:targetRange];
515 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d%@", host, port, path]]];
516 } else {
517 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", host, port]]];
518 }
519 }
520 else if ([type isEqual:@"_ftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", host, port]]];
521 else if ([type isEqual:@"_ssh._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", host, port]]];
522 else if ([type isEqual:@"_afpovertcp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", host, port]]];
523 else if ([type isEqual:@"_smb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", host, port]]];
524 else if ([type isEqual:@"_rfb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"vnc://%@:%d/", host, port]]];
525
526 return;
527 }
528
529
530 - (IBAction)handleTableClick:(id)sender
531 {
532 //populate the text fields
533 }
534
535
536 - (IBAction)removeSelected:(id)sender
537 {
538 // remove the selected row and force a refresh
539
540 int selectedRow = [serviceDisplayTable selectedRow];
541
542 if (selectedRow) {
543
544 [_srvtypeKeys removeObjectAtIndex:selectedRow];
545 [_srvnameKeys removeObjectAtIndex:selectedRow];
546
547 [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
548 [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
549
550 [typeField reloadData];
551 [serviceDisplayTable reloadData];
552 }
553 }
554
555
556 - (IBAction)addNewService:(id)sender
557 {
558 // add new entries from the edit fields to the arrays for the defaults
559 NSString *newType = [serviceTypeField stringValue];
560 NSString *newName = [serviceNameField stringValue];
561
562 // 3282283: trim trailing '.' from service type field
563 if ([newType length] && [newType hasSuffix:@"."])
564 newType = [newType substringToIndex:[newType length] - 1];
565
566 if ([newType length] && [newName length]) {
567 [_srvtypeKeys addObject:newType];
568 [_srvnameKeys addObject:newName];
569
570 [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
571 [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
572
573 [typeField reloadData];
574 [serviceDisplayTable reloadData];
575 }
576 }
577
578
579 - (void)_cancelPendingResolve
580 {
581 [_ipv4AddressResolver release];
582 _ipv4AddressResolver = nil;
583
584 [_ipv6AddressResolver release];
585 _ipv6AddressResolver = nil;
586
587 [_serviceResolver release];
588 _serviceResolver = nil;
589
590 [self _clearResolvedInfo];
591 }
592
593
594 - (void)_clearResolvedInfo
595 {
596 [hostField setStringValue:@""];
597 [ipAddressField setStringValue:@""];
598 [ip6AddressField setStringValue:@""];
599 [portField setStringValue:@""];
600 [interfaceField setStringValue:@""];
601 [textField setStringValue:@""];
602 }
603
604 @end // implementation BrowserController
605
606
607 @implementation ServiceController : NSObject
608 {
609 DNSServiceRef fServiceRef;
610 CFSocketRef fSocketRef;
611 CFRunLoopSourceRef fRunloopSrc;
612 }
613
614
615 - (id)initWithServiceRef:(DNSServiceRef)ref
616 {
617 self = [super init];
618 if (self) {
619 fServiceRef = ref;
620 fSocketRef = NULL;
621 fRunloopSrc = NULL;
622 }
623 return self;
624 }
625
626
627 - (void)addToCurrentRunLoop
628 {
629 CFSocketContext context = { 0, (void*)fServiceRef, NULL, NULL, NULL };
630
631 fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef), kCFSocketReadCallBack, ProcessSockData, &context);
632 if (fSocketRef) {
633 // Prevent CFSocketInvalidate from closing DNSServiceRef's socket.
634 CFOptionFlags sockFlags = CFSocketGetSocketFlags(fSocketRef);
635 CFSocketSetSocketFlags(fSocketRef, sockFlags & (~kCFSocketCloseOnInvalidate));
636 fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 0);
637 }
638 if (fRunloopSrc) {
639 CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
640 } else {
641 printf("Could not listen to runloop socket\n");
642 }
643 }
644
645
646 - (DNSServiceRef)serviceRef
647 {
648 return fServiceRef;
649 }
650
651
652 - (void)dealloc
653 {
654 if (fSocketRef) {
655 CFSocketInvalidate(fSocketRef); // Note: Also closes the underlying socket
656 CFRelease(fSocketRef);
657
658 // Workaround that gives time to CFSocket's select thread so it can remove the socket from its
659 // FD set before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273>
660 usleep(1000);
661 }
662
663 if (fRunloopSrc) {
664 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
665 CFRelease(fRunloopSrc);
666 }
667
668 DNSServiceRefDeallocate(fServiceRef);
669
670 [super dealloc];
671 }
672
673
674 @end // implementation ServiceController
675
676 int main(int argc, const char *argv[])
677 {
678 return NSApplicationMain(argc, argv);
679 }