ESPHome 2025.9.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#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
16namespace esphome {
17namespace wireguard {
18
19static const char *const TAG = "wireguard";
20
21/*
22 * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform
23 * because log messages in `Wireguard::update()` method fail.
24 */
25#define LOGMSG_PEER_STATUS "Remote peer is %s (latest handshake %s)"
26
27static const char *const LOGMSG_ONLINE = "online";
28static const char *const LOGMSG_OFFLINE = "offline";
29
31 this->wg_config_.address = this->address_.c_str();
32 this->wg_config_.private_key = this->private_key_.c_str();
33 this->wg_config_.endpoint = this->peer_endpoint_.c_str();
34 this->wg_config_.public_key = this->peer_public_key_.c_str();
35 this->wg_config_.port = this->peer_port_;
36 this->wg_config_.netmask = this->netmask_.c_str();
37 this->wg_config_.persistent_keepalive = this->keepalive_;
38
39 if (!this->preshared_key_.empty())
40 this->wg_config_.preshared_key = this->preshared_key_.c_str();
41
43
44 {
45 LwIPLock lock;
46 this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
47 }
48
49 if (this->wg_initialized_ == ESP_OK) {
50 ESP_LOGI(TAG, "Initialized");
53 this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup
54
55#ifdef USE_TEXT_SENSOR
56 if (this->address_sensor_ != nullptr) {
58 }
59#endif
60 } else {
61 ESP_LOGE(TAG, "Cannot initialize: error code %d", this->wg_initialized_);
62 this->mark_failed();
63 }
64}
65
67 if (!this->enabled_) {
68 return;
69 }
70
71 if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
72 ESP_LOGV(TAG, "Local network connection has been lost, stopping");
73 this->stop_connection_();
74 }
75}
76
78 bool peer_up = this->is_peer_up();
79 time_t lhs = this->get_latest_handshake();
80 bool lhs_updated = (lhs > this->latest_saved_handshake_);
81
82 ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d",
83 (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs,
84 (double) this->latest_saved_handshake_, (int) lhs_updated);
85
86 if (lhs_updated) {
87 this->latest_saved_handshake_ = lhs;
88 }
89
90 std::string latest_handshake =
91 (this->latest_saved_handshake_ > 0)
92 ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
93 : "timestamp not available";
94
95 if (peer_up) {
96 if (this->wg_peer_offline_time_ != 0) {
97 ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
98 this->wg_peer_offline_time_ = 0;
99 } else {
100 ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
101 }
102 } else {
103 if (this->wg_peer_offline_time_ == 0) {
104 ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
106 } else if (this->enabled_) {
107 ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
108 this->start_connection_();
109 }
110
111 // check reboot timeout every time the peer is down
112 if (this->enabled_ && this->reboot_timeout_ > 0) {
113 if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
114 ESP_LOGE(TAG, "Remote peer is unreachable; rebooting");
115 App.reboot();
116 }
117 }
118 }
119
120#ifdef USE_BINARY_SENSOR
121 if (this->status_sensor_ != nullptr) {
122 this->status_sensor_->publish_state(peer_up);
123 }
124#endif
125
126#ifdef USE_SENSOR
127 if (this->handshake_sensor_ != nullptr && lhs_updated) {
128 this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
129 }
130#endif
131}
132
134 ESP_LOGCONFIG(TAG, "WireGuard:");
135 ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str());
136 ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str());
137 ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str());
138 ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str());
139 ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_);
140 ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str());
141 ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"),
142 (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
143 ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
144 for (auto &allowed_ip : this->allowed_ips_) {
145 ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str());
146 }
147 ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
148 (this->keepalive_ > 0 ? "s" : " (DISABLED)"));
149 ESP_LOGCONFIG(TAG, " Reboot Timeout: %" PRIu32 "%s", (this->reboot_timeout_ / 1000),
150 (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)"));
151 // be careful: if proceed_allowed_ is true, require connection is false
152 ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES"));
153 LOG_UPDATE_INTERVAL(this);
154}
155
157
158bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); }
159
161 return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
162 (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
163}
164
166 time_t result;
167 if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
168 result = 0;
169 }
170 return result;
171}
172
173void Wireguard::set_address(const std::string &address) { this->address_ = address; }
174void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; }
175void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; }
176void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; }
177void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; }
178void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; }
179void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; }
180
181void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) {
182 this->allowed_ips_.emplace_back(ip, netmask);
183}
184
185void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; }
186void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; }
187void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; }
188
189#ifdef USE_BINARY_SENSOR
192#endif
193
194#ifdef USE_SENSOR
196#endif
197
198#ifdef USE_TEXT_SENSOR
200#endif
201
203
205 this->enabled_ = true;
206 ESP_LOGI(TAG, "Enabled");
207 this->publish_enabled_state();
208}
209
211 this->enabled_ = false;
212 this->defer(std::bind(&Wireguard::stop_connection_, this)); // defer to avoid blocking running loop
213 ESP_LOGI(TAG, "Disabled");
214 this->publish_enabled_state();
215}
216
218#ifdef USE_BINARY_SENSOR
219 if (this->enabled_sensor_ != nullptr) {
221 }
222#endif
223}
224
225bool Wireguard::is_enabled() { return this->enabled_; }
226
228 if (!this->enabled_) {
229 ESP_LOGV(TAG, "Disabled, cannot start connection");
230 return;
231 }
232
233 if (this->wg_initialized_ != ESP_OK) {
234 ESP_LOGE(TAG, "Cannot start: error code %d", this->wg_initialized_);
235 return;
236 }
237
238 if (!network::is_connected()) {
239 ESP_LOGD(TAG, "Waiting for local network connection to be available");
240 return;
241 }
242
243 if (!this->srctime_->now().is_valid()) {
244 ESP_LOGD(TAG, "Waiting for system time to be synchronized");
245 return;
246 }
247
248 if (this->wg_connected_ == ESP_OK) {
249 ESP_LOGV(TAG, "Connection already started");
250 return;
251 }
252
253 ESP_LOGD(TAG, "Starting connection");
254 {
255 LwIPLock lock;
256 this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
257 }
258
259 if (this->wg_connected_ == ESP_OK) {
260 ESP_LOGI(TAG, "Connection started");
261 } else if (this->wg_connected_ == ESP_ERR_RETRY) {
262 ESP_LOGD(TAG, "Waiting for endpoint IP address to be available");
263 return;
264 } else {
265 ESP_LOGW(TAG, "Cannot start connection, error code %d", this->wg_connected_);
266 return;
267 }
268
269 ESP_LOGD(TAG, "Configuring allowed IPs list");
270 bool allowed_ips_ok = true;
271 for (std::tuple<std::string, std::string> ip : this->allowed_ips_) {
272 allowed_ips_ok &=
273 (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK);
274 }
275
276 if (allowed_ips_ok) {
277 ESP_LOGD(TAG, "Allowed IPs list configured correctly");
278 } else {
279 ESP_LOGE(TAG, "Cannot configure allowed IPs list, aborting");
280 this->stop_connection_();
281 this->mark_failed();
282 }
283}
284
286 if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
287 ESP_LOGD(TAG, "Stopping connection");
288 {
289 LwIPLock lock;
290 esp_wireguard_disconnect(&(this->wg_ctx_));
291 }
292 this->wg_connected_ = ESP_FAIL;
293 }
294}
295
296std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); }
297
298} // namespace wireguard
299} // namespace esphome
300#endif
uint8_t address
Definition bl0906.h:4
virtual void mark_failed()
Mark this component as failed.
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:750
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:59
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:45
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:103
void set_keepalive(uint16_t seconds)
bool enabled_
When false the wireguard link will not be established.
Definition wireguard.h:118
binary_sensor::BinarySensor * status_sensor_
Definition wireguard.h:102
void set_peer_public_key(const std::string &key)
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
void add_allowed_ip(const std::string &ip, const std::string &netmask)
sensor::Sensor * handshake_sensor_
Definition wireguard.h:107
time::RealTimeClock * srctime_
Definition wireguard.h:99
bool proceed_allowed_
Set to false to block the setup step until peer is connected.
Definition wireguard.h:115
std::vector< std::tuple< std::string, std::string > > allowed_ips_
Definition wireguard.h:93
void set_reboot_timeout(uint32_t seconds)
void set_peer_endpoint(const std::string &endpoint)
void set_private_key(const std::string &key)
void disable_auto_proceed()
Block the setup step until peer is connected.
void set_preshared_key(const std::string &key)
void set_address_sensor(text_sensor::TextSensor *sensor)
text_sensor::TextSensor * address_sensor_
Definition wireguard.h:111
uint32_t wg_peer_offline_time_
The last time the remote peer become offline.
Definition wireguard.h:127
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)
void set_address(const std::string &address)
bool is_enabled()
Return if the WireGuard component is or is not enabled.
void enable()
Enable the WireGuard component.
void set_peer_port(uint16_t port)
void set_netmask(const std::string &netmask)
time_t latest_saved_handshake_
The latest saved handshake.
Definition wireguard.h:135
wireguard_config_t wg_config_
Definition wireguard.h:120
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:26
std::string mask_key(const std::string &key)
Strip most part of the key only for secure printing.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
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:83
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:15
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018)
Definition time.h:59