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