1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 //*************************************************************************************************************
20 // Incorporate mDNS.c functionality
22 // We want to use much of the functionality provided by "mDNS.c",
23 // except we'll steal the packets that would be sent to normal mDNSCoreReceive() routine
24 #define mDNSCoreReceive __NOT__mDNSCoreReceive__NOT__
25 #include "../mDNSCore/mDNS.c"
26 #undef mDNSCoreReceive
28 //*************************************************************************************************************
31 #include <stdio.h> // For printf()
32 #include <stdlib.h> // For malloc()
33 #include <string.h> // For strrchr(), strcmp()
34 #include <time.h> // For "struct tm" etc.
35 #include <signal.h> // For SIGINT, SIGTERM
37 // Both mDNS.c and mDNSWin32.h declare UDPSocket_struct type resulting in a compile-time error, so
38 // trick the compiler when including mDNSWin32.h
39 # define UDPSocket_struct _UDPSocket_struct
40 # include <mDNSEmbeddedAPI.h>
41 # include <mDNSWin32.h>
42 # include <PosixCompat.h>
45 static HANDLE gStopEvent
= INVALID_HANDLE_VALUE
;
46 static mDNSBool gRunning
;
47 static void CALLBACK
StopNotification( HANDLE event
, void *context
) { gRunning
= mDNSfalse
; }
48 static BOOL WINAPI
ConsoleControlHandler( DWORD inControlEvent
) { SetEvent( gStopEvent
); return TRUE
; }
49 void setlinebuf( FILE * fp
) {}
51 # include <netdb.h> // For gethostbyname()
52 # include <sys/socket.h> // For AF_INET, AF_INET6, etc.
53 # include <net/if.h> // For IF_NAMESIZE
54 # include <netinet/in.h> // For INADDR_NONE
55 # include <arpa/inet.h> // For inet_addr()
56 # include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform
58 #include "ExampleClientApp.h"
60 //*************************************************************************************************************
61 // Types and structures
65 // Primitive operations
69 // These are meta-categories;
70 // Query and Answer operations are actually subdivided into two classes:
71 // Browse query/answer and
72 // Resolve query/answer
76 // The "Browse" variants of query/answer
81 // The "Resolve" variants of query/answer
89 typedef struct ActivityStat_struct ActivityStat
;
90 struct ActivityStat_struct
96 int stat
[OP_NumTypes
];
99 typedef struct FilterList_struct FilterList
;
100 struct FilterList_struct
106 //*************************************************************************************************************
109 #define kReportTopServices 15
110 #define kReportTopHosts 15
112 //*************************************************************************************************************
115 mDNS mDNSStorage
; // mDNS core uses this to store its globals
116 static mDNS_PlatformSupport PlatformStorage
; // Stores this platform's globals
117 mDNSexport
const char ProgramName
[] = "mDNSNetMonitor";
119 struct timeval tv_start
, tv_end
, tv_interval
;
120 static int FilterInterface
= 0;
121 static FilterList
*Filters
;
122 #define ExactlyOneFilter (Filters && !Filters->next)
123 static mDNSBool AddressType
= mDNSAddrType_IPv4
;
125 static int NumPktQ
, NumPktL
, NumPktR
, NumPktB
; // Query/Legacy/Response/Bad
126 static int NumProbes
, NumGoodbyes
, NumQuestions
, NumLegacy
, NumAnswers
, NumAdditionals
;
128 static ActivityStat
*stats
;
130 #define OPBanner "Total Ops Probe Goodbye BrowseQ BrowseA ResolveQ ResolveA"
132 mDNSexport
void mDNSCoreReceive(mDNS
*const m
, DNSMessage
*const msg
, const mDNSu8
*const end
, const mDNSAddr
*const srcaddr
, const mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, const mDNSIPPort dstport
, const mDNSInterfaceID InterfaceID
);
134 //*************************************************************************************************************
137 // Special version of printf that knows how to print IP addresses, DNS-format name strings, etc.
138 mDNSlocal mDNSu32
mprintf(const char *format
, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2);
139 mDNSlocal mDNSu32
mprintf(const char *format
, ...)
142 unsigned char buffer
[512];
144 va_start(ptr
,format
);
145 length
= mDNS_vsnprintf((char *)buffer
, sizeof(buffer
), format
, ptr
);
147 printf("%s", buffer
);
151 //*************************************************************************************************************
154 // Would benefit from a hash
158 HostPkt_Q
= 0, // Query
159 HostPkt_L
= 1, // Legacy Query
160 HostPkt_R
= 2, // Response
161 HostPkt_B
= 3, // Bad
168 unsigned long pkts
[HostPkt_NumTypes
];
169 unsigned long totalops
;
170 unsigned long stat
[OP_NumTypes
];
173 UTF8str255 HIHardware
;
174 UTF8str255 HISoftware
;
179 #define HostEntryTotalPackets(H) ((H)->pkts[HostPkt_Q] + (H)->pkts[HostPkt_L] + (H)->pkts[HostPkt_R] + (H)->pkts[HostPkt_B])
188 static HostList IPv4HostList
= { 0, 0, 0 };
189 static HostList IPv6HostList
= { 0, 0, 0 };
191 mDNSlocal HostEntry
*FindHost(const mDNSAddr
*addr
, HostList
*list
)
195 for (i
= 0; i
< list
->num
; i
++)
197 HostEntry
*entry
= list
->hosts
+ i
;
198 if (mDNSSameAddress(addr
, &entry
->addr
))
205 mDNSlocal HostEntry
*AddHost(const mDNSAddr
*addr
, HostList
*list
)
209 if (list
->num
>= list
->max
)
211 long newMax
= list
->max
+ 64;
212 HostEntry
*newHosts
= realloc(list
->hosts
, newMax
* sizeof(HostEntry
));
213 if (newHosts
== NULL
)
216 list
->hosts
= newHosts
;
219 entry
= list
->hosts
+ list
->num
++;
222 for (i
=0; i
<HostPkt_NumTypes
; i
++) entry
->pkts
[i
] = 0;
224 for (i
=0; i
<OP_NumTypes
; i
++) entry
->stat
[i
] = 0;
225 entry
->hostname
.c
[0] = 0;
226 entry
->revname
.c
[0] = 0;
227 entry
->HIHardware
.c
[0] = 0;
228 entry
->HISoftware
.c
[0] = 0;
229 entry
->NumQueries
= 0;
231 if (entry
->addr
.type
== mDNSAddrType_IPv4
)
233 mDNSv4Addr ip
= entry
->addr
.ip
.v4
;
235 // Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code
236 mDNS_snprintf(buffer
, sizeof(buffer
), "%d.%d.%d.%d.in-addr.arpa.", ip
.b
[3], ip
.b
[2], ip
.b
[1], ip
.b
[0]);
237 MakeDomainNameFromDNSNameString(&entry
->revname
, buffer
);
243 mDNSlocal HostEntry
*GotPacketFromHost(const mDNSAddr
*addr
, HostPkt_Type t
, mDNSOpaque16 id
)
245 if (ExactlyOneFilter
) return(NULL
);
248 HostList
*list
= (addr
->type
== mDNSAddrType_IPv4
) ? &IPv4HostList
: &IPv6HostList
;
249 HostEntry
*entry
= FindHost(addr
, list
);
250 if (!entry
) entry
= AddHost(addr
, list
);
251 if (!entry
) return(NULL
);
252 // Don't count our own interrogation packets
253 if (id
.NotAnInteger
!= 0xFFFF) entry
->pkts
[t
]++;
258 mDNSlocal
void RecordHostInfo(HostEntry
*entry
, const ResourceRecord
*const pktrr
)
260 if (!entry
->hostname
.c
[0])
262 if (pktrr
->rrtype
== kDNSType_A
|| pktrr
->rrtype
== kDNSType_AAAA
)
264 // Should really check that the rdata in the address record matches the source address of this packet
265 entry
->NumQueries
= 0;
266 AssignDomainName(&entry
->hostname
, pktrr
->name
);
269 if (pktrr
->rrtype
== kDNSType_PTR
)
270 if (SameDomainName(&entry
->revname
, pktrr
->name
))
272 entry
->NumQueries
= 0;
273 AssignDomainName(&entry
->hostname
, &pktrr
->rdata
->u
.name
);
276 else if (pktrr
->rrtype
== kDNSType_HINFO
)
278 RDataBody
*rd
= &pktrr
->rdata
->u
;
279 mDNSu8
*rdend
= (mDNSu8
*)rd
+ pktrr
->rdlength
;
280 mDNSu8
*hw
= rd
->txt
.c
;
281 mDNSu8
*sw
= hw
+ 1 + (mDNSu32
)hw
[0];
282 if (sw
+ 1 + sw
[0] <= rdend
)
284 AssignDomainName(&entry
->hostname
, pktrr
->name
);
285 mDNSPlatformMemCopy(entry
->HIHardware
.c
, hw
, 1 + (mDNSu32
)hw
[0]);
286 mDNSPlatformMemCopy(entry
->HISoftware
.c
, sw
, 1 + (mDNSu32
)sw
[0]);
291 mDNSlocal
void SendUnicastQuery(mDNS
*const m
, HostEntry
*entry
, domainname
*name
, mDNSu16 rrtype
, mDNSInterfaceID InterfaceID
)
293 const mDNSOpaque16 id
= { { 0xFF, 0xFF } };
295 mDNSu8
*qptr
= query
.data
;
296 const mDNSu8
*const limit
= query
.data
+ sizeof(query
.data
);
297 const mDNSAddr
*target
= &entry
->addr
;
298 InitializeDNSMessage(&query
.h
, id
, QueryFlags
);
299 qptr
= putQuestion(&query
, qptr
, limit
, name
, rrtype
, kDNSClass_IN
);
300 entry
->LastQuery
= m
->timenow
;
303 // Note: When there are multiple mDNSResponder agents running on a single machine
304 // (e.g. Apple mDNSResponder plus a SliMP3 server with embedded mDNSResponder)
305 // it is possible that unicast queries may not go to the primary system responder.
306 // We try the first query using unicast, but if that doesn't work we try again via multicast.
307 if (entry
->NumQueries
> 2)
309 target
= &AllDNSLinkGroup_v4
;
313 //mprintf("%#a Q\n", target);
314 InterfaceID
= mDNSInterface_Any
; // Send query from our unicast reply socket
317 mDNSSendDNSMessage(m
, &query
, qptr
, InterfaceID
, mDNSNULL
, target
, MulticastDNSPort
, mDNSNULL
, mDNSNULL
, mDNSfalse
);
320 mDNSlocal
void AnalyseHost(mDNS
*const m
, HostEntry
*entry
, const mDNSInterfaceID InterfaceID
)
322 // If we've done four queries without answer, give up
323 if (entry
->NumQueries
>= 4) return;
325 // If we've done a query in the last second, give the host a chance to reply before trying again
326 if (entry
->NumQueries
&& m
->timenow
- entry
->LastQuery
< mDNSPlatformOneSecond
) return;
328 // If we don't know the host name, try to find that first
329 if (!entry
->hostname
.c
[0])
331 if (entry
->revname
.c
[0])
333 SendUnicastQuery(m
, entry
, &entry
->revname
, kDNSType_PTR
, InterfaceID
);
334 //mprintf("%##s PTR %d\n", entry->revname.c, entry->NumQueries);
337 // If we have the host name but no HINFO, now ask for that
338 else if (!entry
->HIHardware
.c
[0])
340 SendUnicastQuery(m
, entry
, &entry
->hostname
, kDNSType_HINFO
, InterfaceID
);
341 //mprintf("%##s HINFO %d\n", entry->hostname.c, entry->NumQueries);
345 mDNSlocal
int CompareHosts(const void *p1
, const void *p2
)
347 return (int)(HostEntryTotalPackets((HostEntry
*)p2
) - HostEntryTotalPackets((HostEntry
*)p1
));
350 mDNSlocal
void ShowSortedHostList(HostList
*list
, int max
)
352 HostEntry
*e
, *end
= &list
->hosts
[(max
< list
->num
) ? max
: list
->num
];
353 qsort(list
->hosts
, list
->num
, sizeof(HostEntry
), CompareHosts
);
354 if (list
->num
) mprintf("\n%-25s%s%s\n", "Source Address", OPBanner
, " Pkts Query LegacyQ Response");
355 for (e
= &list
->hosts
[0]; e
< end
; e
++)
357 int len
= mprintf("%#-25a", &e
->addr
);
358 if (len
> 25) mprintf("\n%25s", "");
359 mprintf("%8lu %8lu %8lu %8lu %8lu %8lu %8lu", e
->totalops
,
360 e
->stat
[OP_probe
], e
->stat
[OP_goodbye
],
361 e
->stat
[OP_browseq
], e
->stat
[OP_browsea
],
362 e
->stat
[OP_resolveq
], e
->stat
[OP_resolvea
]);
363 mprintf(" %8lu %8lu %8lu %8lu",
364 HostEntryTotalPackets(e
), e
->pkts
[HostPkt_Q
], e
->pkts
[HostPkt_L
], e
->pkts
[HostPkt_R
]);
365 if (e
->pkts
[HostPkt_B
]) mprintf("Bad: %8lu", e
->pkts
[HostPkt_B
]);
367 if (!e
->HISoftware
.c
[0] && e
->NumQueries
> 2)
368 mDNSPlatformMemCopy(&e
->HISoftware
, "\x27*** Unknown (Jaguar, Windows, etc.) ***", 0x28);
369 if (e
->hostname
.c
[0] || e
->HIHardware
.c
[0] || e
->HISoftware
.c
[0])
370 mprintf("%##-45s %#-14s %#s\n", e
->hostname
.c
, e
->HIHardware
.c
, e
->HISoftware
.c
);
374 //*************************************************************************************************************
375 // Receive and process packets
377 mDNSlocal mDNSBool
ExtractServiceType(const domainname
*const fqdn
, domainname
*const srvtype
)
380 const mDNSu8
*src
= fqdn
->c
;
381 mDNSu8
*dst
= srvtype
->c
;
384 if (len
== 0 || len
>= 0x40) return(mDNSfalse
);
385 if (src
[1] != '_') src
+= 1 + len
;
388 if (len
== 0 || len
>= 0x40 || src
[1] != '_') return(mDNSfalse
);
389 for (i
=0; i
<=len
; i
++) *dst
++ = *src
++;
392 if (len
== 0 || len
>= 0x40 || src
[1] != '_') return(mDNSfalse
);
393 for (i
=0; i
<=len
; i
++) *dst
++ = *src
++;
395 *dst
++ = 0; // Put the null root label on the end of the service type
400 mDNSlocal
void recordstat(HostEntry
*entry
, const domainname
*fqdn
, int op
, mDNSu16 rrtype
)
402 ActivityStat
**s
= &stats
;
407 if (rrtype
== kDNSType_SRV
|| rrtype
== kDNSType_TXT
) op
= op
- OP_browsegroup
+ OP_resolvegroup
;
408 else if (rrtype
!= kDNSType_PTR
) return;
411 if (!ExtractServiceType(fqdn
, &srvtype
)) return;
413 while (*s
&& !SameDomainName(&(*s
)->srvtype
, &srvtype
)) s
= &(*s
)->next
;
417 *s
= malloc(sizeof(ActivityStat
));
420 (*s
)->srvtype
= srvtype
;
423 for (i
=0; i
<OP_NumTypes
; i
++) (*s
)->stat
[i
] = 0;
435 mDNSlocal
void printstats(int max
)
439 for (i
=0; i
<max
; i
++)
442 ActivityStat
*s
, *m
= NULL
;
443 for (s
= stats
; s
; s
=s
->next
)
444 if (!s
->printed
&& max_val
< s
->totalops
)
445 { m
= s
; max_val
= s
->totalops
; }
447 m
->printed
= mDNStrue
;
448 if (i
==0) mprintf("%-25s%s\n", "Service Type", OPBanner
);
449 mprintf("%##-25s%8d %8d %8d %8d %8d %8d %8d\n", m
->srvtype
.c
, m
->totalops
, m
->stat
[OP_probe
],
450 m
->stat
[OP_goodbye
], m
->stat
[OP_browseq
], m
->stat
[OP_browsea
], m
->stat
[OP_resolveq
], m
->stat
[OP_resolvea
]);
454 mDNSlocal
const mDNSu8
*FindUpdate(mDNS
*const m
, const DNSMessage
*const query
, const mDNSu8
*ptr
, const mDNSu8
*const end
,
455 DNSQuestion
*q
, LargeCacheRecord
*pkt
)
458 for (i
= 0; i
< query
->h
.numAuthorities
; i
++)
460 const mDNSu8
*p2
= ptr
;
461 ptr
= GetLargeResourceRecord(m
, query
, ptr
, end
, q
->InterfaceID
, kDNSRecordTypePacketAuth
, pkt
);
463 if (m
->rec
.r
.resrec
.RecordType
!= kDNSRecordTypePacketNegative
&& ResourceRecordAnswersQuestion(&pkt
->r
.resrec
, q
)) return(p2
);
468 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
)
470 const char *const ptype
= (msg
->h
.flags
.b
[0] & kDNSFlag0_QR_Response
) ? "-R- " :
471 (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
) ? "-Q- " : "-LQ-";
472 const unsigned length
= end
- (mDNSu8
*)msg
;
475 const mDNSu32 index
= mDNSPlatformInterfaceIndexfromInterfaceID(m
, InterfaceID
, mDNSfalse
);
476 char if_name
[IFNAMSIZ
]; // Older Linux distributions don't define IF_NAMESIZE
477 if_indextoname(index
, if_name
);
478 gettimeofday(&tv
, NULL
);
479 localtime_r((time_t*)&tv
.tv_sec
, &tm
);
480 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
);
482 mprintf("%#-16a %s Q:%3d Ans:%3d Auth:%3d Add:%3d Size:%5d bytes",
483 srcaddr
, ptype
, msg
->h
.numQuestions
, msg
->h
.numAnswers
, msg
->h
.numAuthorities
, msg
->h
.numAdditionals
, length
);
485 if (msg
->h
.id
.NotAnInteger
) mprintf(" ID:%u", mDNSVal16(msg
->h
.id
));
487 if (!mDNSAddrIsDNSMulticast(dstaddr
)) mprintf(" To: %#a", dstaddr
);
489 if (msg
->h
.flags
.b
[0] & kDNSFlag0_TC
)
491 if (msg
->h
.flags
.b
[0] & kDNSFlag0_QR_Response
) mprintf(" Truncated");
492 else mprintf(" Truncated (KA list continues in next packet)");
497 if (length
< sizeof(DNSMessageHeader
) + NormalMaxDNSMessageData
- 192)
498 if (msg
->h
.flags
.b
[0] & kDNSFlag0_TC
)
499 mprintf("%#-16a **** WARNING: Packet suspiciously small. Payload size (excluding IP and UDP headers)\n"
500 "%#-16a **** should usually be closer to %d bytes before truncation becomes necessary.\n",
501 srcaddr
, srcaddr
, sizeof(DNSMessageHeader
) + NormalMaxDNSMessageData
);
504 mDNSlocal
void DisplaySizeCheck(const DNSMessage
*const msg
, const mDNSu8
*const end
, const mDNSAddr
*srcaddr
, int num_opts
)
506 const unsigned length
= end
- (mDNSu8
*)msg
;
507 const int num_records
= msg
->h
.numAnswers
+ msg
->h
.numAuthorities
+ msg
->h
.numAdditionals
- num_opts
;
509 if (length
> sizeof(DNSMessageHeader
) + NormalMaxDNSMessageData
)
511 mprintf("%#-16a **** ERROR: Oversized packet with %d records.\n"
512 "%#-16a **** Many network devices cannot receive packets larger than %d bytes.\n"
513 "%#-16a **** To minimize interoperability failures, oversized packets MUST be limited to a single resource record.\n",
514 srcaddr
, num_records
, srcaddr
, 40 + 8 + sizeof(DNSMessageHeader
) + NormalMaxDNSMessageData
, srcaddr
);
517 mDNSlocal
void DisplayResourceRecord(const mDNSAddr
*const srcaddr
, const char *const op
, const ResourceRecord
*const pktrr
)
519 static const char hexchars
[16] = "0123456789ABCDEF";
521 char buffer
[MaxWidth
+8];
524 RDataBody
*rd
= &pktrr
->rdata
->u
;
525 mDNSu8
*rdend
= (mDNSu8
*)rd
+ pktrr
->rdlength
;
526 int n
= mprintf("%#-16a %-5s %-5s%5lu %##s -> ", srcaddr
, op
, DNSTypeName(pktrr
->rrtype
), pktrr
->rroriginalttl
, pktrr
->name
->c
);
528 if (pktrr
->RecordType
== kDNSRecordTypePacketNegative
) { mprintf("**** ERROR: FAILED TO READ RDATA ****\n"); return; }
530 // The kDNSType_OPT case below just calls GetRRDisplayString_rdb
531 // Perhaps more (or all?) of the cases should do that?
532 switch(pktrr
->rrtype
)
534 case kDNSType_A
: n
+= mprintf("%.4a", &rd
->ipv4
); break;
535 case kDNSType_PTR
: n
+= mprintf("%##.*s", MaxWidth
- n
, rd
->name
.c
); break;
536 case kDNSType_HINFO
: // same as kDNSType_TXT below
538 mDNSu8
*t
= rd
->txt
.c
;
539 while (t
< rdend
&& t
[0] && p
< buffer
+MaxWidth
)
542 for (i
=1; i
<=t
[0] && p
< buffer
+MaxWidth
; i
++)
544 if (t
[i
] == '\\') *p
++ = '\\';
545 if (t
[i
] >= ' ') *p
++ = t
[i
];
551 *p
++ = hexchars
[t
[i
] >> 4];
552 *p
++ = hexchars
[t
[i
] & 0xF];
556 if (t
< rdend
&& t
[0]) { *p
++ = '\\'; *p
++ = ' '; }
559 n
+= mprintf("%.*s", MaxWidth
- n
, buffer
);
561 case kDNSType_AAAA
: n
+= mprintf("%.16a", &rd
->ipv6
); break;
562 case kDNSType_SRV
: n
+= mprintf("%##s:%d", rd
->srv
.target
.c
, mDNSVal16(rd
->srv
.port
)); break;
565 // Quick hack: we don't want the prefix that GetRRDisplayString_rdb puts at the start of its
566 // string, because it duplicates the name and rrtype we already display, so we compute the
567 // length of that prefix and strip that many bytes off the beginning of the string we display.
568 mDNSu32 striplen
= mDNS_snprintf(b
, MaxMsg
-1, "%4d %##s %s ", pktrr
->rdlength
, pktrr
->name
->c
, DNSTypeName(pktrr
->rrtype
));
569 GetRRDisplayString_rdb(pktrr
, &pktrr
->rdata
->u
, b
);
570 n
+= mprintf("%.*s", MaxWidth
- n
, b
+ striplen
);
572 case kDNSType_NSEC
: {
574 // See the quick hack above
575 mDNSu32 striplen
= mDNS_snprintf(b
, MaxMsg
-1, "%4d %##s %s ", pktrr
->rdlength
, pktrr
->name
->c
, DNSTypeName(pktrr
->rrtype
));
576 GetRRDisplayString_rdb(pktrr
, &pktrr
->rdata
->u
, b
);
577 n
+= mprintf("%s", b
+striplen
);
580 mDNSu8
*s
= rd
->data
;
581 while (s
< rdend
&& p
< buffer
+MaxWidth
)
583 if (*s
== '\\') *p
++ = '\\';
584 if (*s
>= ' ') *p
++ = *s
;
590 *p
++ = hexchars
[*s
>> 4];
591 *p
++ = hexchars
[*s
& 0xF];
596 n
+= mprintf("%.*s", MaxWidth
- n
, buffer
);
603 mDNSlocal
void HexDump(const mDNSu8
*ptr
, const mDNSu8
*const end
)
609 if (&ptr
[i
] < end
) mprintf("%02X ", ptr
[i
]);
612 if (&ptr
[i
] < end
) mprintf("%c", ptr
[i
] <= ' ' || ptr
[i
] >= 126 ? '.' : ptr
[i
]);
618 mDNSlocal
void DisplayError(const mDNSAddr
*srcaddr
, const mDNSu8
*ptr
, const mDNSu8
*const end
, char *msg
)
620 mprintf("%#-16a **** ERROR: FAILED TO READ %s ****\n", srcaddr
, msg
);
624 mDNSlocal
void DisplayQuery(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*const end
,
625 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, const mDNSInterfaceID InterfaceID
)
629 const mDNSu8
*ptr
= msg
->data
;
630 const mDNSu8
*auth
= LocateAuthorities(msg
, end
);
631 mDNSBool MQ
= (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
);
632 HostEntry
*entry
= GotPacketFromHost(srcaddr
, MQ
? HostPkt_Q
: HostPkt_L
, msg
->h
.id
);
633 LargeCacheRecord pkt
;
635 DisplayPacketHeader(m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
);
636 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF)
638 if (MQ
) NumPktQ
++; else NumPktL
++;
641 for (i
=0; i
<msg
->h
.numQuestions
; i
++)
644 mDNSu8
*p2
= (mDNSu8
*)getQuestion(msg
, ptr
, end
, InterfaceID
, &q
);
645 mDNSu16 ucbit
= q
.qclass
& kDNSQClass_UnicastResponse
;
646 q
.qclass
&= ~kDNSQClass_UnicastResponse
;
647 if (!p2
) { DisplayError(srcaddr
, ptr
, end
, "QUESTION"); return; }
649 p2
= (mDNSu8
*)FindUpdate(m
, msg
, auth
, end
, &q
, &pkt
);
653 DisplayResourceRecord(srcaddr
, ucbit
? "(PU)" : "(PM)", &pkt
.r
.resrec
);
654 recordstat(entry
, &q
.qname
, OP_probe
, q
.qtype
);
655 p2
= (mDNSu8
*)skipDomainName(msg
, p2
, end
);
656 // Having displayed this update record, clear type and class so we don't display the same one again.
657 p2
[0] = p2
[1] = p2
[2] = p2
[3] = 0;
661 const char *ptype
= ucbit
? "(QU)" : "(QM)";
662 if (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
) NumQuestions
++;
663 else { NumLegacy
++; ptype
= "(LQ)"; }
664 mprintf("%#-16a %-5s %-5s %##s\n", srcaddr
, ptype
, DNSTypeName(q
.qtype
), q
.qname
.c
);
665 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) recordstat(entry
, &q
.qname
, OP_query
, q
.qtype
);
669 for (i
=0; i
<msg
->h
.numAnswers
; i
++)
671 const mDNSu8
*ep
= ptr
;
672 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAns
, &pkt
);
673 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "KNOWN ANSWER"); return; }
674 DisplayResourceRecord(srcaddr
, "(KA)", &pkt
.r
.resrec
);
675 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
)
676 { num_opts
++; mprintf("%#-16a **** ERROR: OPT RECORD IN ANSWER SECTION ****\n", srcaddr
); }
678 // In the case of queries with long multi-packet KA lists, we count each subsequent KA packet
679 // the same as a single query, to more accurately reflect the burden on the network
680 // (A query with a six-packet KA list is *at least* six times the burden on the network as a single-packet query.)
681 if (msg
->h
.numQuestions
== 0 && i
== 0)
682 recordstat(entry
, pkt
.r
.resrec
.name
, OP_query
, pkt
.r
.resrec
.rrtype
);
685 for (i
=0; i
<msg
->h
.numAuthorities
; i
++)
687 const mDNSu8
*ep
= ptr
;
688 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAuth
, &pkt
);
689 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "AUTHORITY"); return; }
690 // After we display an Update record with its matching question (above) we zero out its type and class
691 // If any remain that haven't been zero'd out, display them here
692 if (pkt
.r
.resrec
.rrtype
|| pkt
.r
.resrec
.rrclass
) DisplayResourceRecord(srcaddr
, "(AU)", &pkt
.r
.resrec
);
693 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
)
694 { num_opts
++; mprintf("%#-16a **** ERROR: OPT RECORD IN AUTHORITY SECTION ****\n", srcaddr
); }
697 for (i
=0; i
<msg
->h
.numAdditionals
; i
++)
699 const mDNSu8
*ep
= ptr
;
700 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAdd
, &pkt
);
701 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "ADDITIONAL"); return; }
702 DisplayResourceRecord(srcaddr
, pkt
.r
.resrec
.rrtype
== kDNSType_OPT
? "(OP)" : "(AD)", &pkt
.r
.resrec
);
703 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
) num_opts
++;
706 DisplaySizeCheck(msg
, end
, srcaddr
, num_opts
);
708 // We don't hexdump the DNSMessageHeader here because those six fields (id, flags, numQuestions, numAnswers, numAuthorities, numAdditionals)
709 // have already been swapped to host byte order and displayed, so including them in the hexdump is confusing
710 if (num_opts
> 1) { mprintf("%#-16a **** ERROR: MULTIPLE OPT RECORDS ****\n", srcaddr
); HexDump(msg
->data
, end
); }
712 if (entry
) AnalyseHost(m
, entry
, InterfaceID
);
715 mDNSlocal
void DisplayResponse(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*end
,
716 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, const mDNSInterfaceID InterfaceID
)
720 const mDNSu8
*ptr
= msg
->data
;
721 HostEntry
*entry
= GotPacketFromHost(srcaddr
, HostPkt_R
, msg
->h
.id
);
722 LargeCacheRecord pkt
;
724 DisplayPacketHeader(m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
);
725 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) NumPktR
++;
727 for (i
=0; i
<msg
->h
.numQuestions
; i
++)
730 const mDNSu8
*ep
= ptr
;
731 ptr
= getQuestion(msg
, ptr
, end
, InterfaceID
, &q
);
732 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "QUESTION"); return; }
733 if (mDNSAddrIsDNSMulticast(dstaddr
))
734 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE Q IN mDNS RESPONSE **** %-5s %##s\n", srcaddr
, DNSTypeName(q
.qtype
), q
.qname
.c
);
736 mprintf("%#-16a (Q) %-5s %##s\n", srcaddr
, DNSTypeName(q
.qtype
), q
.qname
.c
);
739 for (i
=0; i
<msg
->h
.numAnswers
; i
++)
741 const mDNSu8
*ep
= ptr
;
742 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAns
, &pkt
);
743 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "ANSWER"); return; }
744 if (pkt
.r
.resrec
.rroriginalttl
)
747 DisplayResourceRecord(srcaddr
, (pkt
.r
.resrec
.RecordType
& kDNSRecordTypePacketUniqueMask
) ? "(AN)" : "(AN+)", &pkt
.r
.resrec
);
748 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) recordstat(entry
, pkt
.r
.resrec
.name
, OP_answer
, pkt
.r
.resrec
.rrtype
);
749 if (entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
754 DisplayResourceRecord(srcaddr
, "(DE)", &pkt
.r
.resrec
);
755 recordstat(entry
, pkt
.r
.resrec
.name
, OP_goodbye
, pkt
.r
.resrec
.rrtype
);
757 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
)
758 { num_opts
++; mprintf("%#-16a **** ERROR: OPT RECORD IN ANSWER SECTION ****\n", srcaddr
); }
761 for (i
=0; i
<msg
->h
.numAuthorities
; i
++)
763 const mDNSu8
*ep
= ptr
;
764 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAuth
, &pkt
);
765 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "AUTHORITY"); return; }
766 DisplayResourceRecord(srcaddr
, "(AU)", &pkt
.r
.resrec
);
767 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
)
768 { num_opts
++; mprintf("%#-16a **** ERROR: OPT RECORD IN AUTHORITY SECTION ****\n", srcaddr
); }
769 else if (pkt
.r
.resrec
.rrtype
!= kDNSType_NSEC3
)
770 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE AUTHORITY IN mDNS RESPONSE **** %-5s %##s\n",
771 srcaddr
, DNSTypeName(pkt
.r
.resrec
.rrtype
), pkt
.r
.resrec
.name
->c
);
774 for (i
=0; i
<msg
->h
.numAdditionals
; i
++)
776 const mDNSu8
*ep
= ptr
;
777 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAdd
, &pkt
);
778 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "ADDITIONAL"); return; }
780 if (pkt
.r
.resrec
.rrtype
== kDNSType_OPT
) num_opts
++;
781 DisplayResourceRecord(srcaddr
,
782 pkt
.r
.resrec
.rrtype
== kDNSType_OPT
? "(OP)" : (pkt
.r
.resrec
.RecordType
& kDNSRecordTypePacketUniqueMask
) ? "(AD)" : "(AD+)",
784 if (entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
787 DisplaySizeCheck(msg
, end
, srcaddr
, num_opts
);
789 // We don't hexdump the DNSMessageHeader here because those six fields (id, flags, numQuestions, numAnswers, numAuthorities, numAdditionals)
790 // have already been swapped to host byte order and displayed, so including them in the hexdump is confusing
791 if (num_opts
> 1) { mprintf("%#-16a **** ERROR: MULTIPLE OPT RECORDS ****\n", srcaddr
); HexDump(msg
->data
, end
); }
793 if (entry
) AnalyseHost(m
, entry
, InterfaceID
);
796 mDNSlocal
void ProcessUnicastResponse(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*end
, const mDNSAddr
*srcaddr
, const mDNSInterfaceID InterfaceID
)
799 const mDNSu8
*ptr
= LocateAnswers(msg
, end
);
800 HostEntry
*entry
= GotPacketFromHost(srcaddr
, HostPkt_R
, msg
->h
.id
);
801 //mprintf("%#a R\n", srcaddr);
803 for (i
=0; i
<msg
->h
.numAnswers
+ msg
->h
.numAuthorities
+ msg
->h
.numAdditionals
; i
++)
805 LargeCacheRecord pkt
;
806 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, kDNSRecordTypePacketAns
, &pkt
);
807 if (ptr
&& pkt
.r
.resrec
.rroriginalttl
&& entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
811 mDNSlocal mDNSBool
AddressMatchesFilterList(const mDNSAddr
*srcaddr
)
814 if (!Filters
) return(srcaddr
->type
== AddressType
);
815 for (f
=Filters
; f
; f
=f
->next
) if (mDNSSameAddress(srcaddr
, &f
->FilterAddr
)) return(mDNStrue
);
819 mDNSexport
void mDNSCoreReceive(mDNS
*const m
, DNSMessage
*const msg
, const mDNSu8
*const end
,
820 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, mDNSIPPort dstport
, const mDNSInterfaceID InterfaceID
)
822 const mDNSu8 StdQ
= kDNSFlag0_QR_Query
| kDNSFlag0_OP_StdQuery
;
823 const mDNSu8 StdR
= kDNSFlag0_QR_Response
| kDNSFlag0_OP_StdQuery
;
824 const mDNSu8 QR_OP
= (mDNSu8
)(msg
->h
.flags
.b
[0] & kDNSFlag0_QROP_Mask
);
825 mDNSu8
*ptr
= (mDNSu8
*)&msg
->h
.numQuestions
;
826 int goodinterface
= (FilterInterface
== 0);
828 (void)dstaddr
; // Unused
829 (void)dstport
; // Unused
831 // Read the integer parts which are in IETF byte-order (MSB first, LSB second)
832 msg
->h
.numQuestions
= (mDNSu16
)((mDNSu16
)ptr
[0] << 8 | ptr
[1]);
833 msg
->h
.numAnswers
= (mDNSu16
)((mDNSu16
)ptr
[2] << 8 | ptr
[3]);
834 msg
->h
.numAuthorities
= (mDNSu16
)((mDNSu16
)ptr
[4] << 8 | ptr
[5]);
835 msg
->h
.numAdditionals
= (mDNSu16
)((mDNSu16
)ptr
[6] << 8 | ptr
[7]);
837 // For now we're only interested in monitoring IPv4 traffic.
838 // All IPv6 packets should just be duplicates of the v4 packets.
839 if (!goodinterface
) goodinterface
= (FilterInterface
== (int)mDNSPlatformInterfaceIndexfromInterfaceID(m
, InterfaceID
, mDNSfalse
));
840 if (goodinterface
&& AddressMatchesFilterList(srcaddr
))
843 if (!mDNSAddrIsDNSMulticast(dstaddr
))
845 if (QR_OP
== StdQ
) mprintf("Unicast query from %#a\n", srcaddr
);
846 else if (QR_OP
== StdR
) ProcessUnicastResponse(m
, msg
, end
, srcaddr
, InterfaceID
);
850 if (QR_OP
== StdQ
) DisplayQuery (m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
);
851 else if (QR_OP
== StdR
) DisplayResponse (m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
);
854 debugf("Unknown DNS packet type %02X%02X (ignored)", msg
->h
.flags
.b
[0], msg
->h
.flags
.b
[1]);
855 GotPacketFromHost(srcaddr
, HostPkt_B
, msg
->h
.id
);
863 mDNSlocal mStatus
mDNSNetMonitor(void)
866 int h
, m
, s
, mul
, div
, TotPkt
;
871 mStatus status
= mDNS_Init(&mDNSStorage
, &PlatformStorage
,
872 mDNS_Init_NoCache
, mDNS_Init_ZeroCacheSize
,
873 mDNS_Init_DontAdvertiseLocalAddresses
,
874 mDNS_Init_NoInitCallback
, mDNS_Init_NoInitCallbackContext
);
875 if (status
) return(status
);
877 gettimeofday(&tv_start
, NULL
);
880 status
= SetupInterfaceList(&mDNSStorage
);
881 if (status
) return(status
);
882 gStopEvent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
883 if (gStopEvent
== INVALID_HANDLE_VALUE
) return mStatus_UnknownErr
;
884 mDNSPollRegisterEvent( gStopEvent
, StopNotification
, NULL
);
885 if (!SetConsoleCtrlHandler(ConsoleControlHandler
, TRUE
)) return mStatus_UnknownErr
;
886 gRunning
= mDNStrue
; while (gRunning
) mDNSPoll( INFINITE
);
887 if (!SetConsoleCtrlHandler(ConsoleControlHandler
, FALSE
)) return mStatus_UnknownErr
;
888 CloseHandle(gStopEvent
);
890 mDNSPosixListenForSignalInEventLoop(SIGINT
);
891 mDNSPosixListenForSignalInEventLoop(SIGTERM
);
895 struct timeval timeout
= { FutureTime
, 0 }; // wait until SIGINT or SIGTERM
896 mDNSBool gotSomething
;
897 mDNSPosixRunEventLoopOnce(&mDNSStorage
, &timeout
, &signals
, &gotSomething
);
899 while ( !( sigismember( &signals
, SIGINT
) || sigismember( &signals
, SIGTERM
)));
902 // Now display final summary
903 TotPkt
= NumPktQ
+ NumPktL
+ NumPktR
;
904 gettimeofday(&tv_end
, NULL
);
905 tv_interval
= tv_end
;
906 if (tv_start
.tv_usec
> tv_interval
.tv_usec
)
907 { tv_interval
.tv_usec
+= 1000000; tv_interval
.tv_sec
--; }
908 tv_interval
.tv_sec
-= tv_start
.tv_sec
;
909 tv_interval
.tv_usec
-= tv_start
.tv_usec
;
910 h
= (tv_interval
.tv_sec
/ 3600);
911 m
= (tv_interval
.tv_sec
% 3600) / 60;
912 s
= (tv_interval
.tv_sec
% 60);
913 if (tv_interval
.tv_sec
> 10)
916 div
= tv_interval
.tv_sec
;
921 div
= tv_interval
.tv_sec
* 1000 + tv_interval
.tv_usec
/ 1000;
926 localtime_r((time_t*)&tv_start
.tv_sec
, &tm
);
927 mprintf("Started %3d:%02d:%02d.%06d\n", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv_start
.tv_usec
);
928 localtime_r((time_t*)&tv_end
.tv_sec
, &tm
);
929 mprintf("End %3d:%02d:%02d.%06d\n", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv_end
.tv_usec
);
930 mprintf("Captured for %3d:%02d:%02d.%06d\n", h
, m
, s
, tv_interval
.tv_usec
);
933 mprintf("Unique source addresses seen on network:");
934 if (IPv4HostList
.num
) mprintf(" %ld (IPv4)", IPv4HostList
.num
);
935 if (IPv6HostList
.num
) mprintf(" %ld (IPv6)", IPv6HostList
.num
);
936 if (!IPv4HostList
.num
&& !IPv6HostList
.num
) mprintf(" None");
940 mprintf("Modern Query Packets: %7d (avg%5d/min)\n", NumPktQ
, NumPktQ
* mul
/ div
);
941 mprintf("Legacy Query Packets: %7d (avg%5d/min)\n", NumPktL
, NumPktL
* mul
/ div
);
942 mprintf("Multicast Response Packets: %7d (avg%5d/min)\n", NumPktR
, NumPktR
* mul
/ div
);
943 mprintf("Total Multicast Packets: %7d (avg%5d/min)\n", TotPkt
, TotPkt
* mul
/ div
);
945 mprintf("Total New Service Probes: %7d (avg%5d/min)\n", NumProbes
, NumProbes
* mul
/ div
);
946 mprintf("Total Goodbye Announcements: %7d (avg%5d/min)\n", NumGoodbyes
, NumGoodbyes
* mul
/ div
);
947 mprintf("Total Query Questions: %7d (avg%5d/min)\n", NumQuestions
, NumQuestions
* mul
/ div
);
948 mprintf("Total Queries from Legacy Clients:%7d (avg%5d/min)\n", NumLegacy
, NumLegacy
* mul
/ div
);
949 mprintf("Total Answers/Announcements: %7d (avg%5d/min)\n", NumAnswers
, NumAnswers
* mul
/ div
);
950 mprintf("Total Additional Records: %7d (avg%5d/min)\n", NumAdditionals
, NumAdditionals
* mul
/ div
);
952 printstats(kReportTopServices
);
954 if (!ExactlyOneFilter
)
956 ShowSortedHostList(&IPv4HostList
, kReportTopHosts
);
957 ShowSortedHostList(&IPv6HostList
, kReportTopHosts
);
960 mDNS_Close(&mDNSStorage
);
964 mDNSexport
int main(int argc
, char **argv
)
966 const char *progname
= strrchr(argv
[0], '/') ? strrchr(argv
[0], '/') + 1 : argv
[0];
971 HeapSetInformation(NULL
, HeapEnableTerminationOnCorruption
, NULL
, 0);
974 setlinebuf(stdout
); // Want to see lines as they appear, not block buffered
976 for (i
=1; i
<argc
; i
++)
978 if (i
+1 < argc
&& !strcmp(argv
[i
], "-i"))
980 FilterInterface
= if_nametoindex(argv
[i
+1]);
981 if (!FilterInterface
) FilterInterface
= atoi(argv
[i
+1]);
982 if (!FilterInterface
) {
983 fprintf(stderr
, "Unknown interface %s\n", argv
[i
+1]);
986 printf("Monitoring interface %d/%s\n", FilterInterface
, argv
[i
+1]);
989 else if (!strcmp(argv
[i
], "-6"))
991 AddressType
= mDNSAddrType_IPv6
;
992 printf("Monitoring IPv6 traffic\n");
1000 a
.type
= mDNSAddrType_IPv4
;
1002 if (inet_pton(AF_INET
, argv
[i
], &s4
) == 1)
1003 a
.ip
.v4
.NotAnInteger
= s4
.s_addr
;
1004 else if (inet_pton(AF_INET6
, argv
[i
], &s6
) == 1)
1006 a
.type
= mDNSAddrType_IPv6
;
1007 mDNSPlatformMemCopy(&a
.ip
.v6
, &s6
, sizeof(a
.ip
.v6
));
1011 struct hostent
*h
= gethostbyname(argv
[i
]);
1012 if (h
) a
.ip
.v4
.NotAnInteger
= *(long*)h
->h_addr
;
1016 f
= malloc(sizeof(*f
));
1023 status
= mDNSNetMonitor();
1024 if (status
) { fprintf(stderr
, "%s: mDNSNetMonitor failed %d\n", progname
, (int)status
); return(status
); }
1028 fprintf(stderr
, "\nmDNS traffic monitor\n");
1029 fprintf(stderr
, "Usage: %s [-i index] [-6] [host]\n", progname
);
1030 fprintf(stderr
, "Optional [-i index] parameter displays only packets from that interface index\n");
1031 fprintf(stderr
, "Optional [-6] parameter displays only ipv6 packets (defaults to only ipv4 packets)\n");
1032 fprintf(stderr
, "Optional [host] parameter displays only packets from that host\n");
1034 fprintf(stderr
, "\nPer-packet header output:\n");
1035 fprintf(stderr
, "-Q- Multicast Query from mDNS client that accepts multicast responses\n");
1036 fprintf(stderr
, "-R- Multicast Response packet containing answers/announcements\n");
1037 fprintf(stderr
, "-LQ- Multicast Query from legacy client that does *not* listen for multicast responses\n");
1038 fprintf(stderr
, "Q/Ans/Auth/Add Number of questions, answers, authority records and additional records in packet\n");
1040 fprintf(stderr
, "\nPer-record display:\n");
1041 fprintf(stderr
, "(PM) Probe Question (new service starting), requesting multicast response\n");
1042 fprintf(stderr
, "(PU) Probe Question (new service starting), requesting unicast response\n");
1043 fprintf(stderr
, "(DE) Deletion/Goodbye (service going away)\n");
1044 fprintf(stderr
, "(LQ) Legacy Query Question\n");
1045 fprintf(stderr
, "(QM) Query Question, requesting multicast response\n");
1046 fprintf(stderr
, "(QU) Query Question, requesting unicast response\n");
1047 fprintf(stderr
, "(KA) Known Answer (information querier already knows)\n");
1048 fprintf(stderr
, "(AN) Unique Answer to question (or periodic announcment) (entire RR Set)\n");
1049 fprintf(stderr
, "(AN+) Answer to question (or periodic announcment) (add to existing RR Set members)\n");
1050 fprintf(stderr
, "(AD) Unique Additional Record Set (entire RR Set)\n");
1051 fprintf(stderr
, "(AD+) Additional records (add to existing RR Set members)\n");
1053 fprintf(stderr
, "\nFinal summary, sorted by service type:\n");
1054 fprintf(stderr
, "Probe Probes for this service type starting up\n");
1055 fprintf(stderr
, "Goodbye Goodbye (deletion) packets for this service type shutting down\n");
1056 fprintf(stderr
, "BrowseQ Browse questions from clients browsing to find a list of instances of this service\n");
1057 fprintf(stderr
, "BrowseA Browse answers/announcments advertising instances of this service\n");
1058 fprintf(stderr
, "ResolveQ Resolve questions from clients actively connecting to an instance of this service\n");
1059 fprintf(stderr
, "ResolveA Resolve answers/announcments giving connection information for an instance of this service\n");
1060 fprintf(stderr
, "\n");