]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSPosix/NetMonitor.c
mDNSResponder-333.10.tar.gz
[apple/mdnsresponder.git] / mDNSPosix / NetMonitor.c
1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2002-2004 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 * Formatting notes:
18 * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
19 * on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
20 * but for the sake of brevity here I will say just this: Curly braces are not syntactially
21 * part of an "if" statement; they are the beginning and ending markers of a compound statement;
22 * therefore common sense dictates that if they are part of a compound statement then they
23 * should be indented to the same level as everything else in that compound statement.
24 * Indenting curly braces at the same level as the "if" implies that curly braces are
25 * part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
26 * thinking that variables x and y are both of type "char*" -- and anyone who doesn't
27 * understand why variable y is not of type "char*" just proves the point that poor code
28 * layout leads people to unfortunate misunderstandings about how the C language really works.)
29 */
30
31 //*************************************************************************************************************
32 // Incorporate mDNS.c functionality
33
34 // We want to use much of the functionality provided by "mDNS.c",
35 // except we'll steal the packets that would be sent to normal mDNSCoreReceive() routine
36 #define mDNSCoreReceive __NOT__mDNSCoreReceive__NOT__
37 #include "mDNS.c"
38 #undef mDNSCoreReceive
39
40 //*************************************************************************************************************
41 // Headers
42
43 #include <stdio.h> // For printf()
44 #include <stdlib.h> // For malloc()
45 #include <string.h> // For strrchr(), strcmp()
46 #include <time.h> // For "struct tm" etc.
47 #include <signal.h> // For SIGINT, SIGTERM
48 #if defined(WIN32)
49 // Both mDNS.c and mDNSWin32.h declare UDPSocket_struct type resulting in a compile-time error, so
50 // trick the compiler when including mDNSWin32.h
51 # define UDPSocket_struct _UDPSocket_struct
52 # include <mDNSEmbeddedAPI.h>
53 # include <mDNSWin32.h>
54 # include <PosixCompat.h>
55 # include <Poll.h>
56 # define IFNAMSIZ 256
57 static HANDLE gStopEvent = INVALID_HANDLE_VALUE;
58 static mDNSBool gRunning;
59 static void CALLBACK StopNotification( HANDLE event, void *context ) { gRunning = mDNSfalse; }
60 static BOOL WINAPI ConsoleControlHandler( DWORD inControlEvent ) { SetEvent( gStopEvent ); return TRUE; }
61 void setlinebuf( FILE * fp ) {}
62 #else
63 # include <netdb.h> // For gethostbyname()
64 # include <sys/socket.h> // For AF_INET, AF_INET6, etc.
65 # include <net/if.h> // For IF_NAMESIZE
66 # include <netinet/in.h> // For INADDR_NONE
67 # include <arpa/inet.h> // For inet_addr()
68 # include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform
69 #endif
70 #include "ExampleClientApp.h"
71
72 //*************************************************************************************************************
73 // Types and structures
74
75 enum
76 {
77 // Primitive operations
78 OP_probe = 0,
79 OP_goodbye = 1,
80
81 // These are meta-categories;
82 // Query and Answer operations are actually subdivided into two classes:
83 // Browse query/answer and
84 // Resolve query/answer
85 OP_query = 2,
86 OP_answer = 3,
87
88 // The "Browse" variants of query/answer
89 OP_browsegroup = 2,
90 OP_browseq = 2,
91 OP_browsea = 3,
92
93 // The "Resolve" variants of query/answer
94 OP_resolvegroup = 4,
95 OP_resolveq = 4,
96 OP_resolvea = 5,
97
98 OP_NumTypes = 6
99 };
100
101 typedef struct ActivityStat_struct ActivityStat;
102 struct ActivityStat_struct
103 {
104 ActivityStat *next;
105 domainname srvtype;
106 int printed;
107 int totalops;
108 int stat[OP_NumTypes];
109 };
110
111 typedef struct FilterList_struct FilterList;
112 struct FilterList_struct
113 {
114 FilterList *next;
115 mDNSAddr FilterAddr;
116 };
117
118 //*************************************************************************************************************
119 // Constants
120
121 #define kReportTopServices 15
122 #define kReportTopHosts 15
123
124 //*************************************************************************************************************
125 // Globals
126
127 mDNS mDNSStorage; // mDNS core uses this to store its globals
128 static mDNS_PlatformSupport PlatformStorage; // Stores this platform's globals
129 mDNSexport const char ProgramName[] = "mDNSNetMonitor";
130
131 struct timeval tv_start, tv_end, tv_interval;
132 static int FilterInterface = 0;
133 static FilterList *Filters;
134 #define ExactlyOneFilter (Filters && !Filters->next)
135
136 static int NumPktQ, NumPktL, NumPktR, NumPktB; // Query/Legacy/Response/Bad
137 static int NumProbes, NumGoodbyes, NumQuestions, NumLegacy, NumAnswers, NumAdditionals;
138
139 static ActivityStat *stats;
140
141 #define OPBanner "Total Ops Probe Goodbye BrowseQ BrowseA ResolveQ ResolveA"
142
143 //*************************************************************************************************************
144 // Utilities
145
146 // Special version of printf that knows how to print IP addresses, DNS-format name strings, etc.
147 mDNSlocal mDNSu32 mprintf(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2);
148 mDNSlocal mDNSu32 mprintf(const char *format, ...)
149 {
150 mDNSu32 length;
151 unsigned char buffer[512];
152 va_list ptr;
153 va_start(ptr,format);
154 length = mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr);
155 va_end(ptr);
156 printf("%s", buffer);
157 return(length);
158 }
159
160 //*************************************************************************************************************
161 // Host Address List
162 //
163 // Would benefit from a hash
164
165 typedef enum
166 {
167 HostPkt_Q = 0, // Query
168 HostPkt_L = 1, // Legacy Query
169 HostPkt_R = 2, // Response
170 HostPkt_B = 3, // Bad
171 HostPkt_NumTypes = 4
172 } HostPkt_Type;
173
174 typedef struct
175 {
176 mDNSAddr addr;
177 unsigned long pkts[HostPkt_NumTypes];
178 unsigned long totalops;
179 unsigned long stat[OP_NumTypes];
180 domainname hostname;
181 domainname revname;
182 UTF8str255 HIHardware;
183 UTF8str255 HISoftware;
184 mDNSu32 NumQueries;
185 mDNSs32 LastQuery;
186 } HostEntry;
187
188 #define HostEntryTotalPackets(H) ((H)->pkts[HostPkt_Q] + (H)->pkts[HostPkt_L] + (H)->pkts[HostPkt_R] + (H)->pkts[HostPkt_B])
189
190 typedef struct
191 {
192 long num;
193 long max;
194 HostEntry *hosts;
195 } HostList;
196
197 static HostList IPv4HostList = { 0, 0, 0 };
198 static HostList IPv6HostList = { 0, 0, 0 };
199
200 mDNSlocal HostEntry *FindHost(const mDNSAddr *addr, HostList *list)
201 {
202 long i;
203
204 for (i = 0; i < list->num; i++)
205 {
206 HostEntry *entry = list->hosts + i;
207 if (mDNSSameAddress(addr, &entry->addr))
208 return entry;
209 }
210
211 return NULL;
212 }
213
214 mDNSlocal HostEntry *AddHost(const mDNSAddr *addr, HostList *list)
215 {
216 int i;
217 HostEntry *entry;
218 if (list->num >= list->max)
219 {
220 long newMax = list->max + 64;
221 HostEntry *newHosts = realloc(list->hosts, newMax * sizeof(HostEntry));
222 if (newHosts == NULL)
223 return NULL;
224 list->max = newMax;
225 list->hosts = newHosts;
226 }
227
228 entry = list->hosts + list->num++;
229
230 entry->addr = *addr;
231 for (i=0; i<HostPkt_NumTypes; i++) entry->pkts[i] = 0;
232 entry->totalops = 0;
233 for (i=0; i<OP_NumTypes; i++) entry->stat[i] = 0;
234 entry->hostname.c[0] = 0;
235 entry->revname.c[0] = 0;
236 entry->HIHardware.c[0] = 0;
237 entry->HISoftware.c[0] = 0;
238 entry->NumQueries = 0;
239
240 if (entry->addr.type == mDNSAddrType_IPv4)
241 {
242 mDNSv4Addr ip = entry->addr.ip.v4;
243 char buffer[32];
244 // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code
245 mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.", ip.b[3], ip.b[2], ip.b[1], ip.b[0]);
246 MakeDomainNameFromDNSNameString(&entry->revname, buffer);
247 }
248
249 return(entry);
250 }
251
252 mDNSlocal HostEntry *GotPacketFromHost(const mDNSAddr *addr, HostPkt_Type t, mDNSOpaque16 id)
253 {
254 if (ExactlyOneFilter) return(NULL);
255 else
256 {
257 HostList *list = (addr->type == mDNSAddrType_IPv4) ? &IPv4HostList : &IPv6HostList;
258 HostEntry *entry = FindHost(addr, list);
259 if (!entry) entry = AddHost(addr, list);
260 if (!entry) return(NULL);
261 // Don't count our own interrogation packets
262 if (id.NotAnInteger != 0xFFFF) entry->pkts[t]++;
263 return(entry);
264 }
265 }
266
267 mDNSlocal void RecordHostInfo(HostEntry *entry, const ResourceRecord *const pktrr)
268 {
269 if (!entry->hostname.c[0])
270 {
271 if (pktrr->rrtype == kDNSType_A || pktrr->rrtype == kDNSType_AAAA)
272 {
273 // Should really check that the rdata in the address record matches the source address of this packet
274 entry->NumQueries = 0;
275 AssignDomainName(&entry->hostname, pktrr->name);
276 }
277
278 if (pktrr->rrtype == kDNSType_PTR)
279 if (SameDomainName(&entry->revname, pktrr->name))
280 {
281 entry->NumQueries = 0;
282 AssignDomainName(&entry->hostname, &pktrr->rdata->u.name);
283 }
284 }
285 else if (pktrr->rrtype == kDNSType_HINFO)
286 {
287 RDataBody *rd = &pktrr->rdata->u;
288 mDNSu8 *rdend = (mDNSu8 *)rd + pktrr->rdlength;
289 mDNSu8 *hw = rd->txt.c;
290 mDNSu8 *sw = hw + 1 + (mDNSu32)hw[0];
291 if (sw + 1 + sw[0] <= rdend)
292 {
293 AssignDomainName(&entry->hostname, pktrr->name);
294 mDNSPlatformMemCopy(entry->HIHardware.c, hw, 1 + (mDNSu32)hw[0]);
295 mDNSPlatformMemCopy(entry->HISoftware.c, sw, 1 + (mDNSu32)sw[0]);
296 }
297 }
298 }
299
300 mDNSlocal void SendUnicastQuery(mDNS *const m, HostEntry *entry, domainname *name, mDNSu16 rrtype, mDNSInterfaceID InterfaceID)
301 {
302 const mDNSOpaque16 id = { { 0xFF, 0xFF } };
303 DNSMessage query;
304 mDNSu8 *qptr = query.data;
305 const mDNSu8 *const limit = query.data + sizeof(query.data);
306 const mDNSAddr *target = &entry->addr;
307 InitializeDNSMessage(&query.h, id, QueryFlags);
308 qptr = putQuestion(&query, qptr, limit, name, rrtype, kDNSClass_IN);
309 entry->LastQuery = m->timenow;
310 entry->NumQueries++;
311
312 // Note: When there are multiple mDNSResponder agents running on a single machine
313 // (e.g. Apple mDNSResponder plus a SliMP3 server with embedded mDNSResponder)
314 // it is possible that unicast queries may not go to the primary system responder.
315 // We try the first query using unicast, but if that doesn't work we try again via multicast.
316 if (entry->NumQueries > 2)
317 {
318 target = &AllDNSLinkGroup_v4;
319 }
320 else
321 {
322 //mprintf("%#a Q\n", target);
323 InterfaceID = mDNSInterface_Any; // Send query from our unicast reply socket
324 }
325
326 mDNSSendDNSMessage(&mDNSStorage, &query, qptr, InterfaceID, mDNSNULL, target, MulticastDNSPort, mDNSNULL, mDNSNULL);
327 }
328
329 mDNSlocal void AnalyseHost(mDNS *const m, HostEntry *entry, const mDNSInterfaceID InterfaceID)
330 {
331 // If we've done four queries without answer, give up
332 if (entry->NumQueries >= 4) return;
333
334 // If we've done a query in the last second, give the host a chance to reply before trying again
335 if (entry->NumQueries && m->timenow - entry->LastQuery < mDNSPlatformOneSecond) return;
336
337 // If we don't know the host name, try to find that first
338 if (!entry->hostname.c[0])
339 {
340 if (entry->revname.c[0])
341 {
342 SendUnicastQuery(m, entry, &entry->revname, kDNSType_PTR, InterfaceID);
343 //mprintf("%##s PTR %d\n", entry->revname.c, entry->NumQueries);
344 }
345 }
346 // If we have the host name but no HINFO, now ask for that
347 else if (!entry->HIHardware.c[0])
348 {
349 SendUnicastQuery(m, entry, &entry->hostname, kDNSType_HINFO, InterfaceID);
350 //mprintf("%##s HINFO %d\n", entry->hostname.c, entry->NumQueries);
351 }
352 }
353
354 mDNSlocal int CompareHosts(const void *p1, const void *p2)
355 {
356 return (int)(HostEntryTotalPackets((HostEntry *)p2) - HostEntryTotalPackets((HostEntry *)p1));
357 }
358
359 mDNSlocal void ShowSortedHostList(HostList *list, int max)
360 {
361 HostEntry *e, *end = &list->hosts[(max < list->num) ? max : list->num];
362 qsort(list->hosts, list->num, sizeof(HostEntry), CompareHosts);
363 if (list->num) mprintf("\n%-25s%s%s\n", "Source Address", OPBanner, " Pkts Query LegacyQ Response");
364 for (e = &list->hosts[0]; e < end; e++)
365 {
366 int len = mprintf("%#-25a", &e->addr);
367 if (len > 25) mprintf("\n%25s", "");
368 mprintf("%8lu %8lu %8lu %8lu %8lu %8lu %8lu", e->totalops,
369 e->stat[OP_probe], e->stat[OP_goodbye],
370 e->stat[OP_browseq], e->stat[OP_browsea],
371 e->stat[OP_resolveq], e->stat[OP_resolvea]);
372 mprintf(" %8lu %8lu %8lu %8lu",
373 HostEntryTotalPackets(e), e->pkts[HostPkt_Q], e->pkts[HostPkt_L], e->pkts[HostPkt_R]);
374 if (e->pkts[HostPkt_B]) mprintf("Bad: %8lu", e->pkts[HostPkt_B]);
375 mprintf("\n");
376 if (!e->HISoftware.c[0] && e->NumQueries > 2)
377 mDNSPlatformMemCopy(&e->HISoftware, "\x27*** Unknown (Jaguar, Windows, etc.) ***", 0x28);
378 if (e->hostname.c[0] || e->HIHardware.c[0] || e->HISoftware.c[0])
379 mprintf("%##-45s %#-14s %#s\n", e->hostname.c, e->HIHardware.c, e->HISoftware.c);
380 }
381 }
382
383 //*************************************************************************************************************
384 // Receive and process packets
385
386 mDNSexport mDNSBool ExtractServiceType(const domainname *const fqdn, domainname *const srvtype)
387 {
388 int i, len;
389 const mDNSu8 *src = fqdn->c;
390 mDNSu8 *dst = srvtype->c;
391
392 len = *src;
393 if (len == 0 || len >= 0x40) return(mDNSfalse);
394 if (src[1] != '_') src += 1 + len;
395
396 len = *src;
397 if (len == 0 || len >= 0x40 || src[1] != '_') return(mDNSfalse);
398 for (i=0; i<=len; i++) *dst++ = *src++;
399
400 len = *src;
401 if (len == 0 || len >= 0x40 || src[1] != '_') return(mDNSfalse);
402 for (i=0; i<=len; i++) *dst++ = *src++;
403
404 *dst++ = 0; // Put the null root label on the end of the service type
405
406 return(mDNStrue);
407 }
408
409 mDNSlocal void recordstat(HostEntry *entry, const domainname *fqdn, int op, mDNSu16 rrtype)
410 {
411 ActivityStat **s = &stats;
412 domainname srvtype;
413
414 if (op != OP_probe)
415 {
416 if (rrtype == kDNSType_SRV || rrtype == kDNSType_TXT) op = op - OP_browsegroup + OP_resolvegroup;
417 else if (rrtype != kDNSType_PTR) return;
418 }
419
420 if (!ExtractServiceType(fqdn, &srvtype)) return;
421
422 while (*s && !SameDomainName(&(*s)->srvtype, &srvtype)) s = &(*s)->next;
423 if (!*s)
424 {
425 int i;
426 *s = malloc(sizeof(ActivityStat));
427 if (!*s) exit(-1);
428 (*s)->next = NULL;
429 (*s)->srvtype = srvtype;
430 (*s)->printed = 0;
431 (*s)->totalops = 0;
432 for (i=0; i<OP_NumTypes; i++) (*s)->stat[i] = 0;
433 }
434
435 (*s)->totalops++;
436 (*s)->stat[op]++;
437 if (entry)
438 {
439 entry->totalops++;
440 entry->stat[op]++;
441 }
442 }
443
444 mDNSlocal void printstats(int max)
445 {
446 int i;
447 if (!stats) return;
448 for (i=0; i<max; i++)
449 {
450 int max = 0;
451 ActivityStat *s, *m = NULL;
452 for (s = stats; s; s=s->next)
453 if (!s->printed && max < s->totalops)
454 { m = s; max = s->totalops; }
455 if (!m) return;
456 m->printed = mDNStrue;
457 if (i==0) mprintf("%-25s%s\n", "Service Type", OPBanner);
458 mprintf("%##-25s%8d %8d %8d %8d %8d %8d %8d\n", m->srvtype.c, m->totalops, m->stat[OP_probe],
459 m->stat[OP_goodbye], m->stat[OP_browseq], m->stat[OP_browsea], m->stat[OP_resolveq], m->stat[OP_resolvea]);
460 }
461 }
462
463 mDNSlocal const mDNSu8 *FindUpdate(mDNS *const m, const DNSMessage *const query, const mDNSu8 *ptr, const mDNSu8 *const end,
464 DNSQuestion *q, LargeCacheRecord *pkt)
465 {
466 int i;
467 for (i = 0; i < query->h.numAuthorities; i++)
468 {
469 const mDNSu8 *p2 = ptr;
470 ptr = GetLargeResourceRecord(m, query, ptr, end, q->InterfaceID, kDNSRecordTypePacketAuth, pkt);
471 if (!ptr) break;
472 if (m->rec.r.resrec.RecordType != kDNSRecordTypePacketNegative && ResourceRecordAnswersQuestion(&pkt->r.resrec, q)) return(p2);
473 }
474 return(mDNSNULL);
475 }
476
477 mDNSlocal void DisplayPacketHeader(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID)
478 {
479 const char *const ptype = (msg->h.flags.b[0] & kDNSFlag0_QR_Response) ? "-R- " :
480 (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger) ? "-Q- " : "-LQ-";
481
482 struct timeval tv;
483 struct tm tm;
484 const mDNSu32 index = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNSfalse);
485 char if_name[IFNAMSIZ]; // Older Linux distributions don't define IF_NAMESIZE
486 if_indextoname(index, if_name);
487 gettimeofday(&tv, NULL);
488 localtime_r((time_t*)&tv.tv_sec, &tm);
489 mprintf("\n%d:%02d:%02d.%06d Interface %d/%s\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec, index, if_name);
490
491 mprintf("%#-16a %s Q:%3d Ans:%3d Auth:%3d Add:%3d Size:%5d bytes",
492 srcaddr, ptype, msg->h.numQuestions, msg->h.numAnswers, msg->h.numAuthorities, msg->h.numAdditionals, end - (mDNSu8 *)msg);
493
494 if (msg->h.id.NotAnInteger) mprintf(" ID:%u", mDNSVal16(msg->h.id));
495
496 if (!mDNSAddrIsDNSMulticast(dstaddr)) mprintf(" To: %#a", dstaddr);
497
498 if (msg->h.flags.b[0] & kDNSFlag0_TC)
499 {
500 if (msg->h.flags.b[0] & kDNSFlag0_QR_Response) mprintf(" Truncated");
501 else mprintf(" Truncated (KA list continues in next packet)");
502 }
503 mprintf("\n");
504 }
505
506 mDNSlocal void DisplayResourceRecord(const mDNSAddr *const srcaddr, const char *const op, const ResourceRecord *const pktrr)
507 {
508 static const char hexchars[16] = "0123456789ABCDEF";
509 #define MaxWidth 132
510 char buffer[MaxWidth+8];
511 char *p = buffer;
512
513 RDataBody *rd = &pktrr->rdata->u;
514 mDNSu8 *rdend = (mDNSu8 *)rd + pktrr->rdlength;
515 int n = mprintf("%#-16a %-5s %-5s%5lu %##s -> ", srcaddr, op, DNSTypeName(pktrr->rrtype), pktrr->rroriginalttl, pktrr->name->c);
516
517 if (pktrr->RecordType == kDNSRecordTypePacketNegative) { mprintf("**** ERROR: FAILED TO READ RDATA ****\n"); return; }
518
519 // The kDNSType_OPT case below just calls GetRRDisplayString_rdb
520 // Perhaps more (or all?) of the cases should do that?
521 switch(pktrr->rrtype)
522 {
523 case kDNSType_A: n += mprintf("%.4a", &rd->ipv4); break;
524 case kDNSType_PTR: n += mprintf("%##.*s", MaxWidth - n, rd->name.c); break;
525 case kDNSType_HINFO:// same as kDNSType_TXT below
526 case kDNSType_TXT: {
527 mDNSu8 *t = rd->txt.c;
528 while (t < rdend && t[0] && p < buffer+MaxWidth)
529 {
530 int i;
531 for (i=1; i<=t[0] && p < buffer+MaxWidth; i++)
532 {
533 if (t[i] == '\\') *p++ = '\\';
534 if (t[i] >= ' ') *p++ = t[i];
535 else
536 {
537 *p++ = '\\';
538 *p++ = '0';
539 *p++ = 'x';
540 *p++ = hexchars[t[i] >> 4];
541 *p++ = hexchars[t[i] & 0xF];
542 }
543 }
544 t += 1+t[0];
545 if (t < rdend && t[0]) { *p++ = '\\'; *p++ = ' '; }
546 }
547 *p++ = 0;
548 n += mprintf("%.*s", MaxWidth - n, buffer);
549 } break;
550 case kDNSType_AAAA: n += mprintf("%.16a", &rd->ipv6); break;
551 case kDNSType_SRV: n += mprintf("%##s:%d", rd->srv.target.c, mDNSVal16(rd->srv.port)); break;
552 case kDNSType_OPT: {
553 char b[MaxMsg];
554 // Quick hack: we don't want the prefix that GetRRDisplayString_rdb puts at the start of its
555 // string, because it duplicates the name and rrtype we already display, so we compute the
556 // length of that prefix and strip that many bytes off the beginning of the string we display.
557 mDNSu32 striplen = mDNS_snprintf(b, MaxMsg-1, "%4d %##s %s ", pktrr->rdlength, pktrr->name->c, DNSTypeName(pktrr->rrtype));
558 GetRRDisplayString_rdb(pktrr, &pktrr->rdata->u, b);
559 n += mprintf("%.*s", MaxWidth - n, b + striplen);
560 } break;
561 case kDNSType_NSEC: {
562 int i;
563 for (i=0; i<255; i++)
564 if (rd->nsec.bitmap[i>>3] & (128 >> (i&7)))
565 n += mprintf("%s ", DNSTypeName(i));
566 } break;
567 default: {
568 mDNSu8 *s = rd->data;
569 while (s < rdend && p < buffer+MaxWidth)
570 {
571 if (*s == '\\') *p++ = '\\';
572 if (*s >= ' ') *p++ = *s;
573 else
574 {
575 *p++ = '\\';
576 *p++ = '0';
577 *p++ = 'x';
578 *p++ = hexchars[*s >> 4];
579 *p++ = hexchars[*s & 0xF];
580 }
581 s++;
582 }
583 *p++ = 0;
584 n += mprintf("%.*s", MaxWidth - n, buffer);
585 } break;
586 }
587
588 mprintf("\n");
589 }
590
591 mDNSlocal void HexDump(const mDNSu8 *ptr, const mDNSu8 *const end)
592 {
593 while (ptr < end)
594 {
595 int i;
596 for (i=0; i<16; i++)
597 if (&ptr[i] < end) mprintf("%02X ", ptr[i]);
598 else mprintf(" ");
599 for (i=0; i<16; i++)
600 if (&ptr[i] < end) mprintf("%c", ptr[i] <= ' ' || ptr[i] >= 126 ? '.' : ptr[i]);
601 ptr += 16;
602 mprintf("\n");
603 }
604 }
605
606 mDNSlocal void DisplayError(const mDNSAddr *srcaddr, const mDNSu8 *ptr, const mDNSu8 *const end, char *msg)
607 {
608 mprintf("%#-16a **** ERROR: FAILED TO READ %s **** \n", srcaddr, msg);
609 HexDump(ptr, end);
610 }
611
612 mDNSlocal void DisplayQuery(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end,
613 const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID)
614 {
615 int i;
616 const mDNSu8 *ptr = msg->data;
617 const mDNSu8 *auth = LocateAuthorities(msg, end);
618 mDNSBool MQ = (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger);
619 HostEntry *entry = GotPacketFromHost(srcaddr, MQ ? HostPkt_Q : HostPkt_L, msg->h.id);
620 LargeCacheRecord pkt;
621
622 DisplayPacketHeader(m, msg, end, srcaddr, srcport, dstaddr, InterfaceID);
623 if (msg->h.id.NotAnInteger != 0xFFFF)
624 {
625 if (MQ) NumPktQ++; else NumPktL++;
626 }
627
628 for (i=0; i<msg->h.numQuestions; i++)
629 {
630 DNSQuestion q;
631 mDNSu8 *p2 = (mDNSu8 *)getQuestion(msg, ptr, end, InterfaceID, &q);
632 mDNSu16 ucbit = q.qclass & kDNSQClass_UnicastResponse;
633 q.qclass &= ~kDNSQClass_UnicastResponse;
634 if (!p2) { DisplayError(srcaddr, ptr, end, "QUESTION"); return; }
635 ptr = p2;
636 p2 = (mDNSu8 *)FindUpdate(m, msg, auth, end, &q, &pkt);
637 if (p2)
638 {
639 NumProbes++;
640 DisplayResourceRecord(srcaddr, ucbit ? "(PU)" : "(PM)", &pkt.r.resrec);
641 recordstat(entry, &q.qname, OP_probe, q.qtype);
642 p2 = (mDNSu8 *)skipDomainName(msg, p2, end);
643 // Having displayed this update record, clear type and class so we don't display the same one again.
644 p2[0] = p2[1] = p2[2] = p2[3] = 0;
645 }
646 else
647 {
648 const char *ptype = ucbit ? "(QU)" : "(QM)";
649 if (srcport.NotAnInteger == MulticastDNSPort.NotAnInteger) NumQuestions++;
650 else { NumLegacy++; ptype = "(LQ)"; }
651 mprintf("%#-16a %-5s %-5s %##s\n", srcaddr, ptype, DNSTypeName(q.qtype), q.qname.c);
652 if (msg->h.id.NotAnInteger != 0xFFFF) recordstat(entry, &q.qname, OP_query, q.qtype);
653 }
654 }
655
656 for (i=0; i<msg->h.numAnswers; i++)
657 {
658 const mDNSu8 *ep = ptr;
659 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt);
660 if (!ptr) { DisplayError(srcaddr, ep, end, "KNOWN ANSWER"); return; }
661 DisplayResourceRecord(srcaddr, "(KA)", &pkt.r.resrec);
662
663 // In the case of queries with long multi-packet KA lists, we count each subsequent KA packet
664 // the same as a single query, to more accurately reflect the burden on the network
665 // (A query with a six-packet KA list is *at least* six times the burden on the network as a single-packet query.)
666 if (msg->h.numQuestions == 0 && i == 0)
667 recordstat(entry, pkt.r.resrec.name, OP_query, pkt.r.resrec.rrtype);
668 }
669
670 for (i=0; i<msg->h.numAuthorities; i++)
671 {
672 const mDNSu8 *ep = ptr;
673 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &pkt);
674 if (!ptr) { DisplayError(srcaddr, ep, end, "AUTHORITY"); return; }
675 // After we display an Update record with its matching question (above) we zero out its type and class
676 // If any remain that haven't been zero'd out, display them here
677 if (pkt.r.resrec.rrtype || pkt.r.resrec.rrclass) DisplayResourceRecord(srcaddr, "(AU)", &pkt.r.resrec);
678 }
679
680 for (i=0; i<msg->h.numAdditionals; i++)
681 {
682 const mDNSu8 *ep = ptr;
683 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &pkt);
684 if (!ptr) { DisplayError(srcaddr, ep, end, "ADDITIONAL"); return; }
685 DisplayResourceRecord(srcaddr, pkt.r.resrec.rrtype == kDNSType_OPT ? "(OP)" : "(AD)", &pkt.r.resrec);
686 }
687
688 if (entry) AnalyseHost(m, entry, InterfaceID);
689 }
690
691 mDNSlocal void DisplayResponse(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *end,
692 const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSInterfaceID InterfaceID)
693 {
694 int i;
695 const mDNSu8 *ptr = msg->data;
696 HostEntry *entry = GotPacketFromHost(srcaddr, HostPkt_R, msg->h.id);
697 LargeCacheRecord pkt;
698
699 DisplayPacketHeader(m, msg, end, srcaddr, srcport, dstaddr, InterfaceID);
700 if (msg->h.id.NotAnInteger != 0xFFFF) NumPktR++;
701
702 for (i=0; i<msg->h.numQuestions; i++)
703 {
704 DNSQuestion q;
705 const mDNSu8 *ep = ptr;
706 ptr = getQuestion(msg, ptr, end, InterfaceID, &q);
707 if (!ptr) { DisplayError(srcaddr, ep, end, "QUESTION"); return; }
708 if (mDNSAddrIsDNSMulticast(dstaddr))
709 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE Q IN mDNS RESPONSE **** %-5s %##s\n", srcaddr, DNSTypeName(q.qtype), q.qname.c);
710 else
711 mprintf("%#-16a (Q) %-5s %##s\n", srcaddr, DNSTypeName(q.qtype), q.qname.c);
712 }
713
714 for (i=0; i<msg->h.numAnswers; i++)
715 {
716 const mDNSu8 *ep = ptr;
717 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt);
718 if (!ptr) { DisplayError(srcaddr, ep, end, "ANSWER"); return; }
719 if (pkt.r.resrec.rroriginalttl)
720 {
721 NumAnswers++;
722 DisplayResourceRecord(srcaddr, (pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? "(AN)" : "(AN+)", &pkt.r.resrec);
723 if (msg->h.id.NotAnInteger != 0xFFFF) recordstat(entry, pkt.r.resrec.name, OP_answer, pkt.r.resrec.rrtype);
724 if (entry) RecordHostInfo(entry, &pkt.r.resrec);
725 }
726 else
727 {
728 NumGoodbyes++;
729 DisplayResourceRecord(srcaddr, "(DE)", &pkt.r.resrec);
730 recordstat(entry, pkt.r.resrec.name, OP_goodbye, pkt.r.resrec.rrtype);
731 }
732 }
733
734 for (i=0; i<msg->h.numAuthorities; i++)
735 {
736 const mDNSu8 *ep = ptr;
737 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &pkt);
738 if (!ptr) { DisplayError(srcaddr, ep, end, "AUTHORITY"); return; }
739 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE AUTHORITY IN mDNS RESPONSE **** %-5s %##s\n",
740 srcaddr, DNSTypeName(pkt.r.resrec.rrtype), pkt.r.resrec.name->c);
741 }
742
743 for (i=0; i<msg->h.numAdditionals; i++)
744 {
745 const mDNSu8 *ep = ptr;
746 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &pkt);
747 if (!ptr) { DisplayError(srcaddr, ep, end, "ADDITIONAL"); return; }
748 NumAdditionals++;
749 DisplayResourceRecord(srcaddr,
750 pkt.r.resrec.rrtype == kDNSType_OPT ? "(OP)" : (pkt.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? "(AD)" : "(AD+)",
751 &pkt.r.resrec);
752 if (entry) RecordHostInfo(entry, &pkt.r.resrec);
753 }
754
755 if (entry) AnalyseHost(m, entry, InterfaceID);
756 }
757
758 mDNSlocal void ProcessUnicastResponse(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *end, const mDNSAddr *srcaddr, const mDNSInterfaceID InterfaceID)
759 {
760 int i;
761 const mDNSu8 *ptr = LocateAnswers(msg, end);
762 HostEntry *entry = GotPacketFromHost(srcaddr, HostPkt_R, msg->h.id);
763 //mprintf("%#a R\n", srcaddr);
764
765 for (i=0; i<msg->h.numAnswers + msg->h.numAuthorities + msg->h.numAdditionals; i++)
766 {
767 LargeCacheRecord pkt;
768 ptr = GetLargeResourceRecord(m, msg, ptr, end, InterfaceID, kDNSRecordTypePacketAns, &pkt);
769 if (ptr && pkt.r.resrec.rroriginalttl && entry) RecordHostInfo(entry, &pkt.r.resrec);
770 }
771 }
772
773 mDNSlocal mDNSBool AddressMatchesFilterList(const mDNSAddr *srcaddr)
774 {
775 FilterList *f;
776 if (!Filters) return(srcaddr->type == mDNSAddrType_IPv4);
777 for (f=Filters; f; f=f->next) if (mDNSSameAddress(srcaddr, &f->FilterAddr)) return(mDNStrue);
778 return(mDNSfalse);
779 }
780
781 mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
782 const mDNSAddr *srcaddr, mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport, const mDNSInterfaceID InterfaceID)
783 {
784 const mDNSu8 StdQ = kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery;
785 const mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery;
786 const mDNSu8 QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask);
787 mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions;
788 int goodinterface = (FilterInterface == 0);
789
790 (void)dstaddr; // Unused
791 (void)dstport; // Unused
792
793 // Read the integer parts which are in IETF byte-order (MSB first, LSB second)
794 msg->h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]);
795 msg->h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]);
796 msg->h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]);
797 msg->h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]);
798
799 // For now we're only interested in monitoring IPv4 traffic.
800 // All IPv6 packets should just be duplicates of the v4 packets.
801 if (!goodinterface) goodinterface = (FilterInterface == (int)mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNSfalse));
802 if (goodinterface && AddressMatchesFilterList(srcaddr))
803 {
804 mDNS_Lock(m);
805 if (!mDNSAddrIsDNSMulticast(dstaddr))
806 {
807 if (QR_OP == StdQ) mprintf("Unicast query from %#a\n", srcaddr);
808 else if (QR_OP == StdR) ProcessUnicastResponse(m, msg, end, srcaddr, InterfaceID);
809 }
810 else
811 {
812 if (QR_OP == StdQ) DisplayQuery (m, msg, end, srcaddr, srcport, dstaddr, InterfaceID);
813 else if (QR_OP == StdR) DisplayResponse (m, msg, end, srcaddr, srcport, dstaddr, InterfaceID);
814 else
815 {
816 debugf("Unknown DNS packet type %02X%02X (ignored)", msg->h.flags.b[0], msg->h.flags.b[1]);
817 GotPacketFromHost(srcaddr, HostPkt_B, msg->h.id);
818 NumPktB++;
819 }
820 }
821 mDNS_Unlock(m);
822 }
823 }
824
825 mDNSlocal mStatus mDNSNetMonitor(void)
826 {
827 struct tm tm;
828 int h, m, s, mul, div, TotPkt;
829 #if !defined(WIN32)
830 sigset_t signals;
831 #endif
832
833 mStatus status = mDNS_Init(&mDNSStorage, &PlatformStorage,
834 mDNS_Init_NoCache, mDNS_Init_ZeroCacheSize,
835 mDNS_Init_DontAdvertiseLocalAddresses,
836 mDNS_Init_NoInitCallback, mDNS_Init_NoInitCallbackContext);
837 if (status) return(status);
838
839 gettimeofday(&tv_start, NULL);
840
841 #if defined( WIN32 )
842 status = SetupInterfaceList(&mDNSStorage);
843 if (status) return(status);
844 gStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
845 if (gStopEvent == INVALID_HANDLE_VALUE) return mStatus_UnknownErr;
846 mDNSPollRegisterEvent( gStopEvent, StopNotification, NULL );
847 if (!SetConsoleCtrlHandler(ConsoleControlHandler, TRUE)) return mStatus_UnknownErr;
848 gRunning = mDNStrue; while (gRunning) mDNSPoll( INFINITE );
849 if (!SetConsoleCtrlHandler(ConsoleControlHandler, FALSE)) return mStatus_UnknownErr;
850 CloseHandle(gStopEvent);
851 #else
852 mDNSPosixListenForSignalInEventLoop(SIGINT);
853 mDNSPosixListenForSignalInEventLoop(SIGTERM);
854
855 do
856 {
857 struct timeval timeout = { 0x3FFFFFFF, 0 }; // wait until SIGINT or SIGTERM
858 mDNSBool gotSomething;
859 mDNSPosixRunEventLoopOnce(&mDNSStorage, &timeout, &signals, &gotSomething);
860 }
861 while ( !( sigismember( &signals, SIGINT) || sigismember( &signals, SIGTERM)));
862 #endif
863
864 // Now display final summary
865 TotPkt = NumPktQ + NumPktL + NumPktR;
866 gettimeofday(&tv_end, NULL);
867 tv_interval = tv_end;
868 if (tv_start.tv_usec > tv_interval.tv_usec)
869 { tv_interval.tv_usec += 1000000; tv_interval.tv_sec--; }
870 tv_interval.tv_sec -= tv_start.tv_sec;
871 tv_interval.tv_usec -= tv_start.tv_usec;
872 h = (tv_interval.tv_sec / 3600);
873 m = (tv_interval.tv_sec % 3600) / 60;
874 s = (tv_interval.tv_sec % 60);
875 if (tv_interval.tv_sec > 10)
876 {
877 mul = 60;
878 div = tv_interval.tv_sec;
879 }
880 else
881 {
882 mul = 60000;
883 div = tv_interval.tv_sec * 1000 + tv_interval.tv_usec / 1000;
884 if (div == 0) div=1;
885 }
886
887 mprintf("\n\n");
888 localtime_r((time_t*)&tv_start.tv_sec, &tm);
889 mprintf("Started %3d:%02d:%02d.%06d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv_start.tv_usec);
890 localtime_r((time_t*)&tv_end.tv_sec, &tm);
891 mprintf("End %3d:%02d:%02d.%06d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tv_end.tv_usec);
892 mprintf("Captured for %3d:%02d:%02d.%06d\n", h, m, s, tv_interval.tv_usec);
893 if (!Filters)
894 {
895 mprintf("Unique source addresses seen on network:");
896 if (IPv4HostList.num) mprintf(" %ld (IPv4)", IPv4HostList.num);
897 if (IPv6HostList.num) mprintf(" %ld (IPv6)", IPv6HostList.num);
898 if (!IPv4HostList.num && !IPv6HostList.num) mprintf(" None");
899 mprintf("\n");
900 }
901 mprintf("\n");
902 mprintf("Modern Query Packets: %7d (avg%5d/min)\n", NumPktQ, NumPktQ * mul / div);
903 mprintf("Legacy Query Packets: %7d (avg%5d/min)\n", NumPktL, NumPktL * mul / div);
904 mprintf("Multicast Response Packets: %7d (avg%5d/min)\n", NumPktR, NumPktR * mul / div);
905 mprintf("Total Multicast Packets: %7d (avg%5d/min)\n", TotPkt, TotPkt * mul / div);
906 mprintf("\n");
907 mprintf("Total New Service Probes: %7d (avg%5d/min)\n", NumProbes, NumProbes * mul / div);
908 mprintf("Total Goodbye Announcements: %7d (avg%5d/min)\n", NumGoodbyes, NumGoodbyes * mul / div);
909 mprintf("Total Query Questions: %7d (avg%5d/min)\n", NumQuestions, NumQuestions * mul / div);
910 mprintf("Total Queries from Legacy Clients:%7d (avg%5d/min)\n", NumLegacy, NumLegacy * mul / div);
911 mprintf("Total Answers/Announcements: %7d (avg%5d/min)\n", NumAnswers, NumAnswers * mul / div);
912 mprintf("Total Additional Records: %7d (avg%5d/min)\n", NumAdditionals, NumAdditionals * mul / div);
913 mprintf("\n");
914 printstats(kReportTopServices);
915
916 if (!ExactlyOneFilter)
917 {
918 ShowSortedHostList(&IPv4HostList, kReportTopHosts);
919 ShowSortedHostList(&IPv6HostList, kReportTopHosts);
920 }
921
922 mDNS_Close(&mDNSStorage);
923 return(0);
924 }
925
926 mDNSexport int main(int argc, char **argv)
927 {
928 const char *progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0];
929 int i;
930 mStatus status;
931
932 #if defined(WIN32)
933 HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
934 #endif
935
936 setlinebuf(stdout); // Want to see lines as they appear, not block buffered
937
938 for (i=1; i<argc; i++)
939 {
940 if (i+1 < argc && !strcmp(argv[i], "-i") && atoi(argv[i+1]))
941 {
942 FilterInterface = atoi(argv[i+1]);
943 i += 2;
944 printf("Monitoring interface %d\n", FilterInterface);
945 }
946 else
947 {
948 struct in_addr s4;
949 struct in6_addr s6;
950 FilterList *f;
951 mDNSAddr a;
952 a.type = mDNSAddrType_IPv4;
953
954 if (inet_pton(AF_INET, argv[i], &s4) == 1)
955 a.ip.v4.NotAnInteger = s4.s_addr;
956 else if (inet_pton(AF_INET6, argv[i], &s6) == 1)
957 {
958 a.type = mDNSAddrType_IPv6;
959 mDNSPlatformMemCopy(&a.ip.v6, &s6, sizeof(a.ip.v6));
960 }
961 else
962 {
963 struct hostent *h = gethostbyname(argv[i]);
964 if (h) a.ip.v4.NotAnInteger = *(long*)h->h_addr;
965 else goto usage;
966 }
967
968 f = malloc(sizeof(*f));
969 f->FilterAddr = a;
970 f->next = Filters;
971 Filters = f;
972 }
973 }
974
975 status = mDNSNetMonitor();
976 if (status) { fprintf(stderr, "%s: mDNSNetMonitor failed %d\n", progname, (int)status); return(status); }
977 return(0);
978
979 usage:
980 fprintf(stderr, "\nmDNS traffic monitor\n");
981 fprintf(stderr, "Usage: %s [-i index] [host]\n", progname);
982 fprintf(stderr, "Optional [-i index] parameter displays only packets from that interface index\n");
983 fprintf(stderr, "Optional [host] parameter displays only packets from that host\n");
984
985 fprintf(stderr, "\nPer-packet header output:\n");
986 fprintf(stderr, "-Q- Multicast Query from mDNS client that accepts multicast responses\n");
987 fprintf(stderr, "-R- Multicast Response packet containing answers/announcements\n");
988 fprintf(stderr, "-LQ- Multicast Query from legacy client that does *not* listen for multicast responses\n");
989 fprintf(stderr, "Q/Ans/Auth/Add Number of questions, answers, authority records and additional records in packet\n");
990
991 fprintf(stderr, "\nPer-record display:\n");
992 fprintf(stderr, "(PM) Probe Question (new service starting), requesting multicast response\n");
993 fprintf(stderr, "(PU) Probe Question (new service starting), requesting unicast response\n");
994 fprintf(stderr, "(DE) Deletion/Goodbye (service going away)\n");
995 fprintf(stderr, "(LQ) Legacy Query Question\n");
996 fprintf(stderr, "(QM) Query Question, requesting multicast response\n");
997 fprintf(stderr, "(QU) Query Question, requesting unicast response\n");
998 fprintf(stderr, "(KA) Known Answer (information querier already knows)\n");
999 fprintf(stderr, "(AN) Unique Answer to question (or periodic announcment) (entire RR Set)\n");
1000 fprintf(stderr, "(AN+) Answer to question (or periodic announcment) (add to existing RR Set members)\n");
1001 fprintf(stderr, "(AD) Unique Additional Record Set (entire RR Set)\n");
1002 fprintf(stderr, "(AD+) Additional records (add to existing RR Set members)\n");
1003
1004 fprintf(stderr, "\nFinal summary, sorted by service type:\n");
1005 fprintf(stderr, "Probe Probes for this service type starting up\n");
1006 fprintf(stderr, "Goodbye Goodbye (deletion) packets for this service type shutting down\n");
1007 fprintf(stderr, "BrowseQ Browse questions from clients browsing to find a list of instances of this service\n");
1008 fprintf(stderr, "BrowseA Browse answers/announcments advertising instances of this service\n");
1009 fprintf(stderr, "ResolveQ Resolve questions from clients actively connecting to an instance of this service\n");
1010 fprintf(stderr, "ResolveA Resolve answers/announcments giving connection information for an instance of this service\n");
1011 fprintf(stderr, "\n");
1012 return(-1);
1013 }