From 2e877832461af3f3522d7fbf30d3311c49f065d0 Mon Sep 17 00:00:00 2001 From: cpq Date: Tue, 22 Dec 2020 09:44:59 +0000 Subject: [PATCH] Add IPv6 client test --- Makefile | 2 +- mongoose.c | 308 +++++++++++++++++++++++++++++++---------------- mongoose.h | 42 ++++--- src/arch_win32.h | 1 + src/dns.c | 115 ++++++++++-------- src/dns.h | 16 ++- src/net.c | 109 ++++++++++++----- src/net.h | 23 ++-- src/sock.c | 67 +++++++---- src/url.c | 11 +- src/util.c | 6 +- src/util.h | 2 +- test/unit_test.c | 57 ++++++++- 13 files changed, 515 insertions(+), 244 deletions(-) diff --git a/Makefile b/Makefile index eaba01b4..ecd8e7c1 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ vc98: Makefile mongoose.c mongoose.h test/unit_test.c $(VC98) wine cl mongoose.c test/unit_test.c $(VCFLAGS) ws2_32.lib /Fe$@.exe $(VC98) wine $@.exe -vc2017: CFLAGS += -DMG_ENABLE_IPV6=1 +#vc2017: VCFLAGS += -DMG_ENABLE_IPV6=1 vc2017: Makefile mongoose.c mongoose.h test/unit_test.c $(VC2017) wine64 cl mongoose.c test/unit_test.c $(VCFLAGS) ws2_32.lib /Fe$@.exe $(VC2017) wine64 $@.exe diff --git a/mongoose.c b/mongoose.c index dbd7afb7..4b8fa857 100644 --- a/mongoose.c +++ b/mongoose.c @@ -147,17 +147,21 @@ struct dns_data { uint16_t txnid; }; -static void mg_dns_free(struct dns_data **head, struct dns_data *d) { - LIST_DELETE(struct dns_data, head, d); +static struct dns_data *s_reqs; // Active DNS requests + +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +static void mg_dns_free(struct dns_data *d) { + LIST_DELETE(struct dns_data, &s_reqs, d); free(d); } -void mg_resolve_cancel(struct mg_mgr *mgr, struct mg_connection *c) { - struct dns_data *tmp, *d, **head; - head = mgr->dnsc == NULL ? NULL : (struct dns_data **) &mgr->dnsc->pfn_data; - for (d = head == NULL ? NULL : *head; d != NULL; d = tmp) { +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; - if (d->c == c) mg_dns_free(head, d); + if (d->c == c) mg_dns_free(d); } } @@ -219,11 +223,16 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { 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_DEBUG, - //("NAME %s, IP len %zu t: %hu, c: %hu", dm->name, n, atype, aclass)); + LOG(LL_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) { - memcpy(&dm->ipaddr, &s[j + 2], 4); + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &s[j + 2], n); + dm->resolved = true; + break; // Return success + } else if (n == 16 && atype == 28 && aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip6, &s[j + 2], n); dm->resolved = true; break; // Return success } @@ -234,7 +243,7 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { static void dns_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - struct dns_data *d, *tmp, **head = (struct dns_data **) &c->pfn_data; + struct dns_data *d, *tmp; if (ev == MG_EV_POLL) { unsigned long now = *(unsigned long *) ev_data; for (d = (struct dns_data *) fn_data; d != NULL; d = tmp) { @@ -250,37 +259,44 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data, LOG(LL_ERROR, ("Unexpected DNS response:\n%s\n", s)); free(s); } else { - for (d = (struct dns_data *) c->pfn_data; d != NULL; d = tmp) { + LOG(LL_DEBUG, ("%s %d", dm.name, dm.resolved)); + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; // LOG(LL_INFO, ("d %p %hu %hu", d, d->txnid, dm.txnid)); if (dm.txnid != d->txnid) continue; if (d->c->is_resolving) { d->c->is_resolving = 0; if (dm.resolved) { - d->c->peer.ip = dm.ipaddr; + dm.addr.port = d->c->peer.port; // Save port + d->c->peer = dm.addr; // Copy resolved address mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0') { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif } else { mg_error(d->c, "%s DNS lookup failed", dm.name); } } else { LOG(LL_ERROR, ("%lu already resolved", d->c->id)); } - mg_dns_free(head, d); + mg_dns_free(d); resolved = 1; } } if (!resolved) LOG(LL_ERROR, ("stray DNS reply")); c->recv.len = 0; } else if (ev == MG_EV_CLOSE) { - for (d = *head; d != NULL; d = tmp) { + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; - mg_dns_free(head, d); + mg_dns_free(d); } } } void mg_dns_send(struct mg_connection *c, const struct mg_str *name, - uint16_t txnid) { + uint16_t txnid, bool ipv6) { struct { struct mg_dns_header header; uint8_t data[256]; @@ -289,7 +305,7 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, memset(&pkt, 0, sizeof(pkt)); pkt.header.transaction_id = mg_htons(txnid); pkt.header.flags = mg_htons(0x100); - pkt.header.num_questions = mg_htons(2); + pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { if (name->ptr[i] == '.' || i >= name->len) { pkt.data[n] = (uint8_t)(i - n); @@ -300,8 +316,9 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, } memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query n += 5; - memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query - n += 6; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; mg_send(c, &pkt, sizeof(pkt.header) + n); #if 0 // Immediately after A query, send AAAA query. Whatever reply comes first, @@ -312,39 +329,43 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, #endif } -void mg_resolve(struct mg_mgr *mgr, struct mg_connection *c, - struct mg_str *name, int ms) { +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + snprintf(dnsc->c->label, sizeof(dnsc->c->label), "%s", "DNS"); + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + d->txnid = s_reqs ? s_reqs->txnid + 1 : 1; + d->next = s_reqs; + s_reqs = d; + d->expire = mg_millis() + ms; + d->c = c; + c->is_resolving = 1; + LOG(LL_DEBUG, ("%lu resolving %.*s, txnid %hu", c->id, (int) name->len, + name->ptr, d->txnid)); + mg_dns_send(dnsc->c, name, d->txnid, ipv6); + } +} + +void mg_resolve(struct mg_connection *c, struct mg_str *name, int ms) { if (mg_aton(*name, &c->peer)) { // name is an IP address, do not fire name resolution mg_connect_resolved(c); } else { // name is not an IP, send DNS resolution request - if (mgr->dnsc == NULL) { - const char *srv = mgr->dnsserver ? mgr->dnsserver : "udp://8.8.8.8:53"; - mgr->dnsc = mg_connect(mgr, srv, NULL, NULL); - if (mgr->dnsc != NULL) { - mgr->dnsc->pfn = dns_cb; - // mgr->dnsc->is_hexdumping = 1; - snprintf(mgr->dnsc->label, sizeof(mgr->dnsc->label), "%s", "RESOLVER"); - } - } - if (mgr->dnsc == NULL) { - mg_error(c, "resolver"); - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { - mg_error(c, "resolve OOM"); - } else { - struct dns_data **head = (struct dns_data **) &mgr->dnsc->pfn_data; - d->txnid = *head ? (*head)->txnid + 1 : 1; - d->next = *head; - *head = d; - d->expire = mg_millis() + ms; - d->c = c; - c->is_resolving = 1; - LOG(LL_DEBUG, ("%lu resolving %.*s, txnid %hu", c->id, (int) name->len, - name->ptr, d->txnid)); - mg_dns_send(mgr->dnsc, name, d->txnid); - } + mg_sendnsreq(c, name, ms, &c->mgr->dns4, false); } } @@ -1929,9 +1950,18 @@ int mg_printf(struct mg_connection *c, const char *fmt, ...) { } char *mg_straddr(struct mg_connection *c, char *buf, size_t len) { - unsigned char *p = (unsigned char *) &c->peer.ip; - snprintf(buf, len, "%d.%d.%d.%d:%hu", p[0], p[1], p[2], p[3], - mg_ntohs(c->peer.port)); + if (c->peer.is_ip6) { + uint16_t *p = (uint16_t *) c->peer.ip6; + snprintf(buf, len, "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu", mg_htons(p[0]), + mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]), + mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7]), + mg_ntohs(c->peer.port)); + + } else { + unsigned char *p = (unsigned char *) &c->peer.ip; + snprintf(buf, len, "%d.%d.%d.%d:%hu", p[0], p[1], p[2], p[3], + mg_ntohs(c->peer.port)); + } return buf; } @@ -1942,35 +1972,73 @@ char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) { return buf; } +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + if (mg_casecmp(str.ptr, "localhost") != 0) return false; + addr->ip = mg_htonl(0x7f000001); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { + int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); + if (octet > 255) return false; + data[num_dots] = octet; + } else if (str.ptr[i] == '.') { + if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; + num_dots++; + } else { + return false; + } + } + if (num_dots != 3 || str.ptr[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + for (i = 0; i < str.len; i++) { + if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || + (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || + (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { + unsigned long val; + if (i > j + 3) return false; + // LOG(LL_DEBUG, ("%zu %zu [%.*s]", i, j, (int) (i - j + 1), + // &str.ptr[j])); + val = mg_unhexn(&str.ptr[j], i - j + 1); + addr->ip6[n] = (uint8_t)((val >> 8) & 255); + addr->ip6[n + 1] = (uint8_t)(val & 255); + } else if (str.ptr[i] == ':') { + j = i + 1; + if (i > 0 && str.ptr[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.ptr[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing :: + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2); + memset(&addr->ip6[dc], 0, 14 - n); + } + addr->is_ip6 = true; + return true; +} + bool mg_aton(struct mg_str str, struct mg_addr *addr) { // LOG(LL_INFO, ("[%.*s]", (int) str.len, str.ptr)); - if (!mg_casecmp(str.ptr, "localhost")) { - addr->ip = mg_htonl(0x7f000001); - return true; - } else if (addr->is_ip6) { - return false; - } else { - uint8_t data[4] = {0, 0, 0, 0}; - size_t i, num_octets = 0; - // LOG(LL_DEBUG, ("[%.*s]", (int) str.len, str.ptr)); - for (i = 0; i < str.len; i++) { - // LOG(LL_DEBUG, - //(" %c %zu %hhu %zu", str.ptr[i], i, data[num_octets], num_octets)); - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { - int octet = data[num_octets] * 10 + (str.ptr[i] - '0'); - if (octet > 255) return false; - data[num_octets] = octet; - } else if (str.ptr[i] == '.') { - if (num_octets >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; - num_octets++; - } else { - return false; - } - } - if (num_octets != 3 || str.ptr[i - 1] == '.') return false; - memcpy(&addr->ip, data, sizeof(data)); - return true; - } + return mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); } void mg_mgr_free(struct mg_mgr *mgr) { @@ -1996,6 +2064,8 @@ void mg_mgr_init(struct mg_mgr *mgr) { #endif memset(mgr, 0, sizeof(*mgr)); mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; } #ifdef MG_ENABLE_LINES @@ -2381,6 +2451,13 @@ static union usa tousa(struct mg_addr *a) { usa.sin.sin_family = AF_INET; usa.sin.sin_port = a->port; *(uint32_t *) &usa.sin.sin_addr = a->ip; +#if MG_ENABLE_IPV6 + if (a->is_ip6) { + usa.sin.sin_family = AF_INET6; + usa.sin6.sin6_port = a->port; + memcpy(&usa.sin6.sin6_addr, a->ip6, sizeof(a->ip6)); + } +#endif return usa; } @@ -2414,10 +2491,20 @@ static int mg_sock_recv(struct mg_connection *c, void *buf, int len, if (c->is_udp) { union usa usa; socklen_t slen = sizeof(usa.sin); +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) slen = sizeof(usa.sin6); +#endif n = recvfrom(FD(c), buf, len, 0, &usa.sa, &slen); if (n > 0) { - c->peer.ip = *(uint32_t *) &usa.sin.sin_addr; - c->peer.port = usa.sin.sin_port; + if (c->peer.is_ip6) { +#if MG_ENABLE_IPV6 + memcpy(c->peer.ip6, &usa.sin6.sin6_addr, sizeof(c->peer.ip6)); + c->peer.port = usa.sin6.sin6_port; +#endif + } else { + c->peer.ip = *(uint32_t *) &usa.sin.sin_addr; + c->peer.port = usa.sin.sin_port; + } } } else { n = recv(FD(c), buf, len, MSG_NONBLOCKING); @@ -2431,7 +2518,11 @@ static int mg_sock_send(struct mg_connection *c, const void *buf, int len, int n = 0; if (c->is_udp) { union usa usa = tousa(&c->peer); - n = sendto(FD(c), buf, len, 0, &usa.sa, sizeof(usa.sin)); + socklen_t slen = sizeof(usa.sin); +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) slen = sizeof(usa.sin6); +#endif + n = sendto(FD(c), buf, len, 0, &usa.sa, slen); } else { n = send(FD(c), buf, len, MSG_NONBLOCKING); } @@ -2447,8 +2538,8 @@ static int ll_read(struct mg_connection *c, void *buf, int len, int *fail) { c->is_udp ? 'U' : 'u', c->is_connecting ? 'C' : 'c', n, len, MG_SOCK_ERRNO, *fail)); if (n > 0 && c->is_hexdumping) { - char *s = mg_hexdump(buf, len); - LOG(LL_INFO, ("\n-- %lu %s %s %d\n%s--", c->id, c->label, "<-", len, s)); + char *s = mg_hexdump(buf, n); + LOG(LL_INFO, ("\n-- %lu %s %s %d\n%s--", c->id, c->label, "<-", n, s)); free(s); } return n; @@ -2569,22 +2660,19 @@ static int write_conn(struct mg_connection *c) { return rc; } -static void close_conn(struct mg_mgr *mgr, struct mg_connection *c) { +static void close_conn(struct mg_connection *c) { // Unlink this connection from the list - LIST_DELETE(struct mg_connection, &mgr->conns, c); -#if 0 - struct mg_connection **head = &mgr->conns; - while (*head != c) head = &(*head)->next; - *head = c->next; -#endif - mg_resolve_cancel(mgr, c); + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + mg_resolve_cancel(c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; mg_call(c, MG_EV_CLOSE, NULL); // while (c->callbacks != NULL) mg_fn_del(c, c->callbacks->fn); LOG(LL_DEBUG, ("%lu closed", c->id)); if (FD(c) != INVALID_SOCKET) { closesocket(FD(c)); #if MG_ARCH == MG_ARCH_FREERTOS - FreeRTOS_FD_CLR(c->fd, mgr->ss, eSELECT_ALL); + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); #endif } mg_tls_free(c); @@ -2592,7 +2680,6 @@ static void close_conn(struct mg_mgr *mgr, struct mg_connection *c) { free(c->send.buf); memset(c, 0, sizeof(*c)); free(c); - if (c == mgr->dnsc) mgr->dnsc = NULL; } static void setsockopts(struct mg_connection *c) { @@ -2622,10 +2709,13 @@ static void setsockopts(struct mg_connection *c) { void mg_connect_resolved(struct mg_connection *c) { char buf[40]; int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int af = AF_INET; +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) af = AF_INET6; +#endif mg_straddr(c, buf, sizeof(buf)); - c->fd = (void *) (long) socket(AF_INET, type, 0); - LOG(LL_DEBUG, ("%lu resolved, sock %p -> %s, tosend %d", c->id, c->fd, buf, - (int) c->send.len)); + c->fd = (void *) (long) socket(af, type, 0); + LOG(LL_DEBUG, ("%lu resolved, sock %p -> %s", c->id, c->fd, buf)); if (FD(c) == INVALID_SOCKET) { mg_error(c, "socket(): %d", MG_SOCK_ERRNO); return; @@ -2635,7 +2725,12 @@ void mg_connect_resolved(struct mg_connection *c) { mg_call(c, MG_EV_RESOLVE, NULL); if (type == SOCK_STREAM) { union usa usa = tousa(&c->peer); - int rc = connect(FD(c), &usa.sa, sizeof(usa.sin)); + socklen_t slen = +#if MG_ENABLE_IPV6 + c->peer.is_ip6 ? sizeof(usa.sin6) : +#endif + sizeof(usa.sin); + int rc = connect(FD(c), &usa.sa, slen); int fail = rc < 0 && mg_sock_failed() ? MG_SOCK_ERRNO : 0; if (fail) { mg_error(c, "connect: %d", MG_SOCK_ERRNO); @@ -2659,7 +2754,7 @@ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, c->fn = fn; c->fn_data = fn_data; LOG(LL_DEBUG, ("%lu -> %s", c->id, url)); - mg_resolve(mgr, c, &host, mgr->dnstimeout); + mg_resolve(c, &host, mgr->dnstimeout); } return c; } @@ -2856,7 +2951,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { } if (c->is_draining && c->send.len == 0) c->is_closing = 1; - if (c->is_closing) close_conn(mgr, c); + if (c->is_closing) close_conn(c); } } #endif @@ -3344,9 +3439,9 @@ int mg_tls_send(struct mg_connection *c, const void *buf, size_t len, #ifdef MG_ENABLE_LINES #line 1 "src/url.c" #endif - #include + struct url { int key, user, pass, host, port, uri, end; }; @@ -3365,6 +3460,8 @@ static struct url urlparse(const char *url) { if (i > 0 && url[i - 1] == '/' && url[i] == '/') { u.host = i + 1; u.port = 0; + } else if (url[i] == ']') { + u.port = 0; // IPv6 URLs, like http://[::1]/bar } else if (url[i] == ':') { u.port = i + 1; } else if (url[i] == '@') { @@ -3387,7 +3484,12 @@ struct mg_str mg_url_host(const char *url) { struct url u = urlparse(url); int n = u.port ? u.port - u.host - 1 : u.uri ? u.uri - u.host : u.end - u.host; - return mg_str_n(url + u.host, n); + struct mg_str s = mg_str_n(url + u.host, n); + if (s.len > 2 && s.ptr[0] == '[' && s.ptr[s.len - 1] == ']') { + s.len -= 2; + s.ptr++; + } + return s; } const char *mg_url_uri(const char *url) { @@ -3538,16 +3640,16 @@ uint16_t mg_ntohs(uint16_t net) { return ((uint16_t) data[1] << 0) | ((uint32_t) data[0] << 8); } -char *mg_hexdump(const void *buf, int len) { +char *mg_hexdump(const void *buf, size_t len) { const unsigned char *p = (const unsigned char *) buf; - int i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; + size_t i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; char ascii[17] = "", *dst = (char *) malloc(dlen); if (dst == NULL) return dst; for (i = 0; i < len; i++) { idx = i % 16; if (idx == 0) { if (i > 0 && dlen > n) n += snprintf(dst + n, dlen - n, " %s\n", ascii); - if (dlen > n) n += snprintf(dst + n, dlen - n, "%04x ", i + ofs); + if (dlen > n) n += snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs)); } if (dlen < n) break; n += snprintf(dst + n, dlen - n, " %02x", p[i]); diff --git a/mongoose.h b/mongoose.h index 354a9285..dc8ba91e 100644 --- a/mongoose.h +++ b/mongoose.h @@ -250,6 +250,7 @@ enum { false = 0, true = 1 }; #else #include #include +#include #endif #include @@ -421,7 +422,7 @@ bool mg_globmatch(const char *pattern, int plen, const char *s, int n); bool mg_next_comma_entry(struct mg_str *s, struct mg_str *k, struct mg_str *v); uint16_t mg_ntohs(uint16_t net); uint32_t mg_ntohl(uint32_t net); -char *mg_hexdump(const void *buf, int len); +char *mg_hexdump(const void *buf, size_t len); char *mg_hex(const void *buf, int len, char *dst); void mg_unhex(const char *buf, int len, unsigned char *to); unsigned long mg_unhexn(const char *s, int len); @@ -581,15 +582,9 @@ enum { -struct mg_mgr { - struct mg_connection *conns; // List of active connections - struct mg_connection *dnsc; // DNS resolver connection - const char *dnsserver; // DNS server URL - int dnstimeout; // DNS resolve timeout in milliseconds - unsigned long nextid; // Next connection ID -#if MG_ARCH == MG_ARCH_FREERTOS - SocketSet_t ss; // NOTE(lsm): referenced from socket struct -#endif +struct mg_dns { + const char *url; // DNS server URL + struct mg_connection *c; // DNS server connection }; struct mg_addr { @@ -599,6 +594,17 @@ struct mg_addr { bool is_ip6; // True when address is IPv6 address }; +struct mg_mgr { + struct mg_connection *conns; // List of active connections + struct mg_dns dns4; // DNS for IPv4 + struct mg_dns dns6; // DNS for IPv6 + int dnstimeout; // DNS resolve timeout in milliseconds + unsigned long nextid; // Next connection ID +#if MG_ARCH == MG_ARCH_FREERTOS + SocketSet_t ss; // NOTE(lsm): referenced from socket struct +#endif +}; + struct mg_connection { struct mg_connection *next; // Linkage in struct mg_mgr :: connections struct mg_mgr *mgr; // Our container @@ -799,13 +805,17 @@ int mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic, +// Mongoose sends DNS queries that contain only one question: +// either A (IPv4) or AAAA (IPv6) address lookup. +// Therefore, we expect zero or one answer. +// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. struct mg_dns_message { - uint16_t txnid; // Transaction ID - bool resolved; // Resolve successful, ipaddr is set - uint32_t ipaddr; // Resolved IPv4 address - char name[256]; // Host name + uint16_t txnid; // Transaction ID + bool resolved; // Resolve successful, addr is set + struct mg_addr addr; // Resolved address + char name[256]; // Host name }; -void mg_resolve(struct mg_mgr *, struct mg_connection *, struct mg_str *, int); -void mg_resolve_cancel(struct mg_mgr *, struct mg_connection *); +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 *); diff --git a/src/arch_win32.h b/src/arch_win32.h index 201d84f7..e842768f 100644 --- a/src/arch_win32.h +++ b/src/arch_win32.h @@ -14,6 +14,7 @@ enum { false = 0, true = 1 }; #else #include #include +#include #endif #include diff --git a/src/dns.c b/src/dns.c index 4e0e552e..5507ffaf 100644 --- a/src/dns.c +++ b/src/dns.c @@ -21,17 +21,21 @@ struct dns_data { uint16_t txnid; }; -static void mg_dns_free(struct dns_data **head, struct dns_data *d) { - LIST_DELETE(struct dns_data, head, d); +static struct dns_data *s_reqs; // Active DNS requests + +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +static void mg_dns_free(struct dns_data *d) { + LIST_DELETE(struct dns_data, &s_reqs, d); free(d); } -void mg_resolve_cancel(struct mg_mgr *mgr, struct mg_connection *c) { - struct dns_data *tmp, *d, **head; - head = mgr->dnsc == NULL ? NULL : (struct dns_data **) &mgr->dnsc->pfn_data; - for (d = head == NULL ? NULL : *head; d != NULL; d = tmp) { +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; - if (d->c == c) mg_dns_free(head, d); + if (d->c == c) mg_dns_free(d); } } @@ -93,11 +97,16 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { 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_DEBUG, - //("NAME %s, IP len %zu t: %hu, c: %hu", dm->name, n, atype, aclass)); + LOG(LL_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) { - memcpy(&dm->ipaddr, &s[j + 2], 4); + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &s[j + 2], n); + dm->resolved = true; + break; // Return success + } else if (n == 16 && atype == 28 && aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip6, &s[j + 2], n); dm->resolved = true; break; // Return success } @@ -108,7 +117,7 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { static void dns_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - struct dns_data *d, *tmp, **head = (struct dns_data **) &c->pfn_data; + struct dns_data *d, *tmp; if (ev == MG_EV_POLL) { unsigned long now = *(unsigned long *) ev_data; for (d = (struct dns_data *) fn_data; d != NULL; d = tmp) { @@ -124,37 +133,44 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data, LOG(LL_ERROR, ("Unexpected DNS response:\n%s\n", s)); free(s); } else { - for (d = (struct dns_data *) c->pfn_data; d != NULL; d = tmp) { + LOG(LL_DEBUG, ("%s %d", dm.name, dm.resolved)); + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; // LOG(LL_INFO, ("d %p %hu %hu", d, d->txnid, dm.txnid)); if (dm.txnid != d->txnid) continue; if (d->c->is_resolving) { d->c->is_resolving = 0; if (dm.resolved) { - d->c->peer.ip = dm.ipaddr; + dm.addr.port = d->c->peer.port; // Save port + d->c->peer = dm.addr; // Copy resolved address mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0') { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif } else { mg_error(d->c, "%s DNS lookup failed", dm.name); } } else { LOG(LL_ERROR, ("%lu already resolved", d->c->id)); } - mg_dns_free(head, d); + mg_dns_free(d); resolved = 1; } } if (!resolved) LOG(LL_ERROR, ("stray DNS reply")); c->recv.len = 0; } else if (ev == MG_EV_CLOSE) { - for (d = *head; d != NULL; d = tmp) { + for (d = s_reqs; d != NULL; d = tmp) { tmp = d->next; - mg_dns_free(head, d); + mg_dns_free(d); } } } void mg_dns_send(struct mg_connection *c, const struct mg_str *name, - uint16_t txnid) { + uint16_t txnid, bool ipv6) { struct { struct mg_dns_header header; uint8_t data[256]; @@ -163,7 +179,7 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, memset(&pkt, 0, sizeof(pkt)); pkt.header.transaction_id = mg_htons(txnid); pkt.header.flags = mg_htons(0x100); - pkt.header.num_questions = mg_htons(2); + pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { if (name->ptr[i] == '.' || i >= name->len) { pkt.data[n] = (uint8_t)(i - n); @@ -174,8 +190,9 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, } memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query n += 5; - memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query - n += 6; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; mg_send(c, &pkt, sizeof(pkt.header) + n); #if 0 // Immediately after A query, send AAAA query. Whatever reply comes first, @@ -186,38 +203,42 @@ void mg_dns_send(struct mg_connection *c, const struct mg_str *name, #endif } -void mg_resolve(struct mg_mgr *mgr, struct mg_connection *c, - struct mg_str *name, int ms) { +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + snprintf(dnsc->c->label, sizeof(dnsc->c->label), "%s", "DNS"); + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + d->txnid = s_reqs ? s_reqs->txnid + 1 : 1; + d->next = s_reqs; + s_reqs = d; + d->expire = mg_millis() + ms; + d->c = c; + c->is_resolving = 1; + LOG(LL_DEBUG, ("%lu resolving %.*s, txnid %hu", c->id, (int) name->len, + name->ptr, d->txnid)); + mg_dns_send(dnsc->c, name, d->txnid, ipv6); + } +} + +void mg_resolve(struct mg_connection *c, struct mg_str *name, int ms) { if (mg_aton(*name, &c->peer)) { // name is an IP address, do not fire name resolution mg_connect_resolved(c); } else { // name is not an IP, send DNS resolution request - if (mgr->dnsc == NULL) { - const char *srv = mgr->dnsserver ? mgr->dnsserver : "udp://8.8.8.8:53"; - mgr->dnsc = mg_connect(mgr, srv, NULL, NULL); - if (mgr->dnsc != NULL) { - mgr->dnsc->pfn = dns_cb; - // mgr->dnsc->is_hexdumping = 1; - snprintf(mgr->dnsc->label, sizeof(mgr->dnsc->label), "%s", "RESOLVER"); - } - } - if (mgr->dnsc == NULL) { - mg_error(c, "resolver"); - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { - mg_error(c, "resolve OOM"); - } else { - struct dns_data **head = (struct dns_data **) &mgr->dnsc->pfn_data; - d->txnid = *head ? (*head)->txnid + 1 : 1; - d->next = *head; - *head = d; - d->expire = mg_millis() + ms; - d->c = c; - c->is_resolving = 1; - LOG(LL_DEBUG, ("%lu resolving %.*s, txnid %hu", c->id, (int) name->len, - name->ptr, d->txnid)); - mg_dns_send(mgr->dnsc, name, d->txnid); - } + mg_sendnsreq(c, name, ms, &c->mgr->dns4, false); } } diff --git a/src/dns.h b/src/dns.h index a6993f0f..28e68bee 100644 --- a/src/dns.h +++ b/src/dns.h @@ -3,13 +3,17 @@ #include "net.h" #include "str.h" +// Mongoose sends DNS queries that contain only one question: +// either A (IPv4) or AAAA (IPv6) address lookup. +// Therefore, we expect zero or one answer. +// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. struct mg_dns_message { - uint16_t txnid; // Transaction ID - bool resolved; // Resolve successful, ipaddr is set - uint32_t ipaddr; // Resolved IPv4 address - char name[256]; // Host name + uint16_t txnid; // Transaction ID + bool resolved; // Resolve successful, addr is set + struct mg_addr addr; // Resolved address + char name[256]; // Host name }; -void mg_resolve(struct mg_mgr *, struct mg_connection *, struct mg_str *, int); -void mg_resolve_cancel(struct mg_mgr *, struct mg_connection *); +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 *); diff --git a/src/net.c b/src/net.c index af34ec83..b9610fb4 100644 --- a/src/net.c +++ b/src/net.c @@ -20,9 +20,18 @@ int mg_printf(struct mg_connection *c, const char *fmt, ...) { } char *mg_straddr(struct mg_connection *c, char *buf, size_t len) { - unsigned char *p = (unsigned char *) &c->peer.ip; - snprintf(buf, len, "%d.%d.%d.%d:%hu", p[0], p[1], p[2], p[3], - mg_ntohs(c->peer.port)); + if (c->peer.is_ip6) { + uint16_t *p = (uint16_t *) c->peer.ip6; + snprintf(buf, len, "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu", mg_htons(p[0]), + mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]), + mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7]), + mg_ntohs(c->peer.port)); + + } else { + unsigned char *p = (unsigned char *) &c->peer.ip; + snprintf(buf, len, "%d.%d.%d.%d:%hu", p[0], p[1], p[2], p[3], + mg_ntohs(c->peer.port)); + } return buf; } @@ -33,35 +42,73 @@ char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) { return buf; } +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + if (mg_casecmp(str.ptr, "localhost") != 0) return false; + addr->ip = mg_htonl(0x7f000001); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { + int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); + if (octet > 255) return false; + data[num_dots] = octet; + } else if (str.ptr[i] == '.') { + if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; + num_dots++; + } else { + return false; + } + } + if (num_dots != 3 || str.ptr[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + for (i = 0; i < str.len; i++) { + if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || + (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || + (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { + unsigned long val; + if (i > j + 3) return false; + // LOG(LL_DEBUG, ("%zu %zu [%.*s]", i, j, (int) (i - j + 1), + // &str.ptr[j])); + val = mg_unhexn(&str.ptr[j], i - j + 1); + addr->ip6[n] = (uint8_t)((val >> 8) & 255); + addr->ip6[n + 1] = (uint8_t)(val & 255); + } else if (str.ptr[i] == ':') { + j = i + 1; + if (i > 0 && str.ptr[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.ptr[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing :: + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2); + memset(&addr->ip6[dc], 0, 14 - n); + } + addr->is_ip6 = true; + return true; +} + bool mg_aton(struct mg_str str, struct mg_addr *addr) { // LOG(LL_INFO, ("[%.*s]", (int) str.len, str.ptr)); - if (!mg_casecmp(str.ptr, "localhost")) { - addr->ip = mg_htonl(0x7f000001); - return true; - } else if (addr->is_ip6) { - return false; - } else { - uint8_t data[4] = {0, 0, 0, 0}; - size_t i, num_octets = 0; - // LOG(LL_DEBUG, ("[%.*s]", (int) str.len, str.ptr)); - for (i = 0; i < str.len; i++) { - // LOG(LL_DEBUG, - //(" %c %zu %hhu %zu", str.ptr[i], i, data[num_octets], num_octets)); - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { - int octet = data[num_octets] * 10 + (str.ptr[i] - '0'); - if (octet > 255) return false; - data[num_octets] = octet; - } else if (str.ptr[i] == '.') { - if (num_octets >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; - num_octets++; - } else { - return false; - } - } - if (num_octets != 3 || str.ptr[i - 1] == '.') return false; - memcpy(&addr->ip, data, sizeof(data)); - return true; - } + return mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); } void mg_mgr_free(struct mg_mgr *mgr) { @@ -87,4 +134,6 @@ void mg_mgr_init(struct mg_mgr *mgr) { #endif memset(mgr, 0, sizeof(*mgr)); mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; } diff --git a/src/net.h b/src/net.h index 8ebf2c91..7738240f 100644 --- a/src/net.h +++ b/src/net.h @@ -4,15 +4,9 @@ #include "event.h" #include "iobuf.h" -struct mg_mgr { - struct mg_connection *conns; // List of active connections - struct mg_connection *dnsc; // DNS resolver connection - const char *dnsserver; // DNS server URL - int dnstimeout; // DNS resolve timeout in milliseconds - unsigned long nextid; // Next connection ID -#if MG_ARCH == MG_ARCH_FREERTOS - SocketSet_t ss; // NOTE(lsm): referenced from socket struct -#endif +struct mg_dns { + const char *url; // DNS server URL + struct mg_connection *c; // DNS server connection }; struct mg_addr { @@ -22,6 +16,17 @@ struct mg_addr { bool is_ip6; // True when address is IPv6 address }; +struct mg_mgr { + struct mg_connection *conns; // List of active connections + struct mg_dns dns4; // DNS for IPv4 + struct mg_dns dns6; // DNS for IPv6 + int dnstimeout; // DNS resolve timeout in milliseconds + unsigned long nextid; // Next connection ID +#if MG_ARCH == MG_ARCH_FREERTOS + SocketSet_t ss; // NOTE(lsm): referenced from socket struct +#endif +}; + struct mg_connection { struct mg_connection *next; // Linkage in struct mg_mgr :: connections struct mg_mgr *mgr; // Our container diff --git a/src/sock.c b/src/sock.c index 92244ef0..ed00875a 100644 --- a/src/sock.c +++ b/src/sock.c @@ -44,6 +44,13 @@ static union usa tousa(struct mg_addr *a) { usa.sin.sin_family = AF_INET; usa.sin.sin_port = a->port; *(uint32_t *) &usa.sin.sin_addr = a->ip; +#if MG_ENABLE_IPV6 + if (a->is_ip6) { + usa.sin.sin_family = AF_INET6; + usa.sin6.sin6_port = a->port; + memcpy(&usa.sin6.sin6_addr, a->ip6, sizeof(a->ip6)); + } +#endif return usa; } @@ -77,10 +84,20 @@ static int mg_sock_recv(struct mg_connection *c, void *buf, int len, if (c->is_udp) { union usa usa; socklen_t slen = sizeof(usa.sin); +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) slen = sizeof(usa.sin6); +#endif n = recvfrom(FD(c), buf, len, 0, &usa.sa, &slen); if (n > 0) { - c->peer.ip = *(uint32_t *) &usa.sin.sin_addr; - c->peer.port = usa.sin.sin_port; + if (c->peer.is_ip6) { +#if MG_ENABLE_IPV6 + memcpy(c->peer.ip6, &usa.sin6.sin6_addr, sizeof(c->peer.ip6)); + c->peer.port = usa.sin6.sin6_port; +#endif + } else { + c->peer.ip = *(uint32_t *) &usa.sin.sin_addr; + c->peer.port = usa.sin.sin_port; + } } } else { n = recv(FD(c), buf, len, MSG_NONBLOCKING); @@ -94,7 +111,11 @@ static int mg_sock_send(struct mg_connection *c, const void *buf, int len, int n = 0; if (c->is_udp) { union usa usa = tousa(&c->peer); - n = sendto(FD(c), buf, len, 0, &usa.sa, sizeof(usa.sin)); + socklen_t slen = sizeof(usa.sin); +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) slen = sizeof(usa.sin6); +#endif + n = sendto(FD(c), buf, len, 0, &usa.sa, slen); } else { n = send(FD(c), buf, len, MSG_NONBLOCKING); } @@ -110,8 +131,8 @@ static int ll_read(struct mg_connection *c, void *buf, int len, int *fail) { c->is_udp ? 'U' : 'u', c->is_connecting ? 'C' : 'c', n, len, MG_SOCK_ERRNO, *fail)); if (n > 0 && c->is_hexdumping) { - char *s = mg_hexdump(buf, len); - LOG(LL_INFO, ("\n-- %lu %s %s %d\n%s--", c->id, c->label, "<-", len, s)); + char *s = mg_hexdump(buf, n); + LOG(LL_INFO, ("\n-- %lu %s %s %d\n%s--", c->id, c->label, "<-", n, s)); free(s); } return n; @@ -232,22 +253,19 @@ static int write_conn(struct mg_connection *c) { return rc; } -static void close_conn(struct mg_mgr *mgr, struct mg_connection *c) { +static void close_conn(struct mg_connection *c) { // Unlink this connection from the list - LIST_DELETE(struct mg_connection, &mgr->conns, c); -#if 0 - struct mg_connection **head = &mgr->conns; - while (*head != c) head = &(*head)->next; - *head = c->next; -#endif - mg_resolve_cancel(mgr, c); + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + mg_resolve_cancel(c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; mg_call(c, MG_EV_CLOSE, NULL); // while (c->callbacks != NULL) mg_fn_del(c, c->callbacks->fn); LOG(LL_DEBUG, ("%lu closed", c->id)); if (FD(c) != INVALID_SOCKET) { closesocket(FD(c)); #if MG_ARCH == MG_ARCH_FREERTOS - FreeRTOS_FD_CLR(c->fd, mgr->ss, eSELECT_ALL); + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); #endif } mg_tls_free(c); @@ -255,7 +273,6 @@ static void close_conn(struct mg_mgr *mgr, struct mg_connection *c) { free(c->send.buf); memset(c, 0, sizeof(*c)); free(c); - if (c == mgr->dnsc) mgr->dnsc = NULL; } static void setsockopts(struct mg_connection *c) { @@ -285,10 +302,13 @@ static void setsockopts(struct mg_connection *c) { void mg_connect_resolved(struct mg_connection *c) { char buf[40]; int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int af = AF_INET; +#if MG_ENABLE_IPV6 + if (c->peer.is_ip6) af = AF_INET6; +#endif mg_straddr(c, buf, sizeof(buf)); - c->fd = (void *) (long) socket(AF_INET, type, 0); - LOG(LL_DEBUG, ("%lu resolved, sock %p -> %s, tosend %d", c->id, c->fd, buf, - (int) c->send.len)); + c->fd = (void *) (long) socket(af, type, 0); + LOG(LL_DEBUG, ("%lu resolved, sock %p -> %s", c->id, c->fd, buf)); if (FD(c) == INVALID_SOCKET) { mg_error(c, "socket(): %d", MG_SOCK_ERRNO); return; @@ -298,7 +318,12 @@ void mg_connect_resolved(struct mg_connection *c) { mg_call(c, MG_EV_RESOLVE, NULL); if (type == SOCK_STREAM) { union usa usa = tousa(&c->peer); - int rc = connect(FD(c), &usa.sa, sizeof(usa.sin)); + socklen_t slen = +#if MG_ENABLE_IPV6 + c->peer.is_ip6 ? sizeof(usa.sin6) : +#endif + sizeof(usa.sin); + int rc = connect(FD(c), &usa.sa, slen); int fail = rc < 0 && mg_sock_failed() ? MG_SOCK_ERRNO : 0; if (fail) { mg_error(c, "connect: %d", MG_SOCK_ERRNO); @@ -322,7 +347,7 @@ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, c->fn = fn; c->fn_data = fn_data; LOG(LL_DEBUG, ("%lu -> %s", c->id, url)); - mg_resolve(mgr, c, &host, mgr->dnstimeout); + mg_resolve(c, &host, mgr->dnstimeout); } return c; } @@ -519,7 +544,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { } if (c->is_draining && c->send.len == 0) c->is_closing = 1; - if (c->is_closing) close_conn(mgr, c); + if (c->is_closing) close_conn(c); } } #endif diff --git a/src/url.c b/src/url.c index 4760a372..aef11014 100644 --- a/src/url.c +++ b/src/url.c @@ -1,5 +1,5 @@ -#include "url.h" #include +#include "url.h" struct url { int key, user, pass, host, port, uri, end; @@ -19,6 +19,8 @@ static struct url urlparse(const char *url) { if (i > 0 && url[i - 1] == '/' && url[i] == '/') { u.host = i + 1; u.port = 0; + } else if (url[i] == ']') { + u.port = 0; // IPv6 URLs, like http://[::1]/bar } else if (url[i] == ':') { u.port = i + 1; } else if (url[i] == '@') { @@ -41,7 +43,12 @@ struct mg_str mg_url_host(const char *url) { struct url u = urlparse(url); int n = u.port ? u.port - u.host - 1 : u.uri ? u.uri - u.host : u.end - u.host; - return mg_str_n(url + u.host, n); + struct mg_str s = mg_str_n(url + u.host, n); + if (s.len > 2 && s.ptr[0] == '[' && s.ptr[s.len - 1] == ']') { + s.len -= 2; + s.ptr++; + } + return s; } const char *mg_url_uri(const char *url) { diff --git a/src/util.c b/src/util.c index aa3e27d8..454782f2 100644 --- a/src/util.c +++ b/src/util.c @@ -107,16 +107,16 @@ uint16_t mg_ntohs(uint16_t net) { return ((uint16_t) data[1] << 0) | ((uint32_t) data[0] << 8); } -char *mg_hexdump(const void *buf, int len) { +char *mg_hexdump(const void *buf, size_t len) { const unsigned char *p = (const unsigned char *) buf; - int i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; + size_t i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; char ascii[17] = "", *dst = (char *) malloc(dlen); if (dst == NULL) return dst; for (i = 0; i < len; i++) { idx = i % 16; if (idx == 0) { if (i > 0 && dlen > n) n += snprintf(dst + n, dlen - n, " %s\n", ascii); - if (dlen > n) n += snprintf(dst + n, dlen - n, "%04x ", i + ofs); + if (dlen > n) n += snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs)); } if (dlen < n) break; n += snprintf(dst + n, dlen - n, " %02x", p[i]); diff --git a/src/util.h b/src/util.h index 4ad8580e..3e62a15d 100644 --- a/src/util.h +++ b/src/util.h @@ -11,7 +11,7 @@ bool mg_globmatch(const char *pattern, int plen, const char *s, int n); bool mg_next_comma_entry(struct mg_str *s, struct mg_str *k, struct mg_str *v); uint16_t mg_ntohs(uint16_t net); uint32_t mg_ntohl(uint32_t net); -char *mg_hexdump(const void *buf, int len); +char *mg_hexdump(const void *buf, size_t len); char *mg_hex(const void *buf, int len, char *dst); void mg_unhex(const char *buf, int len, unsigned char *to); unsigned long mg_unhexn(const char *s, int len); diff --git a/test/unit_test.c b/test/unit_test.c index c8ba58ef..0fde86a3 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -118,6 +118,8 @@ static void test_url(void) { ASSERT(vcmp(mg_url_host("p://bar:1234/a"), "bar")); ASSERT(vcmp(mg_url_host("p://u@bar:1234/a"), "bar")); ASSERT(vcmp(mg_url_host("p://u:p@bar:1234/a"), "bar")); + ASSERT(vcmp(mg_url_host("p://u:p@[::1]:1234/a"), "::1")); + ASSERT(vcmp(mg_url_host("p://u:p@[1:2::3]:1234/a"), "1:2::3")); // Port ASSERT(mg_url_port("foo:1234") == 1234); @@ -134,6 +136,8 @@ static void test_url(void) { ASSERT(mg_url_port("wss://u:p@bar:123") == 123); ASSERT(mg_url_port("wss://u:p@bar:123/") == 123); ASSERT(mg_url_port("wss://u:p@bar:123/abc") == 123); + ASSERT(mg_url_port("http://u:p@[::1]/abc") == 80); + ASSERT(mg_url_port("http://u:p@[::1]:2121/abc") == 2121); // User / pass ASSERT(vcmp(mg_url_user("p://foo"), "")); @@ -155,6 +159,8 @@ static void test_url(void) { ASSERT(strcmp(mg_url_uri("p://foo:12/"), "/") == 0); ASSERT(strcmp(mg_url_uri("p://foo:12/abc"), "/abc") == 0); ASSERT(strcmp(mg_url_uri("p://foo:12/a/b/c"), "/a/b/c") == 0); + ASSERT(strcmp(mg_url_uri("p://[::1]:12/a/b/c"), "/a/b/c") == 0); + ASSERT(strcmp(mg_url_uri("p://[ab::1]:12/a/b/c"), "/a/b/c") == 0); } static void test_base64(void) { @@ -587,7 +593,8 @@ static void f3(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { // LOG(LL_INFO, ("%d", ev)); if (ev == MG_EV_CONNECT) { // c->is_hexdumping = 1; - mg_printf(c, "GET / HTTP/1.0\r\nHost: cesanta.com\r\n\r\n"); + mg_printf(c, "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", + c->peer.is_ip6 ? "ipv6.google.com" : "cesanta.com"); } else if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; // LOG(LL_INFO, ("-->[%.*s]", (int) hm->message.len, hm->message.ptr)); @@ -626,12 +633,12 @@ static void test_http_client(void) { #if MG_ENABLE_IPV6 ok = 0; + // ipv6.google.com does not have IPv4 address, only IPv6, therefore + // it is guaranteed to hit IPv6 resolution path. c = mg_http_connect(&mgr, "http://ipv6.google.com", f3, &ok); ASSERT(c != NULL); for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); - // ASSERT(ok == 200); - // LOG(LL_DEBUG, ("")); - // exit(0); + ASSERT(ok == 200); #endif mg_mgr_free(&mgr); @@ -1005,9 +1012,49 @@ static void test_util(void) { ASSERT(mg_aton(mg_str("0.0.0.256"), &a) == false); ASSERT(mg_aton(mg_str("0.0.0.-1"), &a) == false); ASSERT(mg_aton(mg_str("127.0.0.1"), &a) == true); - ASSERT(a.is_ip6 == 0); + ASSERT(a.is_ip6 == false); ASSERT(a.ip == 0x100007f); ASSERT(strcmp(mg_ntoa(&a, buf, sizeof(buf)), "127.0.0.1") == 0); + + ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:8"), &a) == true); + ASSERT(a.is_ip6 == true); + ASSERT( + memcmp(a.ip6, + "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08", + sizeof(a.ip6)) == 0); + + memset(a.ip6, 0xaa, sizeof(a.ip6)); + ASSERT(mg_aton(mg_str("1::1"), &a) == true); + ASSERT(a.is_ip6 == true); + ASSERT( + memcmp(a.ip6, + "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", + sizeof(a.ip6)) == 0); + + memset(a.ip6, 0xaa, sizeof(a.ip6)); + ASSERT(mg_aton(mg_str("::1"), &a) == true); + ASSERT(a.is_ip6 == true); + ASSERT( + memcmp(a.ip6, + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", + sizeof(a.ip6)) == 0); + + memset(a.ip6, 0xaa, sizeof(a.ip6)); + ASSERT(mg_aton(mg_str("1::"), &a) == true); + ASSERT(a.is_ip6 == true); + ASSERT( + memcmp(a.ip6, + "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + sizeof(a.ip6)) == 0); + + memset(a.ip6, 0xaa, sizeof(a.ip6)); + ASSERT(mg_aton(mg_str("2001:4860:4860::8888"), &a) == true); + ASSERT(a.is_ip6 == true); + ASSERT( + memcmp(a.ip6, + "\x20\x01\x48\x60\x48\x60\x00\x00\x00\x00\x00\x00\x00\x00\x88\x88", + sizeof(a.ip6)) == 0); + ASSERT(strcmp(mg_hex("abc", 3, buf), "616263") == 0); ASSERT(mg_url_decode("a=%", 3, buf, sizeof(buf), 0) < 0); ASSERT(mg_url_decode("&&&a=%", 6, buf, sizeof(buf), 0) < 0);