ESPHome 2026.11.0-dev
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
7#include "esphome/core/log.h"
8#include "esphome/core/time.h"
11
12#include <esp_wireguard.h>
13#include <esp_wireguard_err.h>
14
16
17ESPHOME_LOG_TAG(TAG, "wireguard");
18
19/*
20 * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform
21 * because log messages in `Wireguard::update()` method fail.
22 */
23#define LOGMSG_PEER_STATUS "Remote peer is %s (latest handshake %s)"
24
25static const char *const LOGMSG_ONLINE = "online";
26static const char *const LOGMSG_OFFLINE = "offline";
27
29 this->wg_config_.address = this->address_;
30 this->wg_config_.private_key = this->private_key_;
31 this->wg_config_.endpoint = this->peer_endpoint_;
32 this->wg_config_.public_key = this->peer_public_key_;
33 this->wg_config_.port = this->peer_port_;
34 this->wg_config_.netmask = this->netmask_;
35 this->wg_config_.persistent_keepalive = this->keepalive_;
36
37 if (this->preshared_key_ != nullptr)
38 this->wg_config_.preshared_key = this->preshared_key_;
39
41
42 {
44 this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
45 }
46
47 if (this->wg_initialized_ == ESP_OK) {
48 ESP_LOGI(TAG, "Initialized");
50 this->srctime_->add_on_time_sync_callback([this]() { this->start_connection_(); });
51 this->defer([this]() { this->start_connection_(); }); // defer to avoid blocking setup
52
53#ifdef USE_TEXT_SENSOR
54 if (this->address_sensor_ != nullptr) {
56 }
57#endif
58 } else {
59 ESP_LOGE(TAG, "Cannot initialize: error code %d", this->wg_initialized_);
60 this->mark_failed();
61 }
62}
63
65 if (!this->enabled_) {
66 return;
67 }
68
69 if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
70 ESP_LOGV(TAG, "Local network connection has been lost, stopping");
71 this->stop_connection_();
72 }
73}
74
76 bool peer_up = this->is_peer_up();
77 time_t lhs = this->get_latest_handshake();
78 bool lhs_updated = (lhs > this->latest_saved_handshake_);
79
80 ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d",
81 (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs,
82 (double) this->latest_saved_handshake_, (int) lhs_updated);
83
84 if (lhs_updated) {
85 this->latest_saved_handshake_ = lhs;
86 }
87
88 std::string latest_handshake =
89 (this->latest_saved_handshake_ > 0)
90 ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
91 : "timestamp not available";
92
93 if (peer_up) {
94 if (this->wg_peer_offline_time_ != 0) {
95 ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
96 this->wg_peer_offline_time_ = 0;
97 } else {
98 ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
99 }
100 } else {
101 if (this->wg_peer_offline_time_ == 0) {
102 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 if (millis() - this->wg_reconnect_time_ >= RECONNECT_INTERVAL_MS) {
108 this->wg_reconnect_time_ = millis();
109 this->stop_connection_();
110 }
111 this->start_connection_();
112 }
113
114 // check reboot timeout every time the peer is down
115 if (this->enabled_ && this->reboot_timeout_ > 0) {
116 if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
117 ESP_LOGE(TAG, "Remote peer is unreachable; rebooting");
118 App.reboot();
119 }
120 }
121 }
122
123#ifdef USE_BINARY_SENSOR
124 if (this->status_sensor_ != nullptr) {
125 this->status_sensor_->publish_state(peer_up);
126 }
127#endif
128
129#ifdef USE_SENSOR
130 if (this->handshake_sensor_ != nullptr && lhs_updated) {
131 this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
132 }
133#endif
134}
135
137 char private_key_masked[MASK_KEY_BUFFER_SIZE];
138 char preshared_key_masked[MASK_KEY_BUFFER_SIZE];
139 mask_key_to(private_key_masked, sizeof(private_key_masked), this->private_key_);
140 mask_key_to(preshared_key_masked, sizeof(preshared_key_masked), this->preshared_key_);
141 // clang-format off
142 ESP_LOGCONFIG(
143 TAG,
144 "WireGuard:\n"
145 " Address: %s\n"
146 " Netmask: %s\n"
147 " Private Key: " LOG_SECRET("%s") "\n"
148 " Peer Endpoint: " LOG_SECRET("%s") "\n"
149 " Peer Port: " LOG_SECRET("%d") "\n"
150 " Peer Public Key: " LOG_SECRET("%s") "\n"
151 " Peer Pre-shared Key: " LOG_SECRET("%s"),
152 this->address_, this->netmask_, private_key_masked,
153 this->peer_endpoint_, this->peer_port_, this->peer_public_key_,
154 (this->preshared_key_ != nullptr ? preshared_key_masked : LOG_STR_LITERAL("NOT IN USE")));
155 // clang-format on
156 ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
157 for (const AllowedIP *it = this->allowed_ips_; it != nullptr && it->ip != nullptr; it++) {
158 ESP_LOGCONFIG(TAG, " - %s/%s", it->ip, it->netmask);
159 }
160 ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
161 (this->keepalive_ > 0 ? LOG_STR_LITERAL("s") : LOG_STR_LITERAL(" (DISABLED)")));
162 ESP_LOGCONFIG(TAG, " Reboot Timeout: %" PRIu32 "%s", (this->reboot_timeout_ / 1000),
163 (this->reboot_timeout_ != 0 ? LOG_STR_LITERAL("s") : LOG_STR_LITERAL(" (DISABLED)")));
164 // be careful: if proceed_allowed_ is true, require connection is false
165 ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s",
166 (this->proceed_allowed_ ? LOG_STR_LITERAL("NO") : LOG_STR_LITERAL("YES")));
167 LOG_UPDATE_INTERVAL(this);
168}
169
171
172bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); }
173
175 return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
176 (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
177}
178
180 time_t result;
181 if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
182 result = 0;
183 }
184 return result;
185}
186
188 this->enabled_ = true;
189 ESP_LOGI(TAG, "Enabled");
190 this->publish_enabled_state();
191}
192
194 this->enabled_ = false;
195 this->defer([this]() { this->stop_connection_(); }); // defer to avoid blocking running loop
196 ESP_LOGI(TAG, "Disabled");
197 this->publish_enabled_state();
198}
199
201#ifdef USE_BINARY_SENSOR
202 if (this->enabled_sensor_ != nullptr) {
204 }
205#endif
206}
207
209 if (!this->enabled_) {
210 ESP_LOGV(TAG, "Disabled, cannot start connection");
211 return;
212 }
213
214 if (this->wg_initialized_ != ESP_OK) {
215 ESP_LOGE(TAG, "Cannot start: error code %d", this->wg_initialized_);
216 return;
217 }
218
219 if (!network::is_connected()) {
220 ESP_LOGD(TAG, "Waiting for local network connection to be available");
221 return;
222 }
223
224 if (!this->srctime_->now().is_valid()) {
225 ESP_LOGD(TAG, "Waiting for system time to be synchronized");
226 return;
227 }
228
229 if (this->wg_connected_ == ESP_OK) {
230 ESP_LOGV(TAG, "Connection already started");
231 return;
232 }
233
234 ESP_LOGD(TAG, "Starting connection");
235 {
237 this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
238 }
239
240 if (this->wg_connected_ == ESP_OK) {
241 ESP_LOGI(TAG, "Connection started");
242 } else if (this->wg_connected_ == ESP_ERR_RETRY) {
243 ESP_LOGD(TAG, "Waiting for endpoint IP address to be available");
244 return;
245 } else {
246 ESP_LOGW(TAG, "Cannot start connection, error code %d", this->wg_connected_);
247 return;
248 }
249
250 ESP_LOGD(TAG, "Configuring allowed IPs list");
251 bool allowed_ips_ok = true;
252 for (const AllowedIP *it = this->allowed_ips_; it != nullptr && it->ip != nullptr; it++) {
253 allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), it->ip, it->netmask) == ESP_OK);
254 }
255
256 if (allowed_ips_ok) {
257 ESP_LOGD(TAG, "Allowed IPs list configured correctly");
258 } else {
259 ESP_LOGE(TAG, "Cannot configure allowed IPs list, aborting");
260 this->stop_connection_();
261 this->mark_failed();
262 }
263}
264
266 if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
267 ESP_LOGD(TAG, "Stopping connection");
268 {
270 esp_wireguard_disconnect(&(this->wg_ctx_));
271 }
272 this->wg_connected_ = ESP_FAIL;
273 }
274}
275
276void mask_key_to(char *buffer, size_t len, const char *key) {
277 // Format: "XXXXX[...]=\0" = MASK_KEY_BUFFER_SIZE chars minimum
278 if (len < MASK_KEY_BUFFER_SIZE || key == nullptr) {
279 if (len > 0)
280 buffer[0] = '\0';
281 return;
282 }
283 // Copy first 5 characters of the key
284 size_t i = 0;
285 for (; i < 5 && key[i] != '\0'; ++i) {
286 buffer[i] = key[i];
287 }
288 // Append "[...]="
289 const char *suffix = "[...]=";
290 for (size_t j = 0; suffix[j] != '\0' && (i + j) < len - 1; ++j) {
291 buffer[i + j] = suffix[j];
292 }
293 buffer[i + 6] = '\0';
294}
295
296} // namespace esphome::wireguard
297#endif
void mark_failed()
Mark this component as failed.
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:2183
void publish_state(bool new_state)
Publish a new state to the front-end.
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
void publish_state(const std::string &state)
Definition text_sensor.h:42
ESPTime now()
Get the time in the currently defined timezone.
void add_on_time_sync_callback(F &&callback)
binary_sensor::BinarySensor * enabled_sensor_
Definition wireguard.h:116
bool enabled_
When false the wireguard link will not be established.
Definition wireguard.h:131
binary_sensor::BinarySensor * status_sensor_
Definition wireguard.h:115
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:120
time::RealTimeClock * srctime_
Definition wireguard.h:112
bool proceed_allowed_
Set to false to block the setup step until peer is connected.
Definition wireguard.h:128
text_sensor::TextSensor * address_sensor_
Definition wireguard.h:124
uint32_t wg_peer_offline_time_
The last time the remote peer become offline.
Definition wireguard.h:140
void disable()
Stop any running connection and disable the WireGuard component.
void enable()
Enable the WireGuard component.
const AllowedIP * allowed_ips_
Definition wireguard.h:106
time_t latest_saved_handshake_
The latest saved handshake.
Definition wireguard.h:149
wireguard_config_t wg_config_
Definition wireguard.h:133
const char * key
uint32_t buffer
Definition gif.cpp:50
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.h:36
void mask_key_to(char *buffer, size_t len, const char *key)
Strip most part of the key only for secure printing.
ESPHOME_LOG_TAG(TAG, "wireguard")
const void size_t len
Definition hal.h:68
uint32_t IRAM_ATTR HOT millis()
Definition hal.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:125
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:20
bool is_valid(bool check_day_of_week=true, bool check_day_of_year=true) const
Check if this ESPTime is valid (year >= 2019 and the requested fields are in range).
Definition time.h:82
Allowed IP entry for WireGuard peer configuration.
Definition wireguard.h:27
SemaphoreHandle_t lock