Bonjour/mDNS lib, works with the new network stack
Fork of BonjourLib by
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