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
56static HANDLE gStopEvent = INVALID_HANDLE_VALUE;
57static BOOL WINAPI ConsoleControlHandler( DWORD inControlEvent ) { SetEvent( gStopEvent ); return TRUE; }
58void 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
72enum
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
98typedef struct ActivityStat_struct ActivityStat;
99struct ActivityStat_struct
100	{
101	ActivityStat *next;
102	domainname srvtype;
103	int printed;
104	int totalops;
105	int stat[OP_NumTypes];
106	};
107
108typedef struct FilterList_struct FilterList;
109struct 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
124mDNS mDNSStorage;						// mDNS core uses this to store its globals
125static mDNS_PlatformSupport PlatformStorage;	// Stores this platform's globals
126mDNSexport const char ProgramName[] = "mDNSNetMonitor";
127
128struct timeval tv_start, tv_end, tv_interval;
129static int FilterInterface = 0;
130static FilterList *Filters;
131#define ExactlyOneFilter (Filters && !Filters->next)
132
133static int NumPktQ, NumPktL, NumPktR, NumPktB;	// Query/Legacy/Response/Bad
134static int NumProbes, NumGoodbyes, NumQuestions, NumLegacy, NumAnswers, NumAdditionals;
135
136static 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.
144mDNSlocal mDNSu32 mprintf(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2);
145mDNSlocal 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
162typedef 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
171typedef 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
187typedef struct
188	{
189	long		num;
190	long		max;
191	HostEntry	*hosts;
192	} HostList;
193
194static HostList IPv4HostList = { 0, 0, 0 };
195static HostList IPv6HostList = { 0, 0, 0 };
196
197mDNSlocal 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
211mDNSlocal 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
249mDNSlocal 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
264mDNSlocal 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
297mDNSlocal 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
326mDNSlocal 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
351mDNSlocal int CompareHosts(const void *p1, const void *p2)
352	{
353	return (int)(HostEntryTotalPackets((HostEntry *)p2) - HostEntryTotalPackets((HostEntry *)p1));
354	}
355
356mDNSlocal 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
383mDNSexport 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
406mDNSlocal 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
441mDNSlocal 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
460mDNSlocal 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
474mDNSlocal 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
503mDNSlocal 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
588mDNSlocal 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
603mDNSlocal 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
609mDNSlocal 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
688mDNSlocal 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
755mDNSlocal 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
770mDNSlocal 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
778mDNSexport 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
822mDNSlocal 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
923mDNSexport 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
976usage:
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