Bonjour/mDNS lib, works with the new network stack

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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers mDNSResponder.cpp Source File

mDNSResponder.cpp

00001 /* Class: mDNSResponder
00002  * Copyright 1991, 2003, 2010 Dirk-Willem van Gulik <dirkx(at)apache(punto)org>
00003  *
00004  * License: Any BSD or ASF License.
00005  *
00006  * Rough and ready port of some mDNS code.
00007  *
00008  * Typical use is something like
00009  *
00010  * EthernetNetIf eth;
00011  * HTTPServer svr;
00012  * mDNSResponder mdns;
00013  *
00014  * int main()...
00015  *
00016  *   // get internet
00017  *   EthernetErr ethErr = eth.setup();
00018  *   ... etc ..
00019  *
00020  *   // set up some server
00021  *   svr.addHandler<SimpleHandler>("/"); //Default handler
00022  *   svr.bind(80); * *
00023 
00024  *  // Extract the IP address.
00025  *   IpAddr ip = eth.getIp();
00026  *   printf("mbed IP Address is %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3]);
00027  *
00028  *   // Announce ourselves.
00029  *   mdns.announce(ip, "fred", "_http._tcp", 80, "The Little Server that Could", "path=/demo");
00030  *
00031  *   while()... enter some run loop
00032  *   ...
00033  *
00034  *  This will cause http://fred.local./demo to be announced as 'The Little Server that Could'.
00035  *
00036  *  Or as another example: (http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt)
00037  *  and the various RFCs:
00038  *
00039  *     mdns.announce(ip, "_ssh._tcp", 22, SSH to Serial Gateway", NULL);
00040  *
00041  * CAVEAT - a lot of the buffer overrun and copy checks
00042  *          where removed; and this is not anywhere near
00043  *          threadsafve or sane. Not for production use.
00044  */
00045 #include "lwip/lwipopts.h"
00046 #if LWIP_DNS /* don't build if not configured for use in lwipopts.h */
00047 
00048 #include "mDNSResponder.h"
00049 #include <stdio.h>
00050 #include <ctype.h>
00051 
00052 #ifndef DNS_RRTYPE_SRV
00053 #define DNS_RRTYPE_SRV (33)
00054 #endif
00055 
00056 #ifndef DNS_LOCAL
00057 #define DNS_LOCAL "local"
00058 #endif
00059 
00060 void pretty_print_block(char *b, int len)
00061 {
00062     int x, y, indent, count = 0;
00063 
00064     indent = 16; /* whatever */
00065 
00066     printf("\n\r");
00067 
00068     while (count < len)
00069     {
00070         printf("%04x : ", count);
00071         for (x = 0 ; x < indent ; x++)
00072         {
00073             printf("%02x ", b[x + count]);
00074             if ((x + count + 1) >= len)
00075             {
00076                 x++;
00077                 for (y = 0 ; y < (indent - x) ; y++)
00078                     printf("   ");
00079                 break;
00080             }
00081         }
00082         printf(": ");
00083 
00084         for (x = 0 ; x < indent ; x++)
00085         {
00086             if (isprint(b[x + count]))
00087                 putchar(b[x + count]);
00088             else if (b[x + count] == 0)
00089                 putchar(' ');
00090             else
00091                 putchar('.');
00092 
00093             if ((x + count + 1) >= len)
00094             {
00095                 x++;
00096                 for (y = 0 ; y < (indent - x) ; y++)
00097                     putchar(' ');
00098                 break;
00099             }
00100         }
00101         putchar('\n');
00102         putchar('\r');
00103         count += indent;
00104     }
00105     putchar('\n');
00106     putchar('\r');
00107 }
00108 
00109 
00110 
00111 void announcer(void const *args) {
00112     printf("in announcer\r\n");
00113     mDNSResponder *m = (mDNSResponder *)args;
00114     m->periodic(NULL);
00115 }
00116 
00117 mDNSResponder::mDNSResponder() :
00118         moi_txt(NULL), moi_port(0),
00119         moi_name(NULL), moi_proto(NULL),
00120         moi_local_proto(NULL), moi_local_name(NULL), moi_local_pglue(NULL),
00121 #ifdef MDNS_QCACHE
00122         labelCacheCnt(0), labelCacheOffset(0)
00123 #endif
00124 {
00125     initLabelCache(0);
00126 }
00127 
00128 mDNSResponder::~mDNSResponder() {
00129     close();
00130 }
00131 
00132 void mDNSResponder::announce(ip_addr_t ip, const char * ldn, const char * proto, uint16_t port, const char * name, char ** txt) {
00133 
00134     m_pUDPSocket = new UDPSocket;
00135 //    m_pUDPSocket->init();
00136 
00137     multicast_group = new Endpoint;
00138     multicast_group->set_address(MCAST, MDNS_PORT);
00139 
00140     m_pUDPSocket->bind(MDNS_PORT);
00141     m_pUDPSocket->join_multicast_group(MCAST);
00142 
00143     moi_port = port;
00144     moi_txt = txt;
00145     moi_ip = ip;
00146     moi_proto = proto;
00147     moi_name = name;
00148 
00149     moi_local_proto = (char *)malloc(128);
00150     moi_local_name = (char *)malloc(128);
00151     moi_local_pglue = (char *)malloc(128);
00152 
00153     snprintf(moi_local_proto,128,"%s.%s.", moi_proto, DNS_LOCAL);
00154     snprintf(moi_local_name,128,"%s.%s.", ldn, DNS_LOCAL);
00155     snprintf(moi_local_pglue,128,"%s.%s.%s.", moi_name, moi_proto, DNS_LOCAL);
00156 
00157     printf("starting thread\r\n");
00158 
00159     // Gratuis intro - and repeat such regularly.. taking some
00160     // care to not go beyond DHCP limits - but not more often
00161     // than makes sense for our TTL. See the .h file for the
00162     // various tradeoffs.
00163     //
00164 //    sendReply(DNS_RRTYPE_PTR, 0, NULL);
00165 
00166     printf("starting announcer\r\n");
00167     mdns_announcer = new RtosTimer(announcer, osTimerPeriodic, this);
00168     mdns_announcer->start(1000 * MDNS_INTERVAL);
00169     printf("kicking it\r\n");
00170 
00171     Listen(NULL);    
00172 }
00173 
00174 void mDNSResponder::close() {
00175     m_pUDPSocket->close(true);
00176     delete m_pUDPSocket;
00177 }
00178 
00179 char * index(char * str, char c) {
00180     for (;str && *str;str++) {
00181         if (*str == c) return str;
00182     };
00183     return NULL;
00184 }
00185 
00186 #ifdef MDNS_QCACHE
00187 void mDNSResponder::initLabelCache(char * off) {
00188     labelCacheOffset = off;
00189     labelCacheCnt = 0;
00190     memset(_cache_off,0, sizeof(_cache_off)); // Rely on min value to be 12
00191 };
00192 
00193 uint16_t mDNSResponder::checkLabelCache(char * name) {
00194     // Bail out if not initialized with a valid (>12) offset.
00195     //
00196     if (!labelCacheOffset)
00197         return 0;
00198     
00199     for (int i=0; i < MDNS_MAXCACHEDNSLABELS; i++)
00200         if (_cache_off[i] && !strcmp(_cache_och[i],name))
00201             return _cache_off[i];
00202     return 0;
00203 };
00204 
00205 void mDNSResponder::addLabelCache(char * p, char * name) {
00206     // Bail out if not initialized with a valid (>12) offset.
00207     //
00208     if (!labelCacheOffset)
00209         return;
00210         
00211     _cache_off[labelCacheCnt] = p - labelCacheOffset;
00212     _cache_och[labelCacheCnt] = name;
00213 
00214     labelCacheCnt++;
00215     // we intentionally do not wack the first entries - as they are the most
00216     // likely ones to be used.
00217     if (labelCacheCnt>= MDNS_MAXCACHEDNSLABELS)
00218         labelCacheCnt = MDNS_MAXCACHEDNSLABELS / 3;
00219 };
00220 #else
00221 #define initLabelCache(x) {} /* Ignored */
00222 #define checkLabelCache(x) (0) /* never a hit */
00223 #define addLabelCache(x,y) {} /* Ignored */
00224 #endif
00225 
00226 char * mDNSResponder::breakname(char *p, char *name) {
00227     int l = 0, de = 1;
00228     char * q;
00229     uint16_t off;
00230 
00231     do {
00232         if ((off = checkLabelCache(name)) != 0) {
00233             *p++ = 192 + (off >> 8);
00234             *p++ =       (off &  0xFF);
00235             return p;
00236         } else {
00237             addLabelCache(p, name);
00238         };
00239 
00240         q = index(name,'.');
00241         if (!q) {
00242             q = name + strlen(name);
00243             de = 0;
00244         };
00245         l = q - name;
00246         *p++ = l;
00247         memcpy(p, name, l);
00248         p+=l;
00249         name = q + 1;
00250     } while (l && *name && de);
00251 
00252     // terminating root field if any (not the case for
00253     // things like TXTs).
00254     if (de) *p++ = 0;
00255     return p;
00256 }
00257 
00258 char * mDNSResponder::mRR(char *p, char * name, uint16_t tpe, uint16_t cls, uint32_t ttl) {
00259     uint16_t i = 0;
00260     uint32_t j = 0;
00261 
00262     p = breakname(p, name);
00263 
00264     // NOTE: Cannot assume proper byte boundaries; so
00265     // we assume the compiler does not allow that for
00266     // casts - and write it out with memcpy's
00267     //
00268     i = htons(tpe); // Type
00269     memcpy(p, &i,  2);
00270     p+=2;
00271 
00272     i = htons(cls); // Class
00273     memcpy(p, &i, 2);
00274     p+=2;
00275 
00276     j = htonl(ttl); // TTL (4 bytes)
00277     memcpy(p, &j, 4);
00278     p+=4;
00279 
00280     return p;
00281 }
00282 
00283 char * mDNSResponder::mRRLABEL(char *p, char * name, uint16_t tpe, char ** rr) {
00284     uint16_t i = 0;
00285 
00286     p = mRR(p, name, tpe, DNS_RRCLASS_IN, MDNS_TTL); // Type, IN, TTL
00287 
00288     // RR String
00289     char * q = p + 2;
00290 
00291     for (;*rr;rr++)
00292         q = breakname(q, *rr);
00293 
00294     i = htons(q - p - 2); // RDLEN
00295     memcpy(p, &i, 2);
00296     return q;
00297 }
00298 
00299 char * mDNSResponder::mPTR(char *p, char * name, char * r) {
00300     char *rr[] = { r, NULL };
00301     return mRRLABEL(p, name, DNS_RRTYPE_PTR, rr );
00302 }
00303 
00304 char * mDNSResponder::mTXT(char *p, char * name, char ** rr) {
00305     return mRRLABEL(p, name, DNS_RRTYPE_TXT, rr);
00306 }
00307 
00308 char * mDNSResponder::mARR(char *p, char * name, ip_addr_t ip) {
00309     uint16_t i = 0;
00310 
00311     p = mRR(p, name, DNS_RRTYPE_A, DNS_RRCLASS_IN, MDNS_TTL ); // A, IN
00312 
00313     i = htons(4); // RDLEN - we're just doing a single IPv4 address - our primary link ?
00314     memcpy(p, &i, 2);
00315 
00316     // IP already in network order.
00317     memcpy(p+2, &ip, 4);
00318 
00319     return p + 2 + 4;
00320 }
00321 
00322 char * mDNSResponder::mSRV(char *p, char * name, uint16_t port, char * rr) {
00323     uint16_t i = 0;
00324     char * q;
00325 
00326     p = mRR(p, name, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, MDNS_TTL); // SRV, IN
00327 
00328     // Keep space for RDLEN
00329     q = p;
00330     p+=2;
00331 
00332     i = htons(1); // Priority
00333     memcpy(p, &i, 2);
00334     p+=2;
00335 
00336     i = htons(0); // Weight (see rfc2782)
00337     memcpy(p, &i, 2);
00338     p+=2;
00339 
00340     i = htons(port); // Port
00341     memcpy(p, &i, 2);
00342     p+=2;
00343 
00344     p = breakname(p, rr);
00345 
00346     i = htons(p - q - 2); // RDLEN
00347     memcpy(q, &i, 2);
00348 
00349     return p;
00350 }
00351 
00352 char * mDNSResponder::qANY(char *p, char * name, uint16_t tpe, uint16_t cls) {
00353     uint16_t i = 0;
00354     p = breakname(p, name); // QNAME
00355 
00356     i = htons(tpe); // QTYPE
00357     memcpy(p, &i, 2);
00358     p+=2;
00359 
00360     i = htons(cls); // QCLASS
00361     memcpy(p, &i, 2);
00362     p+=2;
00363 
00364     return p;
00365 }
00366 
00367 char * mDNSResponder::qPTR(char *p, char *name) {
00368     return qANY(p,name,DNS_RRTYPE_PTR,DNS_RRCLASS_IN);
00369 }
00370 
00371 char * mDNSResponder::qA(char *p, char *name) {
00372     return qANY(p,name,DNS_RRTYPE_A,DNS_RRCLASS_IN);
00373 }
00374 
00375 char * mDNSResponder::qSRV(char *p, char *name) {
00376     return qANY(p,name,DNS_RRTYPE_SRV,DNS_RRCLASS_IN);
00377 }
00378 
00379 /* This si wrong, i think we're answering our own question? or something? */
00380 void mDNSResponder::sendAnnounce(void) {
00381     char out[ 1500 ], *p;
00382     DNSPacket * op = (DNSPacket *)out;
00383 
00384     Endpoint quack;
00385     quack.set_address(multicast_group->get_address(), multicast_group->get_port());
00386 
00387     initLabelCache(out); // Offsets are relative to the ID header.
00388 
00389     op->tid = ntohs(0);
00390     op->flags = ntohs(0x0);
00391     op->question_count = ntohs(1); // Courtesy copy of the question.
00392     op->answer_count = ntohs(0); // The record asked for
00393     op->a_count = ntohs(0);
00394     p = out + 12;
00395 
00396     p = qPTR(p, moi_local_proto);
00397 
00398     printf("sendAnnounce1\r\n");
00399 
00400     m_pUDPSocket->sendTo(quack, out, p-out);
00401 
00402     initLabelCache(out); // Offsets are relative to the ID header.
00403 
00404     op->flags = ntohs(0x8400U);
00405     op->question_count = ntohs(0);
00406     op->answer_count = ntohs(4);
00407 
00408     p = out + 12;
00409 
00410     p = mPTR(p, moi_local_proto, moi_local_pglue);
00411 
00412     if (moi_txt && * moi_txt)
00413         p = mTXT(p, moi_local_pglue, moi_txt);
00414 
00415     p = mSRV(p, moi_local_pglue, moi_port, moi_local_name);
00416 
00417     p = mARR(p, moi_local_name, moi_ip);
00418     
00419     printf("sendAnnounce2\r\n");
00420     
00421     m_pUDPSocket->sendTo(quack, out, p-out);
00422     
00423 }
00424 
00425 void mDNSResponder::sendReply(uint16_t t, uint16_t tid, Endpoint *dst) {
00426     // sent a reply - basically a precooked A/PTR/SRV with the TransactionID
00427     // and my URI squeezed in.
00428     char *out, *p;
00429     out = space;
00430     DNSPacket * op = (DNSPacket *) out;
00431 
00432     initLabelCache(out); // Offsets are relative to the ID header.
00433 
00434     op->tid = ntohs(tid);
00435     op->flags = ntohs(0x8000U); // Answer
00436     op->question_count = ntohs(1); // Courtesy copy of the question.
00437     op->answer_count = ntohs(1); // The record asked for
00438     op->a_count = ntohs(0);
00439     p = out + 12;
00440 
00441     switch (t) {
00442         case DNS_RRTYPE_PTR:    // PTR record, SRV, optional TXT and A with my own address.
00443             op->aa_count = ntohs(moi_txt ? 3 : 2);
00444             p = qPTR(p,moi_local_proto);
00445 
00446             p = mPTR(p,moi_local_proto, moi_local_pglue);
00447             p = mSRV(p,moi_local_pglue, moi_port, moi_local_name);
00448 
00449             if (moi_txt && * moi_txt)
00450                 p = mTXT(p,moi_local_pglue,moi_txt);
00451 
00452             break;
00453         case DNS_RRTYPE_A:    // A record (and nothing else)
00454             op->aa_count = ntohs(0);
00455             p = qA(p,moi_local_name);
00456             break;
00457 
00458         case DNS_RRTYPE_SRV:   // SRV record, optional TXT and a gratious A record to complete.
00459             op->aa_count = ntohs(moi_txt ? 2 : 1);
00460             p = qSRV(p,moi_local_pglue);
00461 
00462             p = mSRV(p,moi_local_pglue, moi_port, moi_local_name);
00463             if (moi_txt && * moi_txt)
00464                 p = mTXT(p,moi_local_pglue,moi_txt);
00465             break;        
00466     }
00467     p = mARR(p, moi_local_name, moi_ip);
00468     
00469     printf("sendReply\r\n");
00470 
00471     if (dst == NULL) {
00472         dst = multicast_group;
00473     }
00474     Endpoint quack;
00475     quack.set_address(dst->get_address(), dst->get_port());
00476 
00477     pretty_print_block(out, p - out);
00478     
00479     m_pUDPSocket->sendTo(quack, out, p-out);
00480 }
00481 
00482 char * mDNSResponder::decodeQBlock(char * udp, int len, char *p, char  * fqdn, int fqdn_size, uint32_t *Qclass, uint32_t *Qtype) {
00483     char * endp = udp + len;
00484     int depth = 0;
00485     int l, ll = 0;
00486     char * q = 0;
00487     char * ep = fqdn;
00488 
00489     do {
00490         while (((l = *p++) < 192) && (l > 0)) {
00491             if (p + l >= endp) {
00492                 printf("Malformed packet or bug, RR field larger than packet itself\n\r");
00493                 return NULL;
00494             };
00495 
00496             if (ll + l + 1 > fqdn_size) {
00497                 printf("Malformed packet or bug, FQDN exceeds %d bytes\n\r", fqdn_size);
00498                 return NULL;
00499             }
00500 
00501             memcpy(ep,p,l);
00502             ep[l]='.';
00503             ep += l + 1;
00504             p += l;
00505             ll += l + 1;
00506         };
00507 
00508         if (l >= 192) {
00509             // construct an offset pointer by wiping the top 1 bits
00510             // and then getting the remaining 8 bytes to make 14.
00511             l -= 192;
00512             l = l << 8;
00513             l += *p++;
00514 
00515             // rescue our reference; as we've not gotten the Qt's yet
00516             // and these follow the last entry in our record (and not
00517             // that from the compressed values.
00518             //
00519             if (!q) q = p;
00520 
00521             // printf(" [->%d] ",l);
00522             p = udp + l;
00523 
00524             if (p >= udp + len || p < udp + 12) {
00525                 printf("Malformed packet or bug, pointer outside UDP packet\n\r");
00526                 return NULL;
00527             };
00528         };
00529 
00530         if (depth++ >= 128) {
00531             printf("Malformed packet or bug, depth too high\n\r");
00532             return NULL;
00533         };
00534     } while (l);
00535 
00536     // Terminate the FQDN string.
00537     *ep = 0;
00538 
00539     // in case no compression was used at all.
00540     if (!q)
00541         q = p;
00542 
00543     *Qtype = htons(q[0] + q[1]*256);
00544     *Qclass = htons(q[2] + q[3]*256);
00545     return p;
00546 }
00547 
00548 void mDNSResponder::periodic(void const *n) {
00549     /* send this every timeout */
00550     mDNSResponder::sendAnnounce();
00551 }
00552 
00553 /*
00554  Do we support _services._dns-sd._udp.local as well??!?
00555 */
00556 void mDNSResponder::Listen(void const *args) {
00557     Endpoint from;
00558     int len;
00559 
00560     printf("in Listen\r\n");
00561 
00562     // parse through the packet; find any PTR requests
00563     // and respond to any for _http._tcp._local.
00564     while (true) {
00565         printf("about to receive\r\n");
00566         len = m_pUDPSocket->receiveFrom(from, space, sizeof(space));
00567         printf("Got a packet: %s %d\r\n", from.get_address(), len);
00568         DNSPacket * dp = (DNSPacket *) space;
00569         
00570         unsigned int tid = ntohs(dp->tid);
00571         unsigned int flags = ntohs(dp->flags);
00572         unsigned int nQ = ntohs(dp->question_count);
00573 
00574         if (flags & 2 != 0 || nQ < 1) {
00575             printf("Not a Question\r\n");
00576             continue; // we only want questions
00577         }
00578 
00579         // assume nQ NON terminated fields followed by Qtype & Qclass
00580         //
00581         char * p = space + 12;
00582         for (int i = 0; i < nQ; i++) {
00583             char fqdn[ 256 ];
00584             uint32_t Qt, Qc;
00585 
00586             if ( (p = decodeQBlock(space, len, p, fqdn, sizeof(fqdn), &Qc, &Qt)) == 0)
00587                 break;
00588 
00589             // We only deal with INternet.
00590             //
00591             if (Qc != DNS_RRCLASS_IN)
00592                 continue;
00593 
00594            printf("IN query 0x%x for %s\n\r", Qt, fqdn);
00595             
00596             // Normally we'll respond to a mCast query for the PTR of our conceptual
00597             // service; to which we reply with the services we run; and then the A
00598             // records for our actual endpoint. However if one of the requistors their
00599             // cashing is not up to scratch - we may (also) get hit up a bit later for
00600             // the secondary records under their logical names. So we respond to
00601             // all 3. As this is likely to happen due to resource constraints on the
00602             // side of the requestor; we are careful and reply as 'narrow' as possible.
00603             //
00604             // Note that we also respond to the ANY query - as to make a quick 'dig'
00605             // testing command easier.
00606             //
00607             if ((Qt == DNS_RRTYPE_PTR || Qt == 255) && !(strcmp(fqdn,moi_local_proto)))
00608                 sendReply(DNS_RRTYPE_PTR, tid, &from);
00609             else if ((Qt == DNS_RRTYPE_A || Qt == 255)  && !(strcmp(fqdn,moi_local_name)))
00610                 sendReply(DNS_RRTYPE_A, tid, &from);
00611             else if ((Qt == DNS_RRTYPE_SRV || Qt == 255)  && !(strcmp(fqdn,moi_local_pglue)))
00612                 sendReply(DNS_RRTYPE_SRV, tid, &from);
00613             else
00614                printf(".. which was ignored\n\r");
00615         }
00616     }
00617 }
00618 
00619 #endif