summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/Clients/DNSServiceBrowser.m
diff options
context:
space:
mode:
Diffstat (limited to 'mDNSResponder/Clients/DNSServiceBrowser.m')
-rwxr-xr-xmDNSResponder/Clients/DNSServiceBrowser.m679
1 files changed, 679 insertions, 0 deletions
diff --git a/mDNSResponder/Clients/DNSServiceBrowser.m b/mDNSResponder/Clients/DNSServiceBrowser.m
new file mode 100755
index 00000000..b4e04140
--- /dev/null
+++ b/mDNSResponder/Clients/DNSServiceBrowser.m
@@ -0,0 +1,679 @@
+/* -*- Mode: C; tab-width: 4 -*-
+ *
+ * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <dns_sd.h>
+
+@class ServiceController; // holds state corresponding to outstanding DNSServiceRef
+
+@interface BrowserController : NSObject
+{
+ IBOutlet id nameField;
+ IBOutlet id typeField;
+
+ IBOutlet id serviceDisplayTable;
+ IBOutlet id typeColumn;
+ IBOutlet id nameColumn;
+ IBOutlet id serviceTypeField;
+ IBOutlet id serviceNameField;
+
+ IBOutlet id hostField;
+ IBOutlet id ipAddressField;
+ IBOutlet id ip6AddressField;
+ IBOutlet id portField;
+ IBOutlet id interfaceField;
+ IBOutlet id textField;
+
+ NSMutableArray *_srvtypeKeys;
+ NSMutableArray *_srvnameKeys;
+ NSMutableArray *_sortedServices;
+ NSMutableDictionary *_servicesDict;
+
+ ServiceController *_serviceBrowser;
+ ServiceController *_serviceResolver;
+ ServiceController *_ipv4AddressResolver;
+ ServiceController *_ipv6AddressResolver;
+}
+
+- (void)notifyTypeSelectionChange:(NSNotification*)note;
+- (void)notifyNameSelectionChange:(NSNotification*)note;
+
+- (IBAction)connect:(id)sender;
+
+- (IBAction)handleTableClick:(id)sender;
+- (IBAction)removeSelected:(id)sender;
+- (IBAction)addNewService:(id)sender;
+
+- (IBAction)update:(NSString *)Type;
+
+- (void)updateBrowseWithName:(const char *)name type:(const char *)resulttype domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags;
+- (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen;
+- (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;
+
+- (void)_cancelPendingResolve;
+- (void)_clearResolvedInfo;
+
+@end
+
+// The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
+@interface ServiceController : NSObject
+{
+ DNSServiceRef fServiceRef;
+ CFSocketRef fSocketRef;
+ CFRunLoopSourceRef fRunloopSrc;
+}
+
+- (id)initWithServiceRef:(DNSServiceRef)ref;
+- (void)addToCurrentRunLoop;
+- (DNSServiceRef)serviceRef;
+- (void)dealloc;
+
+@end // interface ServiceController
+
+
+static void
+ProcessSockData(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
+{
+ DNSServiceRef serviceRef = (DNSServiceRef)info;
+ DNSServiceErrorType err = DNSServiceProcessResult(serviceRef);
+ if (err != kDNSServiceErr_NoError) {
+ printf("DNSServiceProcessResult() returned an error! %d\n", err);
+ }
+}
+
+
+static void
+ServiceBrowseReply(DNSServiceRef sdRef, DNSServiceFlags servFlags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+ const char *serviceName, const char *regtype, const char *replyDomain, void *context)
+{
+ if (errorCode == kDNSServiceErr_NoError) {
+ [(BrowserController*)context updateBrowseWithName:serviceName type:regtype domain:replyDomain interface:interfaceIndex flags:servFlags];
+ } else {
+ printf("ServiceBrowseReply got an error! %d\n", errorCode);
+ }
+}
+
+
+static void
+ServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+ const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
+{
+ if (errorCode == kDNSServiceErr_NoError) {
+ [(BrowserController*)context resolveClientWitHost:[NSString stringWithUTF8String:hosttarget] port:port interfaceIndex:interfaceIndex txtRecord:txtRecord txtLen:txtLen];
+ } else {
+ printf("ServiceResolveReply got an error! %d\n", errorCode);
+ }
+}
+
+
+static void
+QueryRecordReply(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+ const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
+{
+ if (errorCode == kDNSServiceErr_NoError) {
+ [(BrowserController*)context updateAddress:rrtype addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex more:(flags & kDNSServiceFlagsMoreComing)];
+ } else {
+ printf("QueryRecordReply got an error! %d\n", errorCode);
+ }
+}
+
+
+static void
+InterfaceIndexToName(uint32_t interface, char *interfaceName)
+{
+ assert(interfaceName);
+
+ if (interface == kDNSServiceInterfaceIndexAny) {
+ // All active network interfaces.
+ strlcpy(interfaceName, "all", IF_NAMESIZE);
+ } else if (interface == kDNSServiceInterfaceIndexLocalOnly) {
+ // Only available locally on this machine.
+ strlcpy(interfaceName, "local", IF_NAMESIZE);
+ } else if (interface == kDNSServiceInterfaceIndexP2P) {
+ // Peer-to-peer.
+ strlcpy(interfaceName, "p2p", IF_NAMESIZE);
+ } else {
+ // Converts interface index to interface name.
+ if_indextoname(interface, interfaceName);
+ }
+}
+
+
+@implementation BrowserController //Begin implementation of BrowserController methods
+
+- (void)registerDefaults
+{
+ NSMutableDictionary *regDict = [NSMutableDictionary dictionary];
+
+ NSArray *typeArray = [NSArray arrayWithObjects:@"_afpovertcp._tcp",
+ @"_smb._tcp",
+ @"_rfb._tcp",
+ @"_ssh._tcp",
+ @"_ftp._tcp",
+ @"_http._tcp",
+ @"_printer._tcp",
+ @"_ipp._tcp",
+ @"_airport._tcp",
+ @"_presence._tcp",
+ @"_daap._tcp",
+ @"_dpap._tcp",
+ nil];
+
+ NSArray *nameArray = [NSArray arrayWithObjects:@"AppleShare Servers",
+ @"Windows Sharing",
+ @"Screen Sharing",
+ @"Secure Shell",
+ @"FTP Servers",
+ @"Web Servers",
+ @"LPR Printers",
+ @"IPP Printers",
+ @"AirPort Base Stations",
+ @"iChat Buddies",
+ @"iTunes Libraries",
+ @"iPhoto Libraries",
+ nil];
+
+ [regDict setObject:typeArray forKey:@"SrvTypeKeys"];
+ [regDict setObject:nameArray forKey:@"SrvNameKeys"];
+
+ [[NSUserDefaults standardUserDefaults] registerDefaults:regDict];
+}
+
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ _srvtypeKeys = nil;
+ _srvnameKeys = nil;
+ _serviceBrowser = nil;
+ _serviceResolver = nil;
+ _ipv4AddressResolver = nil;
+ _ipv6AddressResolver = nil;
+ _sortedServices = [[NSMutableArray alloc] init];
+ _servicesDict = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+
+- (void)awakeFromNib
+{
+ [typeField sizeLastColumnToFit];
+ [nameField sizeLastColumnToFit];
+ [nameField setDoubleAction:@selector(connect:)];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyTypeSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:typeField];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyNameSelectionChange:) name:NSTableViewSelectionDidChangeNotification object:nameField];
+
+ _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
+ _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
+
+ if (!_srvtypeKeys || !_srvnameKeys) {
+ [_srvtypeKeys release];
+ [_srvnameKeys release];
+ [self registerDefaults];
+ _srvtypeKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"] mutableCopy];
+ _srvnameKeys = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"] mutableCopy];
+ }
+
+ [typeField reloadData];
+}
+
+
+- (void)dealloc
+{
+ [_srvtypeKeys release];
+ [_srvnameKeys release];
+ [_servicesDict release];
+ [_sortedServices release];
+ [super dealloc];
+}
+
+
+-(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
+{
+ if (row < 0) return;
+}
+
+
+- (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods
+{
+ if (theTableView == typeField) {
+ return [_srvnameKeys count];
+ }
+ if (theTableView == nameField) {
+ return [_servicesDict count];
+ }
+ if (theTableView == serviceDisplayTable) {
+ return [_srvnameKeys count];
+ }
+ return 0;
+}
+
+
+- (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex
+{
+ if (theTableView == typeField) {
+ return [_srvnameKeys objectAtIndex:rowIndex];
+ }
+ if (theTableView == nameField) {
+ return [[_servicesDict objectForKey:[_sortedServices objectAtIndex:rowIndex]] name];
+ }
+ if (theTableView == serviceDisplayTable) {
+ if (theColumn == typeColumn) {
+ return [_srvtypeKeys objectAtIndex:rowIndex];
+ }
+ if (theColumn == nameColumn) {
+ return [_srvnameKeys objectAtIndex:rowIndex];
+ }
+ return nil;
+ }
+
+ return nil;
+}
+
+
+- (void)notifyTypeSelectionChange:(NSNotification*)note
+{
+ [self _cancelPendingResolve];
+
+ int index = [[note object] selectedRow];
+ if (index != -1) {
+ [self update:[_srvtypeKeys objectAtIndex:index]];
+ } else {
+ [self update:nil];
+ }
+}
+
+
+- (void)notifyNameSelectionChange:(NSNotification*)note
+{
+ [self _cancelPendingResolve];
+
+ int index = [[note object] selectedRow];
+ if (index == -1) {
+ return;
+ }
+
+ // Get the currently selected service
+ NSNetService *service = [_servicesDict objectForKey:[_sortedServices objectAtIndex:index]];
+
+ DNSServiceRef serviceRef;
+ DNSServiceErrorType err = DNSServiceResolve(&serviceRef,
+ (DNSServiceFlags)0,
+ kDNSServiceInterfaceIndexAny,
+ (const char *)[[service name] UTF8String],
+ (const char *)[[service type] UTF8String],
+ (const char *)[[service domain] UTF8String],
+ (DNSServiceResolveReply)ServiceResolveReply,
+ self);
+
+ if (kDNSServiceErr_NoError == err) {
+ _serviceResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
+ [_serviceResolver addToCurrentRunLoop];
+ }
+}
+
+
+- (IBAction)update:(NSString *)theType
+{
+ [_servicesDict removeAllObjects];
+ [_sortedServices removeAllObjects];
+ [nameField reloadData];
+
+ // get rid of the previous browser if one exists
+ if (_serviceBrowser != nil) {
+ [_serviceBrowser release];
+ _serviceBrowser = nil;
+ }
+
+ if (theType) {
+ DNSServiceRef serviceRef;
+ DNSServiceErrorType err = DNSServiceBrowse(&serviceRef, (DNSServiceFlags)0, 0, [theType UTF8String], NULL, ServiceBrowseReply, self);
+ if (kDNSServiceErr_NoError == err) {
+ _serviceBrowser = [[ServiceController alloc] initWithServiceRef:serviceRef];
+ [_serviceBrowser addToCurrentRunLoop];
+ }
+ }
+}
+
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
+{
+ return YES;
+}
+
+
+- (void)updateBrowseWithName:(const char *)name type:(const char *)type domain:(const char *)domain interface:(uint32_t)interface flags:(DNSServiceFlags)flags
+{
+ NSString *key = [NSString stringWithFormat:@"%s.%s%s%d", name, type, domain, interface];
+ NSNetService *service = [[NSNetService alloc] initWithDomain:[NSString stringWithUTF8String:domain] type:[NSString stringWithUTF8String:type] name:[NSString stringWithUTF8String:name]];
+
+ if (flags & kDNSServiceFlagsAdd) {
+ [_servicesDict setObject:service forKey:key];
+ } else {
+ [_servicesDict removeObjectForKey:key];
+ }
+
+ // If not expecting any more data, then reload (redraw) TableView with newly found data
+ if (!(flags & kDNSServiceFlagsMoreComing)) {
+
+ // Save the current TableView selection
+ int index = [nameField selectedRow];
+ NSString *selected = (index != -1) ? [[_sortedServices objectAtIndex:index] copy] : nil;
+
+ [_sortedServices release];
+ _sortedServices = [[_servicesDict allKeys] mutableCopy];
+ [_sortedServices sortUsingSelector:@selector(caseInsensitiveCompare:)];
+ [nameField reloadData];
+
+ // Restore the previous TableView selection
+ index = selected ? [_sortedServices indexOfObject:selected] : NSNotFound;
+ if (index != NSNotFound) {
+ [nameField selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
+ [nameField scrollRowToVisible:index];
+ }
+
+ [selected release];
+ }
+
+ [service release];
+
+ return;
+}
+
+
+- (void)resolveClientWitHost:(NSString *)host port:(uint16_t)port interfaceIndex:(uint32_t)interface txtRecord:(const char*)txtRecord txtLen:(uint16_t)txtLen
+{
+ DNSServiceRef serviceRef;
+
+ if (_ipv4AddressResolver) {
+ [_ipv4AddressResolver release];
+ _ipv4AddressResolver = nil;
+ }
+
+ if (_ipv6AddressResolver) {
+ [_ipv6AddressResolver release];
+ _ipv6AddressResolver = nil;
+ }
+
+ // Start an async lookup for IPv4 addresses
+ DNSServiceErrorType err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_A, kDNSServiceClass_IN, QueryRecordReply, self);
+ if (err == kDNSServiceErr_NoError) {
+ _ipv4AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
+ [_ipv4AddressResolver addToCurrentRunLoop];
+ }
+
+ // Start an async lookup for IPv6 addresses
+ err = DNSServiceQueryRecord(&serviceRef, (DNSServiceFlags)0, interface, [host UTF8String], kDNSServiceType_AAAA, kDNSServiceClass_IN, QueryRecordReply, self);
+ if (err == kDNSServiceErr_NoError) {
+ _ipv6AddressResolver = [[ServiceController alloc] initWithServiceRef:serviceRef];
+ [_ipv6AddressResolver addToCurrentRunLoop];
+ }
+
+ char interfaceName[IF_NAMESIZE];
+ InterfaceIndexToName(interface, interfaceName);
+
+ [hostField setStringValue:host];
+ [interfaceField setStringValue:[NSString stringWithUTF8String:interfaceName]];
+ [portField setIntValue:ntohs(port)];
+
+ // kind of a hack: munge txtRecord so it's human-readable
+ if (txtLen > 0) {
+ char *readableText = (char*) malloc(txtLen);
+ if (readableText != nil) {
+ ByteCount index, subStrLen;
+ memcpy(readableText, txtRecord, txtLen);
+ for (index=0; index < txtLen - 1; index += subStrLen + 1) {
+ subStrLen = readableText[index];
+ readableText[index] = ' ';
+ }
+ [textField setStringValue:[NSString stringWithCString:&readableText[1] length:txtLen - 1]];
+ free(readableText);
+ }
+ }
+}
+
+
+- (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
+{
+ char addrBuff[256];
+
+ if (rrtype == kDNSServiceType_A) {
+ inet_ntop(AF_INET, buff, addrBuff, sizeof(addrBuff));
+ if ([[ipAddressField stringValue] length] > 0) {
+ [ipAddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ipAddressField stringValue]]];
+ }
+ [ipAddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ipAddressField stringValue], addrBuff]];
+
+ if (!moreToCome) {
+ [_ipv4AddressResolver release];
+ _ipv4AddressResolver = nil;
+ }
+ } else if (rrtype == kDNSServiceType_AAAA) {
+ inet_ntop(AF_INET6, buff, addrBuff, sizeof(addrBuff));
+ if ([[ip6AddressField stringValue] length] > 0) {
+ [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@, ", [ip6AddressField stringValue]]];
+ }
+ [ip6AddressField setStringValue:[NSString stringWithFormat:@"%@%s", [ip6AddressField stringValue], addrBuff]];
+
+ if (!moreToCome) {
+ [_ipv6AddressResolver release];
+ _ipv6AddressResolver = nil;
+ }
+ }
+}
+
+
+- (void)connect:(id)sender
+{
+ NSString *host = [hostField stringValue];
+ NSString *txtRecord = [textField stringValue];
+ int port = [portField intValue];
+
+ int index = [nameField selectedRow];
+ NSString *selected = (index >= 0) ? [_sortedServices objectAtIndex:index] : nil;
+ NSString *type = [[_servicesDict objectForKey:selected] type];
+
+ if ([type isEqual:@"_http._tcp."]) {
+ NSString *pathDelim = @"path=";
+ NSRange where;
+
+ // If the TXT record specifies a path, extract it.
+ where = [txtRecord rangeOfString:pathDelim options:NSCaseInsensitiveSearch];
+ if (where.length) {
+ NSRange targetRange = { where.location + where.length, [txtRecord length] - where.location - where.length };
+ NSRange endDelim = [txtRecord rangeOfString:@"\n" options:kNilOptions range:targetRange];
+
+ if (endDelim.length) // if a delimiter was found, truncate the target range
+ targetRange.length = endDelim.location - targetRange.location;
+
+ NSString *path = [txtRecord substringWithRange:targetRange];
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d%@", host, port, path]]];
+ } else {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", host, port]]];
+ }
+ }
+ else if ([type isEqual:@"_ftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", host, port]]];
+ else if ([type isEqual:@"_ssh._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", host, port]]];
+ else if ([type isEqual:@"_afpovertcp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", host, port]]];
+ else if ([type isEqual:@"_smb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", host, port]]];
+ else if ([type isEqual:@"_rfb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"vnc://%@:%d/", host, port]]];
+
+ return;
+}
+
+
+- (IBAction)handleTableClick:(id)sender
+{
+ //populate the text fields
+}
+
+
+- (IBAction)removeSelected:(id)sender
+{
+ // remove the selected row and force a refresh
+
+ int selectedRow = [serviceDisplayTable selectedRow];
+
+ if (selectedRow) {
+
+ [_srvtypeKeys removeObjectAtIndex:selectedRow];
+ [_srvnameKeys removeObjectAtIndex:selectedRow];
+
+ [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
+ [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
+
+ [typeField reloadData];
+ [serviceDisplayTable reloadData];
+ }
+}
+
+
+- (IBAction)addNewService:(id)sender
+{
+ // add new entries from the edit fields to the arrays for the defaults
+ NSString *newType = [serviceTypeField stringValue];
+ NSString *newName = [serviceNameField stringValue];
+
+ // 3282283: trim trailing '.' from service type field
+ if ([newType length] && [newType hasSuffix:@"."])
+ newType = [newType substringToIndex:[newType length] - 1];
+
+ if ([newType length] && [newName length]) {
+ [_srvtypeKeys addObject:newType];
+ [_srvnameKeys addObject:newName];
+
+ [[NSUserDefaults standardUserDefaults] setObject:_srvtypeKeys forKey:@"SrvTypeKeys"];
+ [[NSUserDefaults standardUserDefaults] setObject:_srvnameKeys forKey:@"SrvNameKeys"];
+
+ [typeField reloadData];
+ [serviceDisplayTable reloadData];
+ }
+}
+
+
+- (void)_cancelPendingResolve
+{
+ [_ipv4AddressResolver release];
+ _ipv4AddressResolver = nil;
+
+ [_ipv6AddressResolver release];
+ _ipv6AddressResolver = nil;
+
+ [_serviceResolver release];
+ _serviceResolver = nil;
+
+ [self _clearResolvedInfo];
+}
+
+
+- (void)_clearResolvedInfo
+{
+ [hostField setStringValue:@""];
+ [ipAddressField setStringValue:@""];
+ [ip6AddressField setStringValue:@""];
+ [portField setStringValue:@""];
+ [interfaceField setStringValue:@""];
+ [textField setStringValue:@""];
+}
+
+@end // implementation BrowserController
+
+
+@implementation ServiceController : NSObject
+{
+ DNSServiceRef fServiceRef;
+ CFSocketRef fSocketRef;
+ CFRunLoopSourceRef fRunloopSrc;
+}
+
+
+- (id)initWithServiceRef:(DNSServiceRef)ref
+{
+ self = [super init];
+ if (self) {
+ fServiceRef = ref;
+ fSocketRef = NULL;
+ fRunloopSrc = NULL;
+ }
+ return self;
+}
+
+
+- (void)addToCurrentRunLoop
+{
+ CFSocketContext context = { 0, (void*)fServiceRef, NULL, NULL, NULL };
+
+ fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef), kCFSocketReadCallBack, ProcessSockData, &context);
+ if (fSocketRef) {
+ // Prevent CFSocketInvalidate from closing DNSServiceRef's socket.
+ CFOptionFlags sockFlags = CFSocketGetSocketFlags(fSocketRef);
+ CFSocketSetSocketFlags(fSocketRef, sockFlags & (~kCFSocketCloseOnInvalidate));
+ fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 0);
+ }
+ if (fRunloopSrc) {
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
+ } else {
+ printf("Could not listen to runloop socket\n");
+ }
+}
+
+
+- (DNSServiceRef)serviceRef
+{
+ return fServiceRef;
+}
+
+
+- (void)dealloc
+{
+ if (fSocketRef) {
+ CFSocketInvalidate(fSocketRef); // Note: Also closes the underlying socket
+ CFRelease(fSocketRef);
+
+ // Workaround that gives time to CFSocket's select thread so it can remove the socket from its
+ // FD set before we close the socket by calling DNSServiceRefDeallocate. <rdar://problem/3585273>
+ usleep(1000);
+ }
+
+ if (fRunloopSrc) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
+ CFRelease(fRunloopSrc);
+ }
+
+ DNSServiceRefDeallocate(fServiceRef);
+
+ [super dealloc];
+}
+
+
+@end // implementation ServiceController
+
+int main(int argc, const char *argv[])
+{
+ return NSApplicationMain(argc, argv);
+}