From 4cc0f874f75b1d83245ae2de67512df1ea385223 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 17 Jan 2026 15:51:26 -1000 Subject: [PATCH] [wireguard] Store configuration strings in flash instead of heap (#13331) --- esphome/components/wireguard/__init__.py | 15 ++++- esphome/components/wireguard/wireguard.cpp | 71 ++++++++++++---------- esphome/components/wireguard/wireguard.h | 61 ++++++++++++------- 3 files changed, 91 insertions(+), 56 deletions(-) diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index 50c7980215..124d9a8c32 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -30,6 +30,7 @@ _WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$") wireguard_ns = cg.esphome_ns.namespace("wireguard") Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent) +AllowedIP = wireguard_ns.struct("AllowedIP") WireguardPeerOnlineCondition = wireguard_ns.class_( "WireguardPeerOnlineCondition", automation.Condition ) @@ -108,8 +109,18 @@ async def to_code(config): ) ) - for ip in allowed_ips: - cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask))) + cg.add( + var.set_allowed_ips( + [ + cg.StructInitializer( + AllowedIP, + ("ip", str(ip.network_address)), + ("netmask", str(ip.netmask)), + ) + for ip in allowed_ips + ] + ) + ) cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID]))) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 7810a40ae1..2022e25b6c 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -13,8 +13,7 @@ #include #include -namespace esphome { -namespace wireguard { +namespace esphome::wireguard { static const char *const TAG = "wireguard"; @@ -28,16 +27,16 @@ static const char *const LOGMSG_ONLINE = "online"; static const char *const LOGMSG_OFFLINE = "offline"; void Wireguard::setup() { - this->wg_config_.address = this->address_.c_str(); - this->wg_config_.private_key = this->private_key_.c_str(); - this->wg_config_.endpoint = this->peer_endpoint_.c_str(); - this->wg_config_.public_key = this->peer_public_key_.c_str(); + this->wg_config_.address = this->address_; + this->wg_config_.private_key = this->private_key_; + this->wg_config_.endpoint = this->peer_endpoint_; + this->wg_config_.public_key = this->peer_public_key_; this->wg_config_.port = this->peer_port_; - this->wg_config_.netmask = this->netmask_.c_str(); + this->wg_config_.netmask = this->netmask_; this->wg_config_.persistent_keepalive = this->keepalive_; - if (!this->preshared_key_.empty()) - this->wg_config_.preshared_key = this->preshared_key_.c_str(); + if (this->preshared_key_ != nullptr) + this->wg_config_.preshared_key = this->preshared_key_; this->publish_enabled_state(); @@ -131,6 +130,10 @@ void Wireguard::update() { } void Wireguard::dump_config() { + char private_key_masked[MASK_KEY_BUFFER_SIZE]; + char preshared_key_masked[MASK_KEY_BUFFER_SIZE]; + mask_key_to(private_key_masked, sizeof(private_key_masked), this->private_key_); + mask_key_to(preshared_key_masked, sizeof(preshared_key_masked), this->preshared_key_); // clang-format off ESP_LOGCONFIG( TAG, @@ -142,13 +145,13 @@ void Wireguard::dump_config() { " Peer Port: " LOG_SECRET("%d") "\n" " Peer Public Key: " LOG_SECRET("%s") "\n" " Peer Pre-shared Key: " LOG_SECRET("%s"), - this->address_.c_str(), this->netmask_.c_str(), mask_key(this->private_key_).c_str(), - this->peer_endpoint_.c_str(), this->peer_port_, this->peer_public_key_.c_str(), - (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + this->address_, this->netmask_, private_key_masked, + this->peer_endpoint_, this->peer_port_, this->peer_public_key_, + (this->preshared_key_ != nullptr ? preshared_key_masked : "NOT IN USE")); // clang-format on ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); - for (auto &allowed_ip : this->allowed_ips_) { - ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); + for (const AllowedIP &allowed_ip : this->allowed_ips_) { + ESP_LOGCONFIG(TAG, " - %s/%s", allowed_ip.ip, allowed_ip.netmask); } ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_, (this->keepalive_ > 0 ? "s" : " (DISABLED)")); @@ -176,18 +179,6 @@ time_t Wireguard::get_latest_handshake() const { return result; } -void Wireguard::set_address(const std::string &address) { this->address_ = address; } -void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; } -void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; } -void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; } -void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; } -void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; } -void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; } - -void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) { - this->allowed_ips_.emplace_back(ip, netmask); -} - void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; } void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; } void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; } @@ -274,9 +265,8 @@ void Wireguard::start_connection_() { ESP_LOGD(TAG, "Configuring allowed IPs list"); bool allowed_ips_ok = true; - for (std::tuple ip : this->allowed_ips_) { - allowed_ips_ok &= - (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK); + for (const AllowedIP &ip : this->allowed_ips_) { + allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), ip.ip, ip.netmask) == ESP_OK); } if (allowed_ips_ok) { @@ -299,8 +289,25 @@ void Wireguard::stop_connection_() { } } -std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); } +void mask_key_to(char *buffer, size_t len, const char *key) { + // Format: "XXXXX[...]=\0" = MASK_KEY_BUFFER_SIZE chars minimum + if (len < MASK_KEY_BUFFER_SIZE || key == nullptr) { + if (len > 0) + buffer[0] = '\0'; + return; + } + // Copy first 5 characters of the key + size_t i = 0; + for (; i < 5 && key[i] != '\0'; ++i) { + buffer[i] = key[i]; + } + // Append "[...]=" + const char *suffix = "[...]="; + for (size_t j = 0; suffix[j] != '\0' && (i + j) < len - 1; ++j) { + buffer[i + j] = suffix[j]; + } + buffer[i + 6] = '\0'; +} -} // namespace wireguard -} // namespace esphome +} // namespace esphome::wireguard #endif diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h index f8f79b835d..e8470c75cd 100644 --- a/esphome/components/wireguard/wireguard.h +++ b/esphome/components/wireguard/wireguard.h @@ -2,10 +2,10 @@ #include "esphome/core/defines.h" #ifdef USE_WIREGUARD #include -#include -#include +#include #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/time/real_time_clock.h" #ifdef USE_BINARY_SENSOR @@ -22,8 +22,13 @@ #include -namespace esphome { -namespace wireguard { +namespace esphome::wireguard { + +/// Allowed IP entry for WireGuard peer configuration. +struct AllowedIP { + const char *ip; + const char *netmask; +}; /// Main Wireguard component class. class Wireguard : public PollingComponent { @@ -37,15 +42,25 @@ class Wireguard : public PollingComponent { float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; } - void set_address(const std::string &address); - void set_netmask(const std::string &netmask); - void set_private_key(const std::string &key); - void set_peer_endpoint(const std::string &endpoint); - void set_peer_public_key(const std::string &key); - void set_peer_port(uint16_t port); - void set_preshared_key(const std::string &key); + void set_address(const char *address) { this->address_ = address; } + void set_netmask(const char *netmask) { this->netmask_ = netmask; } + void set_private_key(const char *key) { this->private_key_ = key; } + void set_peer_endpoint(const char *endpoint) { this->peer_endpoint_ = endpoint; } + void set_peer_public_key(const char *key) { this->peer_public_key_ = key; } + void set_peer_port(uint16_t port) { this->peer_port_ = port; } + void set_preshared_key(const char *key) { this->preshared_key_ = key; } - void add_allowed_ip(const std::string &ip, const std::string &netmask); + /// Prevent accidental use of std::string which would dangle + void set_address(const std::string &address) = delete; + void set_netmask(const std::string &netmask) = delete; + void set_private_key(const std::string &key) = delete; + void set_peer_endpoint(const std::string &endpoint) = delete; + void set_peer_public_key(const std::string &key) = delete; + void set_preshared_key(const std::string &key) = delete; + + void set_allowed_ips(std::initializer_list ips) { this->allowed_ips_ = ips; } + /// Prevent accidental use of std::string which would dangle + void set_allowed_ips(std::initializer_list> ips) = delete; void set_keepalive(uint16_t seconds); void set_reboot_timeout(uint32_t seconds); @@ -83,14 +98,14 @@ class Wireguard : public PollingComponent { time_t get_latest_handshake() const; protected: - std::string address_; - std::string netmask_; - std::string private_key_; - std::string peer_endpoint_; - std::string peer_public_key_; - std::string preshared_key_; + const char *address_{nullptr}; + const char *netmask_{nullptr}; + const char *private_key_{nullptr}; + const char *peer_endpoint_{nullptr}; + const char *peer_public_key_{nullptr}; + const char *preshared_key_{nullptr}; - std::vector> allowed_ips_; + FixedVector allowed_ips_; uint16_t peer_port_; uint16_t keepalive_; @@ -142,8 +157,11 @@ class Wireguard : public PollingComponent { void suspend_wdt(); void resume_wdt(); +/// Size of buffer required for mask_key_to: 5 chars + "[...]=" + null = 12 +static constexpr size_t MASK_KEY_BUFFER_SIZE = 12; + /// Strip most part of the key only for secure printing -std::string mask_key(const std::string &key); +void mask_key_to(char *buffer, size_t len, const char *key); /// Condition to check if remote peer is online. template class WireguardPeerOnlineCondition : public Condition, public Parented { @@ -169,6 +187,5 @@ template class WireguardDisableAction : public Action, pu void play(const Ts &...x) override { this->parent_->disable(); } }; -} // namespace wireguard -} // namespace esphome +} // namespace esphome::wireguard #endif