]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/SamplemDNSClient.c
mDNSResponder-108.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / SamplemDNSClient.c
1 /*
2 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * Formatting notes:
24 * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
25 * on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
26 * but for the sake of brevity here I will say just this: Curly braces are not syntactially
27 * part of an "if" statement; they are the beginning and ending markers of a compound statement;
28 * therefore common sense dictates that if they are part of a compound statement then they
29 * should be indented to the same level as everything else in that compound statement.
30 * Indenting curly braces at the same level as the "if" implies that curly braces are
31 * part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
32 * thinking that variables x and y are both of type "char*" -- and anyone who doesn't
33 * understand why variable y is not of type "char*" just proves the point that poor code
34 * layout leads people to unfortunate misunderstandings about how the C language really works.)
35
36 Change History (most recent first):
37
38 $Log: SamplemDNSClient.c,v $
39 Revision 1.46 2004/11/02 01:32:34 cheshire
40 <rdar://problem/3861705> Update code so it still compiles when DNSServiceDiscovery.h is deprecated
41
42 Revision 1.45 2004/06/15 02:39:47 cheshire
43 When displaying error message, only show command name, not entire path
44
45 Revision 1.44 2004/05/28 02:20:06 cheshire
46 If we allow dot or empty string for domain when resolving a service,
47 it should be a synonym for "local"
48
49 Revision 1.43 2004/02/03 22:07:50 cheshire
50 <rdar://problem/3548184>: Widen columns to display non-local domains better
51
52 Revision 1.42 2003/12/03 11:39:17 cheshire
53 <rdar://problem/3468977> Browse output misaligned in mDNS command-line tool
54 (Also fix it to add leading space when the hours part of the time is only one digit)
55
56 Revision 1.41 2003/10/30 22:52:57 cheshire
57 <rdar://problem/3468977> Browse output misaligned in mDNS command-line tool
58
59 Revision 1.40 2003/09/26 01:07:06 cheshire
60 Added test case to test fix for <rdar://problem/3427923>
61
62 Revision 1.39 2003/08/18 19:05:45 cheshire
63 <rdar://problem/3382423> UpdateRecord not working right
64 Added "newrdlength" field to hold new length of updated rdata
65
66 Revision 1.38 2003/08/12 19:56:25 cheshire
67 Update to APSL 2.0
68
69 Revision 1.37 2003/08/05 20:39:25 cheshire
70 <rdar://problem/3362184> mDNS buffered std out makes it impossible to use from another tool
71 Added "setlinebuf(stdout);"
72
73 Revision 1.36 2003/07/19 03:23:13 cheshire
74 <rdar://problem/2986147> mDNSResponder needs to receive and cache larger records
75
76 Revision 1.35 2003/07/11 01:57:18 cheshire
77 Add checkin history header
78
79 */
80
81 #include <libc.h>
82 #define BIND_8_COMPAT
83 #include <arpa/nameser.h>
84 #include <arpa/inet.h>
85 #include <net/if.h>
86 #include <CoreFoundation/CoreFoundation.h>
87
88 // We already know this tool is using the old deprecated API (that's its purpose)
89 // Since we compile with all warnings treated as errors, we have to turn off the warnings here or the project won't compile
90 #include <AvailabilityMacros.h>
91 #undef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED
92 #define AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED
93 #include <DNSServiceDiscovery/DNSServiceDiscovery.h>
94
95 //*************************************************************************************************************
96 // Globals
97
98 typedef union { unsigned char b[2]; unsigned short NotAnInteger; } Opaque16;
99
100 static char operation;
101 static dns_service_discovery_ref client = NULL;
102 static int num_printed;
103 static char addtest = 0;
104 static DNSRecordReference record;
105 static char myhinfo9[11] = "\003Mac\006OS 9.2";
106 static char myhinfoX[ 9] = "\003Mac\004OS X";
107 static char updatetest[3] = "\002AA";
108 static char bigNULL[4096];
109
110 //*************************************************************************************************************
111 // Supporting Utility Functions
112 //
113 // This code takes care of:
114 // 1. Extracting the mach_port_t from the dns_service_discovery_ref
115 // 2. Making a CFMachPortRef from it
116 // 3. Making a CFRunLoopSourceRef from that
117 // 4. Adding that source to the current RunLoop
118 // 5. and passing the resulting messages back to DNSServiceDiscovery_handleReply() for processing
119 //
120 // Code that's not based around a CFRunLoop will need its own mechanism to receive Mach messages
121 // from the mDNSResponder daemon and pass them to the DNSServiceDiscovery_handleReply() routine.
122 // (There is no way to automate this, because it varies depending on the application's existing
123 // event handling model.)
124
125 static void MyHandleMachMessage(CFMachPortRef port, void *msg, CFIndex size, void *info)
126 {
127 (void)port; // Unused
128 (void)size; // Unused
129 (void)info; // Unused
130 DNSServiceDiscovery_handleReply(msg);
131 }
132
133 static int AddDNSServiceClientToRunLoop(dns_service_discovery_ref client)
134 {
135 mach_port_t port = DNSServiceDiscoveryMachPort(client);
136 if (!port)
137 return(-1);
138 else
139 {
140 CFMachPortContext context = { 0, 0, NULL, NULL, NULL };
141 Boolean shouldFreeInfo;
142 CFMachPortRef cfMachPort = CFMachPortCreateWithPort(kCFAllocatorDefault, port, MyHandleMachMessage, &context, &shouldFreeInfo);
143 CFRunLoopSourceRef rls = CFMachPortCreateRunLoopSource(NULL, cfMachPort, 0);
144 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
145 CFRelease(rls);
146 return(0);
147 }
148 }
149
150 //*************************************************************************************************************
151 // Sample callback functions for each of the operation types
152
153 static void printtimestamp(void)
154 {
155 struct timeval tv;
156 struct tm tm;
157 gettimeofday(&tv, NULL);
158 localtime_r((time_t*)&tv.tv_sec, &tm);
159 printf("%2d:%02d:%02d.%03d ", tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec/1000);
160 }
161
162 #define DomainMsg(X) ((X) == DNSServiceDomainEnumerationReplyAddDomain ? "Added" : \
163 (X) == DNSServiceDomainEnumerationReplyAddDomainDefault ? "(Default)" : \
164 (X) == DNSServiceDomainEnumerationReplyRemoveDomain ? "Removed" : "Unknown")
165
166 static void regdom_reply(DNSServiceDomainEnumerationReplyResultType resultType, const char *replyDomain,
167 DNSServiceDiscoveryReplyFlags flags, void *context)
168 {
169 (void)context; // Unused
170 printtimestamp();
171 printf("Recommended Registration Domain %s %s", replyDomain, DomainMsg(resultType));
172 if (flags) printf(" Flags: %X", flags);
173 printf("\n");
174 }
175
176 static void browsedom_reply(DNSServiceDomainEnumerationReplyResultType resultType, const char *replyDomain,
177 DNSServiceDiscoveryReplyFlags flags, void *context)
178 {
179 (void)context; // Unused
180 printtimestamp();
181 printf("Recommended Browsing Domain %s %s", replyDomain, DomainMsg(resultType));
182 if (flags) printf(" Flags: %X", flags);
183 printf("\n");
184 }
185
186 static void browse_reply(DNSServiceBrowserReplyResultType resultType,
187 const char *replyName, const char *replyType, const char *replyDomain, DNSServiceDiscoveryReplyFlags flags, void *context)
188 {
189 char *op = (resultType == DNSServiceBrowserReplyAddInstance) ? "Add" : "Rmv";
190 (void)context; // Unused
191 if (num_printed++ == 0) printf("Timestamp A/R Flags %-24s %-24s %s\n", "Domain", "Service Type", "Instance Name");
192 printtimestamp();
193 printf("%s%6X %-24s %-24s %s\n", op, flags, replyDomain, replyType, replyName);
194 }
195
196 static void resolve_reply(struct sockaddr *interface, struct sockaddr *address, const char *txtRecord, DNSServiceDiscoveryReplyFlags flags, void *context)
197 {
198 (void)interface; // Unused
199 (void)context; // Unused
200 if (address->sa_family != AF_INET && address->sa_family != AF_INET6)
201 printf("Unknown address family %d\n", address->sa_family);
202 else
203 {
204 const char *src = txtRecord;
205 printtimestamp();
206
207 if (address->sa_family == AF_INET)
208 {
209 struct sockaddr_in *ip = (struct sockaddr_in *)address;
210 union { uint32_t l; u_char b[4]; } addr = { ip->sin_addr.s_addr };
211 union { uint16_t s; u_char b[2]; } port = { ip->sin_port };
212 uint16_t PortAsNumber = ((uint16_t)port.b[0]) << 8 | port.b[1];
213 char ipstring[16];
214 sprintf(ipstring, "%d.%d.%d.%d", addr.b[0], addr.b[1], addr.b[2], addr.b[3]);
215 printf("Service can be reached at %-15s:%u", ipstring, PortAsNumber);
216 }
217 else if (address->sa_family == AF_INET6)
218 {
219 struct sockaddr_in6 *ip6 = (struct sockaddr_in6 *)address;
220 u_int16_t *w = ip6->sin6_addr.__u6_addr.__u6_addr16;
221 union { uint16_t s; u_char b[2]; } port = { ip6->sin6_port };
222 uint16_t PortAsNumber = ((uint16_t)port.b[0]) << 8 | port.b[1];
223 char ipstring[40];
224 char ifname[IF_NAMESIZE + 1] = "";
225 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]);
226 if (ip6->sin6_scope_id) { ifname[0] = '%'; if_indextoname(ip6->sin6_scope_id, &ifname[1]); }
227 printf("%s%s:%u", ipstring, ifname, PortAsNumber);
228 }
229 if (flags) printf(" Flags: %X", flags);
230 if (*src)
231 {
232 char txtInfo[64]; // Display at most first 64 characters of TXT record
233 char *dst = txtInfo;
234 const char *const lim = &txtInfo[sizeof(txtInfo)];
235 while (*src && dst < lim-1)
236 {
237 if (*src == '\\') *dst++ = '\\'; // '\' displays as "\\"
238 if (*src >= ' ') *dst++ = *src++; // Display normal characters as-is
239 else
240 {
241 *dst++ = '\\'; // Display a backslash
242 if (*src == 1) *dst++ = ' '; // String boundary displayed as "\ "
243 else // Other chararacters displayed as "\0xHH"
244 {
245 static const char hexchars[16] = "0123456789ABCDEF";
246 *dst++ = '0';
247 *dst++ = 'x';
248 *dst++ = hexchars[*src >> 4];
249 *dst++ = hexchars[*src & 0xF];
250 }
251 src++;
252 }
253 }
254 *dst++ = 0;
255 printf(" TXT %s", txtInfo);
256 }
257 printf("\n");
258 }
259 }
260
261 static void myCFRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info)
262 {
263 (void)timer; // Parameter not used
264 (void)info; // Parameter not used
265
266 switch (operation)
267 {
268 case 'A':
269 {
270 switch (addtest)
271 {
272 case 0: printf("Adding Test HINFO record\n");
273 record = DNSServiceRegistrationAddRecord(client, T_HINFO, sizeof(myhinfo9), &myhinfo9[0], 120);
274 addtest = 1;
275 break;
276 case 1: printf("Updating Test HINFO record\n");
277 DNSServiceRegistrationUpdateRecord(client, record, sizeof(myhinfoX), &myhinfoX[0], 120);
278 addtest = 2;
279 break;
280 case 2: printf("Removing Test HINFO record\n");
281 DNSServiceRegistrationRemoveRecord(client, record);
282 addtest = 0;
283 break;
284 }
285 }
286 break;
287
288 case 'U':
289 {
290 if (updatetest[1] != 'Z') updatetest[1]++;
291 else updatetest[1] = 'A';
292 updatetest[0] = 3 - updatetest[0];
293 updatetest[2] = updatetest[1];
294 printf("Updating Test TXT record to %c\n", updatetest[1]);
295 DNSServiceRegistrationUpdateRecord(client, 0, 1+updatetest[0], &updatetest[0], 120);
296 }
297 break;
298
299 case 'N':
300 {
301 printf("Adding big NULL record\n");
302 DNSServiceRegistrationAddRecord(client, T_NULL, sizeof(bigNULL), &bigNULL[0], 120);
303 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
304 }
305 break;
306 }
307 }
308
309 static void reg_reply(DNSServiceRegistrationReplyErrorType errorCode, void *context)
310 {
311 (void)context; // Unused
312 printf("Got a reply from the server: ");
313 switch (errorCode)
314 {
315 case kDNSServiceDiscoveryNoError: printf("Name now registered and active\n"); break;
316 case kDNSServiceDiscoveryNameConflict: printf("Name in use, please choose another\n"); exit(-1);
317 default: printf("Error %d\n", errorCode); return;
318 }
319
320 if (operation == 'A' || operation == 'U' || operation == 'N')
321 {
322 CFRunLoopTimerContext myCFRunLoopTimerContext = { 0, 0, NULL, NULL, NULL };
323 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
324 CFAbsoluteTimeGetCurrent() + 5.0, 5.0, 0, 1, // Next fire time, periodic interval, flags, and order
325 myCFRunLoopTimerCallBack, &myCFRunLoopTimerContext);
326 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
327 }
328 }
329
330 //*************************************************************************************************************
331 // The main test function
332
333 int main(int argc, char **argv)
334 {
335 const char *progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0];
336 char *dom;
337 setlinebuf(stdout); // Want to see lines as they appear, not block buffered
338
339 if (argc < 2) goto Fail; // Minimum command line is the command name and one argument
340 operation = getopt(argc, (char * const *)argv, "EFBLRAUNTMI");
341 if (operation == -1) goto Fail;
342
343 switch (operation)
344 {
345 case 'E': printf("Looking for recommended registration domains:\n");
346 client = DNSServiceDomainEnumerationCreate(1, regdom_reply, nil);
347 break;
348
349 case 'F': printf("Looking for recommended browsing domains:\n");
350 client = DNSServiceDomainEnumerationCreate(0, browsedom_reply, nil);
351 break;
352
353 case 'B': if (argc < optind+1) goto Fail;
354 dom = (argc < optind+2) ? "local" : argv[optind+1];
355 if (dom[0] == '.' && dom[1] == 0) dom[0] = 0; // We allow '.' on the command line as a synonym for empty string
356 printf("Browsing for %s%s\n", argv[optind+0], dom);
357 client = DNSServiceBrowserCreate(argv[optind+0], dom, browse_reply, nil);
358 break;
359
360 case 'L': if (argc < optind+2) goto Fail;
361 dom = (argc < optind+3) ? "" : argv[optind+2];
362 if (dom[0] == '.' && dom[1] == 0) dom = "local"; // We allow '.' on the command line as a synonym for "local"
363 printf("Lookup %s.%s%s\n", argv[optind+0], argv[optind+1], dom);
364 client = DNSServiceResolverResolve(argv[optind+0], argv[optind+1], dom, resolve_reply, nil);
365 break;
366
367 case 'R': if (argc < optind+4) goto Fail;
368 {
369 char *nam = argv[optind+0];
370 char *typ = argv[optind+1];
371 char *dom = argv[optind+2];
372 uint16_t PortAsNumber = atoi(argv[optind+3]);
373 Opaque16 registerPort = { { PortAsNumber >> 8, PortAsNumber & 0xFF } };
374 char txt[2048];
375 char *ptr = txt;
376 int i;
377
378 if (nam[0] == '.' && nam[1] == 0) nam[0] = 0; // We allow '.' on the command line as a synonym for empty string
379 if (dom[0] == '.' && dom[1] == 0) dom[0] = 0; // We allow '.' on the command line as a synonym for empty string
380
381 // Copy all the TXT strings into one C string separated by ASCII-1 delimiters
382 for (i = optind+4; i < argc; i++)
383 {
384 strcpy(ptr, argv[i]);
385 ptr += strlen(argv[i]);
386 *ptr++ = 1;
387 }
388 if (ptr > txt) ptr--;
389 *ptr = 0;
390
391 printf("Registering Service %s.%s%s port %s %s\n", nam, typ, dom, argv[optind+3], txt);
392 client = DNSServiceRegistrationCreate(nam, typ, dom, registerPort.NotAnInteger, txt, reg_reply, nil);
393 break;
394 }
395
396 case 'A':
397 case 'U':
398 case 'N': {
399 Opaque16 registerPort = { { 0x12, 0x34 } };
400 static const char TXT[] = "First String\001Second String\001Third String";
401 printf("Registering Service Test._testupdate._tcp.local.\n");
402 client = DNSServiceRegistrationCreate("Test", "_testupdate._tcp.", "", registerPort.NotAnInteger, TXT, reg_reply, nil);
403 break;
404 }
405
406 case 'T': {
407 Opaque16 registerPort = { { 0x23, 0x45 } };
408 char TXT[1000];
409 unsigned int i;
410 for (i=0; i<sizeof(TXT)-1; i++)
411 if ((i & 0x1F) == 0x1F) TXT[i] = 1; else TXT[i] = 'A' + (i >> 5);
412 TXT[i] = 0;
413 printf("Registering Service Test._testlargetxt._tcp.local.\n");
414 client = DNSServiceRegistrationCreate("Test", "_testlargetxt._tcp.", "", registerPort.NotAnInteger, TXT, reg_reply, nil);
415 break;
416 }
417
418 case 'M': {
419 pid_t pid = getpid();
420 Opaque16 registerPort = { { pid >> 8, pid & 0xFF } };
421 static const char TXT1[] = "First String\001Second String\001Third String";
422 static const char TXT2[] = "\x0D" "Fourth String" "\x0C" "Fifth String" "\x0C" "Sixth String";
423 printf("Registering Service Test._testdualtxt._tcp.local.\n");
424 client = DNSServiceRegistrationCreate("", "_testdualtxt._tcp.", "", registerPort.NotAnInteger, TXT1, reg_reply, nil);
425 // use "sizeof(TXT2)-1" because we don't wan't the C compiler's null byte on the end of the string
426 record = DNSServiceRegistrationAddRecord(client, T_TXT, sizeof(TXT2)-1, TXT2, 120);
427 break;
428 }
429
430 case 'I': {
431 pid_t pid = getpid();
432 Opaque16 registerPort = { { pid >> 8, pid & 0xFF } };
433 static const char TXT[] = "\x09" "Test Data";
434 printf("Registering Service Test._testtxt._tcp.local.\n");
435 client = DNSServiceRegistrationCreate("", "_testtxt._tcp.", "", registerPort.NotAnInteger, "", reg_reply, nil);
436 if (client) DNSServiceRegistrationUpdateRecord(client, 0, 1+TXT[0], &TXT[0], 120);
437 break;
438 }
439
440 default: goto Exit;
441 }
442
443 if (!client) { fprintf(stderr, "DNSService call failed\n"); return (-1); }
444 if (AddDNSServiceClientToRunLoop(client) != 0) { fprintf(stderr, "AddDNSServiceClientToRunLoop failed\n"); return (-1); }
445 printf("Talking to DNS SD Daemon at Mach port %d\n", DNSServiceDiscoveryMachPort(client));
446 CFRunLoopRun();
447
448 // Be sure to deallocate the dns_service_discovery_ref when you're finished
449 // Note: What other cleanup has to be done here?
450 // We should probably invalidate, remove and release our CFRunLoopSourceRef?
451 DNSServiceDiscoveryDeallocate(client);
452
453 Exit:
454 return 0;
455
456 Fail:
457 fprintf(stderr, "%s -E (Enumerate recommended registration domains)\n", progname);
458 fprintf(stderr, "%s -F (Enumerate recommended browsing domains)\n", progname);
459 fprintf(stderr, "%s -B <Type> <Domain> (Browse for services instances)\n", progname);
460 fprintf(stderr, "%s -L <Name> <Type> <Domain> (Look up a service instance)\n", progname);
461 fprintf(stderr, "%s -R <Name> <Type> <Domain> <Port> [<TXT>...] (Register a service)\n", progname);
462 fprintf(stderr, "%s -A (Test Adding/Updating/Deleting a record)\n", progname);
463 fprintf(stderr, "%s -U (Test updating a TXT record)\n", progname);
464 fprintf(stderr, "%s -N (Test adding a large NULL record)\n", progname);
465 fprintf(stderr, "%s -T (Test creating a large TXT record)\n", progname);
466 fprintf(stderr, "%s -M (Test creating a registration with multiple TXT records)\n", progname);
467 fprintf(stderr, "%s -I (Test registering and then immediately updating TXT record)\n", progname);
468 return 0;
469 }