mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 19:44:08 +00:00
[ethernet] Fix IPv6 SLAAC: link-local on link-up, pass all-nodes multicast (#19752)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
Claude Opus 5.5
J. Nick Koston
parent
a5178a0f3d
commit
de0a0b2af4
@@ -220,6 +220,9 @@ class EthernetComponent final : public Component {
|
||||
protected:
|
||||
void start_connect_();
|
||||
void finish_connect_();
|
||||
#if LWIP_IPV6
|
||||
esp_err_t ensure_ip6_linklocal_();
|
||||
#endif
|
||||
void dump_connect_params_();
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include <lwip/dns.h>
|
||||
#include <cinttypes>
|
||||
#include "esp_event.h"
|
||||
#if USE_NETWORK_IPV6
|
||||
#include <esp_netif_net_stack.h>
|
||||
#include <lwip/netif.h>
|
||||
#endif
|
||||
#ifdef USE_PSRAM
|
||||
#include <esp_psram.h>
|
||||
#endif
|
||||
@@ -509,6 +513,18 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
// Since IDF 5.5 the internal EMAC drops multicast groups that were never added (before,
|
||||
// it passed all multicast), and lwIP never adds all-nodes, so router advertisements
|
||||
// were lost and SLAAC never ran.
|
||||
{
|
||||
uint8_t all_nodes[6] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01};
|
||||
if (esp_err_t filter_err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_ADD_MAC_FILTER, all_nodes);
|
||||
filter_err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 all-nodes multicast filter not added: %s", esp_err_to_name(filter_err));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Register user defined event handers
|
||||
err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
|
||||
@@ -755,6 +771,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV6
|
||||
// Start SLAAC on link-up, not after the DHCPv4 lease. This also restores the
|
||||
// link-local after a link flap, which clears the IPv6 addresses.
|
||||
if (esp_err_t ll_err = esp_netif_create_ip6_linklocal(global_eth_component->eth_netif_); ll_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed on link-up: %s", esp_err_to_name(ll_err));
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
@@ -791,7 +814,10 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
|
||||
void *event_data) {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
|
||||
global_eth_component->ipv6_count_ += 1;
|
||||
// Count the addresses on the interface, not the events: recreating the link-local
|
||||
// after a link flap fires another event for the same address.
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
global_eth_component->ipv6_count_ = esp_netif_get_all_ip6(global_eth_component->eth_netif_, if_ip6s);
|
||||
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ =
|
||||
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
|
||||
@@ -806,6 +832,29 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
// Create the link-local address unless the interface already has one, including one still in
|
||||
// duplicate address detection: recreating it would restart DAD. esp_netif_get_ip6_linklocal()
|
||||
// only reports a preferred address, so ask lwIP for the slot state instead.
|
||||
esp_err_t EthernetComponent::ensure_ip6_linklocal_() {
|
||||
if (auto *netif = static_cast<struct netif *>(esp_netif_get_netif_impl(this->eth_netif_)); netif != nullptr) {
|
||||
u8_t state;
|
||||
{
|
||||
LwIPLock lock;
|
||||
state = netif_ip6_addr_state(netif, 0);
|
||||
}
|
||||
if (ip6_addr_istentative(state) || ip6_addr_isvalid(state)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
#if USE_NETWORK_IPV6
|
||||
// Retry IPv6 link-local setup if it failed during initial connect
|
||||
@@ -816,10 +865,7 @@ void EthernetComponent::finish_connect_() {
|
||||
// - Cable unplugged/network interruption (#10705)
|
||||
// We can now retry since we're in CONNECTED state and the interface is definitely up.
|
||||
if (!this->ipv6_setup_done_) {
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
|
||||
}
|
||||
this->ensure_ip6_linklocal_();
|
||||
// Always set the flag to prevent continuous retries
|
||||
// If IPv6 setup fails here with the interface up and stable, it's
|
||||
// likely a persistent issue (IPv6 disabled at router, hardware
|
||||
@@ -833,7 +879,9 @@ void EthernetComponent::finish_connect_() {
|
||||
void EthernetComponent::start_connect_() {
|
||||
global_eth_component->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
// Recount rather than zero: addresses that survive a reconnect are not announced again.
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
global_eth_component->ipv6_count_ = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
this->ipv6_setup_done_ = false;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
@@ -912,7 +960,7 @@ void EthernetComponent::start_connect_() {
|
||||
// - At bootup when link isn't ready (#10281)
|
||||
// - After disconnection/cable unplugged (#10705)
|
||||
// We'll retry in finish_connect_() if it fails here.
|
||||
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
err = this->ensure_ip6_linklocal_();
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
|
||||
// This is a programming error, not a transient failure
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
ethernet:
|
||||
type: IP101
|
||||
mdc_pin: 23
|
||||
mdio_pin: 18
|
||||
clk:
|
||||
pin: 0
|
||||
mode: CLK_EXT_IN
|
||||
phy_addr: 1
|
||||
power_pin: 5
|
||||
Reference in New Issue
Block a user