ESPHome  2024.3.2
wireguard.cpp
Go to the documentation of this file.
1 #include "wireguard.h"
2 
3 #ifdef USE_ESP32
4 
5 #include <cinttypes>
6 #include <ctime>
7 #include <functional>
8 
10 #include "esphome/core/log.h"
11 #include "esphome/core/time.h"
13 
14 #include <esp_err.h>
15 
16 #include <esp_wireguard.h>
17 
18 // includes for resume/suspend wdt
19 #if defined(USE_ESP_IDF)
20 #include <esp_task_wdt.h>
21 #if ESP_IDF_VERSION_MAJOR >= 5
22 #include <spi_flash_mmap.h>
23 #endif
24 #elif defined(USE_ARDUINO)
25 #include <esp32-hal.h>
26 #endif
27 
28 namespace esphome {
29 namespace wireguard {
30 
31 static const char *const TAG = "wireguard";
32 
33 static const char *const LOGMSG_PEER_STATUS = "WireGuard remote peer is %s (latest handshake %s)";
34 static const char *const LOGMSG_ONLINE = "online";
35 static const char *const LOGMSG_OFFLINE = "offline";
36 
38  ESP_LOGD(TAG, "initializing WireGuard...");
39 
40  this->wg_config_.address = this->address_.c_str();
41  this->wg_config_.private_key = this->private_key_.c_str();
42  this->wg_config_.endpoint = this->peer_endpoint_.c_str();
43  this->wg_config_.public_key = this->peer_public_key_.c_str();
44  this->wg_config_.port = this->peer_port_;
45  this->wg_config_.netmask = this->netmask_.c_str();
46  this->wg_config_.persistent_keepalive = this->keepalive_;
47 
48  if (this->preshared_key_.length() > 0)
49  this->wg_config_.preshared_key = this->preshared_key_.c_str();
50 
51  this->publish_enabled_state();
52 
53  this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
54 
55  if (this->wg_initialized_ == ESP_OK) {
56  ESP_LOGI(TAG, "WireGuard initialized");
57  this->wg_peer_offline_time_ = millis();
59  this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup
60 
61 #ifdef USE_TEXT_SENSOR
62  if (this->address_sensor_ != nullptr) {
64  }
65 #endif
66  } else {
67  ESP_LOGE(TAG, "cannot initialize WireGuard, error code %d", this->wg_initialized_);
68  this->mark_failed();
69  }
70 }
71 
73  if (!this->enabled_) {
74  return;
75  }
76 
77  if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
78  ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard...");
79  this->stop_connection_();
80  }
81 }
82 
84  bool peer_up = this->is_peer_up();
85  time_t lhs = this->get_latest_handshake();
86  bool lhs_updated = (lhs > this->latest_saved_handshake_);
87 
88  ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d",
89  (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs,
90  (double) this->latest_saved_handshake_, (int) lhs_updated);
91 
92  if (lhs_updated) {
93  this->latest_saved_handshake_ = lhs;
94  }
95 
96  std::string latest_handshake =
97  (this->latest_saved_handshake_ > 0)
98  ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
99  : "timestamp not available";
100 
101  if (peer_up) {
102  if (this->wg_peer_offline_time_ != 0) {
103  ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
104  this->wg_peer_offline_time_ = 0;
105  } else {
106  ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
107  }
108  } else {
109  if (this->wg_peer_offline_time_ == 0) {
110  ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
111  this->wg_peer_offline_time_ = millis();
112  } else if (this->enabled_) {
113  ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
114  this->start_connection_();
115  }
116 
117  // check reboot timeout every time the peer is down
118  if (this->enabled_ && this->reboot_timeout_ > 0) {
119  if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
120  ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting...");
121  App.reboot();
122  }
123  }
124  }
125 
126 #ifdef USE_BINARY_SENSOR
127  if (this->status_sensor_ != nullptr) {
128  this->status_sensor_->publish_state(peer_up);
129  }
130 #endif
131 
132 #ifdef USE_SENSOR
133  if (this->handshake_sensor_ != nullptr && lhs_updated) {
134  this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
135  }
136 #endif
137 }
138 
140  ESP_LOGCONFIG(TAG, "WireGuard:");
141  ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str());
142  ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str());
143  ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str());
144  ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str());
145  ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_);
146  ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str());
147  ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"),
148  (this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
149  ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
150  for (auto &allowed_ip : this->allowed_ips_) {
151  ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str());
152  }
153  ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
154  (this->keepalive_ > 0 ? "s" : " (DISABLED)"));
155  ESP_LOGCONFIG(TAG, " Reboot Timeout: %" PRIu32 "%s", (this->reboot_timeout_ / 1000),
156  (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)"));
157  // be careful: if proceed_allowed_ is true, require connection is false
158  ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES"));
159  LOG_UPDATE_INTERVAL(this);
160 }
161 
163 
164 bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); }
165 
166 bool Wireguard::is_peer_up() const {
167  return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
168  (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
169 }
170 
172  time_t result;
173  if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
174  result = 0;
175  }
176  return result;
177 }
178 
179 void Wireguard::set_address(const std::string &address) { this->address_ = address; }
180 void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; }
181 void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; }
182 void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; }
183 void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; }
184 void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; }
185 void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; }
186 
187 void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) {
188  this->allowed_ips_.emplace_back(ip, netmask);
189 }
190 
191 void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; }
192 void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; }
193 void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; }
194 
195 #ifdef USE_BINARY_SENSOR
198 #endif
199 
200 #ifdef USE_SENSOR
202 #endif
203 
204 #ifdef USE_TEXT_SENSOR
206 #endif
207 
209 
211  this->enabled_ = true;
212  ESP_LOGI(TAG, "WireGuard enabled");
213  this->publish_enabled_state();
214 }
215 
217  this->enabled_ = false;
218  this->defer(std::bind(&Wireguard::stop_connection_, this)); // defer to avoid blocking running loop
219  ESP_LOGI(TAG, "WireGuard disabled");
220  this->publish_enabled_state();
221 }
222 
224 #ifdef USE_BINARY_SENSOR
225  if (this->enabled_sensor_ != nullptr) {
226  this->enabled_sensor_->publish_state(this->enabled_);
227  }
228 #endif
229 }
230 
231 bool Wireguard::is_enabled() { return this->enabled_; }
232 
234  if (!this->enabled_) {
235  ESP_LOGV(TAG, "WireGuard is disabled, cannot start connection");
236  return;
237  }
238 
239  if (this->wg_initialized_ != ESP_OK) {
240  ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_);
241  return;
242  }
243 
244  if (!network::is_connected()) {
245  ESP_LOGD(TAG, "WireGuard is waiting for local network connection to be available");
246  return;
247  }
248 
249  if (!this->srctime_->now().is_valid()) {
250  ESP_LOGD(TAG, "WireGuard is waiting for system time to be synchronized");
251  return;
252  }
253 
254  if (this->wg_connected_ == ESP_OK) {
255  ESP_LOGV(TAG, "WireGuard connection already started");
256  return;
257  }
258 
259  ESP_LOGD(TAG, "starting WireGuard connection...");
260 
261  /*
262  * The function esp_wireguard_connect() contains a DNS resolution
263  * that could trigger the watchdog, so before it we suspend (or
264  * increase the time, it depends on the platform) the wdt and
265  * then we resume the normal timeout.
266  */
267  suspend_wdt();
268  ESP_LOGV(TAG, "executing esp_wireguard_connect");
269  this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
270  resume_wdt();
271 
272  if (this->wg_connected_ == ESP_OK) {
273  ESP_LOGI(TAG, "WireGuard connection started");
274  } else {
275  ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_);
276  return;
277  }
278 
279  ESP_LOGD(TAG, "configuring WireGuard allowed IPs list...");
280  bool allowed_ips_ok = true;
281  for (std::tuple<std::string, std::string> ip : this->allowed_ips_) {
282  allowed_ips_ok &=
283  (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK);
284  }
285 
286  if (allowed_ips_ok) {
287  ESP_LOGD(TAG, "allowed IPs list configured correctly");
288  } else {
289  ESP_LOGE(TAG, "cannot configure WireGuard allowed IPs list, aborting...");
290  this->stop_connection_();
291  this->mark_failed();
292  }
293 }
294 
296  if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
297  ESP_LOGD(TAG, "stopping WireGuard connection...");
298  esp_wireguard_disconnect(&(this->wg_ctx_));
299  this->wg_connected_ = ESP_FAIL;
300  }
301 }
302 
303 void suspend_wdt() {
304 #if defined(USE_ESP_IDF)
305 #if ESP_IDF_VERSION_MAJOR >= 5
306  ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15000 ms");
307  esp_task_wdt_config_t wdtc;
308  wdtc.timeout_ms = 15000;
309  wdtc.idle_core_mask = 0;
310  wdtc.trigger_panic = false;
311  esp_task_wdt_reconfigure(&wdtc);
312 #else
313  ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15 seconds");
314  esp_task_wdt_init(15, false);
315 #endif
316 #elif defined(USE_ARDUINO)
317  ESP_LOGV(TAG, "temporarily disabling the wdt");
318  disableLoopWDT();
319 #endif
320 }
321 
322 void resume_wdt() {
323 #if defined(USE_ESP_IDF)
324 #if ESP_IDF_VERSION_MAJOR >= 5
325  wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
326  esp_task_wdt_reconfigure(&wdtc);
327  ESP_LOGV(TAG, "wdt resumed with %" PRIu32 " ms timeout", wdtc.timeout_ms);
328 #else
329  esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
330  ESP_LOGV(TAG, "wdt resumed with %d seconds timeout", CONFIG_ESP_TASK_WDT_TIMEOUT_S);
331 #endif
332 #elif defined(USE_ARDUINO)
333  enableLoopWDT();
334  ESP_LOGV(TAG, "wdt resumed");
335 #endif
336 }
337 
338 std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); }
339 
340 } // namespace wireguard
341 } // namespace esphome
342 
343 #endif // USE_ESP32
void set_status_sensor(binary_sensor::BinarySensor *sensor)
Definition: wireguard.cpp:196
ESPTime now()
Get the time in the currently defined timezone.
wireguard_config_t wg_config_
Definition: wireguard.h:121
binary_sensor::BinarySensor * status_sensor_
Definition: wireguard.h:103
void set_preshared_key(const std::string &key)
Definition: wireguard.cpp:185
uint32_t wg_peer_offline_time_
The last time the remote peer become offline.
Definition: wireguard.h:128
text_sensor::TextSensor * address_sensor_
Definition: wireguard.h:112
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
sensor::Sensor * handshake_sensor_
Definition: wireguard.h:108
The RealTimeClock class exposes common timekeeping functions via the device&#39;s local real-time clock...
time::RealTimeClock * srctime_
Definition: wireguard.h:100
bool proceed_allowed_
Set to false to block the setup step until peer is connected.
Definition: wireguard.h:116
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:125
void add_on_time_sync_callback(std::function< void()> callback)
void disable_auto_proceed()
Block the setup step until peer is connected.
Definition: wireguard.cpp:208
void set_address(const std::string &address)
Definition: wireguard.cpp:179
void set_keepalive(uint16_t seconds)
Definition: wireguard.cpp:191
std::string mask_key(const std::string &key)
Strip most part of the key only for secure printing.
Definition: wireguard.cpp:338
void enable()
Enable the WireGuard component.
Definition: wireguard.cpp:210
void publish_state(const std::string &state)
Definition: text_sensor.cpp:9
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
void set_address_sensor(text_sensor::TextSensor *sensor)
Definition: wireguard.cpp:205
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
time_t latest_saved_handshake_
The latest saved handshake.
Definition: wireguard.h:136
void set_private_key(const std::string &key)
Definition: wireguard.cpp:181
bool enabled_
When false the wireguard link will not be established.
Definition: wireguard.h:119
void set_srctime(time::RealTimeClock *srctime)
Definition: wireguard.cpp:193
static ESPTime from_epoch_local(time_t epoch)
Convert an UTC epoch timestamp to a local time ESPTime instance.
Definition: time.h:89
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void add_allowed_ip(const std::string &ip, const std::string &netmask)
Definition: wireguard.cpp:187
void set_reboot_timeout(uint32_t seconds)
Definition: wireguard.cpp:192
Application App
Global storage of Application pointer - only one Application can exist.
void set_netmask(const std::string &netmask)
Definition: wireguard.cpp:180
void publish_state(bool state)
Publish a new state to the front-end.
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018) ...
Definition: time.h:61
binary_sensor::BinarySensor * enabled_sensor_
Definition: wireguard.h:104
void disable()
Stop any running connection and disable the WireGuard component.
Definition: wireguard.cpp:216
void publish_enabled_state()
Publish the enabled state if the enabled binary sensor is configured.
Definition: wireguard.cpp:223
void set_peer_endpoint(const std::string &endpoint)
Definition: wireguard.cpp:182
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:113
time_t get_latest_handshake() const
Definition: wireguard.cpp:171
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:37
bool is_enabled()
Return if the WireGuard component is or is not enabled.
Definition: wireguard.cpp:231
void set_handshake_sensor(sensor::Sensor *sensor)
Definition: wireguard.cpp:201
Base-class for all sensors.
Definition: sensor.h:57
void set_peer_public_key(const std::string &key)
Definition: wireguard.cpp:183
void set_enabled_sensor(binary_sensor::BinarySensor *sensor)
Definition: wireguard.cpp:197
void set_peer_port(uint16_t port)
Definition: wireguard.cpp:184
std::vector< std::tuple< std::string, std::string > > allowed_ips_
Definition: wireguard.h:94