diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1626f395e6..dab4395fcc 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -133,8 +133,8 @@ void APIConnection::start() { return; } // Initialize client name with peername (IP address) until Hello message provides actual name - const char *peername = this->helper_->get_client_peername(); - this->helper_->set_client_name(peername, strlen(peername)); + char peername[socket::SOCKADDR_STR_LEN]; + this->helper_->set_client_name(this->helper_->get_peername_to(peername), strlen(peername)); } APIConnection::~APIConnection() { @@ -1524,8 +1524,11 @@ void APIConnection::complete_authentication_() { this->flags_.connection_state = static_cast(ConnectionState::AUTHENTICATED); this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected")); #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()), - std::string(this->helper_->get_client_peername())); + { + char peername[socket::SOCKADDR_STR_LEN]; + this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()), + std::string(this->helper_->get_peername_to(peername))); + } #endif #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { @@ -1544,8 +1547,9 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size()); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; + char peername[socket::SOCKADDR_STR_LEN]; ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(), - this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_); + this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; @@ -1862,7 +1866,8 @@ void APIConnection::on_no_setup_connection() { this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup")); } void APIConnection::on_fatal_error() { - this->helper_->close(); + // Don't close socket here - keep it open so getpeername() works for logging + // Socket will be closed when client is removed from the list in APIServer::loop() this->flags_.remove = true; } @@ -2218,12 +2223,14 @@ void APIConnection::process_state_subscriptions_() { #endif // USE_API_HOMEASSISTANT_STATES void APIConnection::log_client_(int level, const LogString *message) { + char peername[socket::SOCKADDR_STR_LEN]; esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(), - this->helper_->get_client_peername(), LOG_STR_ARG(message)); + this->helper_->get_peername_to(peername), LOG_STR_ARG(message)); } void APIConnection::log_warning_(const LogString *message, APIError err) { - ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(), + char peername[socket::SOCKADDR_STR_LEN]; + ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_peername_to(peername), LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 21bf4c4073..ceaba5ddaf 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -281,8 +281,8 @@ class APIConnection final : public APIServerConnection { bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; const char *get_name() const { return this->helper_->get_client_name(); } - /// Get peer name (IP address) - cached at connection init time - const char *get_peername() const { return this->helper_->get_client_peername(); } + /// Get peer name (IP address) into caller-provided buffer, returns buf for convenience + const char *get_peername_to(char *buf) const { return this->helper_->get_peername_to(buf); } protected: // Helper function to handle authentication completion diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index dd44fe9e17..3c28324c6d 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -16,7 +16,12 @@ static const char *const TAG = "api.frame_helper"; static constexpr size_t API_MAX_LOG_BYTES = 168; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE -#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#define HELPER_LOG(msg, ...) \ + do { \ + char peername_buf[socket::SOCKADDR_STR_LEN]; \ + this->get_peername_to(peername_buf); \ + ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername_buf, ##__VA_ARGS__); \ + } while (0) #else #define HELPER_LOG(msg, ...) ((void) 0) #endif @@ -245,8 +250,6 @@ APIError APIFrameHelper::init_common_() { HELPER_LOG("Bad state for init %d", (int) state_); return APIError::BAD_STATE; } - // Cache peername now while socket is valid - needed for error logging after socket failure - this->socket_->getpeername_to(this->client_peername_); int err = this->socket_->setblocking(false); if (err != 0) { state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index f311e34fd7..a857fa2fcc 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -90,8 +90,16 @@ class APIFrameHelper { // Get client name (null-terminated) const char *get_client_name() const { return this->client_name_; } - // Get client peername/IP (null-terminated, cached at init time for availability after socket failure) - const char *get_client_peername() const { return this->client_peername_; } + // Get client peername/IP into caller-provided buffer (fetches on-demand from socket) + // Returns pointer to buf for convenience in printf-style calls + const char *get_peername_to(char *buf) const { + if (this->socket_) { + this->socket_->getpeername_to(std::span(buf, socket::SOCKADDR_STR_LEN)); + } else { + buf[0] = '\0'; + } + return buf; + } // Set client name from buffer with length (truncates if needed) void set_client_name(const char *name, size_t len) { size_t copy_len = std::min(len, sizeof(this->client_name_) - 1); @@ -105,6 +113,8 @@ class APIFrameHelper { bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { + if (state_ == State::CLOSED) + return APIError::OK; // Already closed state_ = State::CLOSED; int err = this->socket_->close(); if (err == -1) @@ -231,8 +241,6 @@ class APIFrameHelper { // Client name buffer - stores name from Hello message or initial peername char client_name_[CLIENT_INFO_NAME_MAX_LEN]{}; - // Cached peername/IP address - captured at init time for availability after socket failure - char client_peername_[socket::SOCKADDR_STR_LEN]{}; // Group smaller types together uint16_t rx_buf_len_ = 0; diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 4a9257231d..c1641b398a 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -29,7 +29,12 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") static constexpr size_t API_MAX_LOG_BYTES = 168; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE -#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#define HELPER_LOG(msg, ...) \ + do { \ + char peername_buf[socket::SOCKADDR_STR_LEN]; \ + this->get_peername_to(peername_buf); \ + ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername_buf, ##__VA_ARGS__); \ + } while (0) #else #define HELPER_LOG(msg, ...) ((void) 0) #endif diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 3dfd683929..ed3cc8934e 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -21,7 +21,12 @@ static const char *const TAG = "api.plaintext"; static constexpr size_t API_MAX_LOG_BYTES = 168; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE -#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#define HELPER_LOG(msg, ...) \ + do { \ + char peername_buf[socket::SOCKADDR_STR_LEN]; \ + this->get_peername_to(peername_buf); \ + ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername_buf, ##__VA_ARGS__); \ + } while (0) #else #define HELPER_LOG(msg, ...) ((void) 0) #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index ed97c3b9a2..379445d149 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -192,11 +192,15 @@ void APIServer::loop() { ESP_LOGV(TAG, "Remove connection %s", client->get_name()); #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - // Save client info before removal for the trigger + // Save client info before closing socket and removal for the trigger + char peername_buf[socket::SOCKADDR_STR_LEN]; std::string client_name(client->get_name()); - std::string client_peername(client->get_peername()); + std::string client_peername(client->get_peername_to(peername_buf)); #endif + // Close socket now (was deferred from on_fatal_error to allow getpeername) + client->helper_->close(); + // Swap with the last element and pop (avoids expensive vector shifts) if (client_index < this->clients_.size() - 1) { std::swap(this->clients_[client_index], this->clients_.back());