add WireGuard implementation to increase security

WireGuard is really ideal for those IoT devices with limited resources.
This commit is contained in:
Jaroslav Kysela 2023-11-29 20:41:28 +01:00
parent 3789d446bd
commit 2c94573edb
16 changed files with 384 additions and 21 deletions

View File

@ -1 +1 @@
{"en":{"language":"English","home":{"title":"Main Menu","btn":"Main Menu","nav":"Home"},"save":"Save Settings","user":"Username","pass":"Password","hasp":{"title":"HASP Design","btn":"HASP Design","theme":"UI Theme","color1":"Primary color","color2":"Secondary color","pages":"Start Layout","font":"Default Font","startpage":"Startup Page","startdim":"Startup Dim"},"screenshot":{"title":"Screenshot","btn":"Screenshot","nav":"Screenshot","prev":"Prev Page","next":"Next Page","refresh":"Refresh"},"info":{"title":"Information","btn":"Information","nav":"Information"},"config":{"title":"Configuration","btn":"Configuration","nav":"Settings"},"ota":{"title":"Firmware Update","btn":"Firmware Update","nav":"Firmware","submit":"Update Firmware","file":"Firmware File","url":"Firmware URL","redirect":"Follow Redirects","never":"Never","strict":"Strict","always":"Always"},"editor":{"title":"File Editor","btn":"File Editor","nav":"File Editor"},"reset":{"title":"Factory Reset","btn":"Factory Reset","warning":"Warning","message":"This process will reset all settings to the default values. The internal flash will be erased and the device is restarted. You may need to connect to the WiFi AP displayed on the panel to reconfigure the device before accessing it again.","fileloss":"ALL FILES WILL BE LOST!"},"reboot":{"title":"Rebooting...","btn":"Restart","nav":"Reboot","message":"The device is rebooting."},"about":{"credits":"Based on the previous work of the following open source developers:","copyright":"Copyright ","rights":"All rights reserved.","clause1":"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:","clause2":"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.","clause3":"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.","mit":"MIT License","bsd":"BSD License","freebsd":"FreeBSD License","apache2":"Apache2 License"},"wifi":{"title":"Wifi Settings","btn":"Wifi Settings","ssid":"SSID"},"mqtt":{"title":"MQTT Settings","btn":"MQTT Settings","name":"Hostname","group":"Groupname","host":"Broker","port":"Port","node_t":"Node Topic","group_t":"Group Topic","broadcast_t":"Broadcast Topic","hass_t":"HA LWT Topic"},"http":{"title":"HTTP Settings","btn":"HTTP Settings"},"ftp":{"title":"FTP Settings","btn":"FTP Settings","port":"FTP Port","pasv":"Passive Port"},"gui":{"title":"Display Settings","btn":"Display Settings","antiburn":"Antiburn","calibrate":"Calibrate"},"gpio":"GPIO Settings","debug":{"title":"Debug Settings","btn":"Debug Settings","baud":"Baudrate","tele":"Tele Period","ansi":"Use ANSI codes","host":"Syslog Server","port":"Syslog Port","ietf":"IETF (RFC 5424)","bsd":"BSD (RFC 3164)","log":"Facility"},"time":{"title":"Time Settings","btn":"Time Settings","region":"Region","zone":"Timezone","tz":"Timezone","ntp":"NTP Servers"},"region":{"etc":"Etcetera ","continents":"Continents ","af":"Africa ","as":"Asia ","au":"Australia ","aq":"Antarctica ","eu":"Europe ","na":"North America ","sa":"South America ","islands":"Islands ","at":"Atlantic Ocean ","in":"Indian Ocean ","pa":"Pacific Ocean "}}}
{"en":{"language":"English","home":{"title":"Main Menu","btn":"Main Menu","nav":"Home"},"save":"Save Settings","user":"Username","pass":"Password","hasp":{"title":"HASP Design","btn":"HASP Design","theme":"UI Theme","color1":"Primary color","color2":"Secondary color","pages":"Start Layout","font":"Default Font","startpage":"Startup Page","startdim":"Startup Dim"},"screenshot":{"title":"Screenshot","btn":"Screenshot","nav":"Screenshot","prev":"Prev Page","next":"Next Page","refresh":"Refresh"},"info":{"title":"Information","btn":"Information","nav":"Information"},"config":{"title":"Configuration","btn":"Configuration","nav":"Settings"},"ota":{"title":"Firmware Update","btn":"Firmware Update","nav":"Firmware","submit":"Update Firmware","file":"Firmware File","url":"Firmware URL","redirect":"Follow Redirects","never":"Never","strict":"Strict","always":"Always"},"editor":{"title":"File Editor","btn":"File Editor","nav":"File Editor"},"reset":{"title":"Factory Reset","btn":"Factory Reset","warning":"Warning","message":"This process will reset all settings to the default values. The internal flash will be erased and the device is restarted. You may need to connect to the WiFi AP displayed on the panel to reconfigure the device before accessing it again.","fileloss":"ALL FILES WILL BE LOST!"},"reboot":{"title":"Rebooting...","btn":"Restart","nav":"Reboot","message":"The device is rebooting."},"about":{"credits":"Based on the previous work of the following open source developers:","copyright":"Copyright ","rights":"All rights reserved.","clause1":"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:","clause2":"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.","clause3":"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.","mit":"MIT License","bsd":"BSD License","freebsd":"FreeBSD License","apache2":"Apache2 License"},"wifi":{"title":"Wifi Settings","btn":"Wifi Settings","ssid":"SSID"},"wg":{"title":"WireGuard Settings","btn":"WireGuard Settings","vpnip":"VPN IP","privkey":"Private Key","host":"Remote IP","port":"Remote Port","pubkey":"Remote Public Key"},"mqtt":{"title":"MQTT Settings","btn":"MQTT Settings","name":"Hostname","group":"Groupname","host":"Broker","port":"Port","node_t":"Node Topic","group_t":"Group Topic","broadcast_t":"Broadcast Topic","hass_t":"HA LWT Topic"},"http":{"title":"HTTP Settings","btn":"HTTP Settings"},"ftp":{"title":"FTP Settings","btn":"FTP Settings","port":"FTP Port","pasv":"Passive Port"},"gui":{"title":"Display Settings","btn":"Display Settings","antiburn":"Antiburn","calibrate":"Calibrate"},"gpio":"GPIO Settings","debug":{"title":"Debug Settings","btn":"Debug Settings","baud":"Baudrate","tele":"Tele Period","ansi":"Use ANSI codes","host":"Syslog Server","port":"Syslog Port","ietf":"IETF (RFC 5424)","bsd":"BSD (RFC 3164)","log":"Facility"},"time":{"title":"Time Settings","btn":"Time Settings","region":"Region","zone":"Timezone","tz":"Timezone","ntp":"NTP Servers"},"region":{"etc":"Etcetera ","continents":"Continents ","af":"Africa ","as":"Asia ","au":"Australia ","aq":"Antarctica ","eu":"Europe ","na":"North America ","sa":"South America ","islands":"Islands ","at":"Atlantic Ocean ","in":"Indian Ocean ","pa":"Pacific Ocean "}}}

File diff suppressed because one or more lines are too long

View File

@ -65,6 +65,10 @@
#define HASP_USE_MQTT (HASP_HAS_NETWORK)
#endif
#ifndef HASP_USE_WIREGUARD
#define HASP_USE_WIREGUARD (HASP_HAS_NETWORK)
#endif
#ifndef HASP_USE_BROADCAST
#define HASP_USE_BROADCAST 1
#endif
@ -232,6 +236,10 @@ static WiFiSpiClass WiFi;
#endif
#endif // HASP_USE_WIFI
#if HASP_USE_WIREGUARD > 0
#include "sys/net/hasp_wireguard.h"
#endif
#if HASP_USE_ETHERNET > 0
#if defined(ARDUINO_ARCH_ESP32)
#include "sys/net/hasp_ethernet_esp32.h"

View File

@ -503,6 +503,14 @@ void dispatch_config(const char* topic, const char* payload, uint8_t source)
else
timeGetConfig(settings);
}
#if HASP_USE_WIREGUARD > 0
else if(strcasecmp_P(topic, PSTR(FP_WG)) == 0) {
if(update)
wgSetConfig(settings);
else
wgGetConfig(settings);
}
#endif
#if HASP_USE_MQTT > 0
else if(strcasecmp_P(topic, PSTR(FP_MQTT)) == 0) {
if(update)

View File

@ -6,6 +6,8 @@
#include "hasplib.h"
#include "hasp_nvs.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
bool nvs_user_begin(Preferences& preferences, const char* key, bool readonly)
{
if(preferences.begin(key, false, "config")) {
@ -22,16 +24,16 @@ bool nvs_user_begin(Preferences& preferences, const char* key, bool readonly)
bool nvs_clear_user_config()
{
const char* name[] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI};
const char* name[] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI, FP_WG};
Preferences preferences;
bool state = true;
for(int i = 0; i < sizeof(name) / sizeof(name[0]); i++) {
for(int i = 0; i < ARRAY_SIZE(name); i++) {
if(preferences.begin(name[i], false) && !preferences.clear()) state = false;
preferences.end();
}
for(int i = 0; i < sizeof(name) / sizeof(name[0]); i++) {
for(int i = 0; i < ARRAY_SIZE(name); i++) {
if(preferences.begin(name[i], false, "config") && !preferences.clear()) state = false;
preferences.end();
}
@ -226,19 +228,30 @@ void nvs_setup()
nvs_stats.free_entries, nvs_stats.total_entries);
{ // TODO: remove migratrion of keys from default NVS partition to CONFIG partition
const char* name[8] = {FP_TIME, FP_OTA, FP_HTTP, FP_FTP, FP_MQTT, FP_WIFI};
const struct {
const char* name;
const char* config;
} sec[] = {
{ .name = FP_TIME, .config = FP_CONFIG_PASS },
{ .name = FP_OTA, .config = FP_CONFIG_PASS },
{ .name = FP_HTTP, .config = FP_CONFIG_PASS },
{ .name = FP_FTP, .config = FP_CONFIG_PASS },
{ .name = FP_MQTT, .config = FP_CONFIG_PASS },
{ .name = FP_WIFI, .config = FP_CONFIG_PASS },
{ .name = FP_WG, .config = FP_CONFIG_PRIVATE_KEY }
};
Preferences oldPrefs, newPrefs;
for(int i = 0; i < 6; i++) {
if(oldPrefs.begin(name[i], false) && newPrefs.begin(name[i], false, "config")) {
LOG_INFO(TAG_NVS, "opened %s", name[i]);
String password = oldPrefs.getString(FP_CONFIG_PASS, D_PASSWORD_MASK);
for(int i = 0; i < ARRAY_SIZE(sec); i++) {
if(oldPrefs.begin(sec[i].name, false) && newPrefs.begin(sec[i].name, false, "config")) {
LOG_INFO(TAG_NVS, "opened %s", sec[i].name);
String password = oldPrefs.getString(sec[i].config, D_PASSWORD_MASK);
if(password != D_PASSWORD_MASK) {
LOG_INFO(TAG_NVS, "found %s %s => %s", name[i], D_PASSWORD_MASK, password.c_str());
size_t len = newPrefs.putString(FP_CONFIG_PASS, password);
LOG_INFO(TAG_NVS, "found %s %s => %s", sec[i].name, D_PASSWORD_MASK, password.c_str());
size_t len = newPrefs.putString(sec[i].config, password);
if(len == password.length()) {
oldPrefs.remove(FP_CONFIG_PASS);
LOG_INFO(TAG_NVS, "Moved %s key %s to new NVS partition", name[i], FP_CONFIG_PASS);
oldPrefs.remove(sec[i].config);
LOG_INFO(TAG_NVS, "Moved %s key %s to new NVS partition", sec[i].name, sec[i].config);
}
}
}

View File

@ -117,6 +117,19 @@ bool configSet(lv_color_t& value, const JsonVariant& setting, const __FlashStrin
}
return false;
}
bool configSet(char *value, size_t size, const JsonVariant& setting, const __FlashStringHelper* fstr_name)
{
if(!setting.isNull()) {
const char *val = setting;
if(strcmp(value, val) != 0) {
confDebugSet(fstr_name);
strncpy(value, val, size - 1);
value[size - 1] = '\0';
return true;
}
}
return false;
}
bool configSet(bool& value, const JsonVariant& setting, const char* fstr_name)
{
@ -198,28 +211,30 @@ void configSetupDebug(JsonDocument& settings)
debugStart(); // Debug started, now we can use it; HASP header sent
}
void configStorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass)
void configStorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass, String &wgPrivKey)
{
const char* pass = ("pass");
wifiPass = settings[FPSTR(FP_WIFI)][pass].as<String>();
mqttPass = settings[FPSTR(FP_MQTT)][pass].as<String>();
httpPass = settings[FPSTR(FP_HTTP)][pass].as<String>();
wgPrivKey = settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].as<String>();
}
void configRestorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass)
void configRestorePasswords(JsonDocument& settings, String& wifiPass, String& mqttPass, String& httpPass, String& wgPrivKey)
{
const char* pass = ("pass");
if(!settings[FPSTR(FP_WIFI)][pass].isNull()) settings[FPSTR(FP_WIFI)][pass] = wifiPass;
if(!settings[FPSTR(FP_MQTT)][pass].isNull()) settings[FPSTR(FP_MQTT)][pass] = mqttPass;
if(!settings[FPSTR(FP_HTTP)][pass].isNull()) settings[FPSTR(FP_HTTP)][pass] = httpPass;
if(!settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull()) settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)] = wgPrivKey;
}
void configMaskPasswords(JsonDocument& settings)
{
String passmask = F(D_PASSWORD_MASK);
configRestorePasswords(settings, passmask, passmask, passmask);
configRestorePasswords(settings, passmask, passmask, passmask, passmask);
}
DeserializationError configParseFile(String& configFile, JsonDocument& settings)
@ -254,7 +269,7 @@ DeserializationError configRead(JsonDocument& settings, bool setupdebug)
#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0
error = configParseFile(configFile, settings);
if(!error) {
String output, wifiPass, mqttPass, httpPass;
String output, wifiPass, mqttPass, httpPass, wgPrivKey;
/* Load Debug params */
if(setupdebug) {
@ -266,14 +281,14 @@ DeserializationError configRead(JsonDocument& settings, bool setupdebug)
}
LOG_TRACE(TAG_CONF, F(D_FILE_LOADING), configFile.c_str());
configStorePasswords(settings, wifiPass, mqttPass, httpPass);
configStorePasswords(settings, wifiPass, mqttPass, httpPass, wgPrivKey);
// Output settings in log with masked passwords
configMaskPasswords(settings);
serializeJson(settings, output);
LOG_VERBOSE(TAG_CONF, output.c_str());
configRestorePasswords(settings, wifiPass, mqttPass, httpPass);
configRestorePasswords(settings, wifiPass, mqttPass, httpPass, wgPrivKey);
LOG_INFO(TAG_CONF, F(D_FILE_LOADED), configFile.c_str());
// if(setupdebug) debugSetup();
@ -382,6 +397,17 @@ void configWrite()
}
#endif
#if HASP_USE_WIREGUARD > 0
module = FPSTR(FP_WG);
if(settings[module].as<JsonObject>().isNull()) settings.createNestedObject(module);
changed = wgGetConfig(settings[module]);
if(changed) {
LOG_VERBOSE(TAG_WG, settingsChanged.c_str());
configOutput(settings[module], TAG_WG);
writefile = true;
}
#endif
#if HASP_USE_MQTT > 0
module = FPSTR(FP_MQTT);
if(settings[module].as<JsonObject>().isNull()) settings.createNestedObject(module);
@ -549,6 +575,11 @@ void configSetup()
wifiSetConfig(settings[FPSTR(FP_WIFI)]);
#endif
#if HASP_USE_WIREGUARD > 0
LOG_INFO(TAG_WG, F("Loading WireGuard settings"));
wgSetConfig(settings[FPSTR(FP_WG)]);
#endif
#if HASP_USE_MQTT > 0
LOG_INFO(TAG_MQTT, F("Loading MQTT settings"));
mqttSetConfig(settings[FPSTR(FP_MQTT)]);
@ -624,6 +655,14 @@ void configOutput(const JsonObject& settings, uint8_t tag)
output.replace(password, passmask);
}
if(!settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull()) {
password = F("\"privkey\":\"");
password += settings[FPSTR(FP_WG)][FPSTR(FP_CONFIG_PRIVATE_KEY)].as<String>();
password += F("\"");
passmask = F("\"privkey\":\"" D_PASSWORD_MASK "\"");
output.replace(password, passmask);
}
LOG_VERBOSE(tag, output.c_str());
}

View File

@ -31,6 +31,7 @@ bool configSet(uint8_t& value, const JsonVariant& setting, const __FlashStringHe
bool configSet(uint16_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name);
bool configSet(int32_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name);
bool configSet(lv_color_t& value, const JsonVariant& setting, const __FlashStringHelper* fstr_name);
bool configSet(char *value, size_t size, const JsonVariant& setting, const __FlashStringHelper* fstr_name);
bool configSet(bool& value, const JsonVariant& setting, const char* fstr_name);
bool configSet(int8_t& value, const JsonVariant& setting, const char* fstr_name);
bool configSet(uint8_t& value, const JsonVariant& setting, const char* fstr_name);
@ -71,6 +72,9 @@ const char FP_CONFIG_BROADCAST_TOPIC[] PROGMEM = "broadcast_t";
const char FP_CONFIG_BAUD[] PROGMEM = "baud";
const char FP_CONFIG_LOG[] PROGMEM = "log";
const char FP_CONFIG_PROTOCOL[] PROGMEM = "proto";
const char FP_CONFIG_VPN_IP[] PROGMEM = "vpnip";
const char FP_CONFIG_PRIVATE_KEY[] PROGMEM = "privkey";
const char FP_CONFIG_PUBLIC_KEY[] PROGMEM = "pubkey";
const char FP_GUI_ROTATION[] PROGMEM = "rotate";
const char FP_GUI_INVERT[] PROGMEM = "invert";
const char FP_GUI_TICKPERIOD[] PROGMEM = "tick";
@ -89,6 +93,7 @@ const char FP_GPIO_CONFIG[] PROGMEM = "config";
const char FP_HASP_CONFIG_FILE[] PROGMEM = "/config.json";
const char FP_WIFI[] PROGMEM = "wifi";
const char FP_WG[] PROGMEM = "wg";
const char FP_MQTT[] PROGMEM = "mqtt";
const char FP_HTTP[] PROGMEM = "http";
const char FP_FTP[] PROGMEM = "ftp";

View File

@ -321,6 +321,10 @@ void debug_get_tag(uint8_t tag, char* buffer)
memcpy_P(buffer, PSTR("CUST"), 5);
break;
case TAG_WG:
memcpy_P(buffer, PSTR("WG "), 5);
break;
default:
memcpy_P(buffer, PSTR("----"), 5);
break;

View File

@ -194,6 +194,7 @@ enum {
TAG_FTP = 68,
TAG_TIME = 69,
TAG_NETW = 70,
TAG_WG = 71,
TAG_LVGL = 90,
TAG_LVFS = 91,

View File

@ -127,6 +127,7 @@
#define D_HTTP_HTTP_SETTINGS "HTTP Settings"
#define D_HTTP_FTP_SETTINGS "FTP Settings"
#define D_HTTP_WIFI_SETTINGS "Wifi Settings"
#define D_HTTP_WIREGUARD_SETTINGS "WireGuard Settings"
#define D_HTTP_MQTT_SETTINGS "MQTT Settings"
#define D_HTTP_GPIO_SETTINGS "GPIO Settings"
#define D_HTTP_MDNS_SETTINGS "mDNS Settings"
@ -191,6 +192,7 @@
#define D_INFO_FAILED "Failed"
#define D_INFO_ETHERNET "Ethernet"
#define D_INFO_WIFI "Wifi"
#define D_INFO_WIREGUARD "WireGuard"
#define D_INFO_LINK_SPEED "Link Speed"
#define D_INFO_FULL_DUPLEX "Full Duplex"
#define D_INFO_BSSID "BSSID"
@ -200,6 +202,8 @@
#define D_INFO_MAC_ADDRESS "MAC Address"
#define D_INFO_GATEWAY "Gateway"
#define D_INFO_DNS_SERVER "DNS Server"
#define D_INFO_ENDPOINT_IP "Endpoint IP"
#define D_INFO_ENDPOINT_PORT "Endpoint Port"
#define D_OOBE_MSG "Tap the screen to setup WiFi or connect to this Access Point:"
#define D_OOBE_SCAN_TO_CONNECT "Scan to connect"
@ -212,6 +216,9 @@
#define D_WIFI_RSSI_WEAK "Weak"
#define D_WIFI_RSSI_BAD "Very bad"
#define D_WG_INITIALIZED "Initialized"
#define D_WG_BAD_CONFIG "Missing or bad configuration"
#define D_GPIO_SWITCH "Switch"
#define D_GPIO_BUTTON "Push Button"
#define D_GPIO_TOUCH "Capacitive Touch"

View File

@ -14,6 +14,11 @@ uint16_t network_reconnect_counter = 0;
#if HASP_USE_ETHERNET > 0 || HASP_USE_WIFI > 0
bool network_is_connected()
{
return current_network_state;
}
void network_disconnected()
{
@ -23,6 +28,9 @@ void network_disconnected()
// if(!current_network_state) return; // we were not connected
current_network_state = false; // now we are disconnected
#if HASP_USE_WIREGUARD
wg_network_disconnected();
#endif
network_reconnect_counter++;
// LOG_VERBOSE(TAG_NETW, F("Connected = %s"),
// WiFi.status() == WL_CONNECTED ? PSTR(D_NETWORK_ONLINE) : PSTR(D_NETWORK_OFFLINE));
@ -32,10 +40,15 @@ void network_connected()
{
if(current_network_state) return; // already connected
#if HASP_USE_WIREGUARD
wg_network_connected();
#endif
current_network_state = true; // now we are connected
network_reconnect_counter = 0;
LOG_VERBOSE(TAG_NETW, F("Connected = %s"),
WiFi.status() == WL_CONNECTED ? PSTR(D_NETWORK_ONLINE) : PSTR(D_NETWORK_OFFLINE));
}
void network_run_scripts()
@ -101,6 +114,10 @@ void networkSetup()
#if HASP_USE_WIFI > 0
wifiSetup();
#endif
#if HASP_USE_WIREGUARD > 0
wg_setup();
#endif
}
IRAM_ATTR void networkLoop(void)
@ -178,10 +195,20 @@ void network_get_statusupdate(char* buffer, size_t len)
#if HASP_USE_WIFI > 0
wifi_get_statusupdate(buffer, len);
#endif
#if HASP_USE_WIREGUARD > 0
size_t l = strlen(buffer);
wg_get_statusupdate(buffer + l, len - l);
#endif
}
void network_get_ipaddress(char* buffer, size_t len)
{
#if HASP_USE_WIREGUARD > 0
if (wg_get_ipaddress(buffer, len))
return;
#endif
#if HASP_USE_ETHERNET > 0
#if defined(ARDUINO_ARCH_ESP32)
#if HASP_USE_ETHSPI > 0
@ -222,6 +249,10 @@ void network_get_info(JsonDocument& doc)
#if HASP_USE_WIFI > 0
wifi_get_info(doc);
#endif
#if HASP_USE_WIREGUARD > 0
wg_get_info(doc);
#endif
}
#endif

View File

@ -11,6 +11,7 @@ bool networkEvery5Seconds(void);
// bool networkEverySecond(void);
void networkStart(void);
void networkStop(void);
bool network_is_connected();
/* ===== Special Event Processors ===== */
void network_connected();

View File

@ -0,0 +1,131 @@
/* MIT License - Copyright (c) 2023 Jaroslav Kysela
For full license information read the LICENSE file in the project folder */
#include "hasplib.h"
#if HASP_USE_WIREGUARD > 0
#include "hal/hasp_hal.h"
#include "hasp_debug.h"
#include "hasp_network.h"
#include "WireGuard-ESP32.h"
char wg_ip[16] = WIREGUARD_IP;
char wg_private_key[45] = WIREGUARD_PRIVATE_KEY;
char wg_ep_ip[16] = WIREGUARD_EP_IP;
uint16_t wg_ep_port = WIREGUARD_EP_PORT;
char wg_ep_public_key[45] = WIREGUARD_EP_PUBLIC_KEY;
static WireGuard wg;
void wg_setup()
{
Preferences preferences;
nvs_user_begin(preferences, FP_WG, true);
String privkey = preferences.getString(FP_CONFIG_PRIVATE_KEY, String(wg_private_key)); // Update from NVS if it exists
strncpy(wg_private_key, privkey.c_str(), sizeof(wg_private_key)-1);
wg_private_key[sizeof(wg_private_key)-1] = '\0';
}
int wg_config_valid()
{
return strlen(wg_ip) > 7 && strlen(wg_ep_ip) > 7 &&
strlen(wg_private_key) == 44 && strlen(wg_ep_public_key) == 44 &&
wg_ep_port > 0;
}
void wg_network_disconnected()
{
wg.end();
}
void wg_network_connected()
{
IPAddress local_ip;
LOG_VERBOSE(TAG_WG, F("WireGuard connected"));
if (local_ip.fromString(wg_ip) && wg_config_valid()) {
LOG_INFO(TAG_WG, F("WireGuard begin (%s -> %s:%u)"), wg_ip, wg_ep_ip, wg_ep_port);
wg.begin(local_ip, wg_private_key, wg_ep_ip, wg_ep_public_key, wg_ep_port);
}
}
void wg_get_statusupdate(char* buffer, size_t len)
{
snprintf_P(buffer, len, PSTR("\"wg\":\"%s\","), wg.is_initialized() ? "on" : "off");
}
int wg_get_ipaddress(char* buffer, size_t len)
{
if (wg.is_initialized()) {
snprintf(buffer, len, "%s", wg_ip);
return 1;
}
return 0;
}
void wg_get_info(JsonDocument& doc)
{
JsonObject info = doc.createNestedObject(F(D_INFO_WIREGUARD));
info[F(D_INFO_STATUS)] = wg.is_initialized() ? F(D_WG_INITIALIZED) : F(D_WG_BAD_CONFIG);
info[F(D_INFO_IP_ADDRESS)] = String(wg_ip);
info[F(D_INFO_ENDPOINT_IP)] = String(wg_ep_ip);
info[F(D_INFO_ENDPOINT_PORT)] = String(wg_ep_port);
}
#if HASP_USE_CONFIG > 0
bool wgGetConfig(const JsonObject& settings)
{
bool changed = false;
if(strcmp(wg_ip, settings[FPSTR(FP_CONFIG_VPN_IP)].as<String>().c_str()) != 0) changed = true;
settings[FPSTR(FP_CONFIG_VPN_IP)] = wg_ip;
if(strcmp(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].as<String>().c_str()) != 0) changed = true;
//settings[FPSTR(FP_CONFIG_PRIVATE_KEY)] = wg_private_key;
settings[FPSTR(FP_CONFIG_PRIVATE_KEY)] = D_PASSWORD_MASK;
if(strcmp(wg_ep_ip, settings[FPSTR(FP_CONFIG_HOST)].as<String>().c_str()) != 0) changed = true;
settings[FPSTR(FP_CONFIG_HOST)] = wg_ep_ip;
if(wg_ep_port != settings[FPSTR(FP_CONFIG_PORT)].as<uint16_t>()) changed = true;
settings[FPSTR(FP_CONFIG_PORT)] = wg_ep_port;
if(strcmp(wg_ep_public_key, settings[FPSTR(FP_CONFIG_PUBLIC_KEY)].as<String>().c_str()) != 0) changed = true;
settings[FPSTR(FP_CONFIG_PUBLIC_KEY)] = wg_ep_public_key;
if(changed) configOutput(settings, TAG_WG);
return changed;
}
bool wgSetConfig(const JsonObject& settings)
{
Preferences preferences;
nvs_user_begin(preferences, "wg", false);
configOutput(settings, TAG_WG);
bool changed = false;
bool changed_privkey = false;
changed |= configSet((char *)wg_ip, sizeof(wg_ip), settings[FPSTR(FP_CONFIG_VPN_IP)], F("wgIp"));
if(!settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].isNull() &&
settings[FPSTR(FP_CONFIG_PRIVATE_KEY)].as<String>() != String(FPSTR(D_PASSWORD_MASK))) {
changed |= strcmp(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)]) != 0;
strncpy(wg_private_key, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)], sizeof(wg_private_key)-1);
wg_private_key[sizeof(wg_private_key)-1] = '\0';
nvsUpdateString(preferences, FP_CONFIG_PRIVATE_KEY, settings[FPSTR(FP_CONFIG_PRIVATE_KEY)]);
}
changed |= changed_privkey;
changed |= configSet((char *)wg_ep_ip, sizeof(wg_ep_ip), settings[FPSTR(FP_CONFIG_HOST)], F("wgEpIp"));
changed |= configSet(wg_ep_port, settings[FPSTR(FP_CONFIG_PORT)], F("wgEpPort"));
changed |= configSet((char *)wg_ep_public_key, sizeof(wg_ep_public_key), settings[FPSTR(FP_CONFIG_PUBLIC_KEY)], F("wgEpPubKey"));
if (changed && network_is_connected()) {
wg.end();
wg_network_connected();
}
return changed;
}
#endif
#endif /* WIREGUARD */

View File

@ -0,0 +1,40 @@
/* MIT License - Copyright (c) 2023 Jaroslav Kysela
For full license information read the LICENSE file in the project folder */
#ifndef HASP_WIREGUARD_H
#define HASP_WIREGUARD_H
void wg_setup();
int wg_config_valid();
void wg_network_disconnected();
void wg_network_connected();
void wg_get_statusupdate(char* buffer, size_t len);
int wg_get_ipaddress(char* buffer, size_t len);
void wg_get_info(JsonDocument& doc);
#if HASP_USE_CONFIG > 0
bool wgGetConfig(const JsonObject& settings);
bool wgSetConfig(const JsonObject& settings);
#endif
#ifndef WIREGUARD_IP
#define WIREGUARD_IP ""
#endif
#ifndef WIREGUARD_PRIVATE_KEY
#define WIREGUARD_PRIVATE_KEY ""
#endif
#ifndef WIREGUARD_EP_IP
#define WIREGUARD_EP_IP ""
#endif
#ifndef WIREGUARD_EP_PORT
#define WIREGUARD_EP_PORT 51820
#endif
#ifndef WIREGUARD_EP_PUBLIC_KEY
#define WIREGUARD_EP_PUBLIC_KEY ""
#endif
#endif

View File

@ -408,6 +408,10 @@ bool http_save_config()
#if HASP_USE_WIFI > 0
} else if(save == FP_WIFI) {
updated = wifiSetConfig(settings.as<JsonObject>());
#endif
#if HASP_USE_WIREGUARD > 0
} else if(save == FP_WG) {
updated = wgSetConfig(settings.as<JsonObject>());
#endif
}
}
@ -635,6 +639,10 @@ static void webHandleApi()
add_license(obj, "AceButton", "2018", "Brian T. Park", "mit");
obj = doc.createNestedObject();
add_license(obj, "QR Code generator", "", "Project Nayuki", "mit");
#if HASP_USE_WIREGUARD > 0
obj = doc.createNestedObject();
add_license(obj, "WireGuard", "2021", "Kenta Ida fugafuga.org, Daniel Hope www.floorsense.nz", "bsd", 1);
#endif
}
{
char output[HTTP_PAGE_SIZE];
@ -688,6 +696,11 @@ static void webHandleApi()
settings.createNestedObject(module);
timeGetConfig(settings[module]);
#endif
#if HASP_USE_WIREGUARD > 0
module = FPSTR(FP_WG);
settings.createNestedObject(module);
wgGetConfig(settings[module]);
#endif
#if HASP_USE_MQTT > 0
module = FPSTR(FP_MQTT);
settings.createNestedObject(module);
@ -790,6 +803,11 @@ static void webHandleApiConfig()
timeSetConfig(settings);
} else
#endif
#if HASP_USE_WIREGUARD > 0
if(!strcasecmp(endpoint_key, FP_WG)) {
wgSetConfig(settings);
} else
#endif
#if HASP_USE_MQTT > 0
if(!strcasecmp(endpoint_key, FP_MQTT)) {
mqttSetConfig(settings);
@ -831,6 +849,11 @@ static void webHandleApiConfig()
timeGetConfig(settings);
} else
#endif
#if HASP_USE_WIREGUARD > 0
if(!strcasecmp(endpoint_key, FP_WG)) {
wgGetConfig(settings);
} else
#endif
#if HASP_USE_MQTT > 0
if(!strcasecmp(endpoint_key, FP_MQTT)) {
mqttGetConfig(settings);
@ -937,6 +960,8 @@ static void http_handle_info()
<tr v-for="(item, key) in info.MQTT"><td v-t="key"></td><td v-if="item">{{ item }}</td></tr>
<th v-if="info.Wifi" colspan="2">Wifi</th>
<tr v-for="(item, key) in info.Wifi"><td v-t="key"></td><td v-if="item">{{ item }}</td></tr>
<th v-if="info.WireGuard" colspan="2">WireGuard</th>
<tr v-for="(item, key) in info.WireGuard"><td v-t="key"></td><td v-if="item">{{ item }}</td></tr>
<th v-if="info.Module" colspan="2">Module</th>
<tr v-for="(item, key) in info.Module"><td v-t="key"></td><td v-if="item">{{ item }}</td></tr>
</table>)";
@ -1415,6 +1440,9 @@ static void http_handle_config()
#if HASP_USE_WIFI > 0
html[min(i++, len)] = R"(<a href="/config/wifi" v-t="'wifi.btn'"></a>)";
#endif
#if HASP_USE_WIREGUARD > 0
html[min(i++, len)] = R"(<a href="/config/wireguard" v-t="'wg.btn'"></a>)";
#endif
#if HASP_USE_MQTT > 0
html[min(i++, len)] = R"(<a href="/config/mqtt" v-t="'mqtt.btn'"></a>)";
#endif
@ -2302,6 +2330,50 @@ static void http_handle_wifi()
#endif // HASP_USE_WIFI
////////////////////////////////////////////////////////////////////////////////////////////////////
#if HASP_USE_WIREGUARD > 0
static void http_handle_wireguard()
{ // http://plate01/config/wireguard
if(!http_is_authenticated(F("config/wireguard"))) return;
const char* html[20];
int i = 0;
int len = (sizeof(html) / sizeof(html[0])) - 1;
html[min(i++, len)] = "<h1>";
html[min(i++, len)] = haspDevice.get_hostname();
html[min(i++, len)] = "</h1><hr>";
html[min(i++, len)] = R"(
<h2 v-t="'wg.title'" @vue:mounted="showConfig('wg');"></h2>
<div class="container" v-cloak v-if="config.wg">
<form @submit.prevent="submitOldConfig('wg') ">
<div class="row">
<div class="col-25"><label for="vpnip" v-t="'wg.vpnip'"></label></div>
<div class="col-75"><input type="text" id="vpnip" maxlength="15" placeholder="VPN IP" v-model="config.wg.vpnip" pattern="^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$"></div>
</div>
<div class="row gap">
<div class="col-25"><label for="privkey" v-t="'wg.privkey'"></label></div>
<div class="col-75"><input type="password" id="privkey" maxlength="44" placeholder="Private Key" v-model="config.wg.privkey" pattern="^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2})$"></div>
</div>
<div class="row">
<div class="col-25"><label for="host" v-t="'wg.host'"></label></div>
<div class="col-75"><input type="text" id="host" maxlength="15" placeholder="Remote IP" v-model="config.wg.host" pattern="^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$"></div>
</div>
<div class="row">
<div class="col-25"><label for="port" v-t="'wg.port'"></label></div>
<div class="col-75"><input id="port" type="number" min="0" max="65535" placeholder="Remote Port" v-model="config.wg.port"></div>
</div>
<div class="row">
<div class="col-25"><label for="pubkey" v-t="'wg.pubkey'"></label></div>
<div class="col-75"><input type="text" id="pubkey" maxlength="44" placeholder="Remote Public Key" v-model="config.wg.pubkey" pattern="^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2})$"></div>
</div>)";
html[min(i++, len)] = R"(<button type="submit" v-t="'save'"></button></form></div>)";
html[min(i++, len)] = R"(<a v-t="'home.btn'" href="/"></a>)";
http_send_content(html, min(i, len));
}
#endif // HASP_USE_WIREGUARD
static inline int handleFirmwareFile(String path)
{
String contentType((char*)0);
@ -2715,6 +2787,9 @@ void httpSetup()
#if HASP_USE_WIFI > 0
webServer.on("/config/wifi", http_handle_wifi);
#endif
#if HASP_USE_WIREGUARD > 0
webServer.on("/config/wireguard", http_handle_wireguard);
#endif
#if HASP_USE_GPIO > 0
webServer.on("/config/gpio", webHandleGpioConfig);
webServer.on("/config/gpio/options", webHandleGpioOutput);

View File

@ -1259,7 +1259,7 @@ void webHandleMqttConfig(AsyncWebServerRequest* request)
////////////////////////////////////////////////////////////////////////////////////////////////////
void webHandleGuiConfig(AsyncWebServerRequest* request)
{ // http://plate01/config/wifi
{ // http://plate01/config/gui
if(!httpIsAuthenticated(request, F("config/gui"))) return;
{