mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-13 10:15:00 +00:00
Merge pull request #3643 from cesanta/mdns
Add SD query and response to mDNS
This commit is contained in:
+154
-44
@@ -1907,16 +1907,16 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
uint16_t txnid, bool ipv6) {
|
||||
struct {
|
||||
static bool dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
unsigned int rtype, uint16_t txnid, uint16_t flags) {
|
||||
struct { // RFC-1035 4.1.2
|
||||
struct mg_dns_header header;
|
||||
uint8_t data[256];
|
||||
} pkt;
|
||||
size_t i, n;
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.header.txnid = mg_htons(txnid);
|
||||
pkt.header.flags = mg_htons(0x100);
|
||||
pkt.header.flags = mg_htons(flags);
|
||||
pkt.header.num_questions = mg_htons(1);
|
||||
for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
|
||||
if (name->buf[i] == '.' || i >= name->len) {
|
||||
@@ -1926,14 +1926,19 @@ static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
}
|
||||
if (i >= name->len) break;
|
||||
}
|
||||
memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
|
||||
memset(&pkt.data[n], 0, 5); // nul, QTYPE, QCLASS
|
||||
pkt.data[n + 2] = (uint8_t) rtype; // QTYPE = rtype, only 0-255 supported
|
||||
pkt.data[n + 4] = 1; // QCLASS = IN
|
||||
n += 5;
|
||||
if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
|
||||
// memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query
|
||||
// n += 6;
|
||||
return mg_send(c, &pkt, sizeof(pkt.header) + n);
|
||||
}
|
||||
|
||||
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
uint16_t txnid, bool ipv6) {
|
||||
return dns_send(c, name, ipv6 ? MG_DNS_RTYPE_AAAA : MG_DNS_RTYPE_A, txnid,
|
||||
0x100); // RD
|
||||
}
|
||||
|
||||
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc);
|
||||
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) {
|
||||
if (dnsc->url == NULL) {
|
||||
@@ -2253,43 +2258,149 @@ static void handle_mdns_query(struct mg_connection *c) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static size_t srvtoname(char *svc) {
|
||||
char *dot = strchr(svc, '.');
|
||||
if (dot == NULL) return 0;
|
||||
*dot = '\0';
|
||||
return (size_t) (dot - svc);
|
||||
}
|
||||
|
||||
#define MG_MAX_MDNS_RECORDS 8
|
||||
// Condense one useful mDNS/DNS-SD chain from a response datagram into a single
|
||||
// MG_EV_MDNS_RESP event. A/AAAA as the first supported record are terminal
|
||||
// address answers (AAAA currently ignored). For PTR/SRV/TXT, subsequent
|
||||
// records are attached only when their label matches the service instance, or
|
||||
// the SRV target for address records.
|
||||
// Some devices send SRV/TXT as extra Answer RRs instead of Additional RRs, so
|
||||
// both sections are parsed as one chain.
|
||||
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);
|
||||
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);
|
||||
struct mg_dns_rr rr, rr_[5]; // A, PTR, SRV, TXT, AAAA, in that order
|
||||
struct mg_mdns_resp resp;
|
||||
size_t n, i, num, answers, roff_[5], roff = 12; // offset 12 is header size
|
||||
// RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
|
||||
char name[256], srvcproto[256], instance[256], host[256];
|
||||
uint16_t atype = 0;
|
||||
answers = mg_ntohs(rh->num_answers);
|
||||
num = answers + mg_ntohs(rh->num_other_prs);
|
||||
if (answers == 0) return;
|
||||
MG_VERBOSE(("mDNS response: %u answer, %u additional", (unsigned) answers,
|
||||
(unsigned) (num - answers)));
|
||||
if (num > MG_MAX_MDNS_RECORDS) {
|
||||
num = MG_MAX_MDNS_RECORDS;
|
||||
MG_DEBUG(("ignoring > %u records", MG_MAX_MDNS_RECORDS));
|
||||
}
|
||||
memset(roff_, 0, sizeof(roff_));
|
||||
memset(&rr_, 0, sizeof(rr_));
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
instance[0] = host[0] = '\0';
|
||||
for (i = 0; i < num; i++) { // First Answer RR is primary; the rest must match it
|
||||
n = mg_dns_parse_rr(c->recv.buf, c->recv.len, roff, false, &rr);
|
||||
MG_VERBOSE(("mDNS record parsed, result=%u", n));
|
||||
if (n == 0) return;
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff, name, sizeof(name)) == 0) return;
|
||||
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 &&
|
||||
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
|
||||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[0].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.name = mg_str(name);
|
||||
resp.addr.is_ip6 = false;
|
||||
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 4, 4);
|
||||
MG_VERBOSE(("A record"));
|
||||
roff_[0] = roff, rr_[0] = rr;
|
||||
if (atype == MG_DNS_RTYPE_A) break;
|
||||
} else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
|
||||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
# if 0
|
||||
if (rr_[4].atype != 0) break;
|
||||
resp.addr.is_ip6 = true;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.name = mg_str(name);
|
||||
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 16, 16);
|
||||
MG_VERBOSE(("AAAA record"));
|
||||
roff_[4] = roff, rr_[4] = rr;
|
||||
if (atype == MG_DNS_RTYPE_AAAA) break;
|
||||
#else
|
||||
MG_VERBOSE(("ignored AAAA record"));
|
||||
#endif
|
||||
} else if (rr.atype == MG_DNS_RTYPE_PTR && (rr.aclass & 0x7FFF) == 1 && i == 0) {
|
||||
if (rr_[1].atype != 0) break;
|
||||
atype = rr.atype;
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 10,
|
||||
instance, sizeof(instance)) == 0) return;
|
||||
MG_VERBOSE(("PTR record"));
|
||||
roff_[1] = roff, rr_[1] = rr;
|
||||
} else if (rr.atype == MG_DNS_RTYPE_SRV && rr.alen >= 6 && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[2].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.sd.port = MG_LOAD_BE16(c->recv.buf + roff + rr.nlen + 14);
|
||||
resp.addr.port = mg_htons(resp.sd.port);
|
||||
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 16,
|
||||
host, sizeof(host)) == 0) return;
|
||||
MG_VERBOSE(("SRV record"));
|
||||
roff_[2] = roff, rr_[2] = rr;
|
||||
} else if (rr.atype == MG_DNS_RTYPE_TXT && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[3].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
|
||||
resp.sd.txt = mg_str_n((char *) c->recv.buf + roff + rr.nlen + 10, rr.alen);
|
||||
MG_VERBOSE(("TXT record"));
|
||||
roff_[3] = roff, rr_[3] = rr;
|
||||
}
|
||||
roff += n;
|
||||
if (i == 0 && atype == 0) return;
|
||||
}
|
||||
if (atype == MG_DNS_RTYPE_A) {
|
||||
resp.rr = &rr_[0];
|
||||
MG_DEBUG(("A response from %s = %M", name, mg_print_ip, &resp.addr));
|
||||
#if 0
|
||||
} else if (atype == MG_DNS_RTYPE_AAAA) {
|
||||
resp.rr = &rr_[4];
|
||||
MG_DEBUG(("AAAA response from %s = %M", name, mg_print_ip6, resp.addr.addr.ip));
|
||||
#endif
|
||||
} else if (atype == MG_DNS_RTYPE_PTR) {
|
||||
size_t len;
|
||||
resp.rr = &rr_[1];
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[1], srvcproto,
|
||||
sizeof(srvcproto)) == 0) return;
|
||||
len = strlen(srvcproto);
|
||||
if (len <= 6) return;
|
||||
resp.sd.srvcproto = mg_str_n(srvcproto, len - 6); // remove .local
|
||||
if (host[0] != '\0') {
|
||||
resp.name = mg_str(host);
|
||||
} else if (rr_[0].atype == 0) {
|
||||
mg_snprintf(name, sizeof(name), "%s", instance);
|
||||
resp.name = mg_str_n(name, srvtoname(name));
|
||||
} else {
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[0], name,
|
||||
sizeof(name)) == 0) return;
|
||||
resp.name = mg_str(name);
|
||||
}
|
||||
MG_DEBUG(("PTR response for %s from %s", srvcproto, name));
|
||||
} else if (atype == MG_DNS_RTYPE_SRV || atype == MG_DNS_RTYPE_TXT) {
|
||||
size_t len, name_len;
|
||||
resp.rr = atype == MG_DNS_RTYPE_SRV ? &rr_[2] : &rr_[3];
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len,
|
||||
atype == MG_DNS_RTYPE_SRV ? roff_[2] : roff_[3],
|
||||
srvcproto, sizeof(srvcproto)) == 0)
|
||||
return;
|
||||
len = strlen(srvcproto);
|
||||
name_len = srvtoname(srvcproto);
|
||||
if (name_len == 0 || len <= name_len + 7) return; // remove .local
|
||||
resp.sd.srvcproto = mg_str_n(srvcproto + name_len + 1, len - name_len - 7);
|
||||
resp.name = mg_str_n(srvcproto, name_len);
|
||||
MG_DEBUG(("%s response for %s from %s", atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", srvcproto, name));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
mg_call(c, MG_EV_MDNS_RESP, &resp);
|
||||
}
|
||||
|
||||
static void handle_mdns_record(struct mg_connection *c) {
|
||||
@@ -2363,8 +2474,7 @@ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
|
||||
static bool mdns_query(struct mg_connection *c, struct mg_str *name,
|
||||
unsigned int rtype) {
|
||||
mg_multicast_restore(c, (uint8_t *) &c->loc);
|
||||
(void) rtype;
|
||||
return mg_dns_send(c, name, 0, false);
|
||||
return dns_send(c, name, rtype, 0, 0); // RFC-6762 18.1 id = 0, 18.6 RD = 0
|
||||
}
|
||||
|
||||
bool mg_mdns_query(struct mg_connection *c, const char *name,
|
||||
|
||||
+3
-2
@@ -4032,11 +4032,12 @@ struct mg_mdns_req {
|
||||
bool is_unicast; // True if the client requested a unicast (QU) response
|
||||
};
|
||||
|
||||
// ev_data for MG_EV_MDNS_RESP, carrying the resolved address from an mDNS response.
|
||||
// ev_data for MG_EV_MDNS_RESP, carrying address/data from a mDNS response
|
||||
struct mg_mdns_resp {
|
||||
struct mg_dns_rr *rr; // Resource record from the response
|
||||
struct mg_dns_rr *rr; // Resource record from the response (1st in chain)
|
||||
struct mg_str name; // Resolved hostname, without the .local suffix
|
||||
struct mg_addr addr; // Resolved IP address
|
||||
struct mg_dnssd_record sd; // Service Discovery data
|
||||
};
|
||||
|
||||
// Parses a DNS query or response from buf/len into dm. Returns true on success.
|
||||
|
||||
@@ -228,16 +228,16 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
uint16_t txnid, bool ipv6) {
|
||||
struct {
|
||||
static bool dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
unsigned int rtype, uint16_t txnid, uint16_t flags) {
|
||||
struct { // RFC-1035 4.1.2
|
||||
struct mg_dns_header header;
|
||||
uint8_t data[256];
|
||||
} pkt;
|
||||
size_t i, n;
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.header.txnid = mg_htons(txnid);
|
||||
pkt.header.flags = mg_htons(0x100);
|
||||
pkt.header.flags = mg_htons(flags);
|
||||
pkt.header.num_questions = mg_htons(1);
|
||||
for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
|
||||
if (name->buf[i] == '.' || i >= name->len) {
|
||||
@@ -247,14 +247,19 @@ static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
}
|
||||
if (i >= name->len) break;
|
||||
}
|
||||
memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
|
||||
memset(&pkt.data[n], 0, 5); // nul, QTYPE, QCLASS
|
||||
pkt.data[n + 2] = (uint8_t) rtype; // QTYPE = rtype, only 0-255 supported
|
||||
pkt.data[n + 4] = 1; // QCLASS = IN
|
||||
n += 5;
|
||||
if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
|
||||
// memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query
|
||||
// n += 6;
|
||||
return mg_send(c, &pkt, sizeof(pkt.header) + n);
|
||||
}
|
||||
|
||||
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
uint16_t txnid, bool ipv6) {
|
||||
return dns_send(c, name, ipv6 ? MG_DNS_RTYPE_AAAA : MG_DNS_RTYPE_A, txnid,
|
||||
0x100); // RD
|
||||
}
|
||||
|
||||
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc);
|
||||
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) {
|
||||
if (dnsc->url == NULL) {
|
||||
@@ -574,43 +579,149 @@ static void handle_mdns_query(struct mg_connection *c) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static size_t srvtoname(char *svc) {
|
||||
char *dot = strchr(svc, '.');
|
||||
if (dot == NULL) return 0;
|
||||
*dot = '\0';
|
||||
return (size_t) (dot - svc);
|
||||
}
|
||||
|
||||
#define MG_MAX_MDNS_RECORDS 8
|
||||
// Condense one useful mDNS/DNS-SD chain from a response datagram into a single
|
||||
// MG_EV_MDNS_RESP event. A/AAAA as the first supported record are terminal
|
||||
// address answers (AAAA currently ignored). For PTR/SRV/TXT, subsequent
|
||||
// records are attached only when their label matches the service instance, or
|
||||
// the SRV target for address records.
|
||||
// Some devices send SRV/TXT as extra Answer RRs instead of Additional RRs, so
|
||||
// both sections are parsed as one chain.
|
||||
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);
|
||||
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);
|
||||
struct mg_dns_rr rr, rr_[5]; // A, PTR, SRV, TXT, AAAA, in that order
|
||||
struct mg_mdns_resp resp;
|
||||
size_t n, i, num, answers, roff_[5], roff = 12; // offset 12 is header size
|
||||
// RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
|
||||
char name[256], srvcproto[256], instance[256], host[256];
|
||||
uint16_t atype = 0;
|
||||
answers = mg_ntohs(rh->num_answers);
|
||||
num = answers + mg_ntohs(rh->num_other_prs);
|
||||
if (answers == 0) return;
|
||||
MG_VERBOSE(("mDNS response: %u answer, %u additional", (unsigned) answers,
|
||||
(unsigned) (num - answers)));
|
||||
if (num > MG_MAX_MDNS_RECORDS) {
|
||||
num = MG_MAX_MDNS_RECORDS;
|
||||
MG_DEBUG(("ignoring > %u records", MG_MAX_MDNS_RECORDS));
|
||||
}
|
||||
memset(roff_, 0, sizeof(roff_));
|
||||
memset(&rr_, 0, sizeof(rr_));
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
instance[0] = host[0] = '\0';
|
||||
for (i = 0; i < num; i++) { // First Answer RR is primary; the rest must match it
|
||||
n = mg_dns_parse_rr(c->recv.buf, c->recv.len, roff, false, &rr);
|
||||
MG_VERBOSE(("mDNS record parsed, result=%u", n));
|
||||
if (n == 0) return;
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff, name, sizeof(name)) == 0) return;
|
||||
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 &&
|
||||
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
|
||||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[0].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.name = mg_str(name);
|
||||
resp.addr.is_ip6 = false;
|
||||
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 4, 4);
|
||||
MG_VERBOSE(("A record"));
|
||||
roff_[0] = roff, rr_[0] = rr;
|
||||
if (atype == MG_DNS_RTYPE_A) break;
|
||||
} else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
|
||||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
# if 0
|
||||
if (rr_[4].atype != 0) break;
|
||||
resp.addr.is_ip6 = true;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.name = mg_str(name);
|
||||
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 16, 16);
|
||||
MG_VERBOSE(("AAAA record"));
|
||||
roff_[4] = roff, rr_[4] = rr;
|
||||
if (atype == MG_DNS_RTYPE_AAAA) break;
|
||||
#else
|
||||
MG_VERBOSE(("ignored AAAA record"));
|
||||
#endif
|
||||
} else if (rr.atype == MG_DNS_RTYPE_PTR && (rr.aclass & 0x7FFF) == 1 && i == 0) {
|
||||
if (rr_[1].atype != 0) break;
|
||||
atype = rr.atype;
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 10,
|
||||
instance, sizeof(instance)) == 0) return;
|
||||
MG_VERBOSE(("PTR record"));
|
||||
roff_[1] = roff, rr_[1] = rr;
|
||||
} else if (rr.atype == MG_DNS_RTYPE_SRV && rr.alen >= 6 && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[2].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
resp.sd.port = MG_LOAD_BE16(c->recv.buf + roff + rr.nlen + 14);
|
||||
resp.addr.port = mg_htons(resp.sd.port);
|
||||
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 16,
|
||||
host, sizeof(host)) == 0) return;
|
||||
MG_VERBOSE(("SRV record"));
|
||||
roff_[2] = roff, rr_[2] = rr;
|
||||
} else if (rr.atype == MG_DNS_RTYPE_TXT && (rr.aclass & 0x7FFF) == 1 &&
|
||||
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
|
||||
if (rr_[3].atype != 0) break;
|
||||
if (i == 0) atype = rr.atype;
|
||||
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
|
||||
resp.sd.txt = mg_str_n((char *) c->recv.buf + roff + rr.nlen + 10, rr.alen);
|
||||
MG_VERBOSE(("TXT record"));
|
||||
roff_[3] = roff, rr_[3] = rr;
|
||||
}
|
||||
roff += n;
|
||||
if (i == 0 && atype == 0) return;
|
||||
}
|
||||
if (atype == MG_DNS_RTYPE_A) {
|
||||
resp.rr = &rr_[0];
|
||||
MG_DEBUG(("A response from %s = %M", name, mg_print_ip, &resp.addr));
|
||||
#if 0
|
||||
} else if (atype == MG_DNS_RTYPE_AAAA) {
|
||||
resp.rr = &rr_[4];
|
||||
MG_DEBUG(("AAAA response from %s = %M", name, mg_print_ip6, resp.addr.addr.ip));
|
||||
#endif
|
||||
} else if (atype == MG_DNS_RTYPE_PTR) {
|
||||
size_t len;
|
||||
resp.rr = &rr_[1];
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[1], srvcproto,
|
||||
sizeof(srvcproto)) == 0) return;
|
||||
len = strlen(srvcproto);
|
||||
if (len <= 6) return;
|
||||
resp.sd.srvcproto = mg_str_n(srvcproto, len - 6); // remove .local
|
||||
if (host[0] != '\0') {
|
||||
resp.name = mg_str(host);
|
||||
} else if (rr_[0].atype == 0) {
|
||||
mg_snprintf(name, sizeof(name), "%s", instance);
|
||||
resp.name = mg_str_n(name, srvtoname(name));
|
||||
} else {
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[0], name,
|
||||
sizeof(name)) == 0) return;
|
||||
resp.name = mg_str(name);
|
||||
}
|
||||
MG_DEBUG(("PTR response for %s from %s", srvcproto, name));
|
||||
} else if (atype == MG_DNS_RTYPE_SRV || atype == MG_DNS_RTYPE_TXT) {
|
||||
size_t len, name_len;
|
||||
resp.rr = atype == MG_DNS_RTYPE_SRV ? &rr_[2] : &rr_[3];
|
||||
if (mg_dns_parse_name(c->recv.buf, c->recv.len,
|
||||
atype == MG_DNS_RTYPE_SRV ? roff_[2] : roff_[3],
|
||||
srvcproto, sizeof(srvcproto)) == 0)
|
||||
return;
|
||||
len = strlen(srvcproto);
|
||||
name_len = srvtoname(srvcproto);
|
||||
if (name_len == 0 || len <= name_len + 7) return; // remove .local
|
||||
resp.sd.srvcproto = mg_str_n(srvcproto + name_len + 1, len - name_len - 7);
|
||||
resp.name = mg_str_n(srvcproto, name_len);
|
||||
MG_DEBUG(("%s response for %s from %s", atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", srvcproto, name));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
mg_call(c, MG_EV_MDNS_RESP, &resp);
|
||||
}
|
||||
|
||||
static void handle_mdns_record(struct mg_connection *c) {
|
||||
@@ -684,8 +795,7 @@ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
|
||||
static bool mdns_query(struct mg_connection *c, struct mg_str *name,
|
||||
unsigned int rtype) {
|
||||
mg_multicast_restore(c, (uint8_t *) &c->loc);
|
||||
(void) rtype;
|
||||
return mg_dns_send(c, name, 0, false);
|
||||
return dns_send(c, name, rtype, 0, 0); // RFC-6762 18.1 id = 0, 18.6 RD = 0
|
||||
}
|
||||
|
||||
bool mg_mdns_query(struct mg_connection *c, const char *name,
|
||||
|
||||
@@ -60,11 +60,12 @@ struct mg_mdns_req {
|
||||
bool is_unicast; // True if the client requested a unicast (QU) response
|
||||
};
|
||||
|
||||
// ev_data for MG_EV_MDNS_RESP, carrying the resolved address from an mDNS response.
|
||||
// ev_data for MG_EV_MDNS_RESP, carrying address/data from a mDNS response
|
||||
struct mg_mdns_resp {
|
||||
struct mg_dns_rr *rr; // Resource record from the response
|
||||
struct mg_dns_rr *rr; // Resource record from the response (1st in chain)
|
||||
struct mg_str name; // Resolved hostname, without the .local suffix
|
||||
struct mg_addr addr; // Resolved IP address
|
||||
struct mg_dnssd_record sd; // Service Discovery data
|
||||
};
|
||||
|
||||
// Parses a DNS query or response from buf/len into dm. Returns true on success.
|
||||
|
||||
@@ -8,7 +8,8 @@ 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)
|
||||
if (resp->rr->atype == MG_DNS_RTYPE_A &&
|
||||
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;
|
||||
|
||||
@@ -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_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,43 @@
|
||||
|
||||
Browses for `_http._tcp.local` services on the local network using mDNS/DNS-SD.
|
||||
|
||||
```sh
|
||||
make
|
||||
cc main.c mongoose.c -W -Wall -Wextra -g -I. -DMG_ENABLE_LINES -o example
|
||||
./example
|
||||
1304aa594 3 net.c:311:mg_mgr_init MG_IO_SIZE: 16384, TLS: none
|
||||
1304aa594 3 net.c:224:mg_listen 1 4 udp://224.0.0.251:5353
|
||||
1304aa594 3 sock.c:167:mg_send 1 4 0:0:0 34 err 0
|
||||
1304aa594 3 sock.c:361:read_conn 1 4 0:0:0 34 err 0
|
||||
1304aa594 3 dns.c:478:handle_mdns_query PTR request for _http._tcp
|
||||
1304aa5d0 3 sock.c:361:read_conn 1 4 0:0:0 289 err 0
|
||||
1304aa5d1 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from 3D03F3CPAU00096.local
|
||||
1304aa5d1 2 main.c:15:mdns_ev_handler Got a response
|
||||
SERVICE -> _http._tcp
|
||||
NAME -> 3D03F3CPAU00096.local
|
||||
PORT -> 80
|
||||
TXT -> host=3D03F3CPAU00096mac=e0:50:8b:8d:2f:75ip=192.168.69.220
|
||||
URL -> http://3D03F3CPAU00096.local:80
|
||||
|
||||
1304aa622 3 sock.c:361:read_conn 1 4 0:0:0 226 err 0
|
||||
1304aa622 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from DIP-3TIR3020140717CCWR473261824.local
|
||||
1304aa622 2 main.c:15:mdns_ev_handler Got a response
|
||||
SERVICE -> _http._tcp
|
||||
NAME -> DIP-3TIR3020140717CCWR473261824.local
|
||||
PORT -> 80
|
||||
ADDRESS -> 192.168.69.225
|
||||
TXT -> path=/
|
||||
URL -> http://192.168.69.225:80
|
||||
|
||||
1304aa6f5 3 sock.c:361:read_conn 1 4 0:0:0 169 err 0
|
||||
1304aa6f5 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from PHILIPS_HD9255_3_6B7E5E.local
|
||||
1304aa6f5 2 main.c:15:mdns_ev_handler Got a response
|
||||
SERVICE -> _http._tcp
|
||||
NAME -> PHILIPS_HD9255_3_6B7E5E.local
|
||||
PORT -> 80
|
||||
ADDRESS -> 192.168.69.167
|
||||
TXT -> Device Type=Domestic Appliance
|
||||
URL -> http://192.168.69.167:80
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static char s_name[] = "Mongoose";
|
||||
static char s_service[] = "_http._tcp";
|
||||
|
||||
static void mdns_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_MDNS_RESP) {
|
||||
struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data;
|
||||
if (resp->rr->atype == MG_DNS_RTYPE_PTR &&
|
||||
mg_strcasecmp(mg_str(s_service), resp->sd.srvcproto) == 0) {
|
||||
char addr[40];
|
||||
MG_INFO(("Got a response"));
|
||||
printf("\tSERVICE -> %.*s\n", (int) resp->sd.srvcproto.len,
|
||||
resp->sd.srvcproto.buf);
|
||||
printf("\tNAME -> %.*s\n", (int) resp->name.len, resp->name.buf);
|
||||
if (resp->sd.port != 0)
|
||||
printf("\tPORT -> %u\n", resp->sd.port);
|
||||
if (resp->addr.addr.ip4 != 0) {
|
||||
mg_snprintf(addr, sizeof(addr), "%M", mg_print_ip, &resp->addr);
|
||||
printf("\tADDRESS -> %s\n", addr);
|
||||
}
|
||||
if (resp->sd.txt.buf != NULL)
|
||||
printf("\tTXT -> %.*s\n", (int) resp->sd.txt.len, resp->sd.txt.buf);
|
||||
if (resp->addr.addr.ip4 != 0 && resp->addr.port != 0) {
|
||||
mg_snprintf(addr, sizeof(addr), "http://%M:%u", mg_print_ip, &resp->addr,
|
||||
mg_ntohs(resp->addr.port));
|
||||
printf("\tURL -> %s\n", addr);
|
||||
} else if (resp->sd.port != 0 && resp->name.buf != NULL) {
|
||||
printf("\tURL -> http://%.*s:%u\n", (int) resp->name.len,
|
||||
resp->name.buf, resp->sd.port);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
(void) c;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
char query[256];
|
||||
static struct mg_connection *c;
|
||||
mg_log_set(MG_LL_DEBUG); // Set log level
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
|
||||
c = mg_mdns_listen(&mgr, mdns_ev_handler, s_name); // Start mDNS server
|
||||
if (c == NULL) return 1;
|
||||
mg_snprintf(query, sizeof(query), "%s.local", s_service);
|
||||
// Request service providers
|
||||
if (!mg_mdns_query(c, query, MG_DNS_RTYPE_PTR)) 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
|
||||
Reference in New Issue
Block a user