diff --git a/examples/captive-dns-server/Makefile b/examples/captive-dns-server/Makefile new file mode 100644 index 00000000..7e1e7fe2 --- /dev/null +++ b/examples/captive-dns-server/Makefile @@ -0,0 +1,11 @@ +PROG ?= example +CFLAGS ?= -DMG_IO_SIZE=8192 -DMG_ENABLE_LINES + +all: $(PROG) + $(DEBUGGER) ./$(PROG) $(ARGS) + +$(PROG): main.c + $(CC) ../../mongoose.c -I../.. -W -Wall $(CFLAGS) -o $(PROG) main.c + +clean: + rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb diff --git a/examples/captive-dns-server/main.c b/examples/captive-dns-server/main.c new file mode 100644 index 00000000..049b1805 --- /dev/null +++ b/examples/captive-dns-server/main.c @@ -0,0 +1,55 @@ +// Copyright (c) 2020 Cesanta Software Limited +// All rights reserved +// +// Example captive DNS server (captive portal). +// 1. Run `make`. This builds and starts a server on port 5533 +// 2. Run `dig -t A www.google.com -4 @localhost -p 5533` + +#include "mongoose.h" + +static const char *s_listen_url = "udp://0.0.0.0:5533"; + +// DNS answer section. We response with IP 1.2.3.4 - you can change it +// in the last 4 bytes of this array +uint8_t answer[] = { + 0xc0, 0x0c, // Point to the name in the DNS question + 0, 1, // 2 bytes - record type, A + 0, 1, // 2 bytes - address class, INET + 0, 0, 0, 120, // 4 bytes - TTL + 0, 4, // 2 bytes - address length + 1, 2, 3, 4 // 4 bytes - IP address +}; + +static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_READ) { + struct mg_dns_rr rr; // Parse first question, offset 12 is header size + size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); + if (n > 0) { + char buf[12 + n + sizeof(answer)]; + struct mg_dns_header *h = (struct mg_dns_header *) buf; + memset(buf, 0, sizeof(buf)); // Clear the whole datagram + h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid + h->num_questions = mg_htons(1); // We use only the 1st question + h->num_answers = mg_htons(1); // And only one answer + h->flags = mg_htons(0x8400); // Authoritative response + memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question + memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer + mg_send(c, buf, sizeof(buf)); // And send it! + } + mg_iobuf_delete(&c->recv, c->recv.len); + } + (void) fn_data; + (void) ev_data; +} + +int main(void) { + struct mg_mgr mgr; + + mg_log_set("3"); // Set log level + mg_mgr_init(&mgr); // Initialise event manager + mg_listen(&mgr, s_listen_url, fn, NULL); // Start DNS server + for (;;) mg_mgr_poll(&mgr, 1000); // Event loop + mg_mgr_free(&mgr); + + return 0; +} diff --git a/mongoose.c b/mongoose.c index 7bbe2efc..b62d8104 100644 --- a/mongoose.c +++ b/mongoose.c @@ -131,15 +131,6 @@ int mg_base64_decode(const char *src, int n, char *dst) { -struct mg_dns_header { - uint16_t transaction_id; - uint16_t flags; - uint16_t num_questions; - uint16_t num_answers; - uint16_t num_authority_prs; - uint16_t num_other_prs; -}; - struct dns_data { struct dns_data *next; struct mg_connection *c; @@ -165,73 +156,103 @@ void mg_resolve_cancel(struct mg_connection *c) { } } -static size_t mg_dns_parse_name(const uint8_t *s, const uint8_t *e, size_t off, - char *to, size_t tolen, int depth) { +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, int depth) { size_t i = 0, j = 0; if (tolen > 0) to[0] = '\0'; if (depth > 5) return 0; - while (&s[off + i + 1] < e && s[off + i] > 0) { - size_t n = s[off + i]; - if (n & 0xc0) { - size_t ptr = (((n & 0x3f) << 8) | s[off + i + 1]) - 12; // 12 is hdr len - if (&s[ptr + 1] < e && (s[ptr] & 0xc0) == 0) { - mg_dns_parse_name(s, e, ptr, to, tolen, depth + 1); - } + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { i++; break; } - if (&s[off + i + n + 1] >= e) break; - if (j + n + 1 >= tolen) return 0; // Error - overflow - if (j > 0) to[j++] = '.'; - memcpy(&to[j], &s[off + i + 1], n); + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); j += n; i += n + 1; - to[j] = '\0'; // Zero-terminate this chunk - // LOG(LL_DEBUG, ("-- %zu/%zu %zu %zu", i, e - s, j, n)); + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk } if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term return i; } -// txid flags numQ numA numAP numOP -// 0000 00 01 81 80 00 01 00 01 00 00 00 00 07 63 65 73 .............ces -// 0010 61 6e 74 61 03 63 6f 6d 00 00 01 00 01 c0 0c 00 anta.com........ -// 0020 01 00 01 00 00 02 57 00 04 94 fb 36 ec ......W....6. -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { - struct mg_dns_header *h = (struct mg_dns_header *) buf; - const uint8_t *s = buf + sizeof(*h), *e = &buf[len]; - uint16_t atype, aclass; - size_t i, j = 0, n; - memset(dm, 0, sizeof(*dm)); +size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, char *dst, + size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); if (len < sizeof(*h)) return 0; // Too small, headers dont fit if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) return 0; + // LOG(LL_INFO, ("%zu %zu %hu %d", ofs, len, rr->nlen, is_question)); + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = ((uint16_t) s[-4] << 8) | s[-3]; + rr->aclass = ((uint16_t) s[-2] << 8) | s[-1]; + if (is_question) return rr->nlen + 4; + + s += 6; + if (s > e) return 0; + rr->alen = ((uint16_t) s[-2] << 8) | s[-1]; + if (s + rr->alen > e) return 0; + return rr->nlen + rr->alen + 10; +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); + + if (len < sizeof(*h)) return 0; // Too small, headers dont fit if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity - dm->txnid = mg_ntohs(h->transaction_id); + dm->txnid = mg_ntohs(h->txnid); + for (i = 0; i < mg_ntohs(h->num_questions); i++) { - j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 5; - // LOG(LL_INFO, ("QUE %zu %zu [%s]", i, j, dm->name)); + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // LOG(LL_INFO, ("Q %zu %zu", ofs, n)); + ofs += n; } for (i = 0; i < mg_ntohs(h->num_answers); i++) { - j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 9; - if (&s[j] + 2 > e) break; - atype = ((int) s[j - 8] << 8) | s[j - 7]; - aclass = ((int) s[j - 6] << 8) | s[j - 5]; - n = ((int) s[j] << 8) | s[j + 1]; - LOG(LL_VERBOSE_DEBUG, ("%s %d %hu %hu", dm->name, (int) n, atype, aclass)); - if (&s[j] + 2 + n > e) break; - if (n == 4 && atype == 1 && aclass == 1) { + // LOG(LL_INFO, ("A -- %zu %zu %s", ofs, n, dm->name)); + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + LOG(LL_INFO, ("A %zu %zu %s", ofs, n, dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { dm->addr.is_ip6 = false; - memcpy(&dm->addr.ip, &s[j + 2], n); + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); dm->resolved = true; break; // Return success - } else if (n == 16 && atype == 28 && aclass == 1) { + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { dm->addr.is_ip6 = true; - memcpy(&dm->addr.ip6, &s[j + 2], n); + memcpy(&dm->addr.ip6, &buf[ofs - 16], 16); dm->resolved = true; break; // Return success } - j += 2 + n; } return true; } @@ -298,7 +319,7 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, } pkt; size_t i, n; memset(&pkt, 0, sizeof(pkt)); - pkt.header.transaction_id = mg_htons(txnid); + pkt.header.txnid = mg_htons(txnid); pkt.header.flags = mg_htons(0x100); pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { @@ -2947,7 +2968,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); if (c->is_resolving || c->is_closing) { // Do nothing - } else if (c->is_listening) { + } else if (c->is_listening && c->is_udp == 0) { if (c->is_readable) accept_conn(mgr, c); } else if (c->is_connecting) { if (c->is_readable || c->is_writable) connect_conn(c); diff --git a/mongoose.h b/mongoose.h index 854630f0..ecceb6b5 100644 --- a/mongoose.h +++ b/mongoose.h @@ -809,6 +809,26 @@ struct mg_dns_message { char name[256]; // Host name }; +struct mg_dns_header { + uint16_t txnid; // Transaction ID + uint16_t flags; + uint16_t num_questions; + uint16_t num_answers; + uint16_t num_authority_prs; + uint16_t num_other_prs; +}; + +// DNS resource record +struct mg_dns_rr { + uint16_t nlen; // Name or pointer length + uint16_t atype; // Address type + uint16_t aclass; // Address class + uint16_t alen; // Address length +}; + void mg_resolve(struct mg_connection *, struct mg_str *, int); void mg_resolve_cancel(struct mg_connection *); bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *); +size_t mg_dns_decode_name(const uint8_t *, size_t, size_t, char *, size_t); diff --git a/src/dns.c b/src/dns.c index 0700a5a9..47f4a7e5 100644 --- a/src/dns.c +++ b/src/dns.c @@ -5,15 +5,6 @@ #include "timer.h" #include "util.h" -struct mg_dns_header { - uint16_t transaction_id; - uint16_t flags; - uint16_t num_questions; - uint16_t num_answers; - uint16_t num_authority_prs; - uint16_t num_other_prs; -}; - struct dns_data { struct dns_data *next; struct mg_connection *c; @@ -39,73 +30,103 @@ void mg_resolve_cancel(struct mg_connection *c) { } } -static size_t mg_dns_parse_name(const uint8_t *s, const uint8_t *e, size_t off, - char *to, size_t tolen, int depth) { +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, int depth) { size_t i = 0, j = 0; if (tolen > 0) to[0] = '\0'; if (depth > 5) return 0; - while (&s[off + i + 1] < e && s[off + i] > 0) { - size_t n = s[off + i]; - if (n & 0xc0) { - size_t ptr = (((n & 0x3f) << 8) | s[off + i + 1]) - 12; // 12 is hdr len - if (&s[ptr + 1] < e && (s[ptr] & 0xc0) == 0) { - mg_dns_parse_name(s, e, ptr, to, tolen, depth + 1); - } + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { i++; break; } - if (&s[off + i + n + 1] >= e) break; - if (j + n + 1 >= tolen) return 0; // Error - overflow - if (j > 0) to[j++] = '.'; - memcpy(&to[j], &s[off + i + 1], n); + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); j += n; i += n + 1; - to[j] = '\0'; // Zero-terminate this chunk - // LOG(LL_DEBUG, ("-- %zu/%zu %zu %zu", i, e - s, j, n)); + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk } if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term return i; } -// txid flags numQ numA numAP numOP -// 0000 00 01 81 80 00 01 00 01 00 00 00 00 07 63 65 73 .............ces -// 0010 61 6e 74 61 03 63 6f 6d 00 00 01 00 01 c0 0c 00 anta.com........ -// 0020 01 00 01 00 00 02 57 00 04 94 fb 36 ec ......W....6. -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { - struct mg_dns_header *h = (struct mg_dns_header *) buf; - const uint8_t *s = buf + sizeof(*h), *e = &buf[len]; - uint16_t atype, aclass; - size_t i, j = 0, n; - memset(dm, 0, sizeof(*dm)); +size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, char *dst, + size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); if (len < sizeof(*h)) return 0; // Too small, headers dont fit if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) return 0; + // LOG(LL_INFO, ("%zu %zu %hu %d", ofs, len, rr->nlen, is_question)); + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = ((uint16_t) s[-4] << 8) | s[-3]; + rr->aclass = ((uint16_t) s[-2] << 8) | s[-1]; + if (is_question) return rr->nlen + 4; + + s += 6; + if (s > e) return 0; + rr->alen = ((uint16_t) s[-2] << 8) | s[-1]; + if (s + rr->alen > e) return 0; + return rr->nlen + rr->alen + 10; +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); + + if (len < sizeof(*h)) return 0; // Too small, headers dont fit if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity - dm->txnid = mg_ntohs(h->transaction_id); + dm->txnid = mg_ntohs(h->txnid); + for (i = 0; i < mg_ntohs(h->num_questions); i++) { - j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 5; - // LOG(LL_INFO, ("QUE %zu %zu [%s]", i, j, dm->name)); + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // LOG(LL_INFO, ("Q %zu %zu", ofs, n)); + ofs += n; } for (i = 0; i < mg_ntohs(h->num_answers); i++) { - j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 9; - if (&s[j] + 2 > e) break; - atype = ((int) s[j - 8] << 8) | s[j - 7]; - aclass = ((int) s[j - 6] << 8) | s[j - 5]; - n = ((int) s[j] << 8) | s[j + 1]; - LOG(LL_VERBOSE_DEBUG, ("%s %d %hu %hu", dm->name, (int) n, atype, aclass)); - if (&s[j] + 2 + n > e) break; - if (n == 4 && atype == 1 && aclass == 1) { + // LOG(LL_INFO, ("A -- %zu %zu %s", ofs, n, dm->name)); + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + LOG(LL_INFO, ("A %zu %zu %s", ofs, n, dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { dm->addr.is_ip6 = false; - memcpy(&dm->addr.ip, &s[j + 2], n); + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); dm->resolved = true; break; // Return success - } else if (n == 16 && atype == 28 && aclass == 1) { + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { dm->addr.is_ip6 = true; - memcpy(&dm->addr.ip6, &s[j + 2], n); + memcpy(&dm->addr.ip6, &buf[ofs - 16], 16); dm->resolved = true; break; // Return success } - j += 2 + n; } return true; } @@ -172,7 +193,7 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, } pkt; size_t i, n; memset(&pkt, 0, sizeof(pkt)); - pkt.header.transaction_id = mg_htons(txnid); + pkt.header.txnid = mg_htons(txnid); pkt.header.flags = mg_htons(0x100); pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { diff --git a/src/dns.h b/src/dns.h index 28e68bee..5d6b1790 100644 --- a/src/dns.h +++ b/src/dns.h @@ -14,6 +14,26 @@ struct mg_dns_message { char name[256]; // Host name }; +struct mg_dns_header { + uint16_t txnid; // Transaction ID + uint16_t flags; + uint16_t num_questions; + uint16_t num_answers; + uint16_t num_authority_prs; + uint16_t num_other_prs; +}; + +// DNS resource record +struct mg_dns_rr { + uint16_t nlen; // Name or pointer length + uint16_t atype; // Address type + uint16_t aclass; // Address class + uint16_t alen; // Address length +}; + void mg_resolve(struct mg_connection *, struct mg_str *, int); void mg_resolve_cancel(struct mg_connection *); bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *); +size_t mg_dns_decode_name(const uint8_t *, size_t, size_t, char *, size_t); diff --git a/src/sock.c b/src/sock.c index 3c5ea5f0..689dcf75 100644 --- a/src/sock.c +++ b/src/sock.c @@ -538,7 +538,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); if (c->is_resolving || c->is_closing) { // Do nothing - } else if (c->is_listening) { + } else if (c->is_listening && c->is_udp == 0) { if (c->is_readable) accept_conn(mgr, c); } else if (c->is_connecting) { if (c->is_readable || c->is_writable) connect_conn(c);