diff --git a/mongoose.c b/mongoose.c index 32e03e0f..943efa12 100644 --- a/mongoose.c +++ b/mongoose.c @@ -184,7 +184,7 @@ static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk // MG_INFO(("--> [%s]", to)); } - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure it is nul-term return i; } @@ -255,12 +255,12 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); ofs += n; - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + 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 == 28 && rr.aclass == 1) { + } 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; @@ -456,7 +456,7 @@ static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p, 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] = 12; // overwrite record type + 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 @@ -472,7 +472,7 @@ static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, uint16_t port = mg_htons(r->port); uint16_t offset = mg_htons(o); memcpy(p, mdns_answer, sizeof(mdns_answer)); - p[1] = 33; // overwrite record type + 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 @@ -490,7 +490,7 @@ static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, 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] = 16; // overwrite record type + 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 @@ -499,12 +499,10 @@ static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) { // RFC-6762 16: case-insensitivity --> RFC-1034, 1035 -static void handle_mdns_record(struct mg_connection *c) { +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; - // flags -> !resp, opcode=0 => query; ignore other opcodes and responses - if (c->recv.len <= 12 || (qh->flags & mg_htons(0xF800)) != 0) return; // 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)); @@ -531,7 +529,7 @@ static void handle_mdns_record(struct mg_connection *c) { 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 == 1) { // A + if (rr.atype == MG_DNS_RTYPE_A) { // TODO(): ensure c->fn_data ends in \0 // if we have a name to match, go; otherwise users will match and fill // req.r.name and set req.is_resp @@ -539,17 +537,15 @@ static void handle_mdns_record(struct mg_connection *c) { return; req.is_resp = (c->fn_data != NULL); req.reqname = mg_str_n(name, name_len); - mg_call(c, MG_EV_MDNS_A, &req); } else // users have to match the request to something in their db, then // fill req.r and set req.is_resp - if (rr.atype == 12) { // PTR + 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); - mg_call(c, MG_EV_MDNS_PTR, &req); - } else if (rr.atype == 33 || rr.atype == 16) { // SRV or TXT - MG_DEBUG(("%s request for %s", rr.atype == 33 ? "SRV" : "TXT", name)); + } 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) { @@ -563,10 +559,11 @@ static void handle_mdns_record(struct mg_connection *c) { } else { req.reqname = mg_str_n(name, name_len); } - mg_call(c, rr.atype == 33 ? MG_EV_MDNS_SRV : MG_EV_MDNS_TXT, &req); } 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; @@ -580,7 +577,7 @@ static void handle_mdns_record(struct mg_connection *c) { // range 20-120 ms. // TODO(): return; - } else if (rr.atype == 12) { // PTR requested, serve PTR + SRV + TXT + A + } 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 @@ -606,10 +603,10 @@ static void handle_mdns_record(struct mg_connection *c) { memcpy(p, &offset, 2); // point to target name, in record *p |= 0xC0, p += 2; p = build_a_record(c, p); - } else if (rr.atype == 16) { // TXT requested + } else if (rr.atype == MG_DNS_RTYPE_TXT) { p = build_srv_name(p, req.r); p = build_txt_record(p, req.r); - } else if (rr.atype == 33) { // SRV requested, serve SRV + A + } 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; @@ -635,6 +632,52 @@ static void handle_mdns_record(struct mg_connection *c) { } } +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; + size_t n; + // Parse first response, offset 12 is header size + n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, false, &rr); + MG_VERBOSE(("mDNS response parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + char name[256]; + uint8_t name_len; + struct mg_mdns_resp resp; + memset(&resp, 0, sizeof(resp)); + if (rh->num_answers > mg_htons(1)) MG_DEBUG(("ignoring > 1 answers")); + 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 + 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) { + resp.addr.is_ip6 = false; + memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 4, 4); + MG_DEBUG(("A response from %.*s = %M", name_len, name, mg_print_ip, &resp.addr)); +// } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1) { +// resp.addr.is_ip6 = true; +// memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 16], 16); +// MG_DEBUG(("AAAA response from %.*s = %M", name_len, name, mg_print_ip, &resp.addr)); + } else { + return; + } + resp.name = mg_str_n(name, name_len); + resp.rr = &rr; + 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) { if (ev == MG_EV_READ) { handle_mdns_record(c); @@ -654,6 +697,14 @@ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, return c; } +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); + mg_multicast_restore(c, (uint8_t *) &c->loc); + (void) rtype; + return mg_dns_send(c, &name_, 0, false); +} + #ifdef MG_ENABLE_LINES #line 1 "src/event.c" #endif diff --git a/mongoose.h b/mongoose.h index 0fa8353d..297af10f 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1658,10 +1658,8 @@ enum { MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data - MG_EV_MDNS_A, // mDNS A record request struct mg_mdns_req * - MG_EV_MDNS_PTR, // mDNS PTR record request struct mg_mdns_req * - MG_EV_MDNS_SRV, // mDNS SRV record request struct mg_mdns_req * - MG_EV_MDNS_TXT, // mDNS TXT record request struct mg_mdns_req * + MG_EV_MDNS_REQ, // mDNS request struct mg_mdns_req * + MG_EV_MDNS_RESP, // mDNS response struct mg_mdns_resp * MG_EV_USER // Starting ID for user events }; @@ -2952,6 +2950,13 @@ size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *, +#define MG_DNS_RTYPE_A 1 +#define MG_DNS_RTYPE_PTR 12 +#define MG_DNS_RTYPE_TXT 16 +#define MG_DNS_RTYPE_AAAA 28 +#define MG_DNS_RTYPE_SRV 33 + + // Mongoose sends DNS queries that contain only one question: // either A (IPv4) or AAAA (IPv6) address lookup. // Therefore, we expect zero or one answer. @@ -2987,18 +2992,26 @@ struct mg_dnssd_record { uint16_t port; // SRV record port }; -// mDNS request +// mDNS request and response data structs passed to event handlers struct mg_mdns_req { struct mg_dns_rr *rr; struct mg_dnssd_record *r; struct mg_str reqname; // requested name in RR - struct mg_str respname; // actual name in response + struct mg_str respname; // actual name to use in response struct mg_addr addr; bool is_listing; bool is_resp; bool is_unicast; }; +struct mg_mdns_resp { + struct mg_dns_rr *rr; +// TODO(scaprile )struct mg_str srvcproto; struct mg_str txt; uint16_t port; ? + struct mg_str name; + struct mg_addr addr; +// TODO(scaprile); bool has_A; bool has_PTR; bool has_SRV; bool has_TXT; ? +}; + void mg_resolve(struct mg_connection *, const char *url); void mg_resolve_cancel(struct mg_connection *); bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); @@ -3007,6 +3020,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data); +bool mg_mdns_query(struct mg_connection *, const char *, unsigned int); diff --git a/src/dns.c b/src/dns.c index ab337e5f..c0aca29e 100644 --- a/src/dns.c +++ b/src/dns.c @@ -63,7 +63,7 @@ static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk // MG_INFO(("--> [%s]", to)); } - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure it is nul-term return i; } @@ -134,12 +134,12 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); ofs += n; - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + 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 == 28 && rr.aclass == 1) { + } 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; @@ -335,7 +335,7 @@ static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p, 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] = 12; // overwrite record type + 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 @@ -351,7 +351,7 @@ static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, uint16_t port = mg_htons(r->port); uint16_t offset = mg_htons(o); memcpy(p, mdns_answer, sizeof(mdns_answer)); - p[1] = 33; // overwrite record type + 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 @@ -369,7 +369,7 @@ static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, 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] = 16; // overwrite record type + 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 @@ -378,12 +378,10 @@ static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) { // RFC-6762 16: case-insensitivity --> RFC-1034, 1035 -static void handle_mdns_record(struct mg_connection *c) { +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; - // flags -> !resp, opcode=0 => query; ignore other opcodes and responses - if (c->recv.len <= 12 || (qh->flags & mg_htons(0xF800)) != 0) return; // 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)); @@ -410,7 +408,7 @@ static void handle_mdns_record(struct mg_connection *c) { 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 == 1) { // A + if (rr.atype == MG_DNS_RTYPE_A) { // TODO(): ensure c->fn_data ends in \0 // if we have a name to match, go; otherwise users will match and fill // req.r.name and set req.is_resp @@ -418,17 +416,15 @@ static void handle_mdns_record(struct mg_connection *c) { return; req.is_resp = (c->fn_data != NULL); req.reqname = mg_str_n(name, name_len); - mg_call(c, MG_EV_MDNS_A, &req); } else // users have to match the request to something in their db, then // fill req.r and set req.is_resp - if (rr.atype == 12) { // PTR + 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); - mg_call(c, MG_EV_MDNS_PTR, &req); - } else if (rr.atype == 33 || rr.atype == 16) { // SRV or TXT - MG_DEBUG(("%s request for %s", rr.atype == 33 ? "SRV" : "TXT", name)); + } 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) { @@ -442,10 +438,11 @@ static void handle_mdns_record(struct mg_connection *c) { } else { req.reqname = mg_str_n(name, name_len); } - mg_call(c, rr.atype == 33 ? MG_EV_MDNS_SRV : MG_EV_MDNS_TXT, &req); } 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; @@ -459,7 +456,7 @@ static void handle_mdns_record(struct mg_connection *c) { // range 20-120 ms. // TODO(): return; - } else if (rr.atype == 12) { // PTR requested, serve PTR + SRV + TXT + A + } 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 @@ -485,10 +482,10 @@ static void handle_mdns_record(struct mg_connection *c) { memcpy(p, &offset, 2); // point to target name, in record *p |= 0xC0, p += 2; p = build_a_record(c, p); - } else if (rr.atype == 16) { // TXT requested + } else if (rr.atype == MG_DNS_RTYPE_TXT) { p = build_srv_name(p, req.r); p = build_txt_record(p, req.r); - } else if (rr.atype == 33) { // SRV requested, serve SRV + A + } 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; @@ -514,6 +511,52 @@ static void handle_mdns_record(struct mg_connection *c) { } } +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; + size_t n; + // Parse first response, offset 12 is header size + n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, false, &rr); + MG_VERBOSE(("mDNS response parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + char name[256]; + uint8_t name_len; + struct mg_mdns_resp resp; + memset(&resp, 0, sizeof(resp)); + if (rh->num_answers > mg_htons(1)) MG_DEBUG(("ignoring > 1 answers")); + 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 + 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) { + resp.addr.is_ip6 = false; + memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 4, 4); + MG_DEBUG(("A response from %.*s = %M", name_len, name, mg_print_ip, &resp.addr)); +// } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1) { +// resp.addr.is_ip6 = true; +// memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 16], 16); +// MG_DEBUG(("AAAA response from %.*s = %M", name_len, name, mg_print_ip, &resp.addr)); + } else { + return; + } + resp.name = mg_str_n(name, name_len); + resp.rr = &rr; + 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) { if (ev == MG_EV_READ) { handle_mdns_record(c); @@ -532,3 +575,11 @@ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, mg_multicast_add(c, (char *) "224.0.0.251"); return c; } + +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); + mg_multicast_restore(c, (uint8_t *) &c->loc); + (void) rtype; + return mg_dns_send(c, &name_, 0, false); +} diff --git a/src/dns.h b/src/dns.h index be5e1bec..c4f618ff 100644 --- a/src/dns.h +++ b/src/dns.h @@ -3,6 +3,13 @@ #include "net.h" #include "str.h" +#define MG_DNS_RTYPE_A 1 +#define MG_DNS_RTYPE_PTR 12 +#define MG_DNS_RTYPE_TXT 16 +#define MG_DNS_RTYPE_AAAA 28 +#define MG_DNS_RTYPE_SRV 33 + + // Mongoose sends DNS queries that contain only one question: // either A (IPv4) or AAAA (IPv6) address lookup. // Therefore, we expect zero or one answer. @@ -38,18 +45,26 @@ struct mg_dnssd_record { uint16_t port; // SRV record port }; -// mDNS request +// mDNS request and response data structs passed to event handlers struct mg_mdns_req { struct mg_dns_rr *rr; struct mg_dnssd_record *r; struct mg_str reqname; // requested name in RR - struct mg_str respname; // actual name in response + struct mg_str respname; // actual name to use in response struct mg_addr addr; bool is_listing; bool is_resp; bool is_unicast; }; +struct mg_mdns_resp { + struct mg_dns_rr *rr; +// TODO(scaprile )struct mg_str srvcproto; struct mg_str txt; uint16_t port; ? + struct mg_str name; + struct mg_addr addr; +// TODO(scaprile); bool has_A; bool has_PTR; bool has_SRV; bool has_TXT; ? +}; + void mg_resolve(struct mg_connection *, const char *url); void mg_resolve_cancel(struct mg_connection *); bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); @@ -58,3 +73,4 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data); +bool mg_mdns_query(struct mg_connection *, const char *, unsigned int); diff --git a/src/event.h b/src/event.h index baf7525e..8f194400 100644 --- a/src/event.h +++ b/src/event.h @@ -27,9 +27,7 @@ enum { MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data - MG_EV_MDNS_A, // mDNS A record request struct mg_mdns_req * - MG_EV_MDNS_PTR, // mDNS PTR record request struct mg_mdns_req * - MG_EV_MDNS_SRV, // mDNS SRV record request struct mg_mdns_req * - MG_EV_MDNS_TXT, // mDNS TXT record request struct mg_mdns_req * + MG_EV_MDNS_REQ, // mDNS request struct mg_mdns_req * + MG_EV_MDNS_RESP, // mDNS response struct mg_mdns_resp * MG_EV_USER // Starting ID for user events }; diff --git a/tutorials/udp/mdns-client/Makefile b/tutorials/udp/mdns-client/Makefile new file mode 100644 index 00000000..8b63f3c8 --- /dev/null +++ b/tutorials/udp/mdns-client/Makefile @@ -0,0 +1,25 @@ +PROG ?= example # Program we are building +DELETE = rm -rf # Command to remove files +OUT ?= -o $(PROG) # Compiler argument for output file +SOURCES = main.c mongoose.c # Source code files +CFLAGS = -W -Wall -Wextra -g -I. # Build options + +# Mongoose build options. See https://mongoose.ws/documentation/#build-options +CFLAGS_MONGOOSE += -DMG_ENABLE_MDNS=1 -DMG_ENABLE_LINES + +ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe + PROG ?= example.exe # Use .exe suffix for the binary + CC = gcc # Use MinGW gcc compiler + CFLAGS += -lws2_32 # Link against Winsock library + DELETE = cmd /C del /Q /F /S # Command prompt command to delete files + OUT ?= -o $(PROG) # Build output +endif + +all: $(PROG) # Default target. Build and run program + $(RUN) ./$(PROG) $(ARGS) + +$(PROG): $(SOURCES) # Build program from sources + $(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT) + +clean: # Cleanup. Delete built program and all build artifacts + $(DELETE) $(PROG) *.o *.obj *.exe *.dSYM diff --git a/tutorials/udp/mdns-client/README.md b/tutorials/udp/mdns-client/README.md new file mode 100644 index 00000000..e69de29b diff --git a/tutorials/udp/mdns-client/main.c b/tutorials/udp/mdns-client/main.c new file mode 100644 index 00000000..8aca7d1e --- /dev/null +++ b/tutorials/udp/mdns-client/main.c @@ -0,0 +1,37 @@ +// Copyright (c) 2022 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" + +char s_theone[] = "Server.local"; + +static void mdns_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_MDNS_RESP) { // is this the one ? + struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data; + if (mg_strcasecmp(mg_str(s_theone), resp->name) == 0) + MG_INFO(("%.*s is: %M", resp->name.len, resp->name.buf, mg_print_ip, &resp->addr)); + } + (void) c; +} + +int main(void) { + uint32_t response_ip = inet_addr("192.168.69.11"); + struct mg_mgr mgr; + static struct mg_connection *c; + mg_log_set(MG_LL_DEBUG); // Set log level + mg_mgr_init(&mgr); // Initialise event manager + + // Desired name must NOT have any dots in it, nor a domain + c = mg_mdns_listen(&mgr, mdns_ev_handler, "Mongoose"); // Start mDNS server + if (c == NULL) return 1; + // if not using our built-in TCP/IP stack, pass the IP address you want to + // use as a response, this depends on your underlying TCP/IP stack and number + // of interfaces available + memcpy(c->data, &response_ip, sizeof(response_ip)); + if (!mg_mdns_query(c, s_theone, MG_DNS_RTYPE_A)) return 1; + + for (;;) mg_mgr_poll(&mgr, 1000); // Event loop + + mg_mgr_free(&mgr); + return 0; +} diff --git a/tutorials/udp/mdns-client/mongoose.c b/tutorials/udp/mdns-client/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/udp/mdns-client/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/udp/mdns-client/mongoose.h b/tutorials/udp/mdns-client/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/udp/mdns-client/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/tutorials/udp/mdns-sd-server/main.c b/tutorials/udp/mdns-sd-server/main.c index fd3b07f9..5ff26e70 100644 --- a/tutorials/udp/mdns-sd-server/main.c +++ b/tutorials/udp/mdns-sd-server/main.c @@ -1,18 +1,23 @@ // Copyright (c) 2022-2026 Cesanta Software Limited // All rights reserved // -// This example runs MDNS server, auto-discoverable as MDNS_NAME -// Also it runs as web server +// This example runs an MDNS server, auto-discoverable as MDNS_NAME. +// Also, it runs a web server; we can find it by discovering MDNS_NAME. + +// Finally, under ADVANCED_EXAMPLE there is an example of how to +// implement a database that can hold those services we serve, and how to +// announce them responding mDNS-SD requests, like this web server +// - make CFLAGS_EXTRA="-DADVANCED_EXAMPLE" +// - avahi -r _http._tcp #include "mongoose.h" +// Desired name must NOT have any dots in it, nor a domain #define MDNS_NAME "mongoose-device" -#define WEB_SERVER_ADDR "http://0.0.0.0:8000" +#define WEB_SERVER_ADDR "http://0.0.0.0:8000" // port is hardcoded below +#define RESPONSE_IP "192.168.69.11" -//static struct mg_connection *s_mdns_conn; -//static struct mg_connection *s_http_conn; - -#if 0 +#ifdef ADVANCED_EXAMPLE // A TXT record data containing JSON, if actually needed. // The whole record must not exceed 256 bytes, so keep this small char txt[] = "\x15{\"property\": \"value\"}"; @@ -20,6 +25,7 @@ char txt[] = "\x15{\"property\": \"value\"}"; // DNS-SD service records static const struct mg_dnssd_record s_records[] = { + {{"_http._tcp", 10}, {"", 0}, 8000}, // port is hardcoded {{"_myservice._tcp", 15}, {"", 0}, 9876}, {{"_myservice._udp", 15}, {txt, 22}, 9876}}; @@ -34,7 +40,7 @@ struct dnssd_db { }; #endif -static struct mg_dnssd_record *dnssd_lookup(struct mg_str name) { +static const struct mg_dnssd_record *dnssd_lookup(struct mg_str name) { size_t i; for (i = 0; i < sizeof(s_records) / sizeof(s_records[0]); i++) { if (mg_strcasecmp(s_records[i].srvcproto, name) == 0) return &s_records[i]; @@ -45,43 +51,46 @@ static struct mg_dnssd_record *dnssd_lookup(struct mg_str name) { // we can do more complex stuff, this is a simple implementation static void mdns_ev_handler(struct mg_connection *c, int ev, void *ev_data) { - struct mg_mdns_req *req = (struct mg_mdns_req *) ev_data; - // MDNS resolver expects response IP address in the c->data - // memcpy(c->data, &response_ip, sizeof(response_ip)); - MG_INFO(("---> %lu %d %p %p", c->id, ev, ev_data, req)); - if (ev == MG_EV_MDNS_PTR) { - // do we serve this service ? - //MG_INFO(("PTR")); - } else if (ev == MG_EV_MDNS_SRV) { - //MG_INFO(("SrV")); - } else if (ev == MG_EV_MDNS_TXT) { - //MG_INFO(("TXT")); - } else if (ev == MG_EV_MDNS_A) { // is this us ? - //MG_INFO(("A, ADDR: %M", mg_print_ip, &req->addr)); +#ifndef ADVANCED_EXAMPLE + if (ev == MG_EV_MDNS_REQ) { + struct mg_mdns_req *req = (struct mg_mdns_req *) ev_data; + if (req->rr->atype == MG_DNS_RTYPE_PTR) { + // do we serve this service ? + MG_INFO(("PTR %.*s", req->reqname.len, req->reqname.buf)); + } else if (req->rr->atype == MG_DNS_RTYPE_SRV) { + MG_INFO(("SRV")); + } else if (req->rr->atype == MG_DNS_RTYPE_TXT) { + MG_INFO(("TXT")); + } else if (req->rr->atype == MG_DNS_RTYPE_A) { + MG_INFO(("A %.*s", req->reqname.len, req->reqname.buf)); + } } -#if 0 - struct mg_mdns_req *req = (struct mg_mdns_req *) ev_data; - if (ev == MG_EV_MDNS_PTR) { // do we serve this service ? - struct mg_dnssd_record *r; - if (req->is_listing) return; // TODO(): handle services listing - r = dnssd_lookup(req->reqname); - if (r == NULL) return; - req->r = r; - req->is_resp = true; - } else if (ev == MG_EV_MDNS_SRV || ev == MG_EV_MDNS_TXT) { - // Mongoose already checked the host name for us, otherwise we need to do - // that ourselves first, then check for service name in db - struct mg_dnssd_record *r = dnssd_lookup(req->reqname); - if (r == NULL) return; - req->r = r; - req->is_resp = true; - } else if (ev == MG_EV_MDNS_A) { // is this us ? - // Mongoose already checked the host name for us, otherwise we need to do - // that ourselves +#else + if (ev == MG_EV_MDNS_REQ) { + struct mg_mdns_req *req = (struct mg_mdns_req *) ev_data; + if (req->rr->atype == MG_DNS_RTYPE_PTR) { // do we serve this service ? + struct mg_dnssd_record *r; + if (req->is_listing) return; // TODO(): handle services listing + r = (struct mg_dnssd_record *) dnssd_lookup(req->reqname); + if (r == NULL) return; + req->r = r; + req->is_resp = true; + } else if (req->rr->atype == MG_DNS_RTYPE_SRV || + req->rr->atype == MG_DNS_RTYPE_TXT) { + // Mongoose already checked the host name for us, otherwise we need to do + // that ourselves first, then check for service name in db + struct mg_dnssd_record *r = (struct mg_dnssd_record *) dnssd_lookup(req->reqname); + if (r == NULL) return; + req->r = r; + req->is_resp = true; + } else if (req->rr->atype == MG_DNS_RTYPE_A) { // is this us ? + // Mongoose already checked the host name for us, otherwise we need to do + // that ourselves + } } - (void) c; #endif + (void) c; } // Simple web server, responds with a JSON like this @@ -97,11 +106,19 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { int main(void) { struct mg_mgr mgr; + static struct mg_connection *c; + uint32_t response_ip = inet_addr(RESPONSE_IP); mg_log_set(MG_LL_DEBUG); // Set log level mg_mgr_init(&mgr); // Initialise event manager - mg_mdns_listen(&mgr, mdns_ev_handler, MDNS_NAME); + c = mg_mdns_listen(&mgr, mdns_ev_handler, MDNS_NAME); + if (c == NULL) return 1; + // if not using our built-in TCP/IP stack, pass the IP address you want to + // use as a response, this depends on your underlying TCP/IP stack and number + // of interfaces available + memcpy(c->data, &response_ip, sizeof(response_ip)); + mg_http_listen(&mgr, WEB_SERVER_ADDR, http_ev_handler, NULL); for (;;) mg_mgr_poll(&mgr, 1000); // Event loop