Bonjour/mDNS lib, works with the new network stack

Fork of BonjourLib by Dirk-Willem van Gulik (NXP/mbed)

mDNSResponder.cpp

Committer:
Jasper
Date:
2014-05-30
Revision:
3:b8a78d6da192
Parent:
2:18d443d86057

File content as of revision 3:b8a78d6da192:

/* Class: mDNSResponder
 * Copyright 1991, 2003, 2010 Dirk-Willem van Gulik <dirkx(at)apache(punto)org>
 *
 * License: Any BSD or ASF License.
 *
 * Rough and ready port of some mDNS code.
 *
 * Typical use is something like
 *
 * EthernetNetIf eth;
 * HTTPServer svr;
 * mDNSResponder mdns;
 *
 * int main()...
 *
 *   // get internet
 *   EthernetErr ethErr = eth.setup();
 *   ... etc ..
 *
 *   // set up some server
 *   svr.addHandler<SimpleHandler>("/"); //Default handler
 *   svr.bind(80); * *

 *  // Extract the IP address.
 *   IpAddr ip = eth.getIp();
 *   printf("mbed IP Address is %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3]);
 *
 *   // Announce ourselves.
 *   mdns.announce(ip, "fred", "_http._tcp", 80, "The Little Server that Could", "path=/demo");
 *
 *   while()... enter some run loop
 *   ...
 *
 *  This will cause http://fred.local./demo to be announced as 'The Little Server that Could'.
 *
 *  Or as another example: (http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt)
 *  and the various RFCs:
 *
 *     mdns.announce(ip, "_ssh._tcp", 22, SSH to Serial Gateway", NULL);
 *
 * CAVEAT - a lot of the buffer overrun and copy checks
 *          where removed; and this is not anywhere near
 *          threadsafve or sane. Not for production use.
 */
#include "lwip/lwipopts.h"
#if LWIP_DNS /* don't build if not configured for use in lwipopts.h */

#include "mDNSResponder.h"
#include <stdio.h>
#include <ctype.h>

#ifndef DNS_RRTYPE_SRV
#define DNS_RRTYPE_SRV (33)
#endif

#ifndef DNS_LOCAL
#define DNS_LOCAL "local"
#endif

void pretty_print_block(char *b, int len)
{
    int x, y, indent, count = 0;

    indent = 16; /* whatever */

    printf("\n\r");

    while (count < len)
    {
        printf("%04x : ", count);
        for (x = 0 ; x < indent ; x++)
        {
            printf("%02x ", b[x + count]);
            if ((x + count + 1) >= len)
            {
                x++;
                for (y = 0 ; y < (indent - x) ; y++)
                    printf("   ");
                break;
            }
        }
        printf(": ");

        for (x = 0 ; x < indent ; x++)
        {
            if (isprint(b[x + count]))
                putchar(b[x + count]);
            else if (b[x + count] == 0)
                putchar(' ');
            else
                putchar('.');

            if ((x + count + 1) >= len)
            {
                x++;
                for (y = 0 ; y < (indent - x) ; y++)
                    putchar(' ');
                break;
            }
        }
        putchar('\n');
        putchar('\r');
        count += indent;
    }
    putchar('\n');
    putchar('\r');
}



void announcer(void const *args) {
    printf("in announcer\r\n");
    mDNSResponder *m = (mDNSResponder *)args;
    m->periodic(NULL);
}

mDNSResponder::mDNSResponder() :
        moi_txt(NULL), moi_port(0),
        moi_name(NULL), moi_proto(NULL),
        moi_local_proto(NULL), moi_local_name(NULL), moi_local_pglue(NULL),
#ifdef MDNS_QCACHE
        labelCacheCnt(0), labelCacheOffset(0)
#endif
{
    initLabelCache(0);
}

mDNSResponder::~mDNSResponder() {
    close();
}

void mDNSResponder::announce(ip_addr_t ip, const char * ldn, const char * proto, uint16_t port, const char * name, char ** txt) {

    m_pUDPSocket = new UDPSocket;
//    m_pUDPSocket->init();

    multicast_group = new Endpoint;
    multicast_group->set_address(MCAST, MDNS_PORT);

    m_pUDPSocket->bind(MDNS_PORT);
    m_pUDPSocket->join_multicast_group(MCAST);

    moi_port = port;
    moi_txt = txt;
    moi_ip = ip;
    moi_proto = proto;
    moi_name = name;

    moi_local_proto = (char *)malloc(128);
    moi_local_name = (char *)malloc(128);
    moi_local_pglue = (char *)malloc(128);

    snprintf(moi_local_proto,128,"%s.%s.", moi_proto, DNS_LOCAL);
    snprintf(moi_local_name,128,"%s.%s.", ldn, DNS_LOCAL);
    snprintf(moi_local_pglue,128,"%s.%s.%s.", moi_name, moi_proto, DNS_LOCAL);

    printf("starting thread\r\n");

    // Gratuis intro - and repeat such regularly.. taking some
    // care to not go beyond DHCP limits - but not more often
    // than makes sense for our TTL. See the .h file for the
    // various tradeoffs.
    //
//    sendReply(DNS_RRTYPE_PTR, 0, NULL);

    printf("starting announcer\r\n");
    mdns_announcer = new RtosTimer(announcer, osTimerPeriodic, this);
    mdns_announcer->start(1000 * MDNS_INTERVAL);
    printf("kicking it\r\n");

    Listen(NULL);    
}

void mDNSResponder::close() {
    m_pUDPSocket->close(true);
    delete m_pUDPSocket;
}

char * index(char * str, char c) {
    for (;str && *str;str++) {
        if (*str == c) return str;
    };
    return NULL;
}

#ifdef MDNS_QCACHE
void mDNSResponder::initLabelCache(char * off) {
    labelCacheOffset = off;
    labelCacheCnt = 0;
    memset(_cache_off,0, sizeof(_cache_off)); // Rely on min value to be 12
};

uint16_t mDNSResponder::checkLabelCache(char * name) {
    // Bail out if not initialized with a valid (>12) offset.
    //
    if (!labelCacheOffset)
        return 0;
    
    for (int i=0; i < MDNS_MAXCACHEDNSLABELS; i++)
        if (_cache_off[i] && !strcmp(_cache_och[i],name))
            return _cache_off[i];
    return 0;
};

void mDNSResponder::addLabelCache(char * p, char * name) {
    // Bail out if not initialized with a valid (>12) offset.
    //
    if (!labelCacheOffset)
        return;
        
    _cache_off[labelCacheCnt] = p - labelCacheOffset;
    _cache_och[labelCacheCnt] = name;

    labelCacheCnt++;
    // we intentionally do not wack the first entries - as they are the most
    // likely ones to be used.
    if (labelCacheCnt>= MDNS_MAXCACHEDNSLABELS)
        labelCacheCnt = MDNS_MAXCACHEDNSLABELS / 3;
};
#else
#define initLabelCache(x) {} /* Ignored */
#define checkLabelCache(x) (0) /* never a hit */
#define addLabelCache(x,y) {} /* Ignored */
#endif

char * mDNSResponder::breakname(char *p, char *name) {
    int l = 0, de = 1;
    char * q;
    uint16_t off;

    do {
        if ((off = checkLabelCache(name)) != 0) {
            *p++ = 192 + (off >> 8);
            *p++ =       (off &  0xFF);
            return p;
        } else {
            addLabelCache(p, name);
        };

        q = index(name,'.');
        if (!q) {
            q = name + strlen(name);
            de = 0;
        };
        l = q - name;
        *p++ = l;
        memcpy(p, name, l);
        p+=l;
        name = q + 1;
    } while (l && *name && de);

    // terminating root field if any (not the case for
    // things like TXTs).
    if (de) *p++ = 0;
    return p;
}

char * mDNSResponder::mRR(char *p, char * name, uint16_t tpe, uint16_t cls, uint32_t ttl) {
    uint16_t i = 0;
    uint32_t j = 0;

    p = breakname(p, name);

    // NOTE: Cannot assume proper byte boundaries; so
    // we assume the compiler does not allow that for
    // casts - and write it out with memcpy's
    //
    i = htons(tpe); // Type
    memcpy(p, &i,  2);
    p+=2;

    i = htons(cls); // Class
    memcpy(p, &i, 2);
    p+=2;

    j = htonl(ttl); // TTL (4 bytes)
    memcpy(p, &j, 4);
    p+=4;

    return p;
}

char * mDNSResponder::mRRLABEL(char *p, char * name, uint16_t tpe, char ** rr) {
    uint16_t i = 0;

    p = mRR(p, name, tpe, DNS_RRCLASS_IN, MDNS_TTL); // Type, IN, TTL

    // RR String
    char * q = p + 2;

    for (;*rr;rr++)
        q = breakname(q, *rr);

    i = htons(q - p - 2); // RDLEN
    memcpy(p, &i, 2);
    return q;
}

char * mDNSResponder::mPTR(char *p, char * name, char * r) {
    char *rr[] = { r, NULL };
    return mRRLABEL(p, name, DNS_RRTYPE_PTR, rr );
}

char * mDNSResponder::mTXT(char *p, char * name, char ** rr) {
    return mRRLABEL(p, name, DNS_RRTYPE_TXT, rr);
}

char * mDNSResponder::mARR(char *p, char * name, ip_addr_t ip) {
    uint16_t i = 0;

    p = mRR(p, name, DNS_RRTYPE_A, DNS_RRCLASS_IN, MDNS_TTL ); // A, IN

    i = htons(4); // RDLEN - we're just doing a single IPv4 address - our primary link ?
    memcpy(p, &i, 2);

    // IP already in network order.
    memcpy(p+2, &ip, 4);

    return p + 2 + 4;
}

char * mDNSResponder::mSRV(char *p, char * name, uint16_t port, char * rr) {
    uint16_t i = 0;
    char * q;

    p = mRR(p, name, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, MDNS_TTL); // SRV, IN

    // Keep space for RDLEN
    q = p;
    p+=2;

    i = htons(1); // Priority
    memcpy(p, &i, 2);
    p+=2;

    i = htons(0); // Weight (see rfc2782)
    memcpy(p, &i, 2);
    p+=2;

    i = htons(port); // Port
    memcpy(p, &i, 2);
    p+=2;

    p = breakname(p, rr);

    i = htons(p - q - 2); // RDLEN
    memcpy(q, &i, 2);

    return p;
}

char * mDNSResponder::qANY(char *p, char * name, uint16_t tpe, uint16_t cls) {
    uint16_t i = 0;
    p = breakname(p, name); // QNAME

    i = htons(tpe); // QTYPE
    memcpy(p, &i, 2);
    p+=2;

    i = htons(cls); // QCLASS
    memcpy(p, &i, 2);
    p+=2;

    return p;
}

char * mDNSResponder::qPTR(char *p, char *name) {
    return qANY(p,name,DNS_RRTYPE_PTR,DNS_RRCLASS_IN);
}

char * mDNSResponder::qA(char *p, char *name) {
    return qANY(p,name,DNS_RRTYPE_A,DNS_RRCLASS_IN);
}

char * mDNSResponder::qSRV(char *p, char *name) {
    return qANY(p,name,DNS_RRTYPE_SRV,DNS_RRCLASS_IN);
}

/* This si wrong, i think we're answering our own question? or something? */
void mDNSResponder::sendAnnounce(void) {
    char out[ 1500 ], *p;
    DNSPacket * op = (DNSPacket *)out;

    Endpoint quack;
    quack.set_address(multicast_group->get_address(), multicast_group->get_port());

    initLabelCache(out); // Offsets are relative to the ID header.

    op->tid = ntohs(0);
    op->flags = ntohs(0x0);
    op->question_count = ntohs(1); // Courtesy copy of the question.
    op->answer_count = ntohs(0); // The record asked for
    op->a_count = ntohs(0);
    p = out + 12;

    p = qPTR(p, moi_local_proto);

    printf("sendAnnounce1\r\n");

    m_pUDPSocket->sendTo(quack, out, p-out);

    initLabelCache(out); // Offsets are relative to the ID header.

    op->flags = ntohs(0x8400U);
    op->question_count = ntohs(0);
    op->answer_count = ntohs(4);

    p = out + 12;

    p = mPTR(p, moi_local_proto, moi_local_pglue);

    if (moi_txt && * moi_txt)
        p = mTXT(p, moi_local_pglue, moi_txt);

    p = mSRV(p, moi_local_pglue, moi_port, moi_local_name);

    p = mARR(p, moi_local_name, moi_ip);
    
    printf("sendAnnounce2\r\n");
    
    m_pUDPSocket->sendTo(quack, out, p-out);
    
}

void mDNSResponder::sendReply(uint16_t t, uint16_t tid, Endpoint *dst) {
    // sent a reply - basically a precooked A/PTR/SRV with the TransactionID
    // and my URI squeezed in.
    char *out, *p;
    out = space;
    DNSPacket * op = (DNSPacket *) out;

    initLabelCache(out); // Offsets are relative to the ID header.

    op->tid = ntohs(tid);
    op->flags = ntohs(0x8000U); // Answer
    op->question_count = ntohs(1); // Courtesy copy of the question.
    op->answer_count = ntohs(1); // The record asked for
    op->a_count = ntohs(0);
    p = out + 12;

    switch (t) {
        case DNS_RRTYPE_PTR:    // PTR record, SRV, optional TXT and A with my own address.
            op->aa_count = ntohs(moi_txt ? 3 : 2);
            p = qPTR(p,moi_local_proto);

            p = mPTR(p,moi_local_proto, moi_local_pglue);
            p = mSRV(p,moi_local_pglue, moi_port, moi_local_name);

            if (moi_txt && * moi_txt)
                p = mTXT(p,moi_local_pglue,moi_txt);

            break;
        case DNS_RRTYPE_A:    // A record (and nothing else)
            op->aa_count = ntohs(0);
            p = qA(p,moi_local_name);
            break;

        case DNS_RRTYPE_SRV:   // SRV record, optional TXT and a gratious A record to complete.
            op->aa_count = ntohs(moi_txt ? 2 : 1);
            p = qSRV(p,moi_local_pglue);

            p = mSRV(p,moi_local_pglue, moi_port, moi_local_name);
            if (moi_txt && * moi_txt)
                p = mTXT(p,moi_local_pglue,moi_txt);
            break;        
    }
    p = mARR(p, moi_local_name, moi_ip);
    
    printf("sendReply\r\n");

    if (dst == NULL) {
        dst = multicast_group;
    }
    Endpoint quack;
    quack.set_address(dst->get_address(), dst->get_port());

    pretty_print_block(out, p - out);
    
    m_pUDPSocket->sendTo(quack, out, p-out);
}

char * mDNSResponder::decodeQBlock(char * udp, int len, char *p, char  * fqdn, int fqdn_size, uint32_t *Qclass, uint32_t *Qtype) {
    char * endp = udp + len;
    int depth = 0;
    int l, ll = 0;
    char * q = 0;
    char * ep = fqdn;

    do {
        while (((l = *p++) < 192) && (l > 0)) {
            if (p + l >= endp) {
                printf("Malformed packet or bug, RR field larger than packet itself\n\r");
                return NULL;
            };

            if (ll + l + 1 > fqdn_size) {
                printf("Malformed packet or bug, FQDN exceeds %d bytes\n\r", fqdn_size);
                return NULL;
            }

            memcpy(ep,p,l);
            ep[l]='.';
            ep += l + 1;
            p += l;
            ll += l + 1;
        };

        if (l >= 192) {
            // construct an offset pointer by wiping the top 1 bits
            // and then getting the remaining 8 bytes to make 14.
            l -= 192;
            l = l << 8;
            l += *p++;

            // rescue our reference; as we've not gotten the Qt's yet
            // and these follow the last entry in our record (and not
            // that from the compressed values.
            //
            if (!q) q = p;

            // printf(" [->%d] ",l);
            p = udp + l;

            if (p >= udp + len || p < udp + 12) {
                printf("Malformed packet or bug, pointer outside UDP packet\n\r");
                return NULL;
            };
        };

        if (depth++ >= 128) {
            printf("Malformed packet or bug, depth too high\n\r");
            return NULL;
        };
    } while (l);

    // Terminate the FQDN string.
    *ep = 0;

    // in case no compression was used at all.
    if (!q)
        q = p;

    *Qtype = htons(q[0] + q[1]*256);
    *Qclass = htons(q[2] + q[3]*256);
    return p;
}

void mDNSResponder::periodic(void const *n) {
    /* send this every timeout */
    mDNSResponder::sendAnnounce();
}

/*
 Do we support _services._dns-sd._udp.local as well??!?
*/
void mDNSResponder::Listen(void const *args) {
    Endpoint from;
    int len;

    printf("in Listen\r\n");

    // parse through the packet; find any PTR requests
    // and respond to any for _http._tcp._local.
    while (true) {
        printf("about to receive\r\n");
        len = m_pUDPSocket->receiveFrom(from, space, sizeof(space));
        printf("Got a packet: %s %d\r\n", from.get_address(), len);
        DNSPacket * dp = (DNSPacket *) space;
        
        unsigned int tid = ntohs(dp->tid);
        unsigned int flags = ntohs(dp->flags);
        unsigned int nQ = ntohs(dp->question_count);

        if (flags & 2 != 0 || nQ < 1) {
            printf("Not a Question\r\n");
            continue; // we only want questions
        }

        // assume nQ NON terminated fields followed by Qtype & Qclass
        //
        char * p = space + 12;
        for (int i = 0; i < nQ; i++) {
            char fqdn[ 256 ];
            uint32_t Qt, Qc;

            if ( (p = decodeQBlock(space, len, p, fqdn, sizeof(fqdn), &Qc, &Qt)) == 0)
                break;

            // We only deal with INternet.
            //
            if (Qc != DNS_RRCLASS_IN)
                continue;

           printf("IN query 0x%x for %s\n\r", Qt, fqdn);
            
            // Normally we'll respond to a mCast query for the PTR of our conceptual
            // service; to which we reply with the services we run; and then the A
            // records for our actual endpoint. However if one of the requistors their
            // cashing is not up to scratch - we may (also) get hit up a bit later for
            // the secondary records under their logical names. So we respond to
            // all 3. As this is likely to happen due to resource constraints on the
            // side of the requestor; we are careful and reply as 'narrow' as possible.
            //
            // Note that we also respond to the ANY query - as to make a quick 'dig'
            // testing command easier.
            //
            if ((Qt == DNS_RRTYPE_PTR || Qt == 255) && !(strcmp(fqdn,moi_local_proto)))
                sendReply(DNS_RRTYPE_PTR, tid, &from);
            else if ((Qt == DNS_RRTYPE_A || Qt == 255)  && !(strcmp(fqdn,moi_local_name)))
                sendReply(DNS_RRTYPE_A, tid, &from);
            else if ((Qt == DNS_RRTYPE_SRV || Qt == 255)  && !(strcmp(fqdn,moi_local_pglue)))
                sendReply(DNS_RRTYPE_SRV, tid, &from);
            else
               printf(".. which was ignored\n\r");
        }
    }
}

#endif