diff --git a/.gitmodules b/.gitmodules index 1ca44949a..a4b1b024a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "vehicle/OVMS.V3/components/wolfssl/wolfssl"] path = vehicle/OVMS.V3/components/wolfssl/wolfssl url = https://github.com/wolfSSL/wolfssl.git +[submodule "vehicle/OVMS.V3/components/esp_wireguard"] + path = vehicle/OVMS.V3/components/esp_wireguard + url = https://github.com/trombik/esp_wireguard.git diff --git a/docs/source/userguide/index.rst b/docs/source/userguide/index.rst index 32e341240..f2cfd812e 100644 --- a/docs/source/userguide/index.rst +++ b/docs/source/userguide/index.rst @@ -14,6 +14,7 @@ User Guide logging configuration wifi + vpn vfs metrics ota diff --git a/docs/source/userguide/vpn-ovms-wireguard.png b/docs/source/userguide/vpn-ovms-wireguard.png new file mode 100644 index 000000000..2fb207a63 Binary files /dev/null and b/docs/source/userguide/vpn-ovms-wireguard.png differ diff --git a/docs/source/userguide/vpn.rst b/docs/source/userguide/vpn.rst new file mode 100644 index 000000000..93243c745 --- /dev/null +++ b/docs/source/userguide/vpn.rst @@ -0,0 +1,273 @@ +.. highlight:: none + +============================= +Virtual Private Network (VPN) +============================= + +.. warning:: **WireGuard VPN support is only available on (upcoming) ESP-IDF v5+ builds** - unfortunately there is no + easy way to make it work with (current) ESP-IDF v3.3.4 builds. + +The OVMS module can be (optionally) configured with a `WireGuard `_ VPN tunnel. +This allows your module to connect (attach) to a private network as if it were part of this network, like +a local node. +In addition WireGuard VPN natively supports roaming, thus if your module changes network, or source IP address +for whatever reason, it will still be connected and visible from your private network. + +Possible use cases : + - securing the (incoming) connection to your module, so that it's not publicly visible (and scannable) on a public IP address, thus reducing the attack surface + - have an always-on SSH connexion to your module - even if it's roaming + - being able to (securely) send files and data, back and forth, from / to your module to / from your private network + +---------------------- +Principle of operation +---------------------- + +At the moment, only one connexion is supported. When configured the VPN tunnel will connect as soon as the +network is ready, and your module will be able to communicate with other nodes of your private network. + + +.. note:: **WireGuard VPN needs the time to be monotonic**, i.e. always goes forward. While not mandatory, it's best to have + a time/date properly setup (NTP, GPS, ...) + +.. note:: You will need a properly configured (and tested) WireGuard peer that we will refer to as **the server** (WireGuard VPN has no + such distinction between clients and servers, but in our case it's easier to grasp), while we will consider that our + module is **the client**. + +The **server** needs to be properly configured to accept incoming WireGuard traffic (i.e. UDP packets) on a public interface - we will +call the address of this public interface the **Peer Endpoint**. + +------------- +Configuration +------------- + +New configuration items are introduced to configure this VPN tunnel, organised using the :doc:`configuration system`:: + + = + +Where **parameter** is always ``network.wireguard``, and **instance** is as follow: + + +========================= ===== +Instance (in OVMS) Comment +========================= ===== +``local_ip_address`` The IP address of your module IN YOUR PRIVATE NETWORK +``local_ip_netmask`` The netmask of your module IN YOUR PRIVATE NETWORK +``local_private_key`` The PRIVATE key of your module for your WireGuard VPN tunnel +``local_port`` The UDP port from which your module will communicate with the WireGuard server +``peer_endpoint`` The IP address or hostname of the WireGuard server +``peer_public_key`` The PUBLIC key of the WireGuard server +``peer_port`` The UDP port on which the WireGuard server listens +``preshared_key`` A (recommended) pre-shared symmetric key for additional protection +``persistent_keepalive`` A keep-alive if your module is behind NAT (recommended for NAT) +========================= ===== + +Example:: + + OVMS# config set network.wireguard local_ip_address 192.168.4.58 + OVMS# config set network.wireguard local_ip_netmask 255.255.255.0 + OVMS# config set network.wireguard local_private_key "IsvT72MAXzA8EtV0FSD1QT59B4x0oe6Uea5rd/dDzhE=" + OVMS# config set network.wireguard local_port 11010 + OVMS# config set network.wireguard peer_endpoint demo.wireguard.com + OVMS# config set network.wireguard peer_public_key "FjrsQ/HD1Q8fUlFILIasDlOuajMeZov4NGqMJpkswiw=" + OVMS# config set network.wireguard peer_port 12912 + OVMS# config set network.wireguard preshared_key "0/2H97Sd5EJ9LAAAYUglVjPYv7ihNIm/ziuv6BtSI50=" + OVMS# config set network.wireguard persistent_keepalive 25 + +.. note:: The preceding values are for documentation only, they won't work as is. If you need to test the tunnel + on a demo server, have a look at `WireGuard Demo `_, download the shell script + and adapt it to your needs. + +``local_ip_address`` and ``local_ip_netmask`` are used to decide: + - what will be the IP address of the module in your private network (``local_ip_address``). A new (local) network interface will be configured with this IP address. + - which routing will be allowed through the VPN tunnel (using ``local_ip_address`` + ``local_ip_netmask``) + +In general, ``local_ip_netmask`` will be the netmask of your private network to enable packets from/to your other network nodes to cross the tunnel. +(In opposite, when setting the reciprocate configuration, you would define only the) + +Correspondance with WireGuard's `Configuration file format `_ + +========================= =========================================== ============================================================ +OVMS Wireguard Client Wireguard Server +========================= =========================================== ============================================================ +``local_ip_address`` Peer / AllowedIPs (one address only) (address of the server in the private network) +``local_ip_netmask`` Peer / AllowedIPs (one address only) (netmask of the private network) +``local_private_key`` Interface / PrivateKey Peer / PublicKey +``local_port`` Interface / ListenPort N/A +``peer_endpoint`` Peer / Endpoint (before colon) (public IP address of the server) +``peer_public_key`` Peer / PublicKey Interface / PrivateKey +``peer_port`` Peer / Endpoint (port number, after colon) Interface / ListenPort +``preshared_key`` Peer / PresharedKey Peer / PresharedKey +``persistent_keepalive`` Peer / PersistentKeepalive Peer / PersistentKeepalive +========================= =========================================== ============================================================ + +^^^^^^^^^^^ +Quick start +^^^^^^^^^^^ +.. note:: for configuring the different keys (private keys, publics keys, shared key) you will need to access a computer with the proper WireGuard + VPN tools installed. In this documentation we will assume that the command line tool ``wg`` is installed on this computer. + +The example configuration is the following: + +.. image:: vpn-ovms-wireguard.png + +* The server listens on ``wg.example.net``, on UDP port ``51820`` +* The private network is ``172.16.0.0/12`` (corresponding netmask ``255.240.0.0``) +* In this private network: + + * the OVMS module has a reserved IP of ``172.16.10.20`` + * the server has a reserved IP of ``172.16.1.10`` (for information purposes, not used in the configuration) +* The OVMS module uses UDP port ``11010`` for its WireGuard VPN purposes. + +Steps:: + + # Generate the server private key + $ wg genkey + SGfeKo9cmhIJ5tpDOjOimnEKi3M+3mJ21+jJ7otllHI= + + # Generate the corresponding server public key + $ echo 'SGfeKo9cmhIJ5tpDOjOimnEKi3M+3mJ21+jJ7otllHI=' | wg pubkey + r5xRjMaiu1apmP6YzRviL8djtBfjcWGnOd2mmHNOqm4= + + # Generate the ovms module private key + $ wg genkey + kI2/adVWRseT2ZtYxn/5lTzQsPKK8F7YHWcIo3iwgHk= + + # Generate the corresponding ovms module public key + $ echo 'kI2/adVWRseT2ZtYxn/5lTzQsPKK8F7YHWcIo3iwgHk=' | wg pubkey + hi0SNx8JJVLWIOuIfeQW5Ea5SK/41g4DeXJ2eJR9R3Y= + + # Generate the pre-shared key + $ wg genpsk + JUvxtI5sFm9n0zY6N4Z8rz/nSnww2DaeFKsOPGnC1WA= + + +Server configuration:: + + [Interface] + PrivateKey = SGfeKo9cmhIJ5tpDOjOimnEKi3M+3mJ21+jJ7otllHI= + ListenPort = 51820 + + [Peer] + PublicKey = hi0SNx8JJVLWIOuIfeQW5Ea5SK/41g4DeXJ2eJR9R3Y= + PresharedKey = JUvxtI5sFm9n0zY6N4Z8rz/nSnww2DaeFKsOPGnC1WA= + AllowedIPs = 172.16.10.20/32 + PersistentKeepalive = 25 + +OVMS configuration:: + + OVMS# config set network.wireguard local_ip_address 172.16.10.20 + OVMS# config set network.wireguard local_ip_netmask 255.240.0.0 + OVMS# config set network.wireguard local_port 11010 + OVMS# config set network.wireguard local_private_key "kI2/adVWRseT2ZtYxn/5lTzQsPKK8F7YHWcIo3iwgHk=" + OVMS# config set network.wireguard peer_endpoint wg.example.net + OVMS# config set network.wireguard peer_port 51820 + OVMS# config set network.wireguard peer_public_key "r5xRjMaiu1apmP6YzRviL8djtBfjcWGnOd2mmHNOqm4=" + OVMS# config set network.wireguard preshared_key "JUvxtI5sFm9n0zY6N4Z8rz/nSnww2DaeFKsOPGnC1WA=" + OVMS# config set network.wireguard persistent_keepalive 25 + +.. note:: The preceding values are for documentation only, they won't work as is. + +^^^^^^^^^^^^^^^ +Troubleshooting +^^^^^^^^^^^^^^^ + +If the module doesn't find your WireGuard server, you can use the following tools for diagnostic:: + + OVMS# wireguard status + + OVMS# network status + + OVMS# wireguard stop + OVMS# wireguard start + OVMS# wireguard restart + + OVMS# network ping X.Y.Z.T + +Set the logs to debug, and every 10s the wireguard subsystem will print if the peer is 'up' or 'down'. + +When neither the WireGuard VPN nor the WiFi client are active, the output of ``network status`` looks like this:: + + OVMS# network status + Interface#2: ap2 (ifup=1 linkup=1) + IPv4: 192.168.4.1/255.255.255.0 gateway 192.168.4.1 + + Interface#1: st1 (ifup=0 linkup=0) + IPv4: 0.0.0.0/0.0.0.0 gateway 0.0.0.0 + + DNS: None + + Default Interface: ap2 (192.168.4.1/255.255.255.0 gateway 192.168.4.1) + +As soon as the WiFi client is connected, the WireGuard VPN interface appears, but not connected (``linkup=0``):: + + OVMS# network status + Interface#3: wg3 (ifup=1 linkup=0) + IPv4: 172.16.10.20/255.255.255.0 gateway 0.0.0.0 + + Interface#2: ap2 (ifup=1 linkup=1) + IPv4: 192.168.4.1/255.255.255.0 gateway 192.168.4.1 + + Interface#1: st1 (ifup=1 linkup=1) + IPv4: 192.168.1.12/255.255.255.0 gateway 192.168.1.1 + + DNS: 192.168.1.1 + + Default Interface: st1 (192.168.1.12/255.255.255.0 gateway 192.168.1.1) + +The status is:: + + OVMS# wireguard status + Connection status: started + Peer status: down + +After a little while, the WireGuard VPN interface connects (``linkup=1``) and the status is ``up``:: + + OVMS# network status + Interface#3: wg3 (ifup=1 linkup=1) + IPv4: 172.16.10.20/255.255.255.0 gateway 0.0.0.0 + + Interface#2: ap2 (ifup=1 linkup=1) + IPv4: 192.168.4.1/255.255.255.0 gateway 192.168.4.1 + + Interface#1: st1 (ifup=1 linkup=1) + IPv4: 192.168.1.12/255.255.255.0 gateway 192.168.1.1 + + DNS: 192.168.1.1 + + Default Interface: st1 (192.168.1.12/255.255.255.0 gateway 192.168.1.1) + + OVMS# wireguard status + Connection status: started + Peer status: up + +And you can ping another host on the private network:: + + OVMS# network ping 172.16.1.2 + PING 172.16.1.2 (172.16.1.2): 64 data bytes + 64 bytes from 172.16.1.2: icmp_seq=0 ttl=64 time=2 ms + 64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=2 ms + 64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=5 ms + 64 bytes from 172.16.1.2: icmp_seq=3 ttl=64 time=3 ms + + --- 172.16.1.2 ping statistics --- + 4 packets transmitted, 4 received, 0% packet loss, time 12ms + round-trip avg = 3.00 ms + +----------------------- +Firewall considerations +----------------------- + +WireGuard is an UDP-based protocol. +There is one UDP port to consider for each node (one for the server, one for the module). +By convention, the UDP port is often **51820** but can be changed with no issue. + +For the **server**, the firewall needs to accept incoming UDP packets on a public interface from the possible source addresses of your module. (In case of a WiFi network you manage, this can be a certain range. However if your module is either roaming or using a mobile network with unknown source +addresses you may have to open this UDP port to the whole internet). + +------------------------ +Bandwidth considerations +------------------------ +If you use the ``persistent_keepalive`` setting, it will send an UDP packet each ``persistent_keepalive`` seconds. +The recommended value for systems behind NATs is 25s, so it can make your module send a lot of (small) packets many time per hour. + +If this is a concern you may want to disable the tunnel when you don't need it (``wireguard stop``) and only enable it when needed (``wireguard start``) diff --git a/vehicle/OVMS.V3/Makefile b/vehicle/OVMS.V3/Makefile index 8d6744e55..dd0628307 100644 --- a/vehicle/OVMS.V3/Makefile +++ b/vehicle/OVMS.V3/Makefile @@ -5,4 +5,7 @@ PROJECT_NAME := ovms3 +# Not compatible with ESP-IDF < 4 +EXCLUDE_COMPONENTS := esp_wireguard + include $(IDF_PATH)/make/project.mk diff --git a/vehicle/OVMS.V3/changes.txt b/vehicle/OVMS.V3/changes.txt index 55c16e981..34534ddd1 100644 --- a/vehicle/OVMS.V3/changes.txt +++ b/vehicle/OVMS.V3/changes.txt @@ -98,6 +98,22 @@ Open Vehicle Monitor System v3 - Change log obd2ecu.stop -- Called before the OBD2ECU process is stopped. - Web UI: Add configuration for Valet and Flatbed geofence to the Locations config page. - Network: New 'network ping' command to ping (ICMP) hostname or IP address. (ESP-IDFv4+ only / needs to be enabled in menuconfig - Developer Options) +- Network: Add support for WireGuard VPN (ESP-IDFv4+ only / needs to be enabled in menuconfig - Developer Options) + New commands: + wireguard start -- start the VPN tunnel + wireguard stop -- stop the VPN tunnel (and reset default route) + wireguard restart -- restart the VPN tunnel (and reset default route) + wireguard default -- set default route to use the VPN tunnel + New Configs: + [network.wireguard] local_ip_address -- The IP address of your module IN YOUR PRIVATE NETWORK + [network.wireguard] local_ip_netmask -- The netmask of your module IN YOUR PRIVATE NETWORK + [network.wireguard] local_private_key -- The PRIVATE key of your module for your WireGuard VPN tunnel + [network.wireguard] local_port -- The UDP port from which your module will communicate with the WireGuard server + [network.wireguard] peer_endpoint -- The IP address or hostname of the WireGuard server + [network.wireguard] peer_public_key -- The PUBLIC key of the WireGuard server + [network.wireguard] peer_port -- The UDP port on which the WireGuard server listens + [network.wireguard] preshared_key -- A (recommended) pre-shared symmetric key for additional protection + [network.wireguard] persistent_keepalive -- A keep-alive if your module is behind NAT (recommended for NAT) 2022-09-01 MWJ 3.3.003 OTA release - Toyota RAV4 EV: Initial support added. Only the Tesla bus is decoded and just listening so far. diff --git a/vehicle/OVMS.V3/components/esp_wireguard b/vehicle/OVMS.V3/components/esp_wireguard new file mode 160000 index 000000000..a441e52b5 --- /dev/null +++ b/vehicle/OVMS.V3/components/esp_wireguard @@ -0,0 +1 @@ +Subproject commit a441e52b5a117f3dc3caa01376726d9dc9331192 diff --git a/vehicle/OVMS.V3/components/ovms_wireguard/CMakeLists.txt b/vehicle/OVMS.V3/components/ovms_wireguard/CMakeLists.txt new file mode 100644 index 000000000..04a680482 --- /dev/null +++ b/vehicle/OVMS.V3/components/ovms_wireguard/CMakeLists.txt @@ -0,0 +1,13 @@ +set(srcs) +set(include_dirs) + +if (CONFIG_OVMS_COMP_WIREGUARD) + list(APPEND srcs "src/ovms_wireguard.cpp") + list(APPEND include_dirs "src") +endif () + +# requirements can't depend on config +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES "main" "esp_wireguard" + WHOLE_ARCHIVE) diff --git a/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.cpp b/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.cpp new file mode 100644 index 000000000..d1deaf5f9 --- /dev/null +++ b/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.cpp @@ -0,0 +1,311 @@ +/* +; Project: Open Vehicle Monitor System +; Subject: Support for Wireguard VPN protocol (client) +; (c) Ludovic LANGE +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in +; all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +; THE SOFTWARE. +*/ + +#include "ovms_log.h" +static const char *TAG = "wireguard"; +#include "ovms_command.h" +#include "ovms_config.h" +#include "ovms_events.h" +#include "ovms_wireguard.h" + +#define MY_WIREGUARD_CLIENT_INIT_PRIORITY 8900 + +OvmsWireguardClient MyWireguardClient + __attribute__((init_priority(MY_WIREGUARD_CLIENT_INIT_PRIORITY))); + +static wireguard_config_t wg_config = ESP_WIREGUARD_CONFIG_DEFAULT(); + +#define WG_PARAM_NAME "network.wireguard" + +// Private key of the WireGuard device +#define WG_PRIVATE_KEY "local_private_key" +// Local IP address of the WireGuard device. +#define WG_LOCAL_IP_ADDRESS "local_ip_address" +// Netmask of the local network the WireGuard device belongs to. +#define WG_LOCAL_IP_NETMASK "local_ip_netmask" +// Local port to listen. +#define WG_LOCAL_PORT "local_port" + +// Public key of the remote peer. +#define WG_PEER_PUBLIC_KEY "peer_public_key" +// Address of the remote peer. +#define WG_PEER_ADDRESS "peer_endpoint" + +// Port number of the remote peer. +#define WG_PEER_PORT "peer_port" + +// Wireguard pre-shared symmetric key +#define WG_PRESHARED_KEY "preshared_key" + +// A seconds interval, between 1 and 65535 inclusive, of how often to +// send an authenticated empty packet to the peer for the purpose of +// keeping a stateful firewall or NAT mapping valid persistently +#define WG_PERSISTENT_KEEPALIVE "persistent_keepalive" + +static esp_err_t wireguard_setup(wireguard_ctx_t *ctx) { + esp_err_t err = ESP_FAIL; + + ESP_LOGI(TAG, "Initializing WireGuard."); + + if (wg_config.private_key) { + free((void *)wg_config.private_key); + } + wg_config.private_key = + strdup(MyConfig.GetParamValue(WG_PARAM_NAME, WG_PRIVATE_KEY).c_str()); + + wg_config.listen_port = + MyConfig.GetParamValueInt(WG_PARAM_NAME, WG_LOCAL_PORT); + + if (wg_config.public_key) { + free((void *)wg_config.public_key); + } + wg_config.public_key = + strdup(MyConfig.GetParamValue(WG_PARAM_NAME, WG_PEER_PUBLIC_KEY).c_str()); + + if (wg_config.preshared_key) { + free((void *)wg_config.preshared_key); + } + std::string ids = + MyConfig.GetParamValue(WG_PARAM_NAME, WG_PRESHARED_KEY).c_str(); + if (ids != "") { + wg_config.preshared_key = strdup(ids.c_str()); + } else { + wg_config.preshared_key = NULL; + } + + if (wg_config.allowed_ip) { + free((void *)wg_config.allowed_ip); + } + wg_config.allowed_ip = strdup( + MyConfig.GetParamValue(WG_PARAM_NAME, WG_LOCAL_IP_ADDRESS).c_str()); + + if (wg_config.allowed_ip_mask) { + free((void *)wg_config.allowed_ip_mask); + } + wg_config.allowed_ip_mask = strdup( + MyConfig.GetParamValue(WG_PARAM_NAME, WG_LOCAL_IP_NETMASK).c_str()); + + if (wg_config.endpoint) { + free((void *)wg_config.endpoint); + } + wg_config.endpoint = + strdup(MyConfig.GetParamValue(WG_PARAM_NAME, WG_PEER_ADDRESS).c_str()); + + wg_config.port = + MyConfig.GetParamValueInt(WG_PARAM_NAME, WG_PEER_PORT, 51820); + wg_config.persistent_keepalive = + MyConfig.GetParamValueInt(WG_PARAM_NAME, WG_PERSISTENT_KEEPALIVE); + + err = esp_wireguard_init(&wg_config, ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wireguard_init: %s", esp_err_to_name(err)); + } + + return err; +} + +// COMMANDS +// -------- + +void wg_status(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, + const char *const *argv) { + OvmsWireguardClient *me = &MyWireguardClient; + if (me == NULL) { + writer->puts("Error: WireguardClient could not be found"); + return; + } + + writer->printf("Connection status: %s\n", + (me->started) ? "started" : "stopped"); + + if (me->started) { + esp_err_t err; + err = esp_wireguardif_peer_is_up(&me->ctx); + writer->printf("Peer status: %s\n", (err == ESP_OK) ? "up" : "down"); + } +} + +void wg_start(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, + const char *const *argv) { + writer->puts("Starting WireguardClient..."); + MyWireguardClient.TryStart(); +} + +void wg_stop(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, + const char *const *argv) { + writer->puts("Stopping WireguardClient..."); + MyWireguardClient.TryStop(); +} + +void wg_restart(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, + const char *const *argv) { + writer->puts("Restarting WireguardClient..."); + MyWireguardClient.TryStop(); + MyWireguardClient.TryStart(); +} + +void wg_set_default(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, + int argc, const char *const *argv) { + if (MyWireguardClient.started) { + esp_err_t err; + err = esp_wireguard_set_default(&MyWireguardClient.ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wireguard_set_default: %s", esp_err_to_name(err)); + } + } +} + +// EVENTS +// ------ + +void OvmsWireguardClient::EventSystemStart(std::string event, void *data) { + this->UpdateSetup(); +} + +void OvmsWireguardClient::EventConfigChanged(std::string event, void *data) { + OvmsConfigParam *p = (OvmsConfigParam *)data; + + if (p->GetName().compare(WG_PARAM_NAME) == 0) { + bool was_started = this->started; + this->TryStop(); + esp_err_t err; + err = this->UpdateSetup(); + if ((err == ESP_OK) && (was_started)) { + this->TryStart(); + } + } +} + +void OvmsWireguardClient::EventTicker(std::string event, void *data) { + if (this->started) { + esp_err_t err; + err = esp_wireguardif_peer_is_up(&this->ctx); + if (err == ESP_OK) { + ESP_LOGD(TAG, "Peer is up"); + } else { + ESP_LOGD(TAG, "Peer is down"); + } + } +} + +void OvmsWireguardClient::EventNetUp(std::string event, void *data) { + ESP_LOGI(TAG, "Starting Wireguard client"); + this->TryStart(); +} + +void OvmsWireguardClient::EventNetDown(std::string event, void *data) { + ESP_LOGI(TAG, "Stopping Wireguard client"); + this->TryStop(); +} + +void OvmsWireguardClient::EventNetReconfigured(std::string event, void *data) { + esp_err_t err; + ESP_LOGI(TAG, "Network was reconfigured: restarting Wireguard client"); + this->TryStop(); + this->TryStart(); +} + +// Constructor / destructor +OvmsWireguardClient::OvmsWireguardClient() { + ESP_LOGI(TAG, "Initialising Wireguard Client (" STR( + MY_WIREGUARD_CLIENT_INIT_PRIORITY) ")"); + + esp_log_level_set("esp_wireguard", ESP_LOG_DEBUG); + esp_log_level_set("wireguardif", ESP_LOG_DEBUG); + esp_log_level_set("wireguard", ESP_LOG_DEBUG); + + MyConfig.RegisterParam(WG_PARAM_NAME, "Wireguard (VPN) Configuration", true, + true); + + OvmsCommand *cmd_wg = MyCommandApp.RegisterCommand( + "wireguard", "Wireguard framework", wg_status, "", 0, 0, false); + cmd_wg->RegisterCommand("status", "Show wireguard status", wg_status, "", 0, + 0, false); + cmd_wg->RegisterCommand("start", "Start wireguard connexion", wg_start, "", 0, + 0, false); + cmd_wg->RegisterCommand("stop", "Stop wireguard connexion", wg_stop, "", 0, 0, + false); + cmd_wg->RegisterCommand("restart", "Restart wireguard connexion", wg_restart, + "", 0, 0, false); + cmd_wg->RegisterCommand("default", "Set interface as default route", + wg_set_default, "", 0, 0, false); + +#undef bind // Kludgy, but works + using std::placeholders::_1; + using std::placeholders::_2; + MyEvents.RegisterEvent( + TAG, "system.start", + std::bind(&OvmsWireguardClient::EventSystemStart, this, _1, _2)); + MyEvents.RegisterEvent( + TAG, "config.changed", + std::bind(&OvmsWireguardClient::EventConfigChanged, this, _1, _2)); + MyEvents.RegisterEvent( + TAG, "ticker.10", + std::bind(&OvmsWireguardClient::EventTicker, this, _1, _2)); + MyEvents.RegisterEvent( + TAG, "network.up", + std::bind(&OvmsWireguardClient::EventNetUp, this, _1, _2)); + MyEvents.RegisterEvent( + TAG, "network.down", + std::bind(&OvmsWireguardClient::EventNetDown, this, _1, _2)); + MyEvents.RegisterEvent( + TAG, "network.reconfigured", + std::bind(&OvmsWireguardClient::EventNetReconfigured, this, _1, _2)); + + this->started = false; +} + +OvmsWireguardClient::~OvmsWireguardClient() {} + +// Internal methods +esp_err_t OvmsWireguardClient::UpdateSetup() { + esp_err_t err; + err = wireguard_setup(&this->ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "wireguard_setup: %s", esp_err_to_name(err)); + } + return err; +} + +// Start only if config allows it +void OvmsWireguardClient::TryStart() { + esp_err_t err; + if (!(this->started)) { + ESP_LOGI(TAG, "Connecting to the peer."); + err = esp_wireguard_connect(&this->ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wireguard_connect: %s", esp_err_to_name(err)); + } else { + this->started = true; + } + } +} + +// Stop only if already started +void OvmsWireguardClient::TryStop() { + if (this->started) { + esp_wireguard_disconnect(&this->ctx); + this->started = false; + } +} diff --git a/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.h b/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.h new file mode 100644 index 000000000..a02451626 --- /dev/null +++ b/vehicle/OVMS.V3/components/ovms_wireguard/src/ovms_wireguard.h @@ -0,0 +1,59 @@ +/* +; Project: Open Vehicle Monitor System +; Subject: Support for Wireguard VPN protocol (client) +; (c) Ludovic LANGE +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in +; all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +; THE SOFTWARE. +*/ + +#ifndef __OVMS_WIREGUARD_H__ +#define __OVMS_WIREGUARD_H__ + +#include +// #include +#include +// #include "ovms_utils.h" +#include + +class OvmsWireguardClient { + public: + OvmsWireguardClient(); + ~OvmsWireguardClient(); + + public: + wireguard_ctx_t ctx = {0}; + bool started = false; + + public: + void EventConfigChanged(std::string event, void* data); + void EventSystemStart(std::string event, void* data); + void EventTicker(std::string event, void* data); + void EventNetUp(std::string event, void* data); + void EventNetDown(std::string event, void* data); + void EventNetReconfigured(std::string event, void* data); + + public: + void TryStart(); + void TryStop(); + esp_err_t UpdateSetup(); +}; + +extern OvmsWireguardClient MyWireguardClient; + +#endif // #ifndef __OVMS_WIREGUARD_H__ diff --git a/vehicle/OVMS.V3/main/Kconfig b/vehicle/OVMS.V3/main/Kconfig index cf6c8984c..ea1db149a 100644 --- a/vehicle/OVMS.V3/main/Kconfig +++ b/vehicle/OVMS.V3/main/Kconfig @@ -833,6 +833,16 @@ config OVMS_COMP_PLUGINS Enable to include support for PLUGINS and server based extension repositories. +menuconfig OVMS_COMP_WIREGUARD + bool "Include support for WireGuard VPN (client)" + default n + depends on OVMS + help + The WireGuard VPN client support allows your OVMS to connect, via VPN, + to a server or network of your choosing. + When enabled, all outgoing connexions will use this VPN tunnel ; and all + incoming connexions through the tunnel will be accepted as local. + endmenu # Component Options diff --git a/vehicle/OVMS.V3/support/sdkconfig.defaults.esp5 b/vehicle/OVMS.V3/support/sdkconfig.defaults.esp5 index 268bd90eb..1ebdca030 100644 --- a/vehicle/OVMS.V3/support/sdkconfig.defaults.esp5 +++ b/vehicle/OVMS.V3/support/sdkconfig.defaults.esp5 @@ -6,6 +6,7 @@ CONFIG_ESP32_REV_MIN_3=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y CONFIG_MG_ENABLE_SSL=n CONFIG_ESP_TASK_WDT=n +CONFIG_OVMS_COMP_WIREGUARD=y # CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y CONFIG_CAN_SPEED_100KBPS=y