diff --git a/.github/workflows/build-ovms.yml b/.github/workflows/build-ovms.yml
new file mode 100644
index 000000000..d9348a53a
--- /dev/null
+++ b/.github/workflows/build-ovms.yml
@@ -0,0 +1,202 @@
+---
+# This is an ESP-IDF workflow to build OVMS v3
+
+name: 'Build and store artifacts for OVMS v3'
+
+# Controls when the action will run.
+on:
+ # # Triggers the workflow on tag create like v1.0, v2.0.0 and so on
+ push:
+ # tags:
+ # - 'v*'
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called 'build_ovms'
+ build_ovms:
+
+ strategy:
+ matrix:
+ # idf_ver: ["latest", "release-v5.1", "release-v5.0", "v5.0.1", "v5.0"]
+ # idf_ver: ["release-v5.0", "v5.0.1", "v5.0", "release-v4.4", "v4.4.4", "v3.3.4"]
+ # idf_ver: ["latest", "release-v5.1", "release-v5.0", "v5.0.1", "v5.0", "release-v4.4", "v3.3.4"]
+ idf_ver: ["release-v5.0", "v5.0.2", "v5.0.1", "v5.0", "release-v4.4", "v3.3.4"] # v3.3.4 is our own fork / special handling
+ idf_target: ["esp32"]
+ mongoose_ver: ["6.11"] # 6.11 is our own fork / special handling
+ include:
+
+ # All the following items are 'default' values for ALL the matrix entries
+ - patch_mongoose_6_11: true
+ build_command: "idf.py build"
+ sdkconfig: "sdkconfig.defaults.esp5.0.1"
+ patch_esp_idf_ovms: false
+ force_wolfssl_470: false
+ patch_esp_idf_whole_archive: false
+
+ # All the following items are updates of specific entries in the matrix
+ - idf_ver: "v5.0"
+ sdkconfig: "sdkconfig.defaults.esp5"
+
+ - idf_ver: "release-v4.4"
+ sdkconfig: "sdkconfig.defaults.esp4"
+ patch_esp_idf_whole_archive: true
+
+ # - idf_ver: "v4.4.4"
+ # sdkconfig: "sdkconfig.defaults.esp4"
+ # patch_mongoose_6_11: false
+ # patch_esp_idf_whole_archive: true
+
+ # - idf_ver: "v3.3.6"
+ # build_command: "make -j all"
+ # patch_mongoose_6_11: false
+ # force_wolfssl_470: true
+
+ - idf_ver: "v3.3.4"
+ build_command: "TERM=vt100 make defconfig all"
+ sdkconfig: "sdkconfig.defaults.esp3"
+ patch_mongoose_6_11: false
+ patch_esp_idf_ovms: true
+ # force_wolfssl_470: false
+
+ # The type of runner that the job will run on
+ runs-on: 'ubuntu-22.04'
+ container: 'espressif/idf:${{ matrix.idf_ver }}'
+
+ # For the moment, only on this branch
+ if: github.repository == 'llange/Open-Vehicle-Monitoring-System-3'
+ steps:
+
+ # For older builds (3.3.x), a few tweaks are needed:
+ # - the git version bundled in the official ESP-IDF docker image is too old
+ # for GitHub's `actions/checkout` below. We upgrade it
+ # - We also replace the official ESP-IDF repo with OVMSv3's customized ESP-IDF repo
+ - name: 'Use ESP-IDF v3.3 with OVMS tweaks'
+ if: matrix.patch_esp_idf_ovms
+ run: |
+ # Git 2.17.x is not enough for the `actions/checkout` below - upgrade to a more recent version
+ apt-get update
+ apt-get install -y software-properties-common
+ add-apt-repository ppa:git-core/ppa
+ apt-get update
+ apt-get upgrade -y git
+ git --version
+ cd "${IDF_PATH}"
+ git remote set-url origin "https://github.com/openvehicles/esp-idf.git"
+ git fetch --all
+ git reset --hard
+ git clean -fxd
+ git pull origin master
+ git submodule update --init --recursive
+ ./install.sh esp32
+ cd -
+
+ # For older builds (4.4.x), a patch is needed:
+ # - the WHOLE_ARCHIVE statement in CMake components has only been introduced
+ # in ESP-IDF v5.0. We patch it.
+ - name: 'Use ESP-IDF v4.4.x with WHOLE_ARCHIVE support'
+ if: matrix.patch_esp_idf_whole_archive
+ run: |
+ cd "${IDF_PATH}"
+ git config user.email "ci@github.com"
+ git config user.name "Github CI"
+ git config merge.renameLimit 999999
+ git fetch --all
+ git reset --hard
+ git clean -fxd
+ git cherry-pick 273633ee310fbc18b17edfaeae3f3121508e3b8d
+ cd -
+
+ # We're now able to fetch our OVMSv3 firmware repo
+ - name: 'Checkout repo'
+ uses: 'actions/checkout@v3'
+ with:
+ submodules: 'recursive'
+
+ - name: 'fix error message'
+ run: 'git config --global --add safe.directory "$GITHUB_WORKSPACE"'
+
+ # For ESP-IDF v5+ builds, we need to patch our "old" version of mongoose
+ - name: 'Patch mongoose (ESP-IDF v5+)'
+ if: |
+ matrix.patch_mongoose_6_11 &&
+ (matrix.mongoose_ver == '6.11')
+ run: 'git apply --directory="vehicle/OVMS.V3/components/mongoose/mongoose" "vehicle/OVMS.V3/support/mongoose-espv5.patch"'
+
+ # For Mongose >= 7, we checkout this specific version
+ - name: 'Switch mongoose dir'
+ if: |
+ (matrix.mongoose_ver != '6.11')
+ run: |
+ cd "vehicle/OVMS.V3/components/mongoose/mongoose"
+ git remote set-url origin "https://github.com/cesanta/mongoose.git"
+ git fetch --all
+ git reset --hard
+ git clean -fxd
+ git checkout "${{ matrix.mongoose_ver }}"
+ cd -
+
+ # For older builds (3.3.x), we need to switch back to the (official) wolfssl version that
+ # was used in those builds + patch it and update the build system
+ # - name: 'Patch WolfSSL (ESP-IDF v3) with OVMS tweaks'
+ # if: matrix.force_wolfssl_470
+ # run: |
+ # cd "vehicle/OVMS.V3/components/wolfssl/wolfssl"
+ # git fetch --unshallow
+ # git checkout "v4.7.0-stable"
+ # cd -
+ # cp "vehicle/OVMS.V3/support/wolfssl-4.7.0-esp3/user_settings.h" "vehicle/OVMS.V3/components/wolfssl/port/"
+ # git apply -p5 --directory="vehicle/OVMS.V3/components/wolfssl/wolfssl" "vehicle/OVMS.V3/support/wolfssl-4.7.0-esp3/wolfssl-4.7.0.patch"
+
+ # For all builds, we have a default sdkconfig file
+ - name: 'Setup configuration'
+ run: 'cp "vehicle/OVMS.V3/support/${{ matrix.sdkconfig }}" "vehicle/OVMS.V3/sdkconfig.defaults"'
+
+ - name: 'Patch configuration for Mongoose 6.11 (SSL cannot compile)'
+ if: |
+ matrix.patch_mongoose_6_11 &&
+ (matrix.mongoose_ver == '6.11')
+ run: 'sed -i -e "s/CONFIG_MG_ENABLE_SSL=y/#CONFIG_MG_ENABLE_SSL=/g" "vehicle/OVMS.V3/sdkconfig.defaults"'
+
+ - name: Cache build dependencies
+ id: esp-idf-build
+ uses: actions/cache@v3
+ with:
+ path: |
+ vehicle/OVMS.V3/build
+ vehicle/OVMS.V3/sdkconfig
+ key: build-${{ runner.os }}-${{ matrix.idf_target }}_esp-idf-${{ matrix.idf_ver }}_mg-${{ matrix.mongoose_ver }}
+
+ # Now, we can build it. Let's not forget to install `dos2unix` first as it is needed.
+ - name: 'Build project with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}'
+ env:
+ IDF_TARGET: ${{ matrix.idf_target }}
+ shell: bash
+ working-directory: vehicle/OVMS.V3
+ run: |
+ apt-get update
+ apt-get install -y dos2unix
+ . ${IDF_PATH}/export.sh
+ # https://github.com/espressif/idf-component-manager/issues/31#issuecomment-1535984388
+ pip install -U "urllib3<2"
+ git status --untracked-files
+ git ls-files -o --exclude-standard
+ ${{ matrix.build_command }}
+
+ - name: 'Archive build output artifacts'
+ uses: 'actions/upload-artifact@v3'
+ with:
+ name: '${{ matrix.idf_target }}_esp-idf-${{ matrix.idf_ver }}_mongoose-${{ matrix.mongoose_ver }}'
+ path: |
+ vehicle/OVMS.V3/build/bootloader/bootloader.bin
+ vehicle/OVMS.V3/build/partition_table/partition-table.bin
+ vehicle/OVMS.V3/build/*.bin
+ vehicle/OVMS.V3/build/*.elf
+ vehicle/OVMS.V3/build/log/*
+ vehicle/OVMS.V3/build/flasher_args.json
+ vehicle/OVMS.V3/build/config/sdkconfig.h
+ vehicle/OVMS.V3/build/config/sdkconfig.json
+ vehicle/OVMS.V3/sdkconfig
+ if-no-files-found: error
diff --git a/.github/workflows/ci-fallback.yaml b/.github/workflows/ci-fallback.yaml
new file mode 100644
index 000000000..93828833f
--- /dev/null
+++ b/.github/workflows/ci-fallback.yaml
@@ -0,0 +1,17 @@
+---
+name: 'CI Fallback'
+
+on:
+ push:
+ paths-ignore:
+ - 'docs/**'
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+
+jobs:
+ documentation:
+ runs-on: 'ubuntu-22.04'
+
+ steps:
+ - run: 'echo "No build required"'
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 000000000..d6b1aec28
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,56 @@
+---
+name: 'CI'
+
+on:
+ push:
+ paths-ignore:
+ - 'docs/**'
+ pull_request:
+ paths:
+ - 'docs/**'
+
+jobs:
+ documentation:
+ runs-on: 'ubuntu-22.04'
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python
+ id: setup-python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3
+ cache: 'pip'
+ cache-dependency-path: 'docs/source/requirements.txt'
+
+ - name: Upgrade pip
+ run: |
+ sudo apt update
+ sudo apt install -y graphviz
+ python -m pip install --upgrade pip
+
+ - name: Cache doc dependencies
+ id: sphinx
+ uses: actions/cache@v3
+ with:
+ path: |
+ docs/build
+ key: sphinx-docs-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('docs/source/conf.py','docs/source/Makefile') }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade -r docs/source/requirements.txt sphinx sphinx_rtd_theme
+
+ - name: Render the documentation
+ run: |
+ cd docs
+ make SPHINXOPTS=-W html
+
+ - name: 'Archive build output artifacts'
+ uses: 'actions/upload-artifact@v3'
+ with:
+ name: 'documentation'
+ path: |
+ docs/build/html
+ if-no-files-found: error
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0c565d182..b3e7ac330 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,3 +65,8 @@ docs/build/
.vscode/
vehicle/OVMS.V3/components/ovms_webserver/assets/charts.js.gz
*.gz
+vehicle/OVMS.V3/sdkconfig.defaults
+
+# Funny, but the component manager seems to always run during builds,
+# thus overwriting this file. No reason to manage it ourselves...
+vehicle/OVMS.V3/dependencies.lock
diff --git a/.gitmodules b/.gitmodules
index d422e44d2..2be11df3e 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/openvehicles/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/README.md b/README.md
index 4a4304707..462fb592f 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,59 @@
+# **Warning**
+> This branch is a Work-In-Progress to add compatibility with ESP-IDF v4.x and v5.x.
+> Not suitable for production use - only for dev / tests.
+> As of now, it (kind-of) works on ESP-IDF v5.0 with the following caveats:
+> * the crash handler (`xt_set_error_handler_callback` and `esp_task_wdt_get_trigger_tasknames`) is disabled for the moment, we need to decide whether we "fork" ESP-IDF again to port it ; or if the new APIs are enough to (partially ?) reimplement it (see commit: "**WIP WIP WIP : comment out ESP-IDF specifics of our fork**")
+> * There is a crash in `OvmsConsole::Poll` which is not analysed (yet) and which is worked around by declaring a variable static (see commit: "**WIP WIP WIP : prevent a crash at boot (to be analysed)**")
+> * Our (previously) local copies of `wolfssh` and `wolfssl` are now in submodules (and moved one level below in terms of directories) - mainly to be able to have a CMakeLists.txt different from the upstream one. In the process, one of our previous patches is now lost : https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/commit/51444539047daef7bd2accb23ef40d1bc14fdb20 and we need to decide how to handle this.
+> * A lot of dependencies are now explicitly (hard-)coded in the CMakeLists.txt - which may, or may not be a good thing. Let's discuss it.
+> * The set of defines (in ovms_webserver) have been transformed into a header generation because it was not known how to implement those in a satisfying manner in cmake.
+> * There are still some warnings during compilation (mainly ADC which needs conversion + some others)
+> * Mongoose is not (yet) ready to compile with TLS enabled.
+> * wolfSSL can't be (yet) compiled with OPENSSL defines (see wolfSSL/wolfssl#6028)
+> * wolfSSL has been updated to tag `v5.3.0-stable` (Note: later versions causing stack overflow during SSH session, to investigate)
+> * wolfSSH has been updated to tag `v1.4.6-stable`
+> * mongoose has not been updated but needs patching (see below for the patch)
+> * Some commits (identified by "WIP WIP WIP") needs to be addressed
+> * No real-world test has been done
+> * We wanted to stay compatible with our 3.3.4 branch, and tried as much as we could to keep that compatibility. In case something is broken, please report and we will fix it.
+> * This branch has mainly been tested using `cmake` build system / `idf.py`, not Makefiles (which have disappeared in v5.x)
+
+## Patch for mongoose
+```diff
+diff --git a/mongoose.c b/mongoose.c
+index b12cff18..60a7f62e 100644
+--- a/mongoose.c
++++ b/mongoose.c
+@@ -9160,7 +9160,7 @@ static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
+ static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm,
+ const char *ssi, char *tag, int include_level,
+ const struct mg_serve_http_opts *opts) {
+- char file_name[MG_MAX_PATH], path[MG_MAX_PATH], *p;
++ char file_name[MG_MAX_PATH], path[MG_MAX_PATH+2], *p;
+ FILE *fp;
+
+ /*
+diff --git a/mongoose.h b/mongoose.h
+index 3bcf8147..5649e1a7 100644
+--- a/mongoose.h
++++ b/mongoose.h
+@@ -1768,7 +1768,7 @@ typedef struct {
+
+ void cs_md5_init(cs_md5_ctx *c);
+ void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len);
+-void cs_md5_final(unsigned char *md, cs_md5_ctx *c);
++void cs_md5_final(unsigned char md[16], cs_md5_ctx *c);
+
+ #ifdef __cplusplus
+ }
+```
+
+Instructions for ESP-IDF v5.0:
+* Setup ESP-IDF where you want and ensure it works, [following the instructions here](https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/get-started/index.html).
+* Build as usual (`idf.py build`, etc...)
+
+---
+
# Open-Vehicle-Monitoring-System-3 (OVMS3)

diff --git a/docs/source/userguide/index.rst b/docs/source/userguide/index.rst
index b3a76b5cd..9fa55e522 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 7bbc22cb7..2cde43bd1 100644
--- a/vehicle/OVMS.V3/changes.txt
+++ b/vehicle/OVMS.V3/changes.txt
@@ -1,6 +1,22 @@
Open Vehicle Monitor System v3 - Change log
????-??-?? ??? ??????? OTA release
+- 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)
2024-03-23 MB 3.3.004 OTA release
- MG EV Added support for MG5 (2020 - 2023) Short Range
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