Bonjour/Zerconf library

Dependencies:   mbed

services/mDNS/mDNSResponder.cpp

Committer:
dirkx
Date:
2010-07-22
Revision:
1:59820ca5c83a
Parent:
0:355018f44c9f
Child:
2:816cbd922d3e

File content as of revision 1:59820ca5c83a:

/* 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 "mDNSResponder.h"
#include <stdio.h>

// #define MCAST 192,168,1,71

mDNSResponder::mDNSResponder() : NetService(false), 
    moi_txt(NULL), moi_port(0), moi_ip(0), 
    moi_name(NULL), moi_proto(NULL),
    moi_local_proto(NULL), moi_local_name(NULL), moi_local_pglue(NULL),
    announcer()
{
    // nothing yet...
}

mDNSResponder::~mDNSResponder() {
      announcer.stop();
      close();
}

void mDNSResponder::announce(IpAddr ip, const char * ldn, const char * proto, uint16_t port, const char * name, char ** txt) {
    Host localhost(IpAddr(MCAST), MDNS_PORT, NULL /* fqdn */);

    m_pUDPSocket = new UDPSocket;
    m_pUDPSocket->bind(localhost);

    m_pUDPSocket->setOnEvent(this, &mDNSResponder::onUDPSocketEvent);

    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);

    #define LOCAL "local"
    snprintf(moi_local_proto,128,"%s.%s.", moi_proto, LOCAL);
    snprintf(moi_local_name,128,"%s.%s.", ldn, LOCAL);
    snprintf(moi_local_pglue,128,"%s.%s.%s.", moi_name,moi_proto, LOCAL);
    
    // Gratuis intro - and repeat such regularly..
    //
    mDNSResponder::sendReply(0, localhost);
    announcer.start();
}

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



void mDNSResponder::poll() { //Called by NetServices
    if (announcer.read_ms() > 1000  * MDNS_INTERVAL) {
    
        Host mch(IpAddr(MCAST), MDNS_PORT, NULL);
        mDNSResponder::sendReply(0, mch);

        announcer.reset();
    }
}

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

#ifndef THREADINGCOMPRESS
#ifndef MAXCACHEDNSLABELS 
#define MAXCACHEDNSLABELS (10)
#endif
static int labelCacheCnt = 0;
static uint16_t _cache_off[MAXCACHEDNSLABELS];
static char * _cache_och[MAXCACHEDNSLABELS];
static char * labelCacheOffset = 0;

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

uint16_t checkLabelCache(char * name) {
    for(int i=0; i < MAXCACHEDNSLABELS; i++)
        if (_cache_off[i] && !strcmp(_cache_och[i],name))
            return _cache_off[i];
    return 0;
};

void addLabelCache(char * p, char * name) {
    _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>=MAXCACHEDNSLABELS)
        labelCacheCnt = MAXCACHEDNSLABELS / 3;
};
#else
#define initLabelCache(x) {} /* Ignored */
#define checkLabelCache(x) (0) /* never a hit */
#define addLabelCache(x,y) {} /* Ignored */
#endif

char * breakname(char *p, char *name) {
   int l = 0, de = 1;
   char * q;
   uint16_t off;
   
   do {
        if (off = checkLabelCache(name)) {
                *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);
    
    // terminating root field if any (not the case for 
    // things like TXTs).
    if (de) *p++ = 0;
    return p;
}

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

    p = breakname(p, name);

    // NOTE: Cannot assume proper byte boundaries.
    //
    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 * mRRLABEL(char *p, char * name, uint16_t tpe, char ** rr) {
    uint16_t i;

    p = mRR(p, name, tpe, 1, 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 * mPTR(char *p, char * name, char * r) {
    char *rr[] = { r, NULL };
    return mRRLABEL(p, name, 12 /* PTR */, rr );
}
    
char * mTXT(char *p, char * name, char ** rr) {
    return mRRLABEL(p, name, 16 /* TXT */, rr);
}

char * mARR(char *p, char * name, IpAddr ip) {
    uint16_t i;

    p = mRR(p, name, 1, 1, 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 * mSRV(char *p, char * name, uint16_t port, char * rr) {
    uint16_t i;
    char * q;
    
    p = mRR(p, name, 33, 1, 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 * qANY(char *p, char * name, uint16_t tpe, uint16_t cls) {
    uint16_t i;
    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 * qPTR(char *p, char *name) {
    return qANY(p,name,12 /* PTR */,1);
}

    
void mDNSResponder::sendReply(uint16_t tid, Host dst) {
    // sent a reply - basically a precooked A/PTR/SRV with the TransactionID
    // and my URI squeezed in.
    char out[ 1500 ], *p;
    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 PTR record asked for
    op->a_count = ntohs(0);
    
    // The 2 or 3 extra records (A, SRV and optional TXT) we know will be needed next
    op->aa_count = ntohs(moi_txt ? 3 : 2);
    
    p = out + 12;
    
    p = qPTR(p,moi_local_proto);
    p = mPTR(p,moi_local_proto, moi_local_pglue);
    p = mSRV(p,moi_local_pglue,80, moi_local_name);
    if (moi_txt && * moi_txt)
         p = mTXT(p,moi_local_pglue,moi_txt);
    p = mARR(p,moi_local_name,moi_ip); // fill out my own IP address.

    m_pUDPSocket->sendto(out,p-out,&dst);
}

void mDNSResponder::onUDPSocketEvent(UDPSocketEvent e) {
    char udp[ 1500 ];
    Host from;
    int len;

    switch (e) {
        case UDPSOCKET_READABLE: //The only event for now
            // parse through the packet; find any PTR requests
            // and respond to any for _http._tcp._local.
            while ( len = m_pUDPSocket->recvfrom( (char*)&udp, sizeof(udp), &from )) {
                DNSPacket * dp = (DNSPacket *) udp;
                unsigned int tid = ntohs(dp->tid);
                unsigned int flags = ntohs(dp->flags);
                unsigned int nQ = ntohs(dp->question_count);
                unsigned int nA = ntohs(dp->answer_count);

                if (flags & 2 != 0 || nQ == 0)
                    continue; // we only want questions

                #define MAXDEPTH 64
                #define MAXRR 192 /* including dot */
                
                // assume nQ zero terminated fields followed by Qtype & Qclass
                char * p = udp + 12;
                for (int i = 0; i < nQ; i++) {
                    char buff[ MAXDEPTH * MAXRR ], * ep = buff;
                    int depth = 0;
                    int l;
                    char * q = 0;

                    do {
                        while (((l = *p++) < 192) && (l > 0)) {
                            if (p + l >= udp + len) {
                                printf("RR longer than packet\r\n");
                                return;
                            };

                            memcpy(ep,p,l);
                            ep[l]='.';
                            ep += l + 1;
                            // printf("%d:%s.", p-udp-12,buff);
                            p += l;
                        };
                        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.
                            if (!q) q = p;

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

                            if (p >= udp + len || p < udp + 12) {
                                printf("Pointer to wrong place\r\n");
                                return;
                            };
                        };
                        if (depth++ >= MAXDEPTH) {
                            printf("Too deep\r\n");
                            return;
                        };
                    } while (l);
                    *ep = 0;
                    // in case no compression was used at all.
                    if (!q) q = p;

                    unsigned int Qt = htons(q[0] + q[1]*256);
                    unsigned int Qc = htons(q[2] + q[3]*256);
                    p = q + 4;
                    
                    printf("Q for %s\r\n", buff);
                    // We want PTR records on the INternet of our type
                    if ((Qt == 12) && (Qc == 1) && !(strcmp(buff,moi_local_proto))) 
                        sendReply(tid,from);                        
                };
            }
            break;
    }
}