mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-09 16:52:09 +07:00
Introduce new mDNS API, add mDNS client example
This commit is contained in:
+70
-19
@@ -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
|
||||
|
||||
+20
-6
@@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-4
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../mongoose.c
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../mongoose.h
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user