#include "dns.h" #include "log.h" #include "printf.h" #include "str.h" #include "timer.h" #include "url.h" #include "util.h" struct dns_data { struct dns_data *next; struct mg_connection *c; uint64_t expire; uint16_t txnid; }; static void sendnsreq(struct mg_connection *, struct mg_str *, int, struct mg_dns *, bool); struct mdns_data { struct mdns_data *next; struct mg_connection *c; uint64_t expire; struct mg_str name; }; static void sendmdnsreq(struct mg_connection *, struct mg_str *, int, struct mg_connection *, bool); static void dns_free(struct dns_data **head, struct dns_data *d) { LIST_DELETE(struct dns_data, head, d); mg_free(d); } static void mdns_free(struct mdns_data **head, struct mdns_data *d) { LIST_DELETE(struct mdns_data, head, d); mg_free((void *) d->name.buf); mg_free(d); } void mg_resolve_cancel(struct mg_connection *c) { struct dns_data *tmp, *d; struct mdns_data *mtmp, *md; struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; struct mdns_data **mhead = (struct mdns_data **) &c->mgr->active_mdns_requests; for (d = *head; d != NULL; d = tmp) { tmp = d->next; if (d->c == c) dns_free(head, d); } for (md = *mhead; md != NULL; md = mtmp) { mtmp = md->next; if (md->c == c) mdns_free(mhead, md); } } static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, char *to, size_t tolen, size_t j, int depth) { size_t i = 0; if (tolen > 0 && depth == 0) to[0] = '\0'; if (depth > 5) return 0; // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); while (ofs + i + 1 < len) { size_t n = s[ofs + i]; if (n == 0) { i++; break; } if (n & 0xc0) { size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len // MG_INFO(("PTR %lx", (unsigned long) ptr)); if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) return 0; i += 2; break; } if (ofs + i + n + 1 >= len) return 0; if (j > 0) { if (j < tolen) to[j] = '.'; j++; } if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); j += n; i += n + 1; if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk // MG_INFO(("--> [%s]", to)); } if (tolen > 0) to[tolen - 1] = '\0'; // Make sure it is nul-term return i; } static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, char *dst, size_t dstlen) { return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); } size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, bool is_question, struct mg_dns_rr *rr) { const uint8_t *s = buf + ofs, *e = &buf[len]; memset(rr, 0, sizeof(*rr)); if (len < sizeof(struct mg_dns_header)) return 0; // Too small if (len > 512) return 0; // Too large, we don't expect that if (s >= e) return 0; // Overflow if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) return 0; s += rr->nlen + 4; if (s > e) return 0; rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); if (is_question) return (size_t) (rr->nlen + 4); s += 6; if (s > e) return 0; rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); if (s + rr->alen > e) return 0; return (size_t) (rr->nlen + rr->alen + 10); } bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { const struct mg_dns_header *h = (struct mg_dns_header *) buf; struct mg_dns_rr rr; size_t i, n, num_answers, ofs = sizeof(*h); bool is_response; memset(dm, 0, sizeof(*dm)); if (len < sizeof(*h)) return 0; // Too small, headers dont fit if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity num_answers = mg_ntohs(h->num_answers); if (num_answers > 10) { MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers)); num_answers = 10; // Sanity cap } dm->txnid = mg_ntohs(h->txnid); is_response = mg_ntohs(h->flags) & 0x8000; for (i = 0; i < mg_ntohs(h->num_questions); i++) { if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); ofs += n; } if (!is_response) { // For queries, there is no need to parse the answers. In this way, // we also ensure the domain name (dm->name) is parsed from // the question field. return true; } for (i = 0; i < num_answers; i++) { if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, // dm->name)); mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); ofs += n; if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && rr.aclass == 1) { dm->addr.is_ip6 = false; memcpy(&dm->addr.addr.ip, &buf[ofs - 4], 4); dm->resolved = true; break; // Return success } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && rr.aclass == 1) { dm->addr.is_ip6 = true; memcpy(&dm->addr.addr.ip, &buf[ofs - 16], 16); dm->resolved = true; break; // Return success } } return true; } static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { struct dns_data *d, *tmp; struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; if (ev == MG_EV_POLL) { uint64_t now = *(uint64_t *) ev_data; for (d = *head; d != NULL; d = tmp) { tmp = d->next; // MG_DEBUG(("%lu %lu dns poll", d->expire, now)); if (now > d->expire) mg_error(d->c, "DNS timeout"); // will remove entry } } else if (ev == MG_EV_READ) { struct mg_dns_message dm; int resolved = 0; if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { MG_ERROR(("Unexpected DNS response:")); mg_hexdump(c->recv.buf, c->recv.len); } else { // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); for (d = *head; d != NULL; d = tmp) { tmp = d->next; // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); if (dm.txnid != d->txnid) continue; if (d->c->is_resolving) { if (dm.resolved) { dm.addr.port = d->c->rem.port; // Save port d->c->rem = dm.addr; // Copy resolved address MG_DEBUG( ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem)); mg_connect_resolved(d->c); #if MG_ENABLE_IPV6 } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && c->mgr->use_dns6 == false) { struct mg_str x = mg_str(dm.name); sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); #endif } else { mg_error(d->c, "%s DNS lookup failed", dm.name); } } else { MG_ERROR(("%lu already resolved", d->c->id)); } dns_free(head, d); resolved = 1; } } if (!resolved) MG_ERROR(("stray DNS reply")); c->recv.len = 0; } else if (ev == MG_EV_CLOSE) { for (d = *head; d != NULL; d = tmp) { tmp = d->next; mg_error(d->c, "DNS error"); // will remove entry } } } static bool dns_send(struct mg_connection *c, const struct mg_str *name, unsigned int rtype, uint16_t txnid, uint16_t flags) { struct { // RFC-1035 4.1.2 struct mg_dns_header header; uint8_t data[256]; } pkt; size_t i, n; memset(&pkt, 0, sizeof(pkt)); pkt.header.txnid = mg_htons(txnid); pkt.header.flags = mg_htons(flags); pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { if (name->buf[i] == '.' || i >= name->len) { pkt.data[n] = (uint8_t) (i - n); memcpy(&pkt.data[n + 1], name->buf + n, i - n); n = i + 1; } if (i >= name->len) break; } memset(&pkt.data[n], 0, 5); // nul, QTYPE, QCLASS pkt.data[n + 2] = (uint8_t) rtype; // QTYPE = rtype, only 0-255 supported pkt.data[n + 4] = 1; // QCLASS = IN n += 5; return mg_send(c, &pkt, sizeof(pkt.header) + n); } static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, uint16_t txnid, bool ipv6) { return dns_send(c, name, ipv6 ? MG_DNS_RTYPE_AAAA : MG_DNS_RTYPE_A, txnid, 0x100); // RD } bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) { if (dnsc->url == NULL) { MG_ERROR(("DNS server URL is NULL. Call mg_mgr_init()")); return false; } if (dnsc->c == NULL) { dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL); if (dnsc->c == NULL) return false; } dnsc->c->pfn = dns_cb; // do it always, DHCP-assigned DNS bootstrap needs it return true; } static void sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, struct mg_dns *dnsc, bool ipv6) { struct dns_data *d = NULL; if (!mg_dnsc_init(c->mgr, dnsc)) { mg_error(c, "resolver"); } else if ((d = (struct dns_data *) mg_calloc(1, sizeof(*d))) == NULL) { mg_error(c, "resolve OOM"); } else { struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; uint16_t id; mg_random(&id, sizeof(uint16_t)); if (reqs != NULL) // no seq, no collision for upto 256 in-flight requests id = (uint16_t) (reqs->txnid + (id & 0xFF) + 1); d->txnid = id; d->next = reqs; c->mgr->active_dns_requests = d; d->expire = mg_millis() + (uint64_t) ms; d->c = c; c->is_resolving = 1; MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, name->buf, dnsc->url, d->txnid)); if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { mg_error(dnsc->c, "DNS send"); } } } void mg_resolve(struct mg_connection *c, const char *url) { struct mg_str host = mg_url_host(url); c->rem.port = mg_htons(mg_url_port(url)); if (mg_aton(host, &c->rem)) { // host is an IP address, do not fire name resolution mg_connect_resolved(c); } else if (host.len > 6 && strncmp(".local", &host.buf[host.len - 6], 6) == 0) { // this is a request for a .local name (mDNS) sendmdnsreq(c, &host, 500, c->mgr->mdns, c->mgr->use_dns6); // 500ms tmout } else { // host is not an IP nor a .local, send DNS resolution request struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); } } // Response header length is 10 bytes static const uint8_t mdns_answer[] = { 0, 1, // 2 bytes - record type, A 0, 1, // 2 bytes - address class, INET 0, 0, 0, 120, // 4 bytes - TTL 0, 4 // 2 bytes - address length }; // A name length is name->len + '.local' + 2 = name->len + 8 static uint8_t *build_name(struct mg_str *name, uint8_t *p) { *p++ = (uint8_t) name->len; // label 1 memcpy(p, name->buf, name->len), p += name->len; *p++ = 5; // label 2 memcpy(p, "local", 5), p += 5; *p++ = 0; // no more labels return p; } void mg_getlocaddr(struct mg_connection *, struct mg_addr *, struct mg_addr *); // An A record length is 10 + 4 = 14 bytes static uint8_t *build_a_record(struct mg_connection *c, uint8_t *p, struct mg_addr *addr) { memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer); if (addr != NULL && !addr->is_ip6) { memcpy(p, &addr->addr.ip4, 4), p += 4; } else { #if MG_ENABLE_TCPIP memcpy(p, &c->mgr->ifp->ip, 4), p += 4; #else struct mg_addr loc, to; memset(&loc, 0, sizeof(loc)); to.is_ip6 = false; to.port = mg_htons(5353); to.addr.ip4 = MG_IPV4(224, 0, 0, 51); mg_getlocaddr(c, &to, &loc); memcpy(p, &loc.addr.ip4, 4), p += 4; #endif } return p; } // A srv name length is r->srvcproto.len + '.local' + 2 = r->srvcproto.len + 8 static uint8_t *build_srv_name(uint8_t *p, struct mg_dnssd_record *r) { *p++ = (uint8_t) r->srvcproto.len - 5; // label 1, up to '._tcp' memcpy(p, r->srvcproto.buf, r->srvcproto.len), p += r->srvcproto.len; p[-5] = 4; // label 2, '_tcp', overwrite '.' *p++ = 5; // label 3 memcpy(p, "local", 5), p += 5; *p++ = 0; // no more labels return p; } #if 0 // TODO(): for listing static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p, struct mg_dnssd_record *r) { *p++ = name->len; // label 1 memcpy(p, name->buf, name->len), p += name->len; return build_srv_name(p, r); } #endif // A PTR record length is 10 + name->len + 3 = name->len + 13 static uint8_t *build_ptr_record(struct mg_str *name, uint8_t *p, uint16_t o) { uint16_t offset = mg_htons(o); memcpy(p, mdns_answer, sizeof(mdns_answer)); p[1] = MG_DNS_RTYPE_PTR; // overwrite record type p += sizeof(mdns_answer); p[-1] = (uint8_t) name->len + 3; // overwrite response length, label length + label + offset *p++ = (uint8_t) name->len; // response: label 1 memcpy(p, name->buf, name->len), p += name->len; // copy label memcpy(p, &offset, 2); *p |= 0xC0, p += 2; return p; } // An SRV record length is 10 + name->len + 9 = name->len + 19 static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, struct mg_dnssd_record *r, uint16_t o) { uint16_t port = mg_htons(r->port); uint16_t offset = mg_htons(o); memcpy(p, mdns_answer, sizeof(mdns_answer)); p[1] = MG_DNS_RTYPE_SRV; // overwrite record type p += sizeof(mdns_answer); p[-1] = (uint8_t) name->len + 9; // overwrite response length (4+2+1+2) *p++ = 0; // priority *p++ = 0; *p++ = 0; // weight *p++ = 0; memcpy(p, &port, 2), p += 2; // port *p++ = (uint8_t) name->len; // label 1 memcpy(p, name->buf, name->len), p += name->len; memcpy(p, &offset, 2); *p |= 0xC0, p += 2; return p; } // A TXT record length is r->txt.len (txt contents) + 10 static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) { uint16_t len = mg_htons((uint16_t) r->txt.len); memcpy(p, mdns_answer, sizeof(mdns_answer)); p[1] = MG_DNS_RTYPE_TXT; // overwrite record type p += sizeof(mdns_answer); memcpy(p - 2, &len, 2); // overwrite response length memcpy(p, r->txt.buf, r->txt.len), p += r->txt.len; // copy record verbatim return p; } // Each additional record has a 2-byte field pointing to the name label // RFC-6762 16: case-insensitivity --> RFC-1034, 1035 static void handle_mdns_query(struct mg_connection *c) { struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf; struct mg_dns_rr rr; size_t n; // Parse first question, offset 12 is header size n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); MG_VERBOSE(("mDNS request parsed, result=%d", (int) n)); if (n > 0) { // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; struct mg_dns_header *h = (struct mg_dns_header *) buf; uint8_t *p = &buf[sizeof(*h)]; char name[256]; uint8_t name_len; // uint16_t q = mg_ntohs(qh->num_questions); struct mg_str defname = mg_str((const char *) c->fn_data); struct mg_str *respname; struct mg_mdns_req req; memset(&req, 0, sizeof(req)); req.is_unicast = (rr.aclass & MG_BIT(15)) != 0; // QU rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response) qh->num_questions = mg_htons(1); // parser sanity mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name)); name_len = (uint8_t) strlen(name); // verify it ends in .local if (name_len <= 6 || strcmp(".local", &name[name_len - 6]) != 0 || (rr.aclass != 1 && rr.aclass != 0xff)) return; name[name_len -= 6] = '\0'; // remove .local MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, (unsigned int) rr.aclass, name)); if (rr.atype == MG_DNS_RTYPE_A) { // if we have a name to match, go; otherwise users will match and fill // req.r.name and set req.is_resp if (c->fn_data != NULL && mg_casecmp((char *) c->fn_data, name) != 0) return; req.is_resp = (c->fn_data != NULL); req.reqname = mg_str_n(name, name_len); } else // users have to match the request to something in their db, then // fill req.r and set req.is_resp if (rr.atype == MG_DNS_RTYPE_PTR) { if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true; MG_DEBUG( ("PTR request for %s", req.is_listing ? "services listing" : name)); req.reqname = mg_str_n(name, name_len); } else if (rr.atype == MG_DNS_RTYPE_SRV || rr.atype == MG_DNS_RTYPE_TXT) { MG_DEBUG(("%s request for %s", rr.atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", name)); // if possible, check it starts with our name, users will check it ends // in a service name they handle if (c->fn_data != NULL) { if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 || name[defname.len] != '.') return; req.reqname = mg_str_n(name + defname.len + 1, name_len - defname.len - 1); MG_DEBUG( ("That's us, handing %.*s", req.reqname.len, req.reqname.buf)); } else { req.reqname = mg_str_n(name, name_len); } } else { // unhandled record return; } req.rr = &rr; mg_call(c, MG_EV_MDNS_REQ, &req); if (!req.is_resp) return; respname = req.respname.buf != NULL ? &req.respname : &defname; memset(h, 0, sizeof(*h)); // clear header h->txnid = req.is_unicast ? qh->txnid : 0; // RFC-6762 18.1 h->num_answers = mg_htons(1); // RFC-6762 6: 0 questions, 1 Answer h->flags = mg_htons(0x8400); // Authoritative response if (req.is_listing) { // TODO(): RFC-6762 6: each responder SHOULD delay its response by a // random amount of time selected with uniform random distribution in the // range 20-120 ms. // TODO(): return; } else if (rr.atype == MG_DNS_RTYPE_PTR) { // serve PTR + SRV + TXT + A // TODO(): RFC-6762 6: each responder SHOULD delay its response by a // random amount of time selected with uniform random distribution in the // range 20-120 ms. Response to PTR is local_name._myservice._tcp.local uint8_t *o = p, *aux; uint16_t offset; if (respname->buf == NULL || respname->len == 0) return; if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 13 + 2 + respname->len + 19 + 2 + req.r->txt.len + 10 + 2 + 14) > sizeof(buf)) // srv name + PTR + 2 + SRV + 2 + TXT + 2 + A return; h->num_other_prs = mg_htons(3); // 3 additional records p = build_srv_name(p, req.r); aux = build_ptr_record(respname, p, (uint16_t) (o - buf)); o = p + sizeof(mdns_answer); // point to PTR response (full srvc name) offset = mg_htons((uint16_t) (o - buf)); o = p - 7; // point to '.local' label (\x05local\x00) p = aux; memcpy(p, &offset, 2); // point to full srvc name, in record *p |= 0xC0, p += 2; aux = p; p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); o = aux + sizeof(mdns_answer) + 6; // point to target in SRV memcpy(p, &offset, 2); // point to full srvc name, in record *p |= 0xC0, p += 2; p = build_txt_record(p, req.r); offset = mg_htons((uint16_t) (o - buf)); memcpy(p, &offset, 2); // point to target name, in record *p |= 0xC0, p += 2; p = build_a_record(c, p, req.addr); } else if (rr.atype == MG_DNS_RTYPE_TXT) { if ((sizeof(*h) + req.r->srvcproto.len + 8 + req.r->txt.len + 10) > sizeof(buf)) // srv name + TXT return; p = build_srv_name(p, req.r); p = build_txt_record(p, req.r); } else if (rr.atype == MG_DNS_RTYPE_SRV) { // serve SRV + A uint8_t *o, *aux; uint16_t offset; if (respname->buf == NULL || respname->len == 0) return; if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 19 + 2 + 14) > sizeof(buf)) // srv name + SRV + 2 + A return; h->num_other_prs = mg_htons(1); // 1 additional record p = build_srv_name(p, req.r); o = p - 7; // point to '.local' label (\x05local\x00) aux = p; p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); o = aux + sizeof(mdns_answer) + 6; // point to target in SRV offset = mg_htons((uint16_t) (o - buf)); memcpy(p, &offset, 2); // point to target name, in record *p |= 0xC0, p += 2; p = build_a_record(c, p, req.addr); } else { // A requested // RFC-6762 6: 0 Auth, 0 Additional RRs if (respname->buf == NULL || respname->len == 0) return; if ((sizeof(*h) + respname->len + 8 + 14) > sizeof(buf)) // name + A return; p = build_name(respname, p); p = build_a_record(c, p, req.addr); } if (!req.is_unicast) mg_multicast_restore(c, (uint8_t *) &c->loc); mg_send(c, buf, (size_t) (p - buf)); // And send it! MG_DEBUG(("%M > %M", mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem)); MG_DEBUG(("mDNS %s response sent", req.is_unicast ? "unicast" : "mcast")); } } static size_t srvtoname(char *svc) { char *dot = strchr(svc, '.'); if (dot == NULL) return 0; *dot = '\0'; return (size_t) (dot - svc); } #define MG_MAX_MDNS_RECORDS 8 // Condense one useful mDNS/DNS-SD chain from a response datagram into a single // MG_EV_MDNS_RESP event. A/AAAA as the first supported record are terminal // address answers (AAAA currently ignored). For PTR/SRV/TXT, subsequent // records are attached only when their label matches the service instance, or // the SRV target for address records. // Some devices send SRV/TXT as extra Answer RRs instead of Additional RRs, so // both sections are parsed as one chain. static void handle_mdns_response(struct mg_connection *c) { struct mg_dns_header *rh = (struct mg_dns_header *) c->recv.buf; struct mg_dns_rr rr, rr_[5]; // A, PTR, SRV, TXT, AAAA, in that order struct mg_mdns_resp resp; size_t n, i, num, answers, roff_[5], roff = 12; // offset 12 is header size // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 char name[256], srvcproto[256], instance[256], host[256]; uint16_t atype = 0; answers = mg_ntohs(rh->num_answers); num = answers + mg_ntohs(rh->num_other_prs); if (answers == 0) return; MG_VERBOSE(("mDNS response: %u answer, %u additional", (unsigned) answers, (unsigned) (num - answers))); if (num > MG_MAX_MDNS_RECORDS) { num = MG_MAX_MDNS_RECORDS; MG_DEBUG(("ignoring > %u records", MG_MAX_MDNS_RECORDS)); } memset(roff_, 0, sizeof(roff_)); memset(&rr_, 0, sizeof(rr_)); memset(&resp, 0, sizeof(resp)); instance[0] = host[0] = '\0'; for (i = 0; i < num; i++) { // First Answer RR is primary; the rest must match it n = mg_dns_parse_rr(c->recv.buf, c->recv.len, roff, false, &rr); MG_VERBOSE(("mDNS record parsed, result=%u", n)); if (n == 0) return; if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff, name, sizeof(name)) == 0) return; MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, (unsigned int) rr.aclass, name)); if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && (rr.aclass & 0x7FFF) == 1 && (i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) || (host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) { if (rr_[0].atype != 0) break; if (i == 0) atype = rr.atype; resp.name = mg_str(name); resp.addr.is_ip6 = false; memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 4, 4); MG_VERBOSE(("A record")); roff_[0] = roff, rr_[0] = rr; if (atype == MG_DNS_RTYPE_A) break; } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1 && (i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) || (host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) { # if 0 if (rr_[4].atype != 0) break; resp.addr.is_ip6 = true; if (i == 0) atype = rr.atype; resp.name = mg_str(name); memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 16, 16); MG_VERBOSE(("AAAA record")); roff_[4] = roff, rr_[4] = rr; if (atype == MG_DNS_RTYPE_AAAA) break; #else MG_VERBOSE(("ignored AAAA record")); #endif } else if (rr.atype == MG_DNS_RTYPE_PTR && (rr.aclass & 0x7FFF) == 1 && i == 0) { if (rr_[1].atype != 0) break; atype = rr.atype; if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 10, instance, sizeof(instance)) == 0) return; MG_VERBOSE(("PTR record")); roff_[1] = roff, rr_[1] = rr; } else if (rr.atype == MG_DNS_RTYPE_SRV && rr.alen >= 6 && (rr.aclass & 0x7FFF) == 1 && (i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) { if (rr_[2].atype != 0) break; if (i == 0) atype = rr.atype; resp.sd.port = MG_LOAD_BE16(c->recv.buf + roff + rr.nlen + 14); resp.addr.port = mg_htons(resp.sd.port); if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name); if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 16, host, sizeof(host)) == 0) return; MG_VERBOSE(("SRV record")); roff_[2] = roff, rr_[2] = rr; } else if (rr.atype == MG_DNS_RTYPE_TXT && (rr.aclass & 0x7FFF) == 1 && (i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) { if (rr_[3].atype != 0) break; if (i == 0) atype = rr.atype; if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name); resp.sd.txt = mg_str_n((char *) c->recv.buf + roff + rr.nlen + 10, rr.alen); MG_VERBOSE(("TXT record")); roff_[3] = roff, rr_[3] = rr; } roff += n; if (i == 0 && atype == 0) return; } if (atype == MG_DNS_RTYPE_A) { resp.rr = &rr_[0]; MG_DEBUG(("A response from %s = %M", name, mg_print_ip, &resp.addr)); #if 0 } else if (atype == MG_DNS_RTYPE_AAAA) { resp.rr = &rr_[4]; MG_DEBUG(("AAAA response from %s = %M", name, mg_print_ip6, resp.addr.addr.ip)); #endif } else if (atype == MG_DNS_RTYPE_PTR) { size_t len; resp.rr = &rr_[1]; if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[1], srvcproto, sizeof(srvcproto)) == 0) return; len = strlen(srvcproto); if (len <= 6) return; resp.sd.srvcproto = mg_str_n(srvcproto, len - 6); // remove .local if (host[0] != '\0') { resp.name = mg_str(host); } else if (rr_[0].atype == 0) { mg_snprintf(name, sizeof(name), "%s", instance); resp.name = mg_str_n(name, srvtoname(name)); } else { if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[0], name, sizeof(name)) == 0) return; resp.name = mg_str(name); } MG_DEBUG(("PTR response for %s from %s", srvcproto, name)); } else if (atype == MG_DNS_RTYPE_SRV || atype == MG_DNS_RTYPE_TXT) { size_t len, name_len; resp.rr = atype == MG_DNS_RTYPE_SRV ? &rr_[2] : &rr_[3]; if (mg_dns_parse_name(c->recv.buf, c->recv.len, atype == MG_DNS_RTYPE_SRV ? roff_[2] : roff_[3], srvcproto, sizeof(srvcproto)) == 0) return; len = strlen(srvcproto); name_len = srvtoname(srvcproto); if (name_len == 0 || len <= name_len + 7) return; // remove .local resp.sd.srvcproto = mg_str_n(srvcproto + name_len + 1, len - name_len - 7); resp.name = mg_str_n(srvcproto, name_len); MG_DEBUG(("%s response for %s from %s", atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", srvcproto, name)); } else { return; } mg_call(c, MG_EV_MDNS_RESP, &resp); } static void handle_mdns_record(struct mg_connection *c) { struct mg_dns_header *h = (struct mg_dns_header *) c->recv.buf; if (c->recv.len <= 12) return; if ((h->flags & mg_htons(0xF800)) == 0) { // flags -> !resp, opcode=0 => query; ignore other opcodes handle_mdns_query(c); } else if ((h->flags & mg_htons(0xF800)) == mg_htons(0x8000)) { // flags -> resp, opcode=0 => response; ignore other opcodes handle_mdns_response(c); } } static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) { struct mdns_data *d, *tmp; struct mdns_data **head = (struct mdns_data **) &c->mgr->active_mdns_requests; // mDNS resolver if (ev == MG_EV_POLL) { uint64_t now = *(uint64_t *) ev_data; for (d = *head; d != NULL; d = tmp) { tmp = d->next; // MG_DEBUG(("%lu %lu mdns poll", d->expire, now)); if (now > d->expire) mg_error(d->c, "mDNS timeout"); // will remove entry } } else if (ev == MG_EV_CLOSE) { for (d = *head; d != NULL; d = tmp) { tmp = d->next; mg_error(d->c, "mDNS listener error"); // this will remove entry } } else if (ev == MG_EV_MDNS_RESP) { struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data; if (resp->rr->atype == MG_DNS_RTYPE_A) { for (d = *head; d != NULL; d = tmp) { tmp = d->next; if (mg_strcasecmp(d->name, resp->name) != 0) continue; if (d->c->is_resolving) { resp->addr.port = d->c->rem.port; // Save port d->c->rem = resp->addr; // Copy resolved address MG_DEBUG(("%lu %.*s is %M", d->c->id, resp->name.len, resp->name.buf, mg_print_ip, &d->c->rem)); mg_connect_resolved(d->c); } else { // this should not happen, unless above does not clear c->is_resolving MG_ERROR(("%lu already resolved", d->c->id)); } mdns_free(head, d); } } } else if (ev == MG_EV_READ) { // generic mDNS[-SD] handling handle_mdns_record(c); // this will call us back with MG_EV_MDNS_RESP mg_iobuf_del(&c->recv, 0, c->recv.len); } (void) ev_data; } void mg_multicast_add(struct mg_connection *c, char *ip); struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data); if (c == NULL) return NULL; c->mgr->mdns = c; // Add mDNS entry to enable resolver to use it c->pfn = mdns_cb, c->pfn_data = fn_data; mg_multicast_add(c, (char *) "224.0.0.251"); return c; } static bool mdns_query(struct mg_connection *c, struct mg_str *name, unsigned int rtype) { mg_multicast_restore(c, (uint8_t *) &c->loc); return dns_send(c, name, rtype, 0, 0); // RFC-6762 18.1 id = 0, 18.6 RD = 0 } bool mg_mdns_query(struct mg_connection *c, const char *name, unsigned int rtype) { struct mg_str name_; name_.buf = (char *) name, name_.len = strlen(name); return mdns_query(c, &name_, rtype); } static void sendmdnsreq(struct mg_connection *c, struct mg_str *name, int ms, struct mg_connection *mdnsc, bool ipv6) { struct mdns_data *d = NULL; if (mdnsc == NULL) { mg_error(c, "no mDNS listener, see mg_mdns_listen()"); } else if ((d = (struct mdns_data *) mg_calloc(1, sizeof(*d))) == NULL) { mg_error(c, "resolve OOM"); } else { struct mdns_data *reqs = (struct mdns_data *) c->mgr->active_mdns_requests; d->next = reqs; c->mgr->active_mdns_requests = d; d->expire = mg_millis() + (uint64_t) ms; d->name = mg_strdup(*name); d->c = c; c->is_resolving = 1; MG_VERBOSE( ("%lu resolving %.*s via mDNS", c->id, (int) name->len, name->buf)); if (!mdns_query(mdnsc, name, MG_DNS_RTYPE_A)) { mg_error(c, "mDNS send"); // will remove newly created entry } } (void) ipv6; }