Report address parse failures from set_sockaddr and drop duplicated policy

This commit is contained in:
J. Nick Koston
2026-09-02 11:36:57 +02:00
parent 35ab455c10
commit ae549b25bb
5 changed files with 30 additions and 24 deletions
+3 -6
View File
@@ -295,14 +295,11 @@ def _consume_api_sockets(config: ConfigType) -> ConfigType:
def _validate_outgoing_connection(config: ConfigType) -> ConfigType:
# The socket-layer constraint (raw lwip_tcp cannot dial out, the default
# on esp8266/rp2040) is checked against the resolved implementation in
# _validate_outgoing_socket_implementation at final validate
if CONF_OUTGOING_CONNECTION not in config:
return config
if CORE.is_esp8266 or CORE.is_rp2:
raise cv.Invalid(
"outgoing_connection is not supported on this platform because its "
"socket layer cannot make outgoing connections",
path=[CONF_OUTGOING_CONNECTION],
)
if CONF_ENCRYPTION not in config:
raise cv.Invalid(
"outgoing_connection requires 'encryption' so the peer is verified by key",
@@ -81,10 +81,7 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) {
struct sockaddr_storage addr;
socklen_t addr_len =
socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, API_OUTGOING_CONNECTION_PORT);
// inet_addr() cannot signal failure: unparsable IPv4 text yields the
// broadcast address, which is never a valid target either
if (addr_len == 0 || (((struct sockaddr *) &addr)->sa_family == AF_INET &&
((struct sockaddr_in *) &addr)->sin_addr.s_addr == ESPHOME_INADDR_NONE)) {
if (addr_len == 0) {
ESP_LOGW(TAG, "Invalid target %s", host);
#ifndef API_OUTGOING_CONNECTION_HOST
// A corrupt remembered value can never become dialable; forget it
@@ -204,9 +201,8 @@ void OutgoingConnectionManager::on_client_removed(APIConnection *conn, bool was_
const uint32_t now = App.get_loop_component_start_time();
if (was_authenticated) {
// A working peer (e.g. a host: target that never sends the flag)
// disconnected normally
// disconnected normally; state is IDLE, so loop() applies the delay
this->backoff_ = BACKOFF_MIN_MS;
this->schedule_wait_(now, API_OUTGOING_CONNECTION_DELAY);
} else {
this->schedule_retry_(now);
}
@@ -245,11 +241,10 @@ void OutgoingConnectionManager::on_target_client(APIConnection *conn) {
}
void OutgoingConnectionManager::dump_config() const {
#ifdef API_OUTGOING_CONNECTION_HOST
const char *host = API_OUTGOING_CONNECTION_HOST;
#else
const char *host = this->saved_.host[0] != '\0' ? this->saved_.host : "none remembered yet";
#endif
const char *host = this->target_host_();
if (host == nullptr) {
host = "none remembered yet";
}
ESP_LOGCONFIG(TAG,
" Outgoing connection port: %u\n"
" Outgoing connection host: %s",
+3 -2
View File
@@ -298,8 +298,9 @@ void APIServer::add_client_(APIConnection *conn) {
APIConnection *APIServer::add_outgoing_client_(std::unique_ptr<socket::Socket> sock) {
// Re-check at the handoff: inbound clients may have filled the slots and
// the PSK may have been cleared (mark_outgoing() needs the noise helper)
if (this->at_client_limit_() || !this->noise_ctx_.has_psk()) {
ESP_LOGW(TAG, "Dropping outgoing connection (%s)", this->at_client_limit_() ? "max connections" : "no key active");
const bool at_limit = this->at_client_limit_();
if (at_limit || !this->noise_ctx_.has_psk()) {
ESP_LOGW(TAG, "Dropping outgoing connection (%s)", at_limit ? "max connections" : "no key");
return nullptr;
}
auto *conn = new APIConnection(std::move(sock), this);
+10 -2
View File
@@ -165,7 +165,10 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
#else
// Use LWIP-specific functions
ip6_addr_t ip6;
inet6_aton(ip_address, &ip6);
if (inet6_aton(ip_address, &ip6) == 0) {
errno = EINVAL;
return 0;
}
memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr));
#endif
return sizeof(sockaddr_in6);
@@ -185,7 +188,12 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
return 0;
}
#else
server->sin_addr.s_addr = inet_addr(ip_address);
// Unlike inet_addr(), inet_aton() can signal failure while still
// accepting the broadcast address 255.255.255.255
if (inet_aton(ip_address, &server->sin_addr) == 0) {
errno = EINVAL;
return 0;
}
#endif
server->sin_port = htons(port);
return sizeof(sockaddr_in);
@@ -67,9 +67,14 @@ def test_outgoing_connection_rejected_on_raw_lwip_platforms(
set_core_config: SetCoreConfigCallable,
platform_framework: PlatformFramework,
) -> None:
set_core_config(platform_framework)
with pytest.raises(cv.Invalid, match="not supported on this platform"):
CONFIG_SCHEMA(_api_config({"host": "192.168.1.2"}))
"""esp8266 and rp2040 default to lwip_tcp and are rejected at final validate."""
set_core_config(
platform_framework,
full_config={"socket": {"implementation": "lwip_tcp"}},
)
config = CONFIG_SCHEMA(_api_config({"host": "192.168.1.2"}))
with pytest.raises(cv.Invalid, match="lwip_tcp"):
_validate_outgoing_socket_implementation(config)
def test_outgoing_connection_rejects_lwip_tcp_socket(