[ethernet][network][wifi] Arbitrate the default route from the network priority list (#17797)

This commit is contained in:
Keith Burzinski
2026-08-12 16:21:37 +12:00
committed by GitHub
parent 8d87ba34d9
commit cffd775450
15 changed files with 366 additions and 16 deletions
@@ -0,0 +1,26 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: arduino
wifi:
ssid: "test_ssid"
password: "test_password"
ethernet:
type: W5500
clk_pin: 19
mosi_pin: 21
miso_pin: 23
cs_pin: 18
interrupt_pin: 36
reset_pin: 22
clock_speed: 10Mhz
network:
priority:
- ethernet
- wifi
@@ -0,0 +1,24 @@
esphome:
name: test
rp2:
board: rpipicow
wifi:
ssid: "test_ssid"
password: "test_password"
ethernet:
type: W5500
clk_pin: 18
mosi_pin: 19
miso_pin: 16
cs_pin: 17
interrupt_pin: 21
reset_pin: 20
mac_address: "02:AA:BB:CC:DD:01"
network:
priority:
- ethernet
- wifi
@@ -0,0 +1,15 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
ssid: "test_ssid"
password: "test_password"
network:
priority:
- wifi
+70 -1
View File
@@ -16,9 +16,10 @@ from esphome.components.network import (
_validate_priority_list,
get_network_priority,
)
from esphome.const import CONF_PRIORITY
from esphome.const import CONF_PRIORITY, PlatformFramework
from esphome.core import CORE
import esphome.final_validate as fv
from tests.component_tests.types import SetCoreConfigCallable
@pytest.fixture(autouse=True)
@@ -138,6 +139,22 @@ def test_final_validate_noop_without_priority_list() -> None:
_final_validate({}) # must not raise
def test_final_validate_rejects_unsupported_arbitration_interface(
set_core_config: SetCoreConfigCallable,
) -> None:
"""The ethernet/wifi-only arbitration tripwire fails as a clean config error.
Unreachable through the public schema today (VALID_NETWORK_TYPES gates the
list), so the config is hand-built to simulate a future interface type that
was added to the schema without extending NetworkComponent::loop().
"""
set_core_config(PlatformFramework.ESP32_IDF)
fv.full_config.set({"openthread": {}, "wifi": {}})
config = {CONF_PRIORITY: [{"interface": "openthread"}, {"interface": "wifi"}]}
with pytest.raises(Invalid, match="arbitration does not support: openthread"):
_final_validate(config)
def _cpp_setup_priority(name: str) -> float:
"""Read a setup_priority constant straight from esphome/core/component.h."""
header = Path(__file__).parents[3] / "esphome" / "core" / "component.h"
@@ -199,3 +216,55 @@ def test_no_primary_interface_define_without_priority(
assert not any(
d.name.startswith("USE_NETWORK_PRIMARY_INTERFACE_") for d in CORE.defines
)
def _dns_per_default_netif_option() -> bool | None:
from esphome.components.esp32.const import KEY_ESP32, KEY_SDKCONFIG_OPTIONS
if KEY_ESP32 not in CORE.data: # non-ESP32 configs have no sdkconfig at all
return None
return CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS].get(
"CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF"
)
@pytest.mark.parametrize(
"config_file",
[
"priority_wifi_first.yaml",
"priority_ethernet_first.yaml",
"priority_arduino.yaml",
],
)
def test_multi_interface_priority_enables_default_route_arbitration(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
) -> None:
"""More than one interface in 'priority' enables default-route arbitration."""
generate_main(component_config_path(config_file))
assert "USE_NETWORK_DEFAULT_ROUTE" in {d.name for d in CORE.defines}
assert _dns_per_default_netif_option() is True
@pytest.mark.parametrize(
"config_file",
[
# Single-entry priority list / no list at all.
"priority_single.yaml",
"wifi_only.yaml",
# Dual-interface on rp2040: validates, but the arbitration is ESP32-only
# (NetworkComponent::loop() is compiled under USE_ESP32) — emitting the
# define here would be a hard build break.
"priority_rp2040.yaml",
],
)
def test_single_interface_has_no_default_route_arbitration(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
) -> None:
"""Single-interface and non-ESP32 configs must not compile in the arbitration."""
generate_main(component_config_path(config_file))
assert "USE_NETWORK_DEFAULT_ROUTE" not in {d.name for d in CORE.defines}
assert _dns_per_default_netif_option() is None