diff --git a/examples/wificfg/Makefile b/examples/wificfg/Makefile index 2cbb996..586b087 100644 --- a/examples/wificfg/Makefile +++ b/examples/wificfg/Makefile @@ -2,4 +2,11 @@ PROGRAM=wificfg EXTRA_COMPONENTS=extras/dhcpserver extras/wificfg +# For the mDNS responder included under extras: +# EXTRA_COMPONENTS+=extras/mdnsresponder +# EXTRA_CFLAGS=-DEXTRAS_MDNS_RESPONDER + +# For the mDNS responder included with lwip: +EXTRA_CFLAGS=-DLWIP_MDNS_RESPONDER=1 -DLWIP_NUM_NETIF_CLIENT_DATA=1 -DLWIP_NETIF_EXT_STATUS_CALLBACK=1 + include ../../common.mk diff --git a/examples/wificfg/wificfg.c b/examples/wificfg/wificfg.c index a90a9da..33e70c0 100644 --- a/examples/wificfg/wificfg.c +++ b/examples/wificfg/wificfg.c @@ -56,20 +56,32 @@ static int handle_index(int s, wificfg_method method, if (wificfg_write_html_title(s, buf, len, "Home") < 0) return -1; if (wificfg_write_string_chunk(s, http_index_content[1], buf, len) < 0) return -1; - socklen_t addr_len; - struct sockaddr addr; - addr_len = sizeof(addr); - getpeername(s, (struct sockaddr*)&addr, &addr_len); - if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; - if (addr.sa_family == AF_INET) { - struct sockaddr_in *sa = (struct sockaddr_in *)&addr; - if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; - snprintf(buf, len, "
" IPSTR " : %d
", - IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); - if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getpeername(s, (struct sockaddr *)&addr, &addr_len) == 0) { + if (((struct sockaddr *)&addr)->sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR ", port %u
", + IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + +#if LWIP_IPV6 + if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + if (inet6_ntoa_r(sa->sin6_addr, buf, len)) { + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + snprintf(buf, len, ", port %u
", ntohs(sa->sin6_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } +#endif } - + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; if (wificfg_write_string_chunk(s, http_index_content[2], buf, len) < 0) return -1; if (wificfg_write_chunk_end(s) < 0) return -1; diff --git a/extras/wificfg/wificfg.c b/extras/wificfg/wificfg.c index 5c7a089..22f796e 100644 --- a/extras/wificfg/wificfg.c +++ b/extras/wificfg/wificfg.c @@ -45,6 +45,15 @@ #include "wificfg.h" #include "sysparam.h" +#if EXTRAS_MDNS_RESPONDER +#include +#endif + +#if LWIP_MDNS_RESPONDER +#include +#endif + + char *wificfg_default_ssid = "EOR_%02X%02X%02X"; char *wificfg_default_password = "esp-open-rtos"; char *wificfg_default_hostname = "eor-%02x%02x%02x"; @@ -529,14 +538,34 @@ static int handle_ipaddr_redirect(int s, char *buf, size_t len) { if (wificfg_write_string(s, "HTTP/1.1 302 \r\nLocation: http://") < 0) return -1; - struct sockaddr addr; + struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); - getsockname(s, &addr, &addr_len); - struct sockaddr_in *sa = (struct sockaddr_in *)&addr; - snprintf(buf, len, "" IPSTR "/\r\n", IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr)); - if (wificfg_write_string(s, buf) < 0) return -1;; + if (getsockname(s, (struct sockaddr *)&addr, &addr_len) == 0) { + if (((struct sockaddr *)&addr)->sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + snprintf(buf, len, IPSTR, IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr)); + if (wificfg_write_string(s, buf) < 0) return -1; + } +#if LWIP_IPV6 + if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&addr; + const ip6_addr_t *addr6 = (const ip6_addr_t*)&(sa->sin6_addr); + if (ip6_addr_isipv4mappedipv6(addr6)) { + snprintf(buf, len, IPSTR, IP2STR((ip4_addr_t *)&addr6->addr[3])); + if (wificfg_write_string(s, buf) < 0) return -1; + } else { + if (wificfg_write_string(s, "[") < 0) return -1; + if (ip6addr_ntoa_r(addr6, buf, len)) { + if (wificfg_write_string(s, buf) < 0) return -1; + } + if (wificfg_write_string(s, "]") < 0) return -1; + } + } +#endif + } + /* Always close here - expect a new connection. */ - return wificfg_write_string(s, "Content-Length: 0\r\n" + return wificfg_write_string(s, "\r\nContent-Length: 0\r\n" "Connection: close\r\n" "\r\n"); } @@ -692,6 +721,20 @@ static int handle_wificfg_index(int s, wificfg_method method, snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.gw)); if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; } +#if LWIP_IPV6 + struct netif *netif = sdk_system_get_netif(STATION_IF); + if (netif) { + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + if (wificfg_write_string_chunk(s, "
Station IPv6
", buf, len) < 0) return -1; + const ip6_addr_t *addr6 = netif_ip6_addr(netif, i); + ip6addr_ntoa_r(addr6, buf, len); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; + } + } + } +#endif } if (opmode == SOFTAP_MODE || opmode == STATIONAP_MODE) { @@ -713,18 +756,51 @@ static int handle_wificfg_index(int s, wificfg_method method, snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.gw)); if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; } + +#if LWIP_IPV6 + struct netif *netif = sdk_system_get_netif(SOFTAP_IF); + if (netif) { + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + if (wificfg_write_string_chunk(s, "
AP IPv6
", buf, len) < 0) return -1; + const ip6_addr_t *addr6 = netif_ip6_addr(netif, i); + ip6addr_ntoa_r(addr6, buf, len); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; + } + } + } +#endif } - struct sockaddr addr; + struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); - getpeername(s, (struct sockaddr*)&addr, &addr_len); + if (getpeername(s, (struct sockaddr *)&addr, &addr_len) == 0) { + if (((struct sockaddr *)&addr)->sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR ", port %u
", + IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } - if (addr.sa_family == AF_INET) { - struct sockaddr_in *sa = (struct sockaddr_in *)&addr; - if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; - snprintf(buf, len, "
" IPSTR " : %u
", - IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); - if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; +#if LWIP_IPV6 + if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + const ip6_addr_t *addr6 = (const ip6_addr_t*)&(sa->sin6_addr); + if (ip6_addr_isipv4mappedipv6(addr6)) { + snprintf(buf, len, IPSTR, IP2STR((ip4_addr_t *)&addr6->addr[3])); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } else { + if (ip6addr_ntoa_r(addr6, buf, len)) { + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + } + snprintf(buf, len, ", port %u
", ntohs(sa->sin6_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } +#endif } if (wificfg_write_string_chunk(s, http_wificfg_content[2], buf, len) < 0) return -1; @@ -1509,6 +1585,41 @@ typedef struct { const wificfg_dispatch *dispatch; } server_params; +/* + * Test if the http host string is a literal IP address. This is needed for the + * softap mode captive portal and must return false for typical server strings + * which will be redirected, and true for literal IP address to avoid a redirect + * for them. + */ +static bool host_is_name(const char *host) +{ + if (host == NULL) { + return true; + } + + size_t len = strlen(host); + + if (len < 4) { + return true; + } + + char first = host[0]; + char last = host[len - 1]; + + if (first == '[' && last == ']') { + /* Likely an IPv6 address */ + return false; + } + + if (first >= '0' && first <= '9' && last >= '0' && last <= '9') { + /* Likely IPv4 address */ + return false; + } + + return true; +} + + /* * The http server uses a single thread to service all requests, one request at * a time, to keep peak resource usage to a minimum. Keeping connections open @@ -1529,14 +1640,52 @@ typedef struct { */ static void server_task(void *pvParameters) { + char *hostname_local = NULL; + char *hostname = NULL; + sysparam_get_string("hostname", &hostname); + if (hostname) { + size_t len = strlen(hostname) + 6 + 1; + hostname_local = (char *)malloc(len); + if (hostname_local) { + snprintf(hostname_local, len, "%s.local", hostname); + } + +#if EXTRAS_MDNS_RESPONDER + mdns_init(); + mdns_add_facility(hostname, "_http", NULL, mdns_TCP + mdns_Browsable, 80, 600); +#endif +#if LWIP_MDNS_RESPONDER + mdns_resp_init(); + struct netif *netif = sdk_system_get_netif(STATION_IF); + if (netif) { + mdns_resp_add_netif(netif, hostname, 120); + mdns_resp_add_service(netif, hostname, "_http", + DNSSD_PROTO_TCP, 80, 3600, NULL, NULL); + } +#endif + + free(hostname); + } + server_params *params = pvParameters; - struct sockaddr_in serv_addr; +#if LWIP_IPV6 + int listenfd = socket(AF_INET6, SOCK_STREAM, 0); + struct sockaddr_in6 serv_addr; + memset(&serv_addr, '0', sizeof(serv_addr)); + serv_addr.sin6_family = AF_INET6; + serv_addr.sin6_port = htons(params->port); + serv_addr.sin6_flowinfo = 0; + serv_addr.sin6_addr = in6addr_any; + serv_addr.sin6_scope_id = IP6_NO_ZONE; +#else int listenfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in serv_addr; memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(params->port); + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(listenfd, 2); @@ -1618,8 +1767,7 @@ static void server_task(void *pvParameters) /* Read the headers, noting some of interest. */ wificfg_content_type content_type = HTTP_CONTENT_TYPE_OTHER; bool connection_close = false; - bool hostp = false; - uint32_t host = IPADDR_NONE; + bool host_redirect = false; long content_length = 0; for (;;) { @@ -1640,8 +1788,10 @@ static void server_task(void *pvParameters) value = skip_whitespace(value); switch (header) { case HTTP_HEADER_HOST: - hostp = true; - host = ipaddr_addr(value); + if (hostname_local && host_is_name(value) && + strcmp(value, hostname_local)) { + host_redirect = true; + } break; case HTTP_HEADER_CONTENT_LENGTH: content_length = strtoul(value, NULL, 10); @@ -1658,12 +1808,14 @@ static void server_task(void *pvParameters) } } - if (hostp && host == IPADDR_NONE) { + if (host_redirect) { /* Redirect to an IP address. */ handle_ipaddr_redirect(s, buf, sizeof(buf)); /* Close the connection. */ break; - } else if (match) { + } + + if (match) { if ((*match->handler)(s, method, content_length, content_type, buf, sizeof(buf)) < 0) break; } else { if (wificfg_write_string(s, not_found_header) < 0) break; @@ -1712,13 +1864,23 @@ static void dns_task(void *pvParameters) ip4_addr_t server_addr; server_addr.addr = ipaddr_addr(wifi_ap_ip_addr); - struct sockaddr_in serv_addr; +#if LWIP_IPV6 + int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + struct sockaddr_in6 serv_addr; + memset(&serv_addr, '0', sizeof(serv_addr)); + serv_addr.sin6_family = AF_INET6; + serv_addr.sin6_port = htons(53); + serv_addr.sin6_flowinfo = 0; + serv_addr.sin6_addr = in6addr_any; + serv_addr.sin6_scope_id = IP6_NO_ZONE; +#else int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - + struct sockaddr_in serv_addr; memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(53); +#endif bind(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); const struct ifreq ifreq0 = { "en0" }; @@ -1729,12 +1891,12 @@ static void dns_task(void *pvParameters) for (;;) { char buffer[96]; - struct sockaddr src_addr; + struct sockaddr_storage src_addr; socklen_t src_addr_len = sizeof(src_addr); size_t count = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_addr_len); /* Drop messages that are too large to send a response in the buffer */ - if (count > 0 && count <= sizeof(buffer) - 16 && src_addr.sa_family == AF_INET) { + if (count > 0 && count <= sizeof(buffer) - 16) { size_t qname_len = strlen(buffer + 12) + 1; uint32_t reply_len = 2 + 10 + qname_len + 16 + 4; @@ -1771,7 +1933,7 @@ static void dns_task(void *pvParameters) *head++ = ip4_addr3(&server_addr); *head++ = ip4_addr4(&server_addr); - sendto(fd, buffer, reply_len, 0, &src_addr, src_addr_len); + sendto(fd, buffer, reply_len, 0, (struct sockaddr*)&src_addr, src_addr_len); } } } @@ -2020,6 +2182,11 @@ void wificfg_init(uint32_t port, const wificfg_dispatch *dispatch) params->wificfg_dispatch = wificfg_dispatch_list; params->dispatch = dispatch; - xTaskCreate(server_task, "WiFi Cfg HTTP", 464, params, 2, NULL); + size_t stack_size = 464; +#if LWIP_MDNS_RESPONDER + /* Uses a lot of stack space, so allocate extra. */ + stack_size += 128; +#endif + xTaskCreate(server_task, "WiFi Cfg HTTP", stack_size, params, 2, NULL); } }