@@ -410,69 +410,245 @@ static const uint8_t mdns_answer[] = {
410410 0, 4 // 2 bytes - address length
411411};
412412
413- static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) {
414- if (ev == MG_EV_READ) {
415- struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf;
416- if (c->recv.len > 12 && (qh->flags & mg_htons(0xF800)) == 0) {
417- // flags -> !resp, opcode=0 => query; ignore other opcodes and responses
418- struct mg_dns_rr rr; // Parse first question, offset 12 is header size
419- size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
420- MG_VERBOSE(("mDNS request parsed, result=%d", (int) n));
421- if (n > 0) {
422- // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
423- // buf and h declared here to ease future expansion to DNS-SD
424- uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4];
425- struct mg_dns_header *h = (struct mg_dns_header *) buf;
426- char local_name[63 + 7]; // name label + '.' + local label + '\0'
427- uint8_t name_len = (uint8_t) strlen((char *)c->fn_data);
428- struct mg_dns_message dm;
429- bool unicast = (rr.aclass & MG_BIT(15)) != 0; // QU
430- // uint16_t q = mg_ntohs(qh->num_questions);
431- rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response)
432- qh->num_questions = mg_htons(1); // parser sanity
433- mg_dns_parse(c->recv.buf, c->recv.len, &dm);
434- if (name_len > (sizeof(local_name) - 7)) // leave room for .local\0
435- name_len = sizeof(local_name) - 7;
436- memcpy(local_name, c->fn_data, name_len);
437- strcpy(local_name + name_len, ".local"); // ensure proper name.local\0
438- if (strcmp(local_name, dm.name) == 0) {
439- uint8_t *p = &buf[sizeof(*h)];
440- memset(h, 0, sizeof(*h)); // clear header
441- h->txnid = unicast ? qh->txnid : 0; // RFC-6762 18.1
442- // RFC-6762 6: 0 questions, 1 Answer, 0 Auth, 0 Additional RRs
443- h->num_answers = mg_htons(1); // only one answer
444- h->flags = mg_htons(0x8400); // Authoritative response
445- *p++ = name_len; // label 1
446- memcpy(p, c->fn_data, name_len), p += name_len;
447- *p++ = 5; // label 2
448- memcpy(p, "local", 5), p += 5;
449- *p++ = 0; // no more labels
450- memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer);
413+ static uint8_t *build_name(struct mg_str *name, uint8_t *p) {
414+ *p++ = (uint8_t) name->len; // label 1
415+ memcpy(p, name->buf, name->len), p += name->len;
416+ *p++ = 5; // label 2
417+ memcpy(p, "local", 5), p += 5;
418+ *p++ = 0; // no more labels
419+ return p;
420+ }
421+
422+ static uint8_t *build_a_record(struct mg_connection *c, uint8_t *p) {
423+ memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer);
451424#if MG_ENABLE_TCPIP
452- memcpy(p, &c->mgr->ifp->ip, 4), p += 4;
425+ memcpy(p, &c->mgr->ifp->ip, 4), p += 4;
453426#else
454- memcpy(p, c->data, 4), p += 4;
427+ memcpy(p, c->data, 4), p += 4;
455428#endif
456- if (!unicast) memcpy(&c->rem, &c->loc, sizeof(c->rem));
457- mg_send(c, buf, (size_t)(p - buf)); // And send it!
458- MG_DEBUG(("mDNS %c response sent", unicast ? 'U' : 'M'));
429+ return p;
430+ }
431+
432+ static uint8_t *build_srv_name(uint8_t *p, struct mg_dnssd_record *r) {
433+ *p++ = (uint8_t) r->srvcproto.len - 5; // label 1, up to '._tcp'
434+ memcpy(p, r->srvcproto.buf, r->srvcproto.len), p += r->srvcproto.len;
435+ p[-5] = 4; // label 2, '_tcp', overwrite '.'
436+ *p++ = 5; // label 3
437+ memcpy(p, "local", 5), p += 5;
438+ *p++ = 0; // no more labels
439+ return p;
440+ }
441+
442+ #if 0
443+ // TODO(): for listing
444+ static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p,
445+ struct mg_dnssd_record *r) {
446+ *p++ = name->len; // label 1
447+ memcpy(p, name->buf, name->len), p += name->len;
448+ return build_srv_name(p, r);
449+ }
450+ #endif
451+
452+ static uint8_t *build_ptr_record(struct mg_str *name, uint8_t *p, uint16_t o) {
453+ uint16_t offset = mg_htons(o);
454+ memcpy(p, mdns_answer, sizeof(mdns_answer));
455+ p[1] = 12; // overwrite record type
456+ p += sizeof(mdns_answer);
457+ p[-1] = (uint8_t) name->len +
458+ 3; // overwrite response length, label length + label + offset
459+ *p++ = (uint8_t) name->len; // response: label 1
460+ memcpy(p, name->buf, name->len), p += name->len; // copy label
461+ memcpy(p, &offset, 2);
462+ *p |= 0xC0, p += 2;
463+ return p;
464+ }
465+
466+ static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p,
467+ struct mg_dnssd_record *r, uint16_t o) {
468+ uint16_t port = mg_htons(r->port);
469+ uint16_t offset = mg_htons(o);
470+ memcpy(p, mdns_answer, sizeof(mdns_answer));
471+ p[1] = 33; // overwrite record type
472+ p += sizeof(mdns_answer);
473+ p[-1] = (uint8_t) name->len + 9; // overwrite response length (4+2+1+2)
474+ *p++ = 0; // priority
475+ *p++ = 0;
476+ *p++ = 0; // weight
477+ *p++ = 0;
478+ memcpy(p, &port, 2), p += 2; // port
479+ *p++ = (uint8_t) name->len; // label 1
480+ memcpy(p, name->buf, name->len), p += name->len;
481+ memcpy(p, &offset, 2);
482+ *p |= 0xC0, p += 2;
483+ return p;
484+ }
485+
486+ static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) {
487+ uint16_t len = mg_htons((uint16_t) r->txt.len);
488+ memcpy(p, mdns_answer, sizeof(mdns_answer));
489+ p[1] = 16; // overwrite record type
490+ p += sizeof(mdns_answer);
491+ memcpy(p - 2, &len, 2); // overwrite response length
492+ memcpy(p, r->txt.buf, r->txt.len), p += r->txt.len; // copy record verbatim
493+ return p;
494+ }
495+
496+ // RFC-6762 16: case-insensitivity --> RFC-1034, 1035
497+
498+ static void handle_mdns_record(struct mg_connection *c) {
499+ struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf;
500+ struct mg_dns_rr rr;
501+ size_t n;
502+ // flags -> !resp, opcode=0 => query; ignore other opcodes and responses
503+ if (c->recv.len <= 12 || (qh->flags & mg_htons(0xF800)) != 0) return;
504+ // Parse first question, offset 12 is header size
505+ n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
506+ MG_VERBOSE(("mDNS request parsed, result=%d", (int) n));
507+ if (n > 0) {
508+ // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
509+ uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4];
510+ struct mg_dns_header *h = (struct mg_dns_header *) buf;
511+ uint8_t *p = &buf[sizeof(*h)];
512+ char name[256];
513+ uint8_t name_len;
514+ // uint16_t q = mg_ntohs(qh->num_questions);
515+ struct mg_str defname = mg_str((const char *) c->fn_data);
516+ struct mg_str *respname;
517+ struct mg_mdns_req req;
518+ memset(&req, 0, sizeof(req));
519+ req.is_unicast = (rr.aclass & MG_BIT(15)) != 0; // QU
520+ rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response)
521+ qh->num_questions = mg_htons(1); // parser sanity
522+ mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name));
523+ name_len = (uint8_t) strlen(name); // verify it ends in .local
524+ if (strcmp(".local", &name[name_len - 6]) != 0 ||
525+ (rr.aclass != 1 && rr.aclass != 0xff))
526+ return;
527+ name[name_len -= 6] = '\0'; // remove .local
528+ MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype,
529+ (unsigned int) rr.aclass, name));
530+ if (rr.atype == 1) { // A
531+ // TODO(): ensure c->fn_data ends in \0
532+ // if we have a name to match, go; otherwise users will match and fill
533+ // req.r.name and set req.is_resp
534+ if (c->fn_data != NULL && mg_casecmp(c->fn_data, name) != 0) return;
535+ req.is_resp = (c->fn_data != NULL);
536+ req.reqname = mg_str_n(name, name_len);
537+ mg_call(c, MG_EV_MDNS_A, &req);
538+ } else // users have to match the request to something in their db, then
539+ // fill req.r and set req.is_resp
540+ if (rr.atype == 12) { // PTR
541+ if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true;
542+ MG_DEBUG(
543+ ("PTR request for %s", req.is_listing ? "services listing" : name));
544+ req.reqname = mg_str_n(name, name_len);
545+ mg_call(c, MG_EV_MDNS_PTR, &req);
546+ } else if (rr.atype == 33 || rr.atype == 16) { // SRV or TXT
547+ MG_DEBUG(("%s request for %s", rr.atype == 33 ? "SRV" : "TXT", name));
548+ // if possible, check it starts with our name, users will check it ends
549+ // in a service name they handle
550+ if (c->fn_data != NULL) {
551+ if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 ||
552+ name[defname.len] != '.')
553+ return;
554+ req.reqname =
555+ mg_str_n(name + defname.len + 1, name_len - defname.len - 1);
556+ MG_DEBUG(
557+ ("That's us, handing %.*s", req.reqname.len, req.reqname.buf));
558+ } else {
559+ req.reqname = mg_str_n(name, name_len);
459560 }
561+ mg_call(c, rr.atype == 33 ? MG_EV_MDNS_SRV : MG_EV_MDNS_TXT, &req);
562+ } else { // unhandled record
563+ return;
460564 }
461- }
565+ if (!req.is_resp) return;
566+ respname = req.respname.buf != NULL ? &req.respname : &defname;
567+
568+ memset(h, 0, sizeof(*h)); // clear header
569+ h->txnid = req.is_unicast ? qh->txnid : 0; // RFC-6762 18.1
570+ h->num_answers = mg_htons(1); // RFC-6762 6: 0 questions, 1 Answer
571+ h->flags = mg_htons(0x8400); // Authoritative response
572+ if (req.is_listing) {
573+ // TODO(): RFC-6762 6: each responder SHOULD delay its response by a
574+ // random amount of time selected with uniform random distribution in the
575+ // range 20-120 ms.
576+ // TODO():
577+ return;
578+ } else if (rr.atype == 12) { // PTR requested, serve PTR + SRV + TXT + A
579+ // TODO(): RFC-6762 6: each responder SHOULD delay its response by a
580+ // random amount of time selected with uniform random distribution in the
581+ // range 20-120 ms. Response to PTR is local_name._myservice._tcp.local
582+ uint8_t *o = p, *aux;
583+ uint16_t offset;
584+ if (respname->buf == NULL || respname->len == 0) return;
585+ h->num_other_prs = mg_htons(3); // 3 additional records
586+ p = build_srv_name(p, req.r);
587+ aux = build_ptr_record(respname, p, (uint16_t) (o - buf));
588+ o = p + sizeof(mdns_answer); // point to PTR response (full srvc name)
589+ offset = mg_htons((uint16_t) (o - buf));
590+ o = p - 7; // point to '.local' label (\x05local\x00)
591+ p = aux;
592+ memcpy(p, &offset, 2); // point to full srvc name, in record
593+ *p |= 0xC0, p += 2;
594+ aux = p;
595+ p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
596+ o = aux + sizeof(mdns_answer) + 6; // point to target in SRV
597+ memcpy(p, &offset, 2); // point to full srvc name, in record
598+ *p |= 0xC0, p += 2;
599+ p = build_txt_record(p, req.r);
600+ offset = mg_htons((uint16_t) (o - buf));
601+ memcpy(p, &offset, 2); // point to target name, in record
602+ *p |= 0xC0, p += 2;
603+ p = build_a_record(c, p);
604+ } else if (rr.atype == 16) { // TXT requested
605+ p = build_srv_name(p, req.r);
606+ p = build_txt_record(p, req.r);
607+ } else if (rr.atype == 33) { // SRV requested, serve SRV + A
608+ uint8_t *o, *aux;
609+ uint16_t offset;
610+ if (respname->buf == NULL || respname->len == 0) return;
611+ h->num_other_prs = mg_htons(1); // 1 additional record
612+ p = build_srv_name(p, req.r);
613+ o = p - 7; // point to '.local' label (\x05local\x00)
614+ aux = p;
615+ p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
616+ o = aux + sizeof(mdns_answer) + 6; // point to target in SRV
617+ offset = mg_htons((uint16_t) (o - buf));
618+ memcpy(p, &offset, 2); // point to target name, in record
619+ *p |= 0xC0, p += 2;
620+ p = build_a_record(c, p);
621+ } else { // A requested
622+ // RFC-6762 6: 0 Auth, 0 Additional RRs
623+ if (respname->buf == NULL || respname->len == 0) return;
624+ p = build_name(respname, p);
625+ p = build_a_record(c, p);
626+ }
627+ if (!req.is_unicast) memcpy(&c->rem, &c->loc, sizeof(c->rem));
628+ mg_send(c, buf, (size_t) (p - buf)); // And send it!
629+ MG_DEBUG(("mDNS %c response sent", req.is_unicast ? 'U' : 'M'));
630+ }
631+ }
632+
633+ static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) {
634+ if (ev == MG_EV_READ) {
635+ handle_mdns_record(c);
462636 mg_iobuf_del(&c->recv, 0, c->recv.len);
463637 }
464638 (void) ev_data;
465639}
466640
467641void mg_multicast_add(struct mg_connection *c, char *ip);
468- struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, char *name) {
642+ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
643+ void *fn_data) {
469644 struct mg_connection *c =
470- mg_listen(mgr, "udp://224.0.0.251:5353", mdns_cb, name);
471- if (c != NULL) mg_multicast_add(c, (char *)"224.0.0.251");
645+ mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data);
646+ if (c == NULL) return NULL;
647+ c->pfn = mdns_cb, c->pfn_data = fn_data;
648+ mg_multicast_add(c, (char *) "224.0.0.251");
472649 return c;
473650}
474651
475-
476652#ifdef MG_ENABLE_LINES
477653#line 1 "src/event.c"
478654#endif
0 commit comments