mirror of
https://github.com/esphome/esphome.git
synced 2026-09-10 06:48:45 +00:00
Gate the dial-back flag on this connection's transport and tune redial policy
This commit is contained in:
@@ -1823,13 +1823,18 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
this->complete_authentication_();
|
||||
|
||||
#ifdef USE_API_OUTGOING_CONNECTION
|
||||
// Only honor the flag once a real key is active: with a PSK set, plaintext
|
||||
// and the all-zeros provisioning PSK are rejected at the transport, so a
|
||||
// client reaching this point has proven possession of the key.
|
||||
if (msg.outgoing_connection_target && !this->flags_.outgoing_connection_target &&
|
||||
this->parent_->get_noise_ctx().has_psk()) {
|
||||
this->flags_.outgoing_connection_target = true;
|
||||
this->parent_->on_outgoing_target_client(this);
|
||||
// Only honor the flag once a real key is active. With a PSK set, plaintext
|
||||
// and the all-zeros provisioning PSK are rejected at the transport, and any
|
||||
// session that predates key activation is force-closed when the key is
|
||||
// applied (see update_noise_psk_), so a client reaching this point has
|
||||
// proven possession of the key.
|
||||
if (msg.outgoing_connection_target && !this->flags_.outgoing_connection_target) {
|
||||
if (this->parent_->get_noise_ctx().has_psk()) {
|
||||
this->flags_.outgoing_connection_target = true;
|
||||
this->parent_->on_outgoing_target_client(this);
|
||||
} else {
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Dial-back target refused; no key active"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -76,6 +79,7 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) {
|
||||
}
|
||||
this->dial_socket_ = socket::socket_loop_monitored(((struct sockaddr *) &addr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (!this->dial_socket_ || this->dial_socket_->setblocking(false) != 0) {
|
||||
ESP_LOGW(TAG, "Socket create failed: errno %d", errno);
|
||||
this->schedule_retry_(now);
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +88,11 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) {
|
||||
if (err == 0) {
|
||||
// Immediate success (possible for localhost)
|
||||
this->dialed_conn_ = server->add_outgoing_client_(std::move(this->dial_socket_));
|
||||
this->schedule_retry_(now);
|
||||
if (this->dialed_conn_ == nullptr) {
|
||||
this->schedule_retry_(now);
|
||||
} else {
|
||||
this->schedule_wait_(now, PRECONDITION_RETRY_MS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (errno != EINPROGRESS) {
|
||||
@@ -118,8 +126,13 @@ void OutgoingConnectionManager::poll_connect_(APIServer *server, uint32_t now) {
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(fd, &writefds);
|
||||
struct timeval tv = {0, 0};
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
// LWIP_COMPAT_SOCKETS may be off (LibreTiny), so use the lwip symbol directly
|
||||
int ret = lwip_select(fd + 1, nullptr, &writefds, nullptr, &tv);
|
||||
#else
|
||||
// Global-scope select: the entity namespace esphome::select shadows it here
|
||||
int ret = ::select(fd + 1, nullptr, &writefds, nullptr, &tv);
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "Connect poll failed: errno %d", errno);
|
||||
this->schedule_retry_(now);
|
||||
@@ -136,9 +149,14 @@ void OutgoingConnectionManager::poll_connect_(APIServer *server, uint32_t now) {
|
||||
return;
|
||||
}
|
||||
this->dialed_conn_ = server->add_outgoing_client_(std::move(this->dial_socket_));
|
||||
// Stay in a retry wait until the peer proves itself by sending a flagged
|
||||
// hello; on_target_client() then resets to idle and clears the backoff.
|
||||
this->schedule_retry_(now);
|
||||
if (this->dialed_conn_ == nullptr) {
|
||||
this->schedule_retry_(now);
|
||||
return;
|
||||
}
|
||||
// The dial worked; hold without escalating until the peer proves itself
|
||||
// with a flagged hello (on_target_client() resets to idle) or dies
|
||||
// unproven (on_client_removed() escalates the backoff).
|
||||
this->schedule_wait_(now, PRECONDITION_RETRY_MS);
|
||||
}
|
||||
|
||||
void OutgoingConnectionManager::schedule_retry_(uint32_t now) {
|
||||
@@ -151,6 +169,13 @@ void OutgoingConnectionManager::schedule_retry_(uint32_t now) {
|
||||
this->backoff_ = std::min(this->backoff_ * 2, BACKOFF_MAX_MS);
|
||||
}
|
||||
|
||||
void OutgoingConnectionManager::on_client_removed(APIConnection *conn) {
|
||||
if (conn == this->dialed_conn_) {
|
||||
this->dialed_conn_ = nullptr;
|
||||
this->schedule_retry_(App.get_loop_component_start_time());
|
||||
}
|
||||
}
|
||||
|
||||
void OutgoingConnectionManager::on_target_client(APIConnection *conn) {
|
||||
// The target is connected; stop any dial in flight and reset the backoff.
|
||||
this->dial_socket_.reset();
|
||||
|
||||
@@ -37,12 +37,10 @@ class OutgoingConnectionManager {
|
||||
/// Called when a key-verified client declares itself a dial-back target;
|
||||
/// the last such client wins as the remembered address.
|
||||
void on_target_client(APIConnection *conn);
|
||||
/// Called for every removed connection so a dialed one stops gating re-dials
|
||||
void on_client_removed(APIConnection *conn) {
|
||||
if (conn == this->dialed_conn_) {
|
||||
this->dialed_conn_ = nullptr;
|
||||
}
|
||||
}
|
||||
/// Called for every removed connection so a dialed one stops gating
|
||||
/// re-dials. A dialed connection dying without ever sending the flagged
|
||||
/// hello is the unproven-peer case, so the backoff escalates here.
|
||||
void on_client_removed(APIConnection *conn);
|
||||
void on_shutdown() { this->dial_socket_.reset(); }
|
||||
void dump_config() const;
|
||||
|
||||
|
||||
@@ -616,6 +616,10 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
if (!c->send_message(req)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Disconnect request");
|
||||
}
|
||||
// Force the disconnect: a session opened before the key was active
|
||||
// (plaintext or zero-PSK) must not survive activation, or its peer
|
||||
// could later claim capabilities reserved for key-verified clients.
|
||||
c->flags_.next_close = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user