Skip to content

Commit 293d8c2

Browse files
committed
Introduce DNS-SD using mDNS
1 parent 5f2ff68 commit 293d8c2

File tree

12 files changed

+626
-99
lines changed

12 files changed

+626
-99
lines changed

mongoose.c

Lines changed: 225 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -410,69 +410,246 @@ 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((char *) c->fn_data, name) != 0)
535+
return;
536+
req.is_resp = (c->fn_data != NULL);
537+
req.reqname = mg_str_n(name, name_len);
538+
mg_call(c, MG_EV_MDNS_A, &req);
539+
} else // users have to match the request to something in their db, then
540+
// fill req.r and set req.is_resp
541+
if (rr.atype == 12) { // PTR
542+
if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true;
543+
MG_DEBUG(
544+
("PTR request for %s", req.is_listing ? "services listing" : name));
545+
req.reqname = mg_str_n(name, name_len);
546+
mg_call(c, MG_EV_MDNS_PTR, &req);
547+
} else if (rr.atype == 33 || rr.atype == 16) { // SRV or TXT
548+
MG_DEBUG(("%s request for %s", rr.atype == 33 ? "SRV" : "TXT", name));
549+
// if possible, check it starts with our name, users will check it ends
550+
// in a service name they handle
551+
if (c->fn_data != NULL) {
552+
if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 ||
553+
name[defname.len] != '.')
554+
return;
555+
req.reqname =
556+
mg_str_n(name + defname.len + 1, name_len - defname.len - 1);
557+
MG_DEBUG(
558+
("That's us, handing %.*s", req.reqname.len, req.reqname.buf));
559+
} else {
560+
req.reqname = mg_str_n(name, name_len);
459561
}
562+
mg_call(c, rr.atype == 33 ? MG_EV_MDNS_SRV : MG_EV_MDNS_TXT, &req);
563+
} else { // unhandled record
564+
return;
460565
}
461-
}
566+
if (!req.is_resp) return;
567+
respname = req.respname.buf != NULL ? &req.respname : &defname;
568+
569+
memset(h, 0, sizeof(*h)); // clear header
570+
h->txnid = req.is_unicast ? qh->txnid : 0; // RFC-6762 18.1
571+
h->num_answers = mg_htons(1); // RFC-6762 6: 0 questions, 1 Answer
572+
h->flags = mg_htons(0x8400); // Authoritative response
573+
if (req.is_listing) {
574+
// TODO(): RFC-6762 6: each responder SHOULD delay its response by a
575+
// random amount of time selected with uniform random distribution in the
576+
// range 20-120 ms.
577+
// TODO():
578+
return;
579+
} else if (rr.atype == 12) { // PTR requested, serve PTR + SRV + TXT + A
580+
// TODO(): RFC-6762 6: each responder SHOULD delay its response by a
581+
// random amount of time selected with uniform random distribution in the
582+
// range 20-120 ms. Response to PTR is local_name._myservice._tcp.local
583+
uint8_t *o = p, *aux;
584+
uint16_t offset;
585+
if (respname->buf == NULL || respname->len == 0) return;
586+
h->num_other_prs = mg_htons(3); // 3 additional records
587+
p = build_srv_name(p, req.r);
588+
aux = build_ptr_record(respname, p, (uint16_t) (o - buf));
589+
o = p + sizeof(mdns_answer); // point to PTR response (full srvc name)
590+
offset = mg_htons((uint16_t) (o - buf));
591+
o = p - 7; // point to '.local' label (\x05local\x00)
592+
p = aux;
593+
memcpy(p, &offset, 2); // point to full srvc name, in record
594+
*p |= 0xC0, p += 2;
595+
aux = p;
596+
p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
597+
o = aux + sizeof(mdns_answer) + 6; // point to target in SRV
598+
memcpy(p, &offset, 2); // point to full srvc name, in record
599+
*p |= 0xC0, p += 2;
600+
p = build_txt_record(p, req.r);
601+
offset = mg_htons((uint16_t) (o - buf));
602+
memcpy(p, &offset, 2); // point to target name, in record
603+
*p |= 0xC0, p += 2;
604+
p = build_a_record(c, p);
605+
} else if (rr.atype == 16) { // TXT requested
606+
p = build_srv_name(p, req.r);
607+
p = build_txt_record(p, req.r);
608+
} else if (rr.atype == 33) { // SRV requested, serve SRV + A
609+
uint8_t *o, *aux;
610+
uint16_t offset;
611+
if (respname->buf == NULL || respname->len == 0) return;
612+
h->num_other_prs = mg_htons(1); // 1 additional record
613+
p = build_srv_name(p, req.r);
614+
o = p - 7; // point to '.local' label (\x05local\x00)
615+
aux = p;
616+
p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
617+
o = aux + sizeof(mdns_answer) + 6; // point to target in SRV
618+
offset = mg_htons((uint16_t) (o - buf));
619+
memcpy(p, &offset, 2); // point to target name, in record
620+
*p |= 0xC0, p += 2;
621+
p = build_a_record(c, p);
622+
} else { // A requested
623+
// RFC-6762 6: 0 Auth, 0 Additional RRs
624+
if (respname->buf == NULL || respname->len == 0) return;
625+
p = build_name(respname, p);
626+
p = build_a_record(c, p);
627+
}
628+
if (!req.is_unicast) memcpy(&c->rem, &c->loc, sizeof(c->rem));
629+
mg_send(c, buf, (size_t) (p - buf)); // And send it!
630+
MG_DEBUG(("mDNS %c response sent", req.is_unicast ? 'U' : 'M'));
631+
}
632+
}
633+
634+
static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) {
635+
if (ev == MG_EV_READ) {
636+
handle_mdns_record(c);
462637
mg_iobuf_del(&c->recv, 0, c->recv.len);
463638
}
464639
(void) ev_data;
465640
}
466641

467642
void mg_multicast_add(struct mg_connection *c, char *ip);
468-
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, char *name) {
643+
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
644+
void *fn_data) {
469645
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");
646+
mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data);
647+
if (c == NULL) return NULL;
648+
c->pfn = mdns_cb, c->pfn_data = fn_data;
649+
mg_multicast_add(c, (char *) "224.0.0.251");
472650
return c;
473651
}
474652

475-
476653
#ifdef MG_ENABLE_LINES
477654
#line 1 "src/event.c"
478655
#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)