Skip to content

Commit 9c603d5

Browse files
committed
Introduce DNS-SD using mDNS
1 parent 5f2ff68 commit 9c603d5

File tree

11 files changed

+623
-98
lines changed

11 files changed

+623
-98
lines changed

mongoose.c

Lines changed: 224 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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

467641
void 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

mongoose.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,10 @@ enum {
16411641
MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
16421642
MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
16431643
MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
1644+
MG_EV_MDNS_A, // mDNS A record request struct mg_mdns_req *
1645+
MG_EV_MDNS_PTR, // mDNS PTR record request struct mg_mdns_req *
1646+
MG_EV_MDNS_SRV, // mDNS SRV record request struct mg_mdns_req *
1647+
MG_EV_MDNS_TXT, // mDNS TXT record request struct mg_mdns_req *
16441648
MG_EV_USER // Starting ID for user events
16451649
};
16461650

@@ -2950,13 +2954,33 @@ struct mg_dns_rr {
29502954
uint16_t alen; // Address length
29512955
};
29522956

2957+
2958+
// DNS-SD response record
2959+
struct mg_dnssd_record {
2960+
struct mg_str srvcproto; // service.proto, service name
2961+
struct mg_str txt; // TXT record contents
2962+
uint16_t port; // SRV record port
2963+
};
2964+
2965+
// mDNS request
2966+
struct mg_mdns_req {
2967+
struct mg_dns_rr *rr;
2968+
struct mg_dnssd_record *r;
2969+
struct mg_str reqname; // requested name in RR
2970+
struct mg_str respname; // actual name in response
2971+
struct mg_addr addr;
2972+
bool is_listing;
2973+
bool is_resp;
2974+
bool is_unicast;
2975+
};
2976+
29532977
void mg_resolve(struct mg_connection *, const char *url);
29542978
void mg_resolve_cancel(struct mg_connection *);
29552979
bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *);
29562980
size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
29572981
bool is_question, struct mg_dns_rr *);
29582982

2959-
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, char *name);
2983+
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data);
29602984

29612985

29622986

0 commit comments

Comments
 (0)