mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 11:56:25 +01:00
Wireguard component (#4256)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Simone Rossetto <simros85@gmail.com> Co-authored-by: Thomas Bernard <thomas0bernard@gmail.com>
This commit is contained in:
parent
d2648657fb
commit
b107948c47
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -232,7 +232,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8]
|
file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -13,6 +13,12 @@ __pycache__/
|
|||||||
# Intellij Idea
|
# Intellij Idea
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.pydevproject
|
||||||
|
.settings/
|
||||||
|
|
||||||
# Vim
|
# Vim
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
@ -333,6 +333,7 @@ esphome/components/web_server_idf/* @dentra
|
|||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
esphome/components/whynter/* @aeonsablaze
|
esphome/components/whynter/* @aeonsablaze
|
||||||
esphome/components/wiegand/* @ssieb
|
esphome/components/wiegand/* @ssieb
|
||||||
|
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
|
||||||
esphome/components/wl_134/* @hobbypunk90
|
esphome/components/wl_134/* @hobbypunk90
|
||||||
esphome/components/x9c/* @EtienneMD
|
esphome/components/x9c/* @EtienneMD
|
||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||||
|
113
esphome/components/wireguard/__init__.py
Normal file
113
esphome/components/wireguard/__init__.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import re
|
||||||
|
import ipaddress
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TIME_ID,
|
||||||
|
CONF_ADDRESS,
|
||||||
|
CONF_REBOOT_TIMEOUT,
|
||||||
|
)
|
||||||
|
from esphome.components import time
|
||||||
|
|
||||||
|
CONF_NETMASK = "netmask"
|
||||||
|
CONF_PRIVATE_KEY = "private_key"
|
||||||
|
CONF_PEER_ENDPOINT = "peer_endpoint"
|
||||||
|
CONF_PEER_PUBLIC_KEY = "peer_public_key"
|
||||||
|
CONF_PEER_PORT = "peer_port"
|
||||||
|
CONF_PEER_PRESHARED_KEY = "peer_preshared_key"
|
||||||
|
CONF_PEER_ALLOWED_IPS = "peer_allowed_ips"
|
||||||
|
CONF_PEER_PERSISTENT_KEEPALIVE = "peer_persistent_keepalive"
|
||||||
|
CONF_REQUIRE_CONNECTION_TO_PROCEED = "require_connection_to_proceed"
|
||||||
|
|
||||||
|
DEPENDENCIES = ["time", "esp32"]
|
||||||
|
CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"]
|
||||||
|
|
||||||
|
# The key validation regex has been described by Jason Donenfeld himself
|
||||||
|
# url: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html
|
||||||
|
_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)
|
||||||
|
|
||||||
|
|
||||||
|
def _wireguard_key(value):
|
||||||
|
if _WG_KEY_REGEX.match(cv.string(value)) is not None:
|
||||||
|
return value
|
||||||
|
raise cv.Invalid(f"Invalid WireGuard key: {value}")
|
||||||
|
|
||||||
|
|
||||||
|
def _cidr_network(value):
|
||||||
|
try:
|
||||||
|
ipaddress.ip_network(value, strict=False)
|
||||||
|
except ValueError as err:
|
||||||
|
raise cv.Invalid(f"Invalid network in CIDR notation: {err}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(Wireguard),
|
||||||
|
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
|
cv.Required(CONF_ADDRESS): cv.ipv4,
|
||||||
|
cv.Optional(CONF_NETMASK, default="255.255.255.255"): cv.ipv4,
|
||||||
|
cv.Required(CONF_PRIVATE_KEY): _wireguard_key,
|
||||||
|
cv.Required(CONF_PEER_ENDPOINT): cv.string,
|
||||||
|
cv.Required(CONF_PEER_PUBLIC_KEY): _wireguard_key,
|
||||||
|
cv.Optional(CONF_PEER_PORT, default=51820): cv.port,
|
||||||
|
cv.Optional(CONF_PEER_PRESHARED_KEY): _wireguard_key,
|
||||||
|
cv.Optional(CONF_PEER_ALLOWED_IPS, default=["0.0.0.0/0"]): cv.ensure_list(
|
||||||
|
_cidr_network
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PEER_PERSISTENT_KEEPALIVE, default=0): cv.Any(
|
||||||
|
cv.positive_time_period_seconds,
|
||||||
|
cv.uint16_t,
|
||||||
|
),
|
||||||
|
cv.Optional(
|
||||||
|
CONF_REBOOT_TIMEOUT, default="15min"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_REQUIRE_CONNECTION_TO_PROCEED, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("10s"))
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
|
cg.add(var.set_address(str(config[CONF_ADDRESS])))
|
||||||
|
cg.add(var.set_netmask(str(config[CONF_NETMASK])))
|
||||||
|
cg.add(var.set_private_key(config[CONF_PRIVATE_KEY]))
|
||||||
|
cg.add(var.set_peer_endpoint(config[CONF_PEER_ENDPOINT]))
|
||||||
|
cg.add(var.set_peer_public_key(config[CONF_PEER_PUBLIC_KEY]))
|
||||||
|
cg.add(var.set_peer_port(config[CONF_PEER_PORT]))
|
||||||
|
cg.add(var.set_keepalive(config[CONF_PEER_PERSISTENT_KEEPALIVE]))
|
||||||
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
|
|
||||||
|
if CONF_PEER_PRESHARED_KEY in config:
|
||||||
|
cg.add(var.set_preshared_key(config[CONF_PEER_PRESHARED_KEY]))
|
||||||
|
|
||||||
|
allowed_ips = list(
|
||||||
|
ipaddress.collapse_addresses(
|
||||||
|
[
|
||||||
|
ipaddress.ip_network(ip, strict=False)
|
||||||
|
for ip in config[CONF_PEER_ALLOWED_IPS]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for ip in allowed_ips:
|
||||||
|
cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask)))
|
||||||
|
|
||||||
|
cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID])))
|
||||||
|
|
||||||
|
if config[CONF_REQUIRE_CONNECTION_TO_PROCEED]:
|
||||||
|
cg.add(var.disable_auto_proceed())
|
||||||
|
|
||||||
|
# This flag is added here because the esp_wireguard library statically
|
||||||
|
# set the size of its allowed_ips list at compile time using this value;
|
||||||
|
# the '+1' modifier is relative to the device's own address that will
|
||||||
|
# be automatically added to the provided list.
|
||||||
|
cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}")
|
||||||
|
cg.add_library("droscy/esp_wireguard", "0.3.2")
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
28
esphome/components/wireguard/binary_sensor.py
Normal file
28
esphome/components/wireguard/binary_sensor.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import binary_sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_STATUS,
|
||||||
|
DEVICE_CLASS_CONNECTIVITY,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import Wireguard
|
||||||
|
|
||||||
|
CONF_WIREGUARD_ID = "wireguard_id"
|
||||||
|
|
||||||
|
DEPENDENCIES = ["wireguard"]
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = {
|
||||||
|
cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard),
|
||||||
|
cv.Optional(CONF_STATUS): binary_sensor.binary_sensor_schema(
|
||||||
|
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
parent = await cg.get_variable(config[CONF_WIREGUARD_ID])
|
||||||
|
|
||||||
|
if status_config := config.get(CONF_STATUS):
|
||||||
|
sens = await binary_sensor.new_binary_sensor(status_config)
|
||||||
|
cg.add(parent.set_status_sensor(sens))
|
30
esphome/components/wireguard/sensor.py
Normal file
30
esphome/components/wireguard/sensor.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import Wireguard
|
||||||
|
|
||||||
|
CONF_WIREGUARD_ID = "wireguard_id"
|
||||||
|
CONF_LATEST_HANDSHAKE = "latest_handshake"
|
||||||
|
|
||||||
|
DEPENDENCIES = ["wireguard"]
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = {
|
||||||
|
cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard),
|
||||||
|
cv.Optional(CONF_LATEST_HANDSHAKE): sensor.sensor_schema(
|
||||||
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
parent = await cg.get_variable(config[CONF_WIREGUARD_ID])
|
||||||
|
|
||||||
|
if latest_handshake_config := config.get(CONF_LATEST_HANDSHAKE):
|
||||||
|
sens = await sensor.new_sensor(latest_handshake_config)
|
||||||
|
cg.add(parent.set_handshake_sensor(sens))
|
296
esphome/components/wireguard/wireguard.cpp
Normal file
296
esphome/components/wireguard/wireguard.cpp
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
#include "wireguard.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/time.h"
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
|
||||||
|
#include <esp_err.h>
|
||||||
|
|
||||||
|
#include <esp_wireguard.h>
|
||||||
|
|
||||||
|
// includes for resume/suspend wdt
|
||||||
|
#if defined(USE_ESP_IDF)
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
#include <spi_flash_mmap.h>
|
||||||
|
#endif
|
||||||
|
#elif defined(USE_ARDUINO)
|
||||||
|
#include <esp32-hal.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace wireguard {
|
||||||
|
|
||||||
|
static const char *const TAG = "wireguard";
|
||||||
|
|
||||||
|
static const char *const LOGMSG_PEER_STATUS = "WireGuard remote peer is %s (latest handshake %s)";
|
||||||
|
static const char *const LOGMSG_ONLINE = "online";
|
||||||
|
static const char *const LOGMSG_OFFLINE = "offline";
|
||||||
|
|
||||||
|
void Wireguard::setup() {
|
||||||
|
ESP_LOGD(TAG, "initializing WireGuard...");
|
||||||
|
|
||||||
|
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_.port = this->peer_port_;
|
||||||
|
this->wg_config_.netmask = this->netmask_.c_str();
|
||||||
|
this->wg_config_.persistent_keepalive = this->keepalive_;
|
||||||
|
|
||||||
|
if (this->preshared_key_.length() > 0)
|
||||||
|
this->wg_config_.preshared_key = this->preshared_key_.c_str();
|
||||||
|
|
||||||
|
this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
|
||||||
|
|
||||||
|
if (this->wg_initialized_ == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "WireGuard initialized");
|
||||||
|
this->wg_peer_offline_time_ = millis();
|
||||||
|
this->srctime_->add_on_time_sync_callback(std::bind(&Wireguard::start_connection_, this));
|
||||||
|
this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "cannot initialize WireGuard, error code %d", this->wg_initialized_);
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wireguard::loop() {
|
||||||
|
if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
|
||||||
|
ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard...");
|
||||||
|
this->stop_connection_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wireguard::update() {
|
||||||
|
bool peer_up = this->is_peer_up();
|
||||||
|
time_t lhs = this->get_latest_handshake();
|
||||||
|
bool lhs_updated = (lhs > this->latest_saved_handshake_);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "handshake: latest=%.0f, saved=%.0f, updated=%d", (double) lhs, (double) this->latest_saved_handshake_,
|
||||||
|
(int) lhs_updated);
|
||||||
|
|
||||||
|
if (lhs_updated) {
|
||||||
|
this->latest_saved_handshake_ = lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string latest_handshake =
|
||||||
|
(this->latest_saved_handshake_ > 0)
|
||||||
|
? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||||
|
: "timestamp not available";
|
||||||
|
|
||||||
|
if (peer_up) {
|
||||||
|
if (this->wg_peer_offline_time_ != 0) {
|
||||||
|
ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
|
||||||
|
this->wg_peer_offline_time_ = 0;
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this->wg_peer_offline_time_ == 0) {
|
||||||
|
ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
|
||||||
|
this->wg_peer_offline_time_ = millis();
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
|
||||||
|
this->start_connection_();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check reboot timeout every time the peer is down
|
||||||
|
if (this->reboot_timeout_ > 0) {
|
||||||
|
if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
|
||||||
|
ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting...");
|
||||||
|
App.reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
if (this->status_sensor_ != nullptr) {
|
||||||
|
this->status_sensor_->publish_state(peer_up);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
if (this->handshake_sensor_ != nullptr && lhs_updated) {
|
||||||
|
this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wireguard::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "WireGuard:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"),
|
||||||
|
(this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
|
||||||
|
(this->keepalive_ > 0 ? "s" : " (DISABLED)"));
|
||||||
|
ESP_LOGCONFIG(TAG, " Reboot Timeout: %d%s", (this->reboot_timeout_ / 1000),
|
||||||
|
(this->reboot_timeout_ != 0 ? "s" : " (DISABLED)"));
|
||||||
|
// be careful: if proceed_allowed_ is true, require connection is false
|
||||||
|
ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES"));
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wireguard::on_shutdown() { this->stop_connection_(); }
|
||||||
|
|
||||||
|
bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up()); }
|
||||||
|
|
||||||
|
bool Wireguard::is_peer_up() const {
|
||||||
|
return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
|
||||||
|
(esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t Wireguard::get_latest_handshake() const {
|
||||||
|
time_t result;
|
||||||
|
if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
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; }
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
void Wireguard::set_status_sensor(binary_sensor::BinarySensor *sensor) { this->status_sensor_ = sensor; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
void Wireguard::set_handshake_sensor(sensor::Sensor *sensor) { this->handshake_sensor_ = sensor; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Wireguard::disable_auto_proceed() { this->proceed_allowed_ = false; }
|
||||||
|
|
||||||
|
void Wireguard::start_connection_() {
|
||||||
|
if (this->wg_initialized_ != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!network::is_connected()) {
|
||||||
|
ESP_LOGD(TAG, "WireGuard is waiting for local network connection to be available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->srctime_->now().is_valid()) {
|
||||||
|
ESP_LOGD(TAG, "WireGuard is waiting for system time to be synchronized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->wg_connected_ == ESP_OK) {
|
||||||
|
ESP_LOGV(TAG, "WireGuard connection already started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "starting WireGuard connection...");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The function esp_wireguard_connect() contains a DNS resolution
|
||||||
|
* that could trigger the watchdog, so before it we suspend (or
|
||||||
|
* increase the time, it depends on the platform) the wdt and
|
||||||
|
* then we resume the normal timeout.
|
||||||
|
*/
|
||||||
|
suspend_wdt();
|
||||||
|
ESP_LOGV(TAG, "executing esp_wireguard_connect");
|
||||||
|
this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
|
||||||
|
resume_wdt();
|
||||||
|
|
||||||
|
if (this->wg_connected_ == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "WireGuard connection started");
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "configuring WireGuard allowed IPs list...");
|
||||||
|
bool allowed_ips_ok = true;
|
||||||
|
for (std::tuple<std::string, std::string> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowed_ips_ok) {
|
||||||
|
ESP_LOGD(TAG, "allowed IPs list configured correctly");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "cannot configure WireGuard allowed IPs list, aborting...");
|
||||||
|
this->stop_connection_();
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wireguard::stop_connection_() {
|
||||||
|
if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
|
||||||
|
ESP_LOGD(TAG, "stopping WireGuard connection...");
|
||||||
|
esp_wireguard_disconnect(&(this->wg_ctx_));
|
||||||
|
this->wg_connected_ = ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void suspend_wdt() {
|
||||||
|
#if defined(USE_ESP_IDF)
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15000 ms");
|
||||||
|
esp_task_wdt_config_t wdtc;
|
||||||
|
wdtc.timeout_ms = 15000;
|
||||||
|
wdtc.idle_core_mask = 0;
|
||||||
|
wdtc.trigger_panic = false;
|
||||||
|
esp_task_wdt_reconfigure(&wdtc);
|
||||||
|
#else
|
||||||
|
ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15 seconds");
|
||||||
|
esp_task_wdt_init(15, false);
|
||||||
|
#endif
|
||||||
|
#elif defined(USE_ARDUINO)
|
||||||
|
ESP_LOGV(TAG, "temporarily disabling the wdt");
|
||||||
|
disableLoopWDT();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume_wdt() {
|
||||||
|
#if defined(USE_ESP_IDF)
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
||||||
|
esp_task_wdt_reconfigure(&wdtc);
|
||||||
|
ESP_LOGV(TAG, "wdt resumed with %d ms timeout", wdtc.timeout_ms);
|
||||||
|
#else
|
||||||
|
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
|
||||||
|
ESP_LOGV(TAG, "wdt resumed with %d seconds timeout", CONFIG_ESP_TASK_WDT_TIMEOUT_S);
|
||||||
|
#endif
|
||||||
|
#elif defined(USE_ARDUINO)
|
||||||
|
enableLoopWDT();
|
||||||
|
ESP_LOGV(TAG, "wdt resumed");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); }
|
||||||
|
|
||||||
|
} // namespace wireguard
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32
|
122
esphome/components/wireguard/wireguard.h
Normal file
122
esphome/components/wireguard/wireguard.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <esp_wireguard.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace wireguard {
|
||||||
|
|
||||||
|
class Wireguard : public PollingComponent {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void on_shutdown() override;
|
||||||
|
bool can_proceed() override;
|
||||||
|
|
||||||
|
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 add_allowed_ip(const std::string &ip, const std::string &netmask);
|
||||||
|
|
||||||
|
void set_keepalive(uint16_t seconds);
|
||||||
|
void set_reboot_timeout(uint32_t seconds);
|
||||||
|
void set_srctime(time::RealTimeClock *srctime);
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
void set_status_sensor(binary_sensor::BinarySensor *sensor);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
void set_handshake_sensor(sensor::Sensor *sensor);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Block the setup step until peer is connected.
|
||||||
|
void disable_auto_proceed();
|
||||||
|
|
||||||
|
bool is_peer_up() const;
|
||||||
|
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_;
|
||||||
|
|
||||||
|
std::vector<std::tuple<std::string, std::string>> allowed_ips_;
|
||||||
|
|
||||||
|
uint16_t peer_port_;
|
||||||
|
uint16_t keepalive_;
|
||||||
|
uint32_t reboot_timeout_;
|
||||||
|
|
||||||
|
time::RealTimeClock *srctime_;
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
binary_sensor::BinarySensor *status_sensor_ = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
sensor::Sensor *handshake_sensor_ = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Set to false to block the setup step until peer is connected.
|
||||||
|
bool proceed_allowed_ = true;
|
||||||
|
|
||||||
|
wireguard_config_t wg_config_ = ESP_WIREGUARD_CONFIG_DEFAULT();
|
||||||
|
wireguard_ctx_t wg_ctx_ = ESP_WIREGUARD_CONTEXT_DEFAULT();
|
||||||
|
|
||||||
|
esp_err_t wg_initialized_ = ESP_FAIL;
|
||||||
|
esp_err_t wg_connected_ = ESP_FAIL;
|
||||||
|
|
||||||
|
/// The last time the remote peer become offline.
|
||||||
|
uint32_t wg_peer_offline_time_ = 0;
|
||||||
|
|
||||||
|
/** \brief The latest saved handshake.
|
||||||
|
*
|
||||||
|
* This is used to save (and log) the latest completed handshake even
|
||||||
|
* after a full refresh of the wireguard keys (for example after a
|
||||||
|
* stop/start connection cycle).
|
||||||
|
*/
|
||||||
|
time_t latest_saved_handshake_ = 0;
|
||||||
|
|
||||||
|
void start_connection_();
|
||||||
|
void stop_connection_();
|
||||||
|
};
|
||||||
|
|
||||||
|
// These are used for possibly long DNS resolution to temporarily suspend the watchdog
|
||||||
|
void suspend_wdt();
|
||||||
|
void resume_wdt();
|
||||||
|
|
||||||
|
/// Strip most part of the key only for secure printing
|
||||||
|
std::string mask_key(const std::string &key);
|
||||||
|
|
||||||
|
} // namespace wireguard
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32
|
@ -123,6 +123,7 @@ lib_deps =
|
|||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
||||||
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
|
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
|
||||||
|
droscy/esp_wireguard@0.3.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
-DUSE_ESP32
|
-DUSE_ESP32
|
||||||
@ -141,6 +142,7 @@ framework = espidf
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${common:idf.lib_deps}
|
${common:idf.lib_deps}
|
||||||
espressif/esp32-camera@1.0.0 ; esp32_camera
|
espressif/esp32-camera@1.0.0 ; esp32_camera
|
||||||
|
droscy/esp_wireguard@0.3.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:idf.build_flags}
|
${common:idf.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
|
@ -27,3 +27,4 @@ Current test_.yaml file contents.
|
|||||||
| test6.yaml | RP2040 | wifi | N/A
|
| test6.yaml | RP2040 | wifi | N/A
|
||||||
| test7.yaml | ESP32-C3 | wifi | N/A
|
| test7.yaml | ESP32-C3 | wifi | N/A
|
||||||
| test8.yaml | ESP32-S3 | wifi | None
|
| test8.yaml | ESP32-S3 | wifi | None
|
||||||
|
| test10.yaml | ESP32 | wifi | None
|
||||||
|
48
tests/test10.yaml
Normal file
48
tests/test10.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
esphome:
|
||||||
|
name: test10
|
||||||
|
build_path: build/test10
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32doit-devkit-v1
|
||||||
|
framework:
|
||||||
|
type: arduino
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "MySSID1"
|
||||||
|
password: "password1"
|
||||||
|
reboot_timeout: 3min
|
||||||
|
power_save_mode: high
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: VERBOSE
|
||||||
|
|
||||||
|
api:
|
||||||
|
reboot_timeout: 10min
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
|
||||||
|
wireguard:
|
||||||
|
id: vpn
|
||||||
|
address: 172.16.34.100
|
||||||
|
netmask: 255.255.255.0
|
||||||
|
# NEVER use the following keys for your vpn, they are now public!
|
||||||
|
private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8=
|
||||||
|
peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc=
|
||||||
|
peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60=
|
||||||
|
peer_endpoint: wg.server.example
|
||||||
|
peer_persistent_keepalive: 25s
|
||||||
|
peer_allowed_ips:
|
||||||
|
- 172.16.34.0/24
|
||||||
|
- 192.168.4.0/24
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: wireguard
|
||||||
|
status:
|
||||||
|
name: 'WireGuard Status'
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: wireguard
|
||||||
|
latest_handshake:
|
||||||
|
name: 'WireGuard Latest Handshake'
|
Loading…
Reference in New Issue
Block a user