2 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
26 * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
27 * on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
28 * but for the sake of brevity here I will say just this: Curly braces are not syntactially
29 * part of an "if" statement; they are the beginning and ending markers of a compound statement;
30 * therefore common sense dictates that if they are part of a compound statement then they
31 * should be indented to the same level as everything else in that compound statement.
32 * Indenting curly braces at the same level as the "if" implies that curly braces are
33 * part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
34 * thinking that variables x and y are both of type "char*" -- and anyone who doesn't
35 * understand why variable y is not of type "char*" just proves the point that poor code
36 * layout leads people to unfortunate misunderstandings about how the C language really works.)
38 Change History (most recent first):
40 $Log: NetMonitor.c,v $
41 Revision 1.63 2004/05/18 23:51:26 cheshire
42 Tidy up all checkin comments to use consistent "<rdar://problem/xxxxxxx>" format for bug numbers
44 Revision 1.62 2004/03/16 18:24:25 cheshire
45 If packet destination was not multicast, then display it
47 Revision 1.61 2004/02/20 09:36:46 cheshire
48 Also show TTL in packet header, if it's not 255
50 Revision 1.60 2004/02/07 02:11:35 cheshire
51 Make mDNSNetMonitor smarter about sending targeted unicast HINFO queries
53 Revision 1.59 2004/02/03 21:42:55 cheshire
54 Add constants kReportTopServices and kReportTopHosts
56 Revision 1.58 2004/01/27 20:15:23 cheshire
57 <rdar://problem/3541288>: Time to prune obsolete code for listening on port 53
59 Revision 1.57 2004/01/24 23:59:42 cheshire
60 Change to use mDNSVal16() instead of shifting and ORing
62 Revision 1.56 2004/01/24 05:25:34 cheshire
63 mDNSNetMonitor now uses the new ability to send unicast queries so that
64 it causes less perturbation of the network traffic it's monitoring.
66 Revision 1.55 2003/12/23 00:21:31 cheshire
67 Send HINFO queries to determine the mDNSResponder version of each host
69 Revision 1.54 2003/12/17 01:06:39 cheshire
70 Also show host name along with HINFO data
72 Revision 1.53 2003/12/17 00:51:22 cheshire
73 Changed mDNSNetMonitor and mDNSIdentify to link the object files
74 instead of #including the "DNSCommon.c" "uDNS.c" and source files
76 Revision 1.52 2003/12/17 00:21:51 cheshire
77 If we received one, display host's HINFO record in final summary
79 Revision 1.51 2003/12/13 03:05:28 ksekar
80 <rdar://problem/3192548>: DynDNS: Unicast query of service records
82 Revision 1.50 2003/12/08 20:47:02 rpantos
83 Add support for mDNSResponder on Linux.
85 Revision 1.49 2003/10/30 19:38:56 cheshire
86 Fix warning on certain compilers
88 Revision 1.48 2003/10/30 19:30:00 cheshire
89 Fix warnings on certain compilers
91 Revision 1.47 2003/09/05 18:49:57 cheshire
92 Add total packet size to display
94 Revision 1.46 2003/09/05 02:33:48 cheshire
95 Set output to be line buffered, so you can redirect to a file and "tail -f" the file in another window
97 Revision 1.45 2003/09/04 00:16:20 cheshire
98 Only show count of unique source addresses seen on network if we're not filtering
100 Revision 1.44 2003/09/02 22:13:28 cheshire
101 Show total host count in final summary table
103 Revision 1.43 2003/09/02 21:42:52 cheshire
104 Improved alignment of final summary table with v6 addresses
106 Revision 1.42 2003/09/02 20:59:24 cheshire
107 Use bcopy() instead of non-portable "__u6_addr.__u6_addr32" fields.
109 Revision 1.41 2003/08/29 22:05:44 cheshire
110 Also count subsequent KA packets for the purposes of statistics counting
112 Revision 1.40 2003/08/29 16:43:24 cheshire
113 Also display breakdown of Probe/Goodbye/BrowseQ etc. for each host
115 Revision 1.39 2003/08/28 02:07:48 vlubet
116 Added "per hosts" statistics
118 Revision 1.38 2003/08/20 22:41:42 cheshire
119 Also display total multicast packet count
121 Revision 1.37 2003/08/20 22:32:08 cheshire
122 Error in DisplayQuery: Authorities come *after* Answers, not before
124 Revision 1.36 2003/08/18 23:20:10 cheshire
125 RDLength moved from the RData to the ResourceRecord object.
127 Revision 1.35 2003/08/15 20:17:28 cheshire
128 "LargeResourceRecord" changed to "LargeCacheRecord"
130 Revision 1.34 2003/08/14 02:19:55 cheshire
131 <rdar://problem/3375491> Split generic ResourceRecord type into two separate types: AuthRecord and CacheRecord
133 Revision 1.33 2003/08/12 19:56:26 cheshire
136 Revision 1.32 2003/08/06 18:57:01 cheshire
139 Revision 1.31 2003/08/06 02:05:12 cheshire
140 Add ability to give a list of hosts to monitor
142 Revision 1.30 2003/08/05 23:56:26 cheshire
143 Update code to compile with the new mDNSCoreReceive() function that requires a TTL
144 (Right now mDNSPosix.c just reports 255 -- we should fix this)
146 Revision 1.29 2003/08/05 00:43:12 cheshire
147 Report errors encountered while processing authority section
149 Revision 1.28 2003/07/29 22:51:08 cheshire
150 Added hexdump for packets we can't decode, so we can find out *why* we couldn't decode them
152 Revision 1.27 2003/07/29 22:48:04 cheshire
153 Completed support for decoding packets containing oversized resource records
155 Revision 1.26 2003/07/19 03:25:23 cheshire
156 Change to make use of new GetLargeResourceRecord() call, for handling larger records
158 Revision 1.25 2003/07/18 00:13:23 cheshire
159 Remove erroneous trailing '\' from TXT record display
161 Revision 1.24 2003/07/17 17:10:51 cheshire
162 <rdar://problem/3315761> Implement "unicast response" request, using top bit of qclass
163 Further work: distinguish between PM and PU
165 Revision 1.23 2003/07/16 22:20:23 cheshire
166 <rdar://problem/3315761> Implement "unicast response" request, using top bit of qclass
167 Made mDNSNetMonitor distinguish between QM and QU in its logging output
169 Revision 1.22 2003/07/02 21:19:58 cheshire
170 <rdar://problem/3313413> Update copyright notices, etc., in source code comments
172 Revision 1.21 2003/06/18 05:48:41 cheshire
175 Revision 1.20 2003/06/06 22:18:22 cheshire
176 Add extra space in Q output to line it up with RR output
178 Revision 1.19 2003/06/06 21:05:04 cheshire
179 Display state of cache-flush bit on additional records
181 Revision 1.18 2003/06/06 20:01:30 cheshire
182 For clarity, rename question fields name/rrtype/rrclass as qname/qtype/qclass
183 (Global search-and-replace; no functional change to code execution.)
185 Revision 1.17 2003/06/06 14:26:50 cheshire
186 Explicitly #include <time.h> for the benefit of certain Linux distributions
188 Revision 1.16 2003/05/29 21:56:36 cheshire
190 Distinguish modern multicast queries from legacy multicast queries
191 In addition to record counts, display packet counts of queries, legacy queries, and responses
192 Include TTL in RR display
194 Revision 1.15 2003/05/29 20:03:57 cheshire
195 Various improvements:
196 Display start and end time, average rates in packets-per-minute,
197 show legacy queries as -LQ-, improve display of TXT and unknown records
199 Revision 1.14 2003/05/26 04:45:42 cheshire
200 Limit line length when printing super-long TXT records
202 Revision 1.13 2003/05/26 03:21:29 cheshire
203 Tidy up address structure naming:
204 mDNSIPAddr => mDNSv4Addr (for consistency with mDNSv6Addr)
205 mDNSAddr.addr.ipv4 => mDNSAddr.ip.v4
206 mDNSAddr.addr.ipv6 => mDNSAddr.ip.v6
208 Revision 1.12 2003/05/26 03:01:28 cheshire
209 <rdar://problem/3268904> sprintf/vsprintf-style functions are unsafe; use snprintf/vsnprintf instead
211 Revision 1.11 2003/05/26 00:48:13 cheshire
212 If mDNS packet contains a non-zero message ID, then display it.
214 Revision 1.10 2003/05/22 01:10:32 cheshire
215 Indicate when the TC bit is set on a query packet
217 Revision 1.9 2003/05/21 03:56:00 cheshire
218 Improve display of Probe queries
220 Revision 1.8 2003/05/09 21:41:56 cheshire
221 Track deletion/goodbye packets as separate category
223 Revision 1.7 2003/05/07 00:16:01 cheshire
224 More detailed decoding of Resource Records
226 Revision 1.6 2003/05/05 21:16:16 cheshire
227 <rdar://problem/3241281> Change timenow from a local variable to a structure member
229 Revision 1.5 2003/04/19 01:16:22 cheshire
230 Add filter option, to monitor only packets from a single specified source address
232 Revision 1.4 2003/04/18 00:45:21 cheshire
233 Distinguish announcements (AN) from deletions (DE)
235 Revision 1.3 2003/04/15 18:26:01 cheshire
236 Added timestamps and help information
238 Revision 1.2 2003/04/04 20:42:02 cheshire
239 Fix broken statistics counting
241 Revision 1.1 2003/04/04 01:37:14 cheshire
246 //*************************************************************************************************************
247 // Incorporate mDNS.c functionality
249 // We want to use much of the functionality provided by "mDNS.c",
250 // except we'll steal the packets that would be sent to normal mDNSCoreReceive() routine
251 #define mDNSCoreReceive __NOT__mDNSCoreReceive__NOT__
253 #undef mDNSCoreReceive
255 //*************************************************************************************************************
258 #include <stdio.h> // For printf()
259 #include <stdlib.h> // For malloc()
260 #include <string.h> // For bcopy()
261 #include <time.h> // For "struct tm" etc.
262 #include <signal.h> // For SIGINT, SIGTERM
263 #include <netdb.h> // For gethostbyname()
264 #include <sys/socket.h> // For AF_INET, AF_INET6, etc.
265 #include <arpa/inet.h> // For inet_addr()
266 #include <netinet/in.h> // For INADDR_NONE
268 #include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform
269 #include "ExampleClientApp.h"
271 //*************************************************************************************************************
272 // Types and structures
276 // Primitive operations
280 // These are meta-categories;
281 // Query and Answer operations are actually subdivided into two classes:
282 // Browse query/answer and
283 // Resolve query/answer
287 // The "Browse" variants of query/answer
292 // The "Resolve" variants of query/answer
300 typedef struct ActivityStat_struct ActivityStat
;
301 struct ActivityStat_struct
307 int stat
[OP_NumTypes
];
310 typedef struct FilterList_struct FilterList
;
311 struct FilterList_struct
317 //*************************************************************************************************************
320 #define kReportTopServices 15
321 #define kReportTopHosts 15
323 //*************************************************************************************************************
326 static mDNS mDNSStorage
; // mDNS core uses this to store its globals
327 static mDNS_PlatformSupport PlatformStorage
; // Stores this platform's globals
329 struct timeval tv_start
, tv_end
, tv_interval
;
331 static FilterList
*Filters
;
332 #define ExactlyOneFilter (Filters && !Filters->next)
334 static int NumPktQ
, NumPktL
, NumPktR
, NumPktB
; // Query/Legacy/Response/Bad
335 static int NumProbes
, NumGoodbyes
, NumQuestions
, NumLegacy
, NumAnswers
, NumAdditionals
;
337 static ActivityStat
*stats
;
339 #define OPBanner "Total Ops Probe Goodbye BrowseQ BrowseA ResolveQ ResolveA"
341 //*************************************************************************************************************
344 // Special version of printf that knows how to print IP addresses, DNS-format name strings, etc.
345 mDNSlocal mDNSu32
mprintf(const char *format
, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2);
346 mDNSlocal mDNSu32
mprintf(const char *format
, ...)
349 unsigned char buffer
[512];
351 va_start(ptr
,format
);
352 length
= mDNS_vsnprintf((char *)buffer
, sizeof(buffer
), format
, ptr
);
354 printf("%s", buffer
);
358 //*************************************************************************************************************
361 // Would benefit from a hash
365 HostPkt_Q
= 0, // Query
366 HostPkt_L
= 1, // Legacy Query
367 HostPkt_R
= 2, // Response
368 HostPkt_B
= 3, // Bad
375 unsigned long pkts
[HostPkt_NumTypes
];
376 unsigned long totalops
;
377 unsigned long stat
[OP_NumTypes
];
380 UTF8str255 HIHardware
;
381 UTF8str255 HISoftware
;
386 #define HostEntryTotalPackets(H) ((H)->pkts[HostPkt_Q] + (H)->pkts[HostPkt_L] + (H)->pkts[HostPkt_R] + (H)->pkts[HostPkt_B])
395 static HostList IPv4HostList
= { 0, 0, 0 };
396 static HostList IPv6HostList
= { 0, 0, 0 };
398 mDNSlocal HostEntry
*FindHost(const mDNSAddr
*addr
, HostList
* list
)
402 for (i
= 0; i
< list
->num
; i
++)
404 HostEntry
*entry
= list
->hosts
+ i
;
405 if (mDNSSameAddress(addr
, &entry
->addr
))
412 mDNSlocal HostEntry
*AddHost(const mDNSAddr
*addr
, HostList
* list
)
416 if (list
->num
>= list
->max
)
418 long newMax
= list
->max
+ 64;
419 HostEntry
*newHosts
= realloc(list
->hosts
, newMax
* sizeof(HostEntry
));
420 if (newHosts
== NULL
)
423 list
->hosts
= newHosts
;
426 entry
= list
->hosts
+ list
->num
++;
429 for (i
=0; i
<HostPkt_NumTypes
; i
++) entry
->pkts
[i
] = 0;
431 for (i
=0; i
<OP_NumTypes
; i
++) entry
->stat
[i
] = 0;
432 entry
->hostname
.c
[0] = 0;
433 entry
->revname
.c
[0] = 0;
434 entry
->HIHardware
.c
[0] = 0;
435 entry
->HISoftware
.c
[0] = 0;
436 entry
->NumQueries
= 0;
438 if (entry
->addr
.type
== mDNSAddrType_IPv4
)
440 mDNSv4Addr ip
= entry
->addr
.ip
.v4
;
442 mDNS_snprintf(buffer
, sizeof(buffer
), "%d.%d.%d.%d.in-addr.arpa.", ip
.b
[3], ip
.b
[2], ip
.b
[1], ip
.b
[0]);
443 MakeDomainNameFromDNSNameString(&entry
->revname
, buffer
);
449 mDNSlocal HostEntry
*GotPacketFromHost(const mDNSAddr
*addr
, HostPkt_Type t
, mDNSOpaque16 id
)
451 if (ExactlyOneFilter
) return(NULL
);
454 HostList
*list
= (addr
->type
== mDNSAddrType_IPv4
) ? &IPv4HostList
: &IPv6HostList
;
455 HostEntry
*entry
= FindHost(addr
, list
);
456 if (!entry
) entry
= AddHost(addr
, list
);
457 if (!entry
) return(NULL
);
458 // Don't count our own interrogation packets
459 if (id
.NotAnInteger
!= 0xFFFF) entry
->pkts
[t
]++;
464 mDNSlocal
void RecordHostInfo(HostEntry
*entry
, const ResourceRecord
*const pktrr
)
466 if (!entry
->hostname
.c
[0])
468 if (pktrr
->rrtype
== kDNSType_A
|| pktrr
->rrtype
== kDNSType_AAAA
)
470 // Should really check that the rdata in the address record matches the source address of this packet
471 entry
->NumQueries
= 0;
472 AssignDomainName(entry
->hostname
, pktrr
->name
);
475 if (pktrr
->rrtype
== kDNSType_PTR
)
476 if (SameDomainName(&entry
->revname
, &pktrr
->name
))
478 entry
->NumQueries
= 0;
479 AssignDomainName(entry
->hostname
, pktrr
->rdata
->u
.name
);
482 else if (pktrr
->rrtype
== kDNSType_HINFO
)
484 RDataBody
*rd
= &pktrr
->rdata
->u
;
485 mDNSu8
*rdend
= (mDNSu8
*)rd
+ pktrr
->rdlength
;
486 mDNSu8
*hw
= rd
->txt
.c
;
487 mDNSu8
*sw
= hw
+ 1 + (mDNSu32
)hw
[0];
488 if (sw
+ 1 + sw
[0] <= rdend
)
490 AssignDomainName(entry
->hostname
, pktrr
->name
);
491 mDNSPlatformMemCopy(hw
, entry
->HIHardware
.c
, 1 + (mDNSu32
)hw
[0]);
492 mDNSPlatformMemCopy(sw
, entry
->HISoftware
.c
, 1 + (mDNSu32
)sw
[0]);
497 mDNSlocal
void SendUnicastQuery(mDNS
*const m
, HostEntry
*entry
, domainname
*name
, mDNSu16 rrtype
, mDNSInterfaceID InterfaceID
)
499 const mDNSOpaque16 id
= { { 0xFF, 0xFF } };
501 mDNSu8
*qptr
= query
.data
;
502 const mDNSu8
*const limit
= query
.data
+ sizeof(query
.data
);
503 const mDNSAddr
*target
= &entry
->addr
;
504 InitializeDNSMessage(&query
.h
, id
, QueryFlags
);
505 qptr
= putQuestion(&query
, qptr
, limit
, name
, rrtype
, kDNSClass_IN
);
506 entry
->LastQuery
= m
->timenow
;
509 // Note: When there are multiple mDNSResponder agents running on a single machine
510 // (e.g. Apple mDNSResponder plus a SliMP3 server with embedded mDNSResponder)
511 // it is possible that unicast queries may not go to the primary system responder.
512 // We try the first query using unicast, but if that doesn't work we try again via multicast.
513 if (entry
->NumQueries
> 2)
515 target
= &AllDNSLinkGroup_v4
;
519 //mprintf("%#a Q\n", target);
520 InterfaceID
= mDNSInterface_Any
; // Send query from our unicast reply socket
521 m
->ExpectUnicastResponse
= m
->timenow
;
524 mDNSSendDNSMessage(&mDNSStorage
, &query
, qptr
, InterfaceID
, target
, MulticastDNSPort
);
527 mDNSlocal
void AnalyseHost(mDNS
*const m
, HostEntry
*entry
, const mDNSInterfaceID InterfaceID
)
529 // If we've done four queries without answer, give up
530 if (entry
->NumQueries
>= 4) return;
532 // If we've done a query in the last second, give the host a chance to reply before trying again
533 if (entry
->NumQueries
&& m
->timenow
- entry
->LastQuery
< mDNSPlatformOneSecond
) return;
535 // If we don't know the host name, try to find that first
536 if (!entry
->hostname
.c
[0])
538 if (entry
->revname
.c
[0])
540 SendUnicastQuery(m
, entry
, &entry
->revname
, kDNSType_PTR
, InterfaceID
);
541 //mprintf("%##s PTR %d\n", entry->revname.c, entry->NumQueries);
544 // If we have the host name but no HINFO, now ask for that
545 else if (!entry
->HIHardware
.c
[0])
547 SendUnicastQuery(m
, entry
, &entry
->hostname
, kDNSType_HINFO
, InterfaceID
);
548 //mprintf("%##s HINFO %d\n", entry->hostname.c, entry->NumQueries);
552 mDNSlocal
int CompareHosts(const void *p1
, const void *p2
)
554 return (int)(HostEntryTotalPackets((HostEntry
*)p2
) - HostEntryTotalPackets((HostEntry
*)p1
));
557 mDNSlocal
void ShowSortedHostList(HostList
*list
, int max
)
559 HostEntry
*e
, *end
= &list
->hosts
[(max
< list
->num
) ? max
: list
->num
];
560 qsort(list
->hosts
, list
->num
, sizeof(HostEntry
), CompareHosts
);
561 if (list
->num
) mprintf("\n%-25s%s%s\n", "Source Address", OPBanner
, " Pkts Query LegacyQ Response");
562 for (e
= &list
->hosts
[0]; e
< end
; e
++)
564 int len
= mprintf("%#-25a", &e
->addr
);
565 if (len
> 25) mprintf("\n%25s", "");
566 mprintf("%8d %8d %8d %8d %8d %8d %8d", e
->totalops
,
567 e
->stat
[OP_probe
], e
->stat
[OP_goodbye
],
568 e
->stat
[OP_browseq
], e
->stat
[OP_browsea
],
569 e
->stat
[OP_resolveq
], e
->stat
[OP_resolvea
]);
570 mprintf(" %8lu %8lu %8lu %8lu",
571 HostEntryTotalPackets(e
), e
->pkts
[HostPkt_Q
], e
->pkts
[HostPkt_L
], e
->pkts
[HostPkt_R
]);
572 if (e
->pkts
[HostPkt_B
]) mprintf("Bad: %8lu", e
->pkts
[HostPkt_B
]);
574 if (!e
->HISoftware
.c
[0] && e
->NumQueries
> 2)
575 mDNSPlatformMemCopy("\x0E*** Jaguar ***", &e
->HISoftware
, 15);
576 if (e
->hostname
.c
[0] || e
->HIHardware
.c
[0] || e
->HISoftware
.c
[0])
577 mprintf("%##-45s %#-14s %#s\n", e
->hostname
.c
, e
->HIHardware
.c
, e
->HISoftware
.c
);
581 //*************************************************************************************************************
582 // Receive and process packets
584 mDNSexport mDNSBool
ExtractServiceType(const domainname
*const fqdn
, domainname
*const srvtype
)
587 const mDNSu8
*src
= fqdn
->c
;
588 mDNSu8
*dst
= srvtype
->c
;
591 if (len
== 0 || len
>= 0x40) return(mDNSfalse
);
592 if (src
[1] != '_') src
+= 1 + len
;
595 if (len
== 0 || len
>= 0x40 || src
[1] != '_') return(mDNSfalse
);
596 for (i
=0; i
<=len
; i
++) *dst
++ = *src
++;
599 if (len
== 0 || len
>= 0x40 || src
[1] != '_') return(mDNSfalse
);
600 for (i
=0; i
<=len
; i
++) *dst
++ = *src
++;
602 *dst
++ = 0; // Put the null root label on the end of the service type
607 mDNSlocal
void recordstat(HostEntry
*entry
, domainname
*fqdn
, int op
, mDNSu16 rrtype
)
609 ActivityStat
**s
= &stats
;
614 if (rrtype
== kDNSType_SRV
|| rrtype
== kDNSType_TXT
) op
= op
- OP_browsegroup
+ OP_resolvegroup
;
615 else if (rrtype
!= kDNSType_PTR
) return;
618 if (!ExtractServiceType(fqdn
, &srvtype
)) return;
620 while (*s
&& !SameDomainName(&(*s
)->srvtype
, &srvtype
)) s
= &(*s
)->next
;
624 *s
= malloc(sizeof(ActivityStat
));
627 (*s
)->srvtype
= srvtype
;
630 for (i
=0; i
<OP_NumTypes
; i
++) (*s
)->stat
[i
] = 0;
642 mDNSlocal
void printstats(int max
)
646 for (i
=0; i
<max
; i
++)
649 ActivityStat
*s
, *m
= NULL
;
650 for (s
= stats
; s
; s
=s
->next
)
651 if (!s
->printed
&& max
< s
->totalops
)
652 { m
= s
; max
= s
->totalops
; }
654 m
->printed
= mDNStrue
;
655 if (i
==0) mprintf("%-25s%s\n", "Service Type", OPBanner
);
656 mprintf("%##-25s%8d %8d %8d %8d %8d %8d %8d\n", &m
->srvtype
, m
->totalops
, m
->stat
[OP_probe
],
657 m
->stat
[OP_goodbye
], m
->stat
[OP_browseq
], m
->stat
[OP_browsea
], m
->stat
[OP_resolveq
], m
->stat
[OP_resolvea
]);
661 mDNSlocal
const mDNSu8
*FindUpdate(mDNS
*const m
, const DNSMessage
*const query
, const mDNSu8
*ptr
, const mDNSu8
*const end
,\
662 DNSQuestion
*q
, LargeCacheRecord
*pkt
)
665 for (i
= 0; i
< query
->h
.numAuthorities
; i
++)
667 const mDNSu8
*p2
= ptr
;
668 ptr
= GetLargeResourceRecord(m
, query
, ptr
, end
, q
->InterfaceID
, 0, pkt
);
670 if (ResourceRecordAnswersQuestion(&pkt
->r
.resrec
, q
)) return(p2
);
675 mDNSlocal
void DisplayTimestamp(void)
679 gettimeofday(&tv
, NULL
);
680 localtime_r((time_t*)&tv
.tv_sec
, &tm
);
681 mprintf("\n%d:%02d:%02d.%06d\n", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv
.tv_usec
);
684 mDNSlocal
void DisplayPacketHeader(const DNSMessage
*const msg
, const mDNSu8
*const end
, const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, mDNSu8 ttl
)
686 const char *const ptype
= (msg
->h
.flags
.b
[0] & kDNSFlag0_QR_Response
) ? "-R- " :
687 (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
) ? "-Q- " : "-LQ-";
690 mprintf("%#-16a %s Q:%3d Ans:%3d Auth:%3d Add:%3d Size:%5d bytes",
691 srcaddr
, ptype
, msg
->h
.numQuestions
, msg
->h
.numAnswers
, msg
->h
.numAuthorities
, msg
->h
.numAdditionals
, end
- (mDNSu8
*)msg
);
693 if (msg
->h
.id
.NotAnInteger
) mprintf(" ID:%u", mDNSVal16(msg
->h
.id
));
695 if (ttl
!= 255) mprintf(" TTL:%u", ttl
);
697 if (!mDNSAddrIsDNSMulticast(dstaddr
)) mprintf(" To: %#a", dstaddr
);
699 if (msg
->h
.flags
.b
[0] & kDNSFlag0_TC
)
701 if (msg
->h
.flags
.b
[0] & kDNSFlag0_QR_Response
) mprintf(" Truncated");
702 else mprintf(" Truncated (KA list continues in next packet)");
707 mDNSlocal
void DisplayResourceRecord(const mDNSAddr
*const srcaddr
, const char *const op
, const ResourceRecord
*const pktrr
)
709 static const char hexchars
[16] = "0123456789ABCDEF";
711 char buffer
[MaxWidth
+8];
714 RDataBody
*rd
= &pktrr
->rdata
->u
;
715 mDNSu8
*rdend
= (mDNSu8
*)rd
+ pktrr
->rdlength
;
716 mDNSu32 n
= mprintf("%#-16a %-5s %-5s%5d %##s -> ", srcaddr
, op
, DNSTypeName(pktrr
->rrtype
), pktrr
->rroriginalttl
, pktrr
->name
.c
);
718 switch(pktrr
->rrtype
)
720 case kDNSType_A
: n
+= mprintf("%.4a", &rd
->ip
); break;
721 case kDNSType_PTR
: n
+= mprintf("%##.*s", MaxWidth
- n
, &rd
->name
); break;
722 case kDNSType_HINFO
:// same as kDNSType_TXT below
724 mDNSu8
*t
= rd
->txt
.c
;
725 while (t
< rdend
&& t
[0] && p
< buffer
+MaxWidth
)
728 for (i
=1; i
<=t
[0] && p
< buffer
+MaxWidth
; i
++)
730 if (t
[i
] == '\\') *p
++ = '\\';
731 if (t
[i
] >= ' ') *p
++ = t
[i
];
737 *p
++ = hexchars
[t
[i
] >> 4];
738 *p
++ = hexchars
[t
[i
] & 0xF];
742 if (t
< rdend
&& t
[0]) { *p
++ = '\\'; *p
++ = ' '; }
745 n
+= mprintf("%.*s", MaxWidth
- n
, buffer
);
747 case kDNSType_AAAA
: n
+= mprintf("%.16a", &rd
->ipv6
); break;
748 case kDNSType_SRV
: n
+= mprintf("%##s:%d", &rd
->srv
.target
, mDNSVal16(rd
->srv
.port
)); break;
750 mDNSu8
*s
= rd
->data
;
751 while (s
< rdend
&& p
< buffer
+MaxWidth
)
753 if (*s
== '\\') *p
++ = '\\';
754 if (*s
>= ' ') *p
++ = *s
;
760 *p
++ = hexchars
[*s
>> 4];
761 *p
++ = hexchars
[*s
& 0xF];
766 n
+= mprintf("%.*s", MaxWidth
- n
, buffer
);
773 mDNSlocal
void HexDump(const mDNSu8
*ptr
, const mDNSu8
*const end
)
779 if (&ptr
[i
] < end
) mprintf("%02X ", ptr
[i
]);
782 if (&ptr
[i
] < end
) mprintf("%c", ptr
[i
] <= ' ' || ptr
[i
] >= 126 ? '.' : ptr
[i
]);
788 mDNSlocal
void DisplayError(const mDNSAddr
*srcaddr
, const mDNSu8
*ptr
, const mDNSu8
*const end
, char *msg
)
790 mprintf("%#-16a **** ERROR: FAILED TO READ %s **** \n", srcaddr
, msg
);
794 mDNSlocal
void DisplayQuery(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*const end
,
795 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, const mDNSInterfaceID InterfaceID
, mDNSu8 ttl
)
798 const mDNSu8
*ptr
= msg
->data
;
799 const mDNSu8
*auth
= LocateAuthorities(msg
, end
);
800 mDNSBool MQ
= (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
);
801 HostEntry
*entry
= GotPacketFromHost(srcaddr
, MQ
? HostPkt_Q
: HostPkt_L
, msg
->h
.id
);
802 LargeCacheRecord pkt
;
804 DisplayPacketHeader(msg
, end
, srcaddr
, srcport
, dstaddr
, ttl
);
805 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF)
807 if (MQ
) NumPktQ
++; else NumPktL
++;
810 for (i
=0; i
<msg
->h
.numQuestions
; i
++)
813 mDNSu8
*p2
= (mDNSu8
*)getQuestion(msg
, ptr
, end
, InterfaceID
, &q
);
814 mDNSu16 ucbit
= q
.qclass
& kDNSQClass_UnicastResponse
;
815 q
.qclass
&= ~kDNSQClass_UnicastResponse
;
816 if (!p2
) { DisplayError(srcaddr
, ptr
, end
, "QUESTION"); return; }
818 p2
= (mDNSu8
*)FindUpdate(m
, msg
, auth
, end
, &q
, &pkt
);
822 DisplayResourceRecord(srcaddr
, ucbit
? "(PU)" : "(PM)", &pkt
.r
.resrec
);
823 recordstat(entry
, &q
.qname
, OP_probe
, q
.qtype
);
824 p2
= (mDNSu8
*)skipDomainName(msg
, p2
, end
);
825 // Having displayed this update record, clear type and class so we don't display the same one again.
826 p2
[0] = p2
[1] = p2
[2] = p2
[3] = 0;
830 const char *ptype
= ucbit
? "(QU)" : "(QM)";
831 if (srcport
.NotAnInteger
== MulticastDNSPort
.NotAnInteger
) NumQuestions
++;
832 else { NumLegacy
++; ptype
= "(LQ)"; }
833 mprintf("%#-16a %-5s %-5s %##s\n", srcaddr
, ptype
, DNSTypeName(q
.qtype
), q
.qname
.c
);
834 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) recordstat(entry
, &q
.qname
, OP_query
, q
.qtype
);
838 for (i
=0; i
<msg
->h
.numAnswers
; i
++)
840 const mDNSu8
*ep
= ptr
;
841 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, 0, &pkt
);
842 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "KNOWN ANSWER"); return; }
843 DisplayResourceRecord(srcaddr
, "(KA)", &pkt
.r
.resrec
);
845 // In the case of queries with long multi-packet KA lists, we count each subsequent KA packet
846 // the same as a single query, to more accurately reflect the burden on the network
847 // (A query with a six-packet KA list is *at least* six times the burden on the network as a single-packet query.)
848 if (msg
->h
.numQuestions
== 0 && i
== 0)
849 recordstat(entry
, &pkt
.r
.resrec
.name
, OP_query
, pkt
.r
.resrec
.rrtype
);
852 for (i
=0; i
<msg
->h
.numAuthorities
; i
++)
854 const mDNSu8
*ep
= ptr
;
855 ptr
= skipResourceRecord(msg
, ptr
, end
);
856 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "AUTHORITY"); return; }
859 if (entry
) AnalyseHost(m
, entry
, InterfaceID
);
862 mDNSlocal
void DisplayResponse(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*end
,
863 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, const mDNSInterfaceID InterfaceID
, mDNSu8 ttl
)
866 const mDNSu8
*ptr
= msg
->data
;
867 HostEntry
*entry
= GotPacketFromHost(srcaddr
, HostPkt_R
, msg
->h
.id
);
868 LargeCacheRecord pkt
;
870 DisplayPacketHeader(msg
, end
, srcaddr
, srcport
, dstaddr
, ttl
);
871 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) NumPktR
++;
873 for (i
=0; i
<msg
->h
.numQuestions
; i
++)
876 const mDNSu8
*ep
= ptr
;
877 ptr
= getQuestion(msg
, ptr
, end
, InterfaceID
, &q
);
878 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "QUESTION"); return; }
879 if (mDNSAddrIsDNSMulticast(dstaddr
))
880 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE Q IN mDNS RESPONSE **** %-5s %##s\n", srcaddr
, DNSTypeName(q
.qtype
), q
.qname
.c
);
882 mprintf("%#-16a (Q) %-5s %##s\n", srcaddr
, DNSTypeName(q
.qtype
), q
.qname
.c
);
885 for (i
=0; i
<msg
->h
.numAnswers
; i
++)
887 const mDNSu8
*ep
= ptr
;
888 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, 0, &pkt
);
889 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "ANSWER"); return; }
890 if (pkt
.r
.resrec
.rroriginalttl
)
893 DisplayResourceRecord(srcaddr
, (pkt
.r
.resrec
.RecordType
& kDNSRecordTypePacketUniqueMask
) ? "(AN)" : "(AN+)", &pkt
.r
.resrec
);
894 if (msg
->h
.id
.NotAnInteger
!= 0xFFFF) recordstat(entry
, &pkt
.r
.resrec
.name
, OP_answer
, pkt
.r
.resrec
.rrtype
);
895 if (entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
900 DisplayResourceRecord(srcaddr
, "(DE)", &pkt
.r
.resrec
);
901 recordstat(entry
, &pkt
.r
.resrec
.name
, OP_goodbye
, pkt
.r
.resrec
.rrtype
);
905 for (i
=0; i
<msg
->h
.numAuthorities
; i
++)
907 const mDNSu8
*ep
= ptr
;
908 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, 0, &pkt
);
909 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "AUTHORITY"); return; }
910 mprintf("%#-16a (?) **** ERROR: SHOULD NOT HAVE AUTHORITY IN mDNS RESPONSE **** %-5s %##s\n",
911 srcaddr
, DNSTypeName(pkt
.r
.resrec
.rrtype
), pkt
.r
.resrec
.name
.c
);
914 for (i
=0; i
<msg
->h
.numAdditionals
; i
++)
916 const mDNSu8
*ep
= ptr
;
917 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, 0, &pkt
);
918 if (!ptr
) { DisplayError(srcaddr
, ep
, end
, "ADDITIONAL"); return; }
920 DisplayResourceRecord(srcaddr
, (pkt
.r
.resrec
.RecordType
& kDNSRecordTypePacketUniqueMask
) ? "(AD)" : "(AD+)", &pkt
.r
.resrec
);
921 if (entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
924 if (entry
) AnalyseHost(m
, entry
, InterfaceID
);
927 mDNSlocal
void ProcessUnicastResponse(mDNS
*const m
, const DNSMessage
*const msg
, const mDNSu8
*end
, const mDNSAddr
*srcaddr
, const mDNSInterfaceID InterfaceID
)
930 const mDNSu8
*ptr
= LocateAnswers(msg
, end
);
931 HostEntry
*entry
= GotPacketFromHost(srcaddr
, HostPkt_R
, msg
->h
.id
);
932 //mprintf("%#a R\n", srcaddr);
934 for (i
=0; i
<msg
->h
.numAnswers
+ msg
->h
.numAuthorities
+ msg
->h
.numAdditionals
; i
++)
936 LargeCacheRecord pkt
;
937 ptr
= GetLargeResourceRecord(m
, msg
, ptr
, end
, InterfaceID
, 0, &pkt
);
938 if (pkt
.r
.resrec
.rroriginalttl
&& entry
) RecordHostInfo(entry
, &pkt
.r
.resrec
);
942 mDNSlocal mDNSBool
AddressMatchesFilterList(const mDNSAddr
*srcaddr
)
945 if (!Filters
) return(srcaddr
->type
== mDNSAddrType_IPv4
);
946 for (f
=Filters
; f
; f
=f
->next
) if (mDNSSameAddress(srcaddr
, &f
->FilterAddr
)) return(mDNStrue
);
950 mDNSexport
void mDNSCoreReceive(mDNS
*const m
, DNSMessage
*const msg
, const mDNSu8
*const end
,
951 const mDNSAddr
*srcaddr
, mDNSIPPort srcport
, const mDNSAddr
*dstaddr
, mDNSIPPort dstport
, const mDNSInterfaceID InterfaceID
, mDNSu8 ttl
)
953 const mDNSu8 StdQ
= kDNSFlag0_QR_Query
| kDNSFlag0_OP_StdQuery
;
954 const mDNSu8 StdR
= kDNSFlag0_QR_Response
| kDNSFlag0_OP_StdQuery
;
955 const mDNSu8 QR_OP
= (mDNSu8
)(msg
->h
.flags
.b
[0] & kDNSFlag0_QROP_Mask
);
956 mDNSu8
*ptr
= (mDNSu8
*)&msg
->h
.numQuestions
;
958 (void)dstaddr
; // Unused
959 (void)dstport
; // Unused
961 // Read the integer parts which are in IETF byte-order (MSB first, LSB second)
962 msg
->h
.numQuestions
= (mDNSu16
)((mDNSu16
)ptr
[0] << 8 | ptr
[1]);
963 msg
->h
.numAnswers
= (mDNSu16
)((mDNSu16
)ptr
[2] << 8 | ptr
[3]);
964 msg
->h
.numAuthorities
= (mDNSu16
)((mDNSu16
)ptr
[4] << 8 | ptr
[5]);
965 msg
->h
.numAdditionals
= (mDNSu16
)((mDNSu16
)ptr
[6] << 8 | ptr
[7]);
969 debugf("** Apparent spoof mDNS %s packet from %#-15a to %#-15a TTL %d on %p with %2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s",
970 (QR_OP
== StdQ
) ? "Query" : (QR_OP
== StdR
) ? "Response" : "Unkown",
971 srcaddr
, dstaddr
, ttl
, InterfaceID
,
972 msg
->h
.numQuestions
, msg
->h
.numQuestions
== 1 ? ", " : "s,",
973 msg
->h
.numAnswers
, msg
->h
.numAnswers
== 1 ? ", " : "s,",
974 msg
->h
.numAuthorities
, msg
->h
.numAuthorities
== 1 ? "y, " : "ies,",
975 msg
->h
.numAdditionals
, msg
->h
.numAdditionals
== 1 ? "" : "s");
978 // For now we're only interested in monitoring IPv4 traffic.
979 // All IPv6 packets should just be duplicates of the v4 packets.
980 if (AddressMatchesFilterList(srcaddr
))
983 if (!mDNSAddrIsDNSMulticast(dstaddr
))
985 if (QR_OP
== StdQ
) mprintf("Unicast query from %#a\n", srcaddr
);
986 else if (QR_OP
== StdR
) ProcessUnicastResponse(m
, msg
, end
, srcaddr
, InterfaceID
);
990 if (QR_OP
== StdQ
) DisplayQuery (m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
, ttl
);
991 else if (QR_OP
== StdR
) DisplayResponse (m
, msg
, end
, srcaddr
, srcport
, dstaddr
, InterfaceID
, ttl
);
994 debugf("Unknown DNS packet type %02X%02X (ignored)", msg
->h
.flags
.b
[0], msg
->h
.flags
.b
[1]);
995 GotPacketFromHost(srcaddr
, HostPkt_B
, msg
->h
.id
);
1003 mDNSlocal mStatus
mDNSNetMonitor(void)
1006 int h
, m
, s
, mul
, div
, TotPkt
;
1009 mStatus status
= mDNS_Init(&mDNSStorage
, &PlatformStorage
,
1010 mDNS_Init_NoCache
, mDNS_Init_ZeroCacheSize
,
1011 mDNS_Init_DontAdvertiseLocalAddresses
,
1012 mDNS_Init_NoInitCallback
, mDNS_Init_NoInitCallbackContext
);
1013 if (status
) return(status
);
1015 gettimeofday(&tv_start
, NULL
);
1016 mDNSPosixListenForSignalInEventLoop(SIGINT
);
1017 mDNSPosixListenForSignalInEventLoop(SIGTERM
);
1021 struct timeval timeout
= { 0x3FFFFFFF, 0 }; // wait until SIGINT or SIGTERM
1022 mDNSBool gotSomething
;
1023 mDNSPosixRunEventLoopOnce(&mDNSStorage
, &timeout
, &signals
, &gotSomething
);
1025 while ( !( sigismember( &signals
, SIGINT
) || sigismember( &signals
, SIGTERM
)));
1027 // Now display final summary
1028 TotPkt
= NumPktQ
+ NumPktL
+ NumPktR
;
1029 gettimeofday(&tv_end
, NULL
);
1030 tv_interval
= tv_end
;
1031 if (tv_start
.tv_usec
> tv_interval
.tv_usec
)
1032 { tv_interval
.tv_usec
+= 1000000; tv_interval
.tv_sec
--; }
1033 tv_interval
.tv_sec
-= tv_start
.tv_sec
;
1034 tv_interval
.tv_usec
-= tv_start
.tv_usec
;
1035 h
= (tv_interval
.tv_sec
/ 3600);
1036 m
= (tv_interval
.tv_sec
% 3600) / 60;
1037 s
= (tv_interval
.tv_sec
% 60);
1038 if (tv_interval
.tv_sec
> 10)
1041 div
= tv_interval
.tv_sec
;
1046 div
= tv_interval
.tv_sec
* 1000 + tv_interval
.tv_usec
/ 1000;
1047 if (div
== 0) div
=1;
1051 localtime_r((time_t*)&tv_start
.tv_sec
, &tm
);
1052 mprintf("Started %3d:%02d:%02d.%06d\n", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv_start
.tv_usec
);
1053 localtime_r((time_t*)&tv_end
.tv_sec
, &tm
);
1054 mprintf("End %3d:%02d:%02d.%06d\n", tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
, tv_end
.tv_usec
);
1055 mprintf("Captured for %3d:%02d:%02d.%06d\n", h
, m
, s
, tv_interval
.tv_usec
);
1056 if (!Filters
) mprintf("Unique source addresses seen on network: %d\n", IPv4HostList
.num
+ IPv6HostList
.num
);
1058 mprintf("Modern Query Packets: %7d (avg%5d/min)\n", NumPktQ
, NumPktQ
* mul
/ div
);
1059 mprintf("Legacy Query Packets: %7d (avg%5d/min)\n", NumPktL
, NumPktL
* mul
/ div
);
1060 mprintf("Multicast Response Packets: %7d (avg%5d/min)\n", NumPktR
, NumPktR
* mul
/ div
);
1061 mprintf("Total Multicast Packets: %7d (avg%5d/min)\n", TotPkt
, TotPkt
* mul
/ div
);
1063 mprintf("Total New Service Probes: %7d (avg%5d/min)\n", NumProbes
, NumProbes
* mul
/ div
);
1064 mprintf("Total Goodbye Announcements: %7d (avg%5d/min)\n", NumGoodbyes
, NumGoodbyes
* mul
/ div
);
1065 mprintf("Total Query Questions: %7d (avg%5d/min)\n", NumQuestions
, NumQuestions
* mul
/ div
);
1066 mprintf("Total Queries from Legacy Clients:%7d (avg%5d/min)\n", NumLegacy
, NumLegacy
* mul
/ div
);
1067 mprintf("Total Answers/Announcements: %7d (avg%5d/min)\n", NumAnswers
, NumAnswers
* mul
/ div
);
1068 mprintf("Total Additional Records: %7d (avg%5d/min)\n", NumAdditionals
, NumAdditionals
* mul
/ div
);
1070 printstats(kReportTopServices
);
1072 if (!ExactlyOneFilter
)
1074 ShowSortedHostList(&IPv4HostList
, kReportTopHosts
);
1075 ShowSortedHostList(&IPv6HostList
, kReportTopHosts
);
1078 mDNS_Close(&mDNSStorage
);
1082 mDNSexport
int main(int argc
, char **argv
)
1087 setlinebuf(stdout
); // Want to see lines as they appear, not block buffered
1089 for (i
=1; i
<argc
; i
++)
1095 a
.type
= mDNSAddrType_IPv4
;
1097 if (inet_pton(AF_INET
, argv
[i
], &s4
) == 1)
1098 a
.ip
.v4
.NotAnInteger
= s4
.s_addr
;
1099 else if (inet_pton(AF_INET6
, argv
[i
], &s6
) == 1)
1101 a
.type
= mDNSAddrType_IPv6
;
1102 bcopy(&s6
, &a
.ip
.v6
, sizeof(a
.ip
.v6
));
1106 struct hostent
*h
= gethostbyname(argv
[i
]);
1107 if (h
) a
.ip
.v4
.NotAnInteger
= *(long*)h
->h_addr
;
1111 f
= malloc(sizeof(*f
));
1117 status
= mDNSNetMonitor();
1118 if (status
) { fprintf(stderr
, "%s: mDNSNetMonitor failed %ld\n", argv
[0], status
); return(status
); }
1122 fprintf(stderr
, "\nmDNS traffic monitor\n");
1123 fprintf(stderr
, "Usage: %s (<host>)\n", argv
[0]);
1124 fprintf(stderr
, "Optional <host> parameter displays only packets from that host\n");
1126 fprintf(stderr
, "\nPer-packet header output:\n");
1127 fprintf(stderr
, "-Q- Multicast Query from mDNS client that accepts multicast responses\n");
1128 fprintf(stderr
, "-R- Multicast Response packet containing answers/announcements\n");
1129 fprintf(stderr
, "-LQ- Multicast Query from legacy client that does *not* listen for multicast responses\n");
1130 fprintf(stderr
, "Q/Ans/Auth/Add Number of questions, answers, authority records and additional records in packet\n");
1132 fprintf(stderr
, "\nPer-record display:\n");
1133 fprintf(stderr
, "(PM) Probe Question (new service starting), requesting multicast response\n");
1134 fprintf(stderr
, "(PU) Probe Question (new service starting), requesting unicast response\n");
1135 fprintf(stderr
, "(DE) Deletion/Goodbye (service going away)\n");
1136 fprintf(stderr
, "(LQ) Legacy Query Question\n");
1137 fprintf(stderr
, "(QM) Query Question, requesting multicast response\n");
1138 fprintf(stderr
, "(QU) Query Question, requesting unicast response\n");
1139 fprintf(stderr
, "(KA) Known Answer (information querier already knows)\n");
1140 fprintf(stderr
, "(AN) Unique Answer to question (or periodic announcment) (entire RR Set)\n");
1141 fprintf(stderr
, "(AN+) Answer to question (or periodic announcment) (add to existing RR Set members)\n");
1142 fprintf(stderr
, "(AD) Unique Additional Record Set (entire RR Set)\n");
1143 fprintf(stderr
, "(AD+) Additional records (add to existing RR Set members)\n");
1145 fprintf(stderr
, "\nFinal summary, sorted by service type:\n");
1146 fprintf(stderr
, "Probe Probes for this service type starting up\n");
1147 fprintf(stderr
, "Goodbye Goodbye (deletion) packets for this service type shutting down\n");
1148 fprintf(stderr
, "BrowseQ Browse questions from clients browsing to find a list of instances of this service\n");
1149 fprintf(stderr
, "BrowseA Browse answers/announcments advertising instances of this service\n");
1150 fprintf(stderr
, "ResolveQ Resolve questions from clients actively connecting to an instance of this service\n");
1151 fprintf(stderr
, "ResolveA Resolve answers/announcments giving connection information for an instance of this service\n");
1152 fprintf(stderr
, "\n");