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