2 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
26 * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
27 * on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
28 * but for the sake of brevity here I will say just this: Curly braces are not syntactially
29 * part of an "if" statement; they are the beginning and ending markers of a compound statement;
30 * therefore common sense dictates that if they are part of a compound statement then they
31 * should be indented to the same level as everything else in that compound statement.
32 * Indenting curly braces at the same level as the "if" implies that curly braces are
33 * part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
34 * thinking that variables x and y are both of type "char*" -- and anyone who doesn't
35 * understand why variable y is not of type "char*" just proves the point that poor code
36 * layout leads people to unfortunate misunderstandings about how the C language really works.)
38 Change History (most recent first):
40 $Log: SamplemDNSClient.c,v $
41 Revision 1.39 2003/08/18 19:05:45 cheshire
42 <rdar://problem/3382423> UpdateRecord not working right
43 Added "newrdlength" field to hold new length of updated rdata
45 Revision 1.38 2003/08/12 19:56:25 cheshire
48 Revision 1.37 2003/08/05 20:39:25 cheshire
49 <rdar://problem/3362184> mDNS buffered std out makes it impossible to use from another tool
50 Added "setlinebuf(stdout);"
52 Revision 1.36 2003/07/19 03:23:13 cheshire
53 <rdar://problem/2986147> mDNSResponder needs to receive and cache larger records
55 Revision 1.35 2003/07/11 01:57:18 cheshire
56 Add checkin history header
62 #include <arpa/nameser.h>
63 #include <arpa/inet.h>
65 #include <CoreFoundation/CoreFoundation.h>
66 #include <DNSServiceDiscovery/DNSServiceDiscovery.h>
68 //*************************************************************************************************************
71 typedef union { unsigned char b
[2]; unsigned short NotAnInteger
; } Opaque16
;
73 static char operation
;
74 static dns_service_discovery_ref client
= NULL
;
75 static int num_printed
;
76 static char addtest
= 0;
77 static DNSRecordReference record
;
78 static char myhinfo9
[11] = "\003Mac\006OS 9.2";
79 static char myhinfoX
[ 9] = "\003Mac\004OS X";
80 static char updatetest
[3] = "\002AA";
81 static char bigNULL
[4096];
83 //*************************************************************************************************************
84 // Supporting Utility Functions
86 // This code takes care of:
87 // 1. Extracting the mach_port_t from the dns_service_discovery_ref
88 // 2. Making a CFMachPortRef from it
89 // 3. Making a CFRunLoopSourceRef from that
90 // 4. Adding that source to the current RunLoop
91 // 5. and passing the resulting messages back to DNSServiceDiscovery_handleReply() for processing
93 // Code that's not based around a CFRunLoop will need its own mechanism to receive Mach messages
94 // from the mDNSResponder daemon and pass them to the DNSServiceDiscovery_handleReply() routine.
95 // (There is no way to automate this, because it varies depending on the application's existing
96 // event handling model.)
98 static void MyHandleMachMessage(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
100 (void)port
; // Unused
101 (void)size
; // Unused
102 (void)info
; // Unused
103 DNSServiceDiscovery_handleReply(msg
);
106 static int AddDNSServiceClientToRunLoop(dns_service_discovery_ref client
)
108 mach_port_t port
= DNSServiceDiscoveryMachPort(client
);
113 CFMachPortContext context
= { 0, 0, NULL
, NULL
, NULL
};
114 Boolean shouldFreeInfo
;
115 CFMachPortRef cfMachPort
= CFMachPortCreateWithPort(kCFAllocatorDefault
, port
, MyHandleMachMessage
, &context
, &shouldFreeInfo
);
116 CFRunLoopSourceRef rls
= CFMachPortCreateRunLoopSource(NULL
, cfMachPort
, 0);
117 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls
, kCFRunLoopDefaultMode
);
123 //*************************************************************************************************************
124 // Sample callback functions for each of the operation types
126 static void printtimestamp(void)
130 gettimeofday(&tv
, NULL
);
131 localtime_r((time_t*)&tv
.tv_sec
, &tm
);
132 printf("%d:%02d:%02d.%03d ", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv
.tv_usec
/1000);
135 #define DomainMsg(X) ((X) == DNSServiceDomainEnumerationReplyAddDomain ? "Added" : \
136 (X) == DNSServiceDomainEnumerationReplyAddDomainDefault ? "(Default)" : \
137 (X) == DNSServiceDomainEnumerationReplyRemoveDomain ? "Removed" : "Unknown")
139 static void regdom_reply(DNSServiceDomainEnumerationReplyResultType resultType
, const char *replyDomain
,
140 DNSServiceDiscoveryReplyFlags flags
, void *context
)
142 (void)context
; // Unused
144 printf("Recommended Registration Domain %s %s", replyDomain
, DomainMsg(resultType
));
145 if (flags
) printf(" Flags: %X", flags
);
149 static void browsedom_reply(DNSServiceDomainEnumerationReplyResultType resultType
, const char *replyDomain
,
150 DNSServiceDiscoveryReplyFlags flags
, void *context
)
152 (void)context
; // Unused
154 printf("Recommended Browsing Domain %s %s", replyDomain
, DomainMsg(resultType
));
155 if (flags
) printf(" Flags: %X", flags
);
159 static void browse_reply(DNSServiceBrowserReplyResultType resultType
,
160 const char *replyName
, const char *replyType
, const char *replyDomain
, DNSServiceDiscoveryReplyFlags flags
, void *context
)
162 char *op
= (resultType
== DNSServiceBrowserReplyAddInstance
) ? "Add" : "Rmv";
163 (void)context
; // Unused
164 if (num_printed
++ == 0) printf("A/R Flags %-8s %-20s %s\n", "Domain", "Service Type", "Instance Name");
166 printf("%s%6X %-8s %-20s %s\n", op
, flags
, replyDomain
, replyType
, replyName
);
169 static void resolve_reply(struct sockaddr
*interface
, struct sockaddr
*address
, const char *txtRecord
, DNSServiceDiscoveryReplyFlags flags
, void *context
)
171 (void)interface
; // Unused
172 (void)context
; // Unused
173 if (address
->sa_family
!= AF_INET
&& address
->sa_family
!= AF_INET6
)
174 printf("Unknown address family %d\n", address
->sa_family
);
177 const char *src
= txtRecord
;
180 if (address
->sa_family
== AF_INET
)
182 struct sockaddr_in
*ip
= (struct sockaddr_in
*)address
;
183 union { uint32_t l
; u_char b
[4]; } addr
= { ip
->sin_addr
.s_addr
};
184 union { uint16_t s
; u_char b
[2]; } port
= { ip
->sin_port
};
185 uint16_t PortAsNumber
= ((uint16_t)port
.b
[0]) << 8 | port
.b
[1];
187 sprintf(ipstring
, "%d.%d.%d.%d", addr
.b
[0], addr
.b
[1], addr
.b
[2], addr
.b
[3]);
188 printf("Service can be reached at %-15s:%u", ipstring
, PortAsNumber
);
190 else if (address
->sa_family
== AF_INET6
)
192 struct sockaddr_in6
*ip6
= (struct sockaddr_in6
*)address
;
193 u_int16_t
*w
= ip6
->sin6_addr
.__u6_addr
.__u6_addr16
;
194 union { uint16_t s
; u_char b
[2]; } port
= { ip6
->sin6_port
};
195 uint16_t PortAsNumber
= ((uint16_t)port
.b
[0]) << 8 | port
.b
[1];
197 char ifname
[IF_NAMESIZE
+ 1] = "";
198 sprintf(ipstring
, "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", w
[0], w
[1], w
[2], w
[3], w
[4], w
[5], w
[6], w
[7]);
199 if (ip6
->sin6_scope_id
) { ifname
[0] = '%'; if_indextoname(ip6
->sin6_scope_id
, &ifname
[1]); }
200 printf("%s%s:%u", ipstring
, ifname
, PortAsNumber
);
202 if (flags
) printf(" Flags: %X", flags
);
205 char txtInfo
[64]; // Display at most first 64 characters of TXT record
207 const char *const lim
= &txtInfo
[sizeof(txtInfo
)];
208 while (*src
&& dst
< lim
-1)
210 if (*src
== '\\') *dst
++ = '\\'; // '\' displays as "\\"
211 if (*src
>= ' ') *dst
++ = *src
++; // Display normal characters as-is
214 *dst
++ = '\\'; // Display a backslash
215 if (*src
== 1) *dst
++ = ' '; // String boundary displayed as "\ "
216 else // Other chararacters displayed as "\0xHH"
218 static const char hexchars
[16] = "0123456789ABCDEF";
221 *dst
++ = hexchars
[*src
>> 4];
222 *dst
++ = hexchars
[*src
& 0xF];
228 printf(" TXT %s", txtInfo
);
234 static void myCFRunLoopTimerCallBack(CFRunLoopTimerRef timer
, void *info
)
236 (void)timer
; // Parameter not used
237 (void)info
; // Parameter not used
245 case 0: printf("Adding Test HINFO record\n");
246 record
= DNSServiceRegistrationAddRecord(client
, T_HINFO
, sizeof(myhinfo9
), &myhinfo9
[0], 120);
249 case 1: printf("Updating Test HINFO record\n");
250 DNSServiceRegistrationUpdateRecord(client
, record
, sizeof(myhinfoX
), &myhinfoX
[0], 120);
253 case 2: printf("Removing Test HINFO record\n");
254 DNSServiceRegistrationRemoveRecord(client
, record
);
263 if (updatetest
[1] != 'Z') updatetest
[1]++;
264 else updatetest
[1] = 'A';
265 updatetest
[0] = 3 - updatetest
[0];
266 updatetest
[2] = updatetest
[1];
267 printf("Updating Test TXT record to %c\n", updatetest
[1]);
268 DNSServiceRegistrationUpdateRecord(client
, 0, 1+updatetest
[0], &updatetest
[0], 120);
274 printf("Adding big NULL record\n");
275 DNSServiceRegistrationAddRecord(client
, T_NULL
, sizeof(bigNULL
), &bigNULL
[0], 120);
276 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer
, kCFRunLoopDefaultMode
);
282 static void reg_reply(DNSServiceRegistrationReplyErrorType errorCode
, void *context
)
284 (void)context
; // Unused
285 printf("Got a reply from the server: ");
288 case kDNSServiceDiscoveryNoError
: printf("Name now registered and active\n"); break;
289 case kDNSServiceDiscoveryNameConflict
: printf("Name in use, please choose another\n"); exit(-1);
290 default: printf("Error %d\n", errorCode
); return;
293 if (operation
== 'A' || operation
== 'U' || operation
== 'N')
295 CFRunLoopTimerContext myCFRunLoopTimerContext
= { 0, 0, NULL
, NULL
, NULL
};
296 CFRunLoopTimerRef timer
= CFRunLoopTimerCreate(kCFAllocatorDefault
,
297 CFAbsoluteTimeGetCurrent() + 5.0, 5.0, 0, 1, // Next fire time, periodic interval, flags, and order
298 myCFRunLoopTimerCallBack
, &myCFRunLoopTimerContext
);
299 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer
, kCFRunLoopDefaultMode
);
303 //*************************************************************************************************************
304 // The main test function
306 int main(int argc
, char **argv
)
309 setlinebuf(stdout
); // Want to see lines as they appear, not block buffered
311 if (argc
< 2) goto Fail
; // Minimum command line is the command name and one argument
312 operation
= getopt(argc
, (char * const *)argv
, "EFBLRAUNTM");
313 if (operation
== -1) goto Fail
;
317 case 'E': printf("Looking for recommended registration domains:\n");
318 client
= DNSServiceDomainEnumerationCreate(1, regdom_reply
, nil
);
321 case 'F': printf("Looking for recommended browsing domains:\n");
322 client
= DNSServiceDomainEnumerationCreate(0, browsedom_reply
, nil
);
325 case 'B': if (argc
< optind
+1) goto Fail
;
326 dom
= (argc
< optind
+2) ? "" : argv
[optind
+1];
327 if (dom
[0] == '.' && dom
[1] == 0) dom
[0] = 0; // We allow '.' on the command line as a synonym for empty string
328 printf("Browsing for %s%s\n", argv
[optind
+0], dom
);
329 client
= DNSServiceBrowserCreate(argv
[optind
+0], dom
, browse_reply
, nil
);
332 case 'L': if (argc
< optind
+2) goto Fail
;
333 dom
= (argc
< optind
+3) ? "" : argv
[optind
+2];
334 if (dom
[0] == '.' && dom
[1] == 0) dom
[0] = 0; // We allow '.' on the command line as a synonym for empty string
335 printf("Lookup %s.%s%s\n", argv
[optind
+0], argv
[optind
+1], dom
);
336 client
= DNSServiceResolverResolve(argv
[optind
+0], argv
[optind
+1], dom
, resolve_reply
, nil
);
339 case 'R': if (argc
< optind
+4) goto Fail
;
341 char *nam
= argv
[optind
+0];
342 char *typ
= argv
[optind
+1];
343 char *dom
= argv
[optind
+2];
344 uint16_t PortAsNumber
= atoi(argv
[optind
+3]);
345 Opaque16 registerPort
= { { PortAsNumber
>> 8, PortAsNumber
& 0xFF } };
350 if (nam
[0] == '.' && nam
[1] == 0) nam
[0] = 0; // We allow '.' on the command line as a synonym for empty string
351 if (dom
[0] == '.' && dom
[1] == 0) dom
[0] = 0; // We allow '.' on the command line as a synonym for empty string
353 // Copy all the TXT strings into one C string separated by ASCII-1 delimiters
354 for (i
= optind
+4; i
< argc
; i
++)
356 strcpy(ptr
, argv
[i
]);
357 ptr
+= strlen(argv
[i
]);
360 if (ptr
> txt
) ptr
--;
363 printf("Registering Service %s.%s%s port %s %s\n", nam
, typ
, dom
, argv
[optind
+3], txt
);
364 client
= DNSServiceRegistrationCreate(nam
, typ
, dom
, registerPort
.NotAnInteger
, txt
, reg_reply
, nil
);
371 Opaque16 registerPort
= { { 0x12, 0x34 } };
372 static const char TXT
[] = "First String\001Second String\001Third String";
373 printf("Registering Service Test._testupdate._tcp.local.\n");
374 client
= DNSServiceRegistrationCreate("Test", "_testupdate._tcp.", "", registerPort
.NotAnInteger
, TXT
, reg_reply
, nil
);
379 Opaque16 registerPort
= { { 0x23, 0x45 } };
382 for (i
=0; i
<sizeof(TXT
)-1; i
++)
383 if ((i
& 0x1F) == 0x1F) TXT
[i
] = 1; else TXT
[i
] = 'A' + (i
>> 5);
385 printf("Registering Service Test._testlargetxt._tcp.local.\n");
386 client
= DNSServiceRegistrationCreate("Test", "_testlargetxt._tcp.", "", registerPort
.NotAnInteger
, TXT
, reg_reply
, nil
);
391 pid_t pid
= getpid();
392 Opaque16 registerPort
= { { pid
>> 8, pid
& 0xFF } };
393 static const char TXT1
[] = "First String\001Second String\001Third String";
394 static const char TXT2
[] = "\x0D" "Fourth String" "\x0C" "Fifth String" "\x0C" "Sixth String";
395 printf("Registering Service Test._testdualtxt._tcp.local.\n");
396 client
= DNSServiceRegistrationCreate("", "_testdualtxt._tcp.", "", registerPort
.NotAnInteger
, TXT1
, reg_reply
, nil
);
397 // use "sizeof(TXT2)-1" because we don't wan't the C compiler's null byte on the end of the string
398 record
= DNSServiceRegistrationAddRecord(client
, T_TXT
, sizeof(TXT2
)-1, TXT2
, 120);
405 if (!client
) { fprintf(stderr
, "DNSService call failed\n"); return (-1); }
406 if (AddDNSServiceClientToRunLoop(client
) != 0) { fprintf(stderr
, "AddDNSServiceClientToRunLoop failed\n"); return (-1); }
407 printf("Talking to DNS SD Daemon at Mach port %d\n", DNSServiceDiscoveryMachPort(client
));
410 // Be sure to deallocate the dns_service_discovery_ref when you're finished
411 // Note: What other cleanup has to be done here?
412 // We should probably invalidate, remove and release our CFRunLoopSourceRef?
413 DNSServiceDiscoveryDeallocate(client
);
419 fprintf(stderr
, "%s -E (Enumerate recommended registration domains)\n", argv
[0]);
420 fprintf(stderr
, "%s -F (Enumerate recommended browsing domains)\n", argv
[0]);
421 fprintf(stderr
, "%s -B <Type> <Domain> (Browse for services instances)\n", argv
[0]);
422 fprintf(stderr
, "%s -L <Name> <Type> <Domain> (Look up a service instance)\n", argv
[0]);
423 fprintf(stderr
, "%s -R <Name> <Type> <Domain> <Port> [<TXT>...] (Register a service)\n", argv
[0]);
424 fprintf(stderr
, "%s -A (Test Adding/Updating/Deleting a record)\n", argv
[0]);
425 fprintf(stderr
, "%s -U (Test updating a TXT record)\n", argv
[0]);
426 fprintf(stderr
, "%s -N (Test adding a large NULL record)\n", argv
[0]);
427 fprintf(stderr
, "%s -T (Test creating a large TXT record)\n", argv
[0]);
428 fprintf(stderr
, "%s -M (Test creating a registration with multiple TXT records)\n", argv
[0]);