]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/SamplemDNSClient.c
1b824ddd184816f1f44ae259d96deb1b07c935f6
[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 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
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
13 * file.
14 *
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.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 *
25 * Formatting notes:
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.)
37
38 Change History (most recent first):
39
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
44
45 Revision 1.38 2003/08/12 19:56:25 cheshire
46 Update to APSL 2.0
47
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);"
51
52 Revision 1.36 2003/07/19 03:23:13 cheshire
53 <rdar://problem/2986147> mDNSResponder needs to receive and cache larger records
54
55 Revision 1.35 2003/07/11 01:57:18 cheshire
56 Add checkin history header
57
58 */
59
60 #include <libc.h>
61 #define BIND_8_COMPAT
62 #include <arpa/nameser.h>
63 #include <arpa/inet.h>
64 #include <net/if.h>
65 #include <CoreFoundation/CoreFoundation.h>
66 #include <DNSServiceDiscovery/DNSServiceDiscovery.h>
67
68 //*************************************************************************************************************
69 // Globals
70
71 typedef union { unsigned char b[2]; unsigned short NotAnInteger; } Opaque16;
72
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];
82
83 //*************************************************************************************************************
84 // Supporting Utility Functions
85 //
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
92 //
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.)
97
98 static void MyHandleMachMessage(CFMachPortRef port, void *msg, CFIndex size, void *info)
99 {
100 (void)port; // Unused
101 (void)size; // Unused
102 (void)info; // Unused
103 DNSServiceDiscovery_handleReply(msg);
104 }
105
106 static int AddDNSServiceClientToRunLoop(dns_service_discovery_ref client)
107 {
108 mach_port_t port = DNSServiceDiscoveryMachPort(client);
109 if (!port)
110 return(-1);
111 else
112 {
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);
118 CFRelease(rls);
119 return(0);
120 }
121 }
122
123 //*************************************************************************************************************
124 // Sample callback functions for each of the operation types
125
126 static void printtimestamp(void)
127 {
128 struct timeval tv;
129 struct tm tm;
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);
133 }
134
135 #define DomainMsg(X) ((X) == DNSServiceDomainEnumerationReplyAddDomain ? "Added" : \
136 (X) == DNSServiceDomainEnumerationReplyAddDomainDefault ? "(Default)" : \
137 (X) == DNSServiceDomainEnumerationReplyRemoveDomain ? "Removed" : "Unknown")
138
139 static void regdom_reply(DNSServiceDomainEnumerationReplyResultType resultType, const char *replyDomain,
140 DNSServiceDiscoveryReplyFlags flags, void *context)
141 {
142 (void)context; // Unused
143 printtimestamp();
144 printf("Recommended Registration Domain %s %s", replyDomain, DomainMsg(resultType));
145 if (flags) printf(" Flags: %X", flags);
146 printf("\n");
147 }
148
149 static void browsedom_reply(DNSServiceDomainEnumerationReplyResultType resultType, const char *replyDomain,
150 DNSServiceDiscoveryReplyFlags flags, void *context)
151 {
152 (void)context; // Unused
153 printtimestamp();
154 printf("Recommended Browsing Domain %s %s", replyDomain, DomainMsg(resultType));
155 if (flags) printf(" Flags: %X", flags);
156 printf("\n");
157 }
158
159 static void browse_reply(DNSServiceBrowserReplyResultType resultType,
160 const char *replyName, const char *replyType, const char *replyDomain, DNSServiceDiscoveryReplyFlags flags, void *context)
161 {
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");
165 printtimestamp();
166 printf("%s%6X %-8s %-20s %s\n", op, flags, replyDomain, replyType, replyName);
167 }
168
169 static void resolve_reply(struct sockaddr *interface, struct sockaddr *address, const char *txtRecord, DNSServiceDiscoveryReplyFlags flags, void *context)
170 {
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);
175 else
176 {
177 const char *src = txtRecord;
178 printtimestamp();
179
180 if (address->sa_family == AF_INET)
181 {
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];
186 char ipstring[16];
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);
189 }
190 else if (address->sa_family == AF_INET6)
191 {
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];
196 char ipstring[40];
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);
201 }
202 if (flags) printf(" Flags: %X", flags);
203 if (*src)
204 {
205 char txtInfo[64]; // Display at most first 64 characters of TXT record
206 char *dst = txtInfo;
207 const char *const lim = &txtInfo[sizeof(txtInfo)];
208 while (*src && dst < lim-1)
209 {
210 if (*src == '\\') *dst++ = '\\'; // '\' displays as "\\"
211 if (*src >= ' ') *dst++ = *src++; // Display normal characters as-is
212 else
213 {
214 *dst++ = '\\'; // Display a backslash
215 if (*src == 1) *dst++ = ' '; // String boundary displayed as "\ "
216 else // Other chararacters displayed as "\0xHH"
217 {
218 static const char hexchars[16] = "0123456789ABCDEF";
219 *dst++ = '0';
220 *dst++ = 'x';
221 *dst++ = hexchars[*src >> 4];
222 *dst++ = hexchars[*src & 0xF];
223 }
224 src++;
225 }
226 }
227 *dst++ = 0;
228 printf(" TXT %s", txtInfo);
229 }
230 printf("\n");
231 }
232 }
233
234 static void myCFRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info)
235 {
236 (void)timer; // Parameter not used
237 (void)info; // Parameter not used
238
239 switch (operation)
240 {
241 case 'A':
242 {
243 switch (addtest)
244 {
245 case 0: printf("Adding Test HINFO record\n");
246 record = DNSServiceRegistrationAddRecord(client, T_HINFO, sizeof(myhinfo9), &myhinfo9[0], 120);
247 addtest = 1;
248 break;
249 case 1: printf("Updating Test HINFO record\n");
250 DNSServiceRegistrationUpdateRecord(client, record, sizeof(myhinfoX), &myhinfoX[0], 120);
251 addtest = 2;
252 break;
253 case 2: printf("Removing Test HINFO record\n");
254 DNSServiceRegistrationRemoveRecord(client, record);
255 addtest = 0;
256 break;
257 }
258 }
259 break;
260
261 case 'U':
262 {
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);
269 }
270 break;
271
272 case 'N':
273 {
274 printf("Adding big NULL record\n");
275 DNSServiceRegistrationAddRecord(client, T_NULL, sizeof(bigNULL), &bigNULL[0], 120);
276 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
277 }
278 break;
279 }
280 }
281
282 static void reg_reply(DNSServiceRegistrationReplyErrorType errorCode, void *context)
283 {
284 (void)context; // Unused
285 printf("Got a reply from the server: ");
286 switch (errorCode)
287 {
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;
291 }
292
293 if (operation == 'A' || operation == 'U' || operation == 'N')
294 {
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);
300 }
301 }
302
303 //*************************************************************************************************************
304 // The main test function
305
306 int main(int argc, char **argv)
307 {
308 char *dom;
309 setlinebuf(stdout); // Want to see lines as they appear, not block buffered
310
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;
314
315 switch (operation)
316 {
317 case 'E': printf("Looking for recommended registration domains:\n");
318 client = DNSServiceDomainEnumerationCreate(1, regdom_reply, nil);
319 break;
320
321 case 'F': printf("Looking for recommended browsing domains:\n");
322 client = DNSServiceDomainEnumerationCreate(0, browsedom_reply, nil);
323 break;
324
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);
330 break;
331
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);
337 break;
338
339 case 'R': if (argc < optind+4) goto Fail;
340 {
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 } };
346 char txt[2048];
347 char *ptr = txt;
348 int i;
349
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
352
353 // Copy all the TXT strings into one C string separated by ASCII-1 delimiters
354 for (i = optind+4; i < argc; i++)
355 {
356 strcpy(ptr, argv[i]);
357 ptr += strlen(argv[i]);
358 *ptr++ = 1;
359 }
360 if (ptr > txt) ptr--;
361 *ptr = 0;
362
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);
365 break;
366 }
367
368 case 'A':
369 case 'U':
370 case 'N': {
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);
375 break;
376 }
377
378 case 'T': {
379 Opaque16 registerPort = { { 0x23, 0x45 } };
380 char TXT[1000];
381 unsigned int i;
382 for (i=0; i<sizeof(TXT)-1; i++)
383 if ((i & 0x1F) == 0x1F) TXT[i] = 1; else TXT[i] = 'A' + (i >> 5);
384 TXT[i] = 0;
385 printf("Registering Service Test._testlargetxt._tcp.local.\n");
386 client = DNSServiceRegistrationCreate("Test", "_testlargetxt._tcp.", "", registerPort.NotAnInteger, TXT, reg_reply, nil);
387 break;
388 }
389
390 case 'M': {
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);
399 break;
400 }
401
402 default: goto Exit;
403 }
404
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));
408 CFRunLoopRun();
409
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);
414
415 Exit:
416 return 0;
417
418 Fail:
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]);
429 return 0;
430 }