ESPHome 2026.2.1
Loading...
Searching...
No Matches
wireguard.cpp
Go to the documentation of this file.
1#include "wireguard.h"
2#ifdef USE_WIREGUARD
3#include <cinttypes>
4#include <ctime>
5#include <functional>
6
8#include "esphome/core/log.h"
9#include "esphome/core/time.h"
12
13#include <esp_wireguard.h>
14#include <esp_wireguard_err.h>
15
17
18static const char *const TAG = "wireguard";
19
20/*
21 * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform
22 * because log messages in `Wireguard::update()` method fail.
23 */
24#define LOGMSG_PEER_STATUS "Remote peer is %s (latest handshake %s)"
25
26static const char *const LOGMSG_ONLINE = "online";
27static const char *const LOGMSG_OFFLINE = "offline";
28
30 this->wg_config_.address = this->address_;
31 this->wg_config_.private_key = this->private_key_;
32 this->wg_config_.endpoint = this->peer_endpoint_;
33 this->wg_config_.public_key = this->peer_public_key_;
34 this->wg_config_.port = this->peer_port_;
35 this->wg_config_.netmask = this->netmask_;
36 this->wg_config_.persistent_keepalive = this->keepalive_;
37
38 if (this->preshared_key_ != nullptr)
39 this->wg_config_.preshared_key = this->preshared_key_;
40
42
43 {
44 LwIPLock lock;
45 this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
46 }
47
48 if (this->wg_initialized_ == ESP_OK) {
49 ESP_LOGI(TAG, "Initialized");
52 this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup
53
54#ifdef USE_TEXT_SENSOR
55 if (this->address_sensor_ != nullptr) {
57 }
58#endif
59 } else {
60 ESP_LOGE(TAG, "Cannot initialize: error code %d", this->wg_initialized_);
61 this->mark_failed();
62 }
63}
64
66 if (!this->enabled_) {
67 return;
68 }
69
70 if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
71 ESP_LOGV(TAG, "Local network connection has been lost, stopping");
72 this->stop_connection_();
73 }
74}
75
77 bool peer_up = this->is_peer_up();
78 time_t lhs = this->get_latest_handshake();
79 bool lhs_updated = (lhs > this->latest_saved_handshake_);
80
81 ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d",
82 (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs,
83 (double) this->latest_saved_handshake_, (int) lhs_updated);
84
85 if (lhs_updated) {
86 this->latest_saved_handshake_ = lhs;
87 }
88
89 std::string latest_handshake =
90 (this->latest_saved_handshake_ > 0)
91 ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
92 : "timestamp not available";
93
94 if (peer_up) {
95 if (this->wg_peer_offline_time_ != 0) {
96 ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
97 this->wg_peer_offline_time_ = 0;
98 } else {
99 ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
100 }
101 } else {
102 if (this->wg_peer_offline_time_ == 0) {
103 ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
105 } else if (this->enabled_) {
106 ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
107 this->start_connection_();
108 }
109
110 // check reboot timeout every time the peer is down
111 if (this->enabled_ && this->reboot_timeout_ > 0) {
112 if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
113 ESP_LOGE(TAG, "Remote peer is unreachable; rebooting");
114 App.reboot();
115 }
116 }
117 }
118
119#ifdef USE_BINARY_SENSOR
120 if (this->status_sensor_ != nullptr) {
121 this->status_sensor_->publish_state(peer_up);
122 }
123#endif
124
125#ifdef USE_SENSOR
126 if (this->handshake_sensor_ != nullptr && lhs_updated) {
127 this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
128 }
129#endif
130}
131
133 char private_key_masked[MASK_KEY_BUFFER_SIZE];
134 char preshared_key_masked[MASK_KEY_BUFFER_SIZE];
135 mask_key_to(private_key_masked, sizeof(private_key_masked), this->private_key_);
136 mask_key_to(preshared_key_masked, sizeof(preshared_key_masked), this->preshared_key_);
137 // clang-format off
138 ESP_LOGCONFIG(
139 TAG,
140 "WireGuard:\n"
141 " Address: %s\n"
142 " Netmask: %s\n"
143 " Private Key: " LOG_SECRET("%s") "\n"
144 " Peer Endpoint: " LOG_SECRET("%s") "\n"
145 " Peer Port: " LOG_SECRET("%d") "\n"
146 " Peer Public Key: " LOG_SECRET("%s") "\n"
147 " Peer Pre-shared Key: " LOG_SECRET("%s"),
148 this->address_, this->netmask_, private_key_masked,
149 this->peer_endpoint_, this->peer_port_, this->peer_public_key_,
150 (this->preshared_key_ != nullptr ? preshared_key_masked : "NOT IN USE"));
151 // clang-format on
152 ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
153 for (const AllowedIP &allowed_ip : this->allowed_ips_) {
154 ESP_LOGCONFIG(TAG, " - %s/%s", allowed_ip.ip, allowed_ip.netmask);
155 }
156 ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
157 (this->keepalive_ > 0 ? "s" : " (DISABLED)"));
158 ESP_LOGCONFIG(TAG, " Reboot Timeout: %" PRIu32 "%s", (this->reboot_timeout_ / 1000),
159 (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)"));
160 // be careful: if proceed_allowed_ is true, require connection is false
161 ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES"));
162 LOG_UPDATE_INTERVAL(this);
163}
164
166
167bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); }
168
170 return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
171 (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
172}
173
175 time_t result;
176 if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
177 result = 0;
178 }
179 return result;
180}
181
182void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; }
183void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; }
184void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; }
185
186#ifdef USE_BINARY_SENSOR
189#endif
190
191#ifdef USE_SENSOR
193#endif
194
195#ifdef USE_TEXT_SENSOR
197#endif
198
200
202 this->enabled_ = true;
203 ESP_LOGI(TAG, "Enabled");
204 this->publish_enabled_state();
205}
206
208 this->enabled_ = false;
209 this->defer(std::bind(&Wireguard::stop_connection_, this)); // defer to avoid blocking running loop
210 ESP_LOGI(TAG, "Disabled");
211 this->publish_enabled_state();
212}
213
215#ifdef USE_BINARY_SENSOR
216 if (this->enabled_sensor_ != nullptr) {
218 }
219#endif
220}
221
222bool Wireguard::is_enabled() { return this->enabled_; }
223
225 if (!this->enabled_) {
226 ESP_LOGV(TAG, "Disabled, cannot start connection");
227 return;
228 }
229
230 if (this->wg_initialized_ != ESP_OK) {
231 ESP_LOGE(TAG, "Cannot start: error code %d", this->wg_initialized_);
232 return;
233 }
234
235 if (!network::is_connected()) {
236 ESP_LOGD(TAG, "Waiting for local network connection to be available");
237 return;
238 }
239
240 if (!this->srctime_->now().is_valid()) {
241 ESP_LOGD(TAG, "Waiting for system time to be synchronized");
242 return;
243 }
244
245 if (this->wg_connected_ == ESP_OK) {
246 ESP_LOGV(TAG, "Connection already started");
247 return;
248 }
249
250 ESP_LOGD(TAG, "Starting connection");
251 {
252 LwIPLock lock;
253 this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
254 }
255
256 if (this->wg_connected_ == ESP_OK) {
257 ESP_LOGI(TAG, "Connection started");
258 } else if (this->wg_connected_ == ESP_ERR_RETRY) {
259 ESP_LOGD(TAG, "Waiting for endpoint IP address to be available");
260 return;
261 } else {
262 ESP_LOGW(TAG, "Cannot start connection, error code %d", this->wg_connected_);
263 return;
264 }
265
266 ESP_LOGD(TAG, "Configuring allowed IPs list");
267 bool allowed_ips_ok = true;
268 for (const AllowedIP &ip : this->allowed_ips_) {
269 allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), ip.ip, ip.netmask) == ESP_OK);
270 }
271
272 if (allowed_ips_ok) {
273 ESP_LOGD(TAG, "Allowed IPs list configured correctly");
274 } else {
275 ESP_LOGE(TAG, "Cannot configure allowed IPs list, aborting");
276 this->stop_connection_();
277 this->mark_failed();
278 }
279}
280
282 if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
283 ESP_LOGD(TAG, "Stopping connection");
284 {
285 LwIPLock lock;
286 esp_wireguard_disconnect(&(this->wg_ctx_));
287 }
288 this->wg_connected_ = ESP_FAIL;
289 }
290}
291
292void mask_key_to(char *buffer, size_t len, const char *key) {
293 // Format: "XXXXX[...]=\0" = MASK_KEY_BUFFER_SIZE chars minimum
294 if (len < MASK_KEY_BUFFER_SIZE || key == nullptr) {
295 if (len > 0)
296 buffer[0] = '\0';
297 return;
298 }
299 // Copy first 5 characters of the key
300 size_t i = 0;
301 for (; i < 5 && key[i] != '\0'; ++i) {
302 buffer[i] = key[i];
303 }
304 // Append "[...]="
305 const char *suffix = "[...]=";
306 for (size_t j = 0; suffix[j] != '\0' && (i + j) < len - 1; ++j) {
307 buffer[i + j] = suffix[j];
308 }
309 buffer[i + 6] = '\0';
310}
311
312} // namespace esphome::wireguard
313#endif
virtual void mark_failed()
Mark this component as failed.
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:479
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:1565
Base class for all binary_sensor-type classes.
void publish_state(bool new_state)
Publish a new state to the front-end.
Base-class for all sensors.
Definition sensor.h:43
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
void publish_state(const std::string &state)
The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock.
void add_on_time_sync_callback(std::function< void()> &&callback)
ESPTime now()
Get the time in the currently defined timezone.
binary_sensor::BinarySensor * enabled_sensor_
Definition wireguard.h:118
FixedVector< AllowedIP > allowed_ips_
Definition wireguard.h:108
void set_keepalive(uint16_t seconds)
bool enabled_
When false the wireguard link will not be established.
Definition wireguard.h:133
binary_sensor::BinarySensor * status_sensor_
Definition wireguard.h:117
void set_status_sensor(binary_sensor::BinarySensor *sensor)
void set_srctime(time::RealTimeClock *srctime)
void publish_enabled_state()
Publish the enabled state if the enabled binary sensor is configured.
time_t get_latest_handshake() const
sensor::Sensor * handshake_sensor_
Definition wireguard.h:122
time::RealTimeClock * srctime_
Definition wireguard.h:114
bool proceed_allowed_
Set to false to block the setup step until peer is connected.
Definition wireguard.h:130
void set_reboot_timeout(uint32_t seconds)
void disable_auto_proceed()
Block the setup step until peer is connected.
void set_address_sensor(text_sensor::TextSensor *sensor)
text_sensor::TextSensor * address_sensor_
Definition wireguard.h:126
uint32_t wg_peer_offline_time_
The last time the remote peer become offline.
Definition wireguard.h:142
void disable()
Stop any running connection and disable the WireGuard component.
void set_enabled_sensor(binary_sensor::BinarySensor *sensor)
void set_handshake_sensor(sensor::Sensor *sensor)
bool is_enabled()
Return if the WireGuard component is or is not enabled.
void enable()
Enable the WireGuard component.
time_t latest_saved_handshake_
The latest saved handshake.
Definition wireguard.h:150
wireguard_config_t wg_config_
Definition wireguard.h:135
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:26
void mask_key_to(char *buffer, size_t len, const char *key)
Strip most part of the key only for secure printing.
std::string size_t len
Definition helpers.h:692
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static ESPTime from_epoch_local(time_t epoch)
Convert an UTC epoch timestamp to a local time ESPTime instance.
Definition time.h:107
size_t strftime(char *buffer, size_t buffer_len, const char *format)
Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument.
Definition time.cpp:16
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018)
Definition time.h:74
Allowed IP entry for WireGuard peer configuration.
Definition wireguard.h:28