diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 3614c7d593..93c9209716 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,7 +24,7 @@ static const char *const TAG = "api"; void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); - socket_ = socket::socket(AF_INET, SOCK_STREAM, 0); + socket_ = socket::socket_ip(SOCK_STREAM, 0); if (socket_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -43,13 +43,16 @@ void APIServer::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; - err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } + + err = socket_->bind((struct sockaddr *) &server, sl); if (err != 0) { ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 6d39023a80..40d420c48c 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,7 +1,27 @@ import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.esp32 import add_idf_sdkconfig_option + +from esphome.const import ( + CONF_ENABLE_IPV6, +) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] network_ns = cg.esphome_ns.namespace("network") IPAddress = network_ns.class_("IPAddress") + +CONFIG_SCHEMA = cv.Schema( + { + cv.SplitDefault(CONF_ENABLE_IPV6, esp32_idf=False): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + } +) + + +async def to_code(config): + if CONF_ENABLE_IPV6 in config and config[CONF_ENABLE_IPV6]: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index c05f2aa546..37da3bdc44 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -36,7 +36,7 @@ std::unique_ptr make_ota_backend() { } void OTAComponent::setup() { - server_ = socket::socket(AF_INET, SOCK_STREAM, 0); + server_ = socket::socket_ip(SOCK_STREAM, 0); if (server_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -55,11 +55,14 @@ void OTAComponent::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } err = server_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp new file mode 100644 index 0000000000..22a4c11df8 --- /dev/null +++ b/esphome/components/socket/socket.cpp @@ -0,0 +1,43 @@ +#include "socket.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace socket { + +std::unique_ptr socket_ip(int type, int protocol) { +#if LWIP_IPV6 + return socket(AF_INET6, type, protocol); +#else + return socket(AF_INET, type, protocol); +#endif +} + +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { +#if LWIP_IPV6 + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in6)); + server->sin6_family = AF_INET6; + server->sin6_port = port; + server->sin6_addr = in6addr_any; + return sizeof(sockaddr_in6); +#else + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in)); + server->sin_family = AF_INET; + server->sin_addr.s_addr = ESPHOME_INADDR_ANY; + server->sin_port = port; + return sizeof(sockaddr_in); +#endif +} +} // namespace socket +} // namespace esphome diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 9920610bf5..ecf117deeb 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -38,7 +38,14 @@ class Socket { virtual int loop() { return 0; }; }; +/// Create a socket of the given domain, type and protocol. std::unique_ptr socket(int domain, int type, int protocol); +/// Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol. +std::unique_ptr socket_ip(int type, int protocol); + +/// Set a sockaddr to the any address for the IP version used by socket_ip(). +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); + } // namespace socket } // namespace esphome diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 60a3bf171d..b838e42b0d 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -56,6 +56,7 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; + ip_event_got_ip6_t ip_got_ip6; ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -79,6 +80,8 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { + memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { @@ -497,12 +500,8 @@ const char *get_auth_mode_str(uint8_t mode) { } } -std::string format_ip4_addr(const esp_ip4_addr_t &ip) { - char buf[20]; - snprintf(buf, sizeof(buf), "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} +std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } +std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -635,10 +634,17 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; +#ifdef LWIP_IPV6_AUTOCONFIG + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); +#endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); s_sta_got_ip = true; + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { + const auto &it = data->data.ip_got_ip6; + ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); s_sta_got_ip = false; diff --git a/esphome/const.py b/esphome/const.py index 0c8e53380e..a008c5ab7f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -188,6 +188,7 @@ CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" +CONF_ENABLE_IPV6 = "enable_ipv6" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy"