Bonjour/mDNS lib, works with the new network stack
Fork of BonjourLib by
Diff: mDNSResponder.cpp
- Revision:
- 2:18d443d86057
- Parent:
- 1:bb6472f455e8
- Child:
- 3:b8a78d6da192
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mDNSResponder.cpp Fri May 30 09:06:53 2014 +0000 @@ -0,0 +1,558 @@ +/* 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> + +#ifndef DNS_RRTYPE_SRV +#define DNS_RRTYPE_SRV (33) +#endif + +#ifndef DNS_LOCAL +#define DNS_LOCAL "local" +#endif + +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); +} + +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); + + 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("sendAnnounce\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[ 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 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(1); + 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()); + + 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(); +} + +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