diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 67958314b4..195b016194 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -409,7 +409,8 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); -void prepareHostname(char* hostname); +void getWLEDhostname(char* hostname, size_t maxLen, bool preferMDNS=false); // maxLen = hostname buffer size including \0; if preferMDNSname=true, use mdns name (sanitized) +void prepareHostname(char* hostname, size_t maxLen); // legacy function - not recommended for new code [[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t moduleID=JSON_LOCK_UNKNOWN); void releaseJSONBufferLock(); diff --git a/wled00/network.cpp b/wled00/network.cpp index 105fdf6b27..9aad1f327f 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -413,9 +413,8 @@ void WiFiEvent(WiFiEvent_t event) if (!apActive) { WiFi.disconnect(true); // disable WiFi entirely } - // convert the "serverDescription" into a valid DNS hostname (alphanumeric) - char hostname[64]; - prepareHostname(hostname); + char hostname[64] = {'\0'}; // any "hostname" within a Fully Qualified Domain Name (FQDN) must not exceed 63 characters + getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name ETH.setHostname(hostname); showWelcomePage = false; break; diff --git a/wled00/util.cpp b/wled00/util.cpp index c6fbb280b4..393e9eb165 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -127,28 +127,65 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in } - -void prepareHostname(char* hostname) -{ - sprintf_P(hostname, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); - const char *pC = serverDescription; - unsigned pos = 5; // keep "wled-" - while (*pC && pos < 24) { // while !null and not over length - if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname - hostname[pos] = *pC; - pos++; - } else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') { - hostname[pos] = '-'; - pos++; +// in-place hostname sanitizer, extracted from prepareHostname() +static void sanitizeHostname(char* hostname, size_t maxLen) { + if (hostname == nullptr || maxLen < 1 || strlen(hostname) < 1) return; + + char *pC = hostname; + unsigned pos = 0; + while (*pC && pos < maxLen) { + char c = *pC; + if (isalnum((unsigned char)c)) { + hostname[pos++] = c; + } else if (c == ' ' || c == '_' || c == '-' || c == '+' || c == '!' || c == '?' || c == '*') { // convert certain characters to hyphens + if (pos > 0 && hostname[pos -1] != '-') hostname[pos++] = '-'; // keep single non-leading hyphens only } - // else do nothing - no leading hyphens and do not include hyphens for all other characters. + // else: drop any character not valid in a DNS hostname label pC++; } - //last character must not be hyphen - if (pos > 5) { - while (pos > 4 && hostname[pos -1] == '-') pos--; - hostname[pos] = '\0'; // terminate string (leave at least "wled") + // Hostname must not end with a hyphen. + while (pos > 0 && hostname[pos -1] == '-') pos--; + hostname[min(pos, maxLen-1)] = '\0'; // terminate string +} + +/* + * Stores sanitized hostname into buffer provided by caller + * maxLen = hostname buffer size including \0 + * preferMDNSname -> use mDNS name if set, otherwise fall back to WLED "server description" name (legacy behaviour) + */ +void getWLEDhostname(char* hostname, size_t maxLen, bool preferMDNS) { + if (maxLen <= 6) { strlcpy(hostname, "wled", maxLen); return; } // buffer too small (should not happen) + if (preferMDNS && (strlen(cmDNS) > 0) && (strcmp_P(cmDNS, PSTR(DEFAULT_MDNS_NAME)) != 0)) { // avoid "x" = not set (use wled-MAC) + strlcpy(hostname, cmDNS, maxLen); + sanitizeHostname(hostname, maxLen); // sanitize cmDNS name + if (strlen(hostname) < 1) { // if result is empty -> fall back to wled-MAC + snprintf_P(hostname, maxLen, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); + hostname[maxLen -1] = '\0'; // ensure string termination + } + } else { + prepareHostname(hostname, maxLen); // use legacy hostname based on "server description" - already sanitized } + DEBUG_PRINTF_P(PSTR("getWLEDHostname: '%s'\n"), hostname); +} + +/* Legacy hostname construction: + * Start with "wled-" + serverDescription as suffix. + * Sanitize only the suffix (always keep wled- prefix intact). + * If the sanitized suffix ends up empty, fall back to wled-. + */ +void prepareHostname(char* hostname, size_t maxLen) +{ + if (maxLen <= 6) { strlcpy(hostname, "wled", maxLen); return; } // buffer too small (should not happen) + if (strncasecmp_P(serverDescription, PSTR("wled"), 4) == 0) // avoid wled-WLED-... as a hostname + strlcpy(hostname, serverDescription, maxLen); + else + snprintf_P(hostname, maxLen, PSTR("wled-%s"), serverDescription); + hostname[maxLen -1] = '\0'; // ensure string termination + + size_t sanOffset = hostname[4] != '-' ? 4 : 5; // ensure that "WLED foo" and "WLED!foo" get sanitized + sanitizeHostname(hostname+sanOffset, maxLen-sanOffset); // sanitize name, keep "wled-" intact + if (strlen(hostname) <= sanOffset) + snprintf_P(hostname, maxLen, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); // fallback to wled-MAC if sanitization cleaned everything } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index d67f784078..305ce35fac 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -507,8 +507,12 @@ void WLED::setup() #ifndef ESP8266 WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFi.persistent(true); // storing credentials in NVM fixes boot-up pause as connection is much faster, is disabled after first connection + // ESP32 DNS name must be set before the first connection to the DHCP server; otherwise, the default ESP name (such as "esp32s3-267D0C") will be used. + char hostname[64] = {'\0'}; + getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name + WiFi.setHostname(hostname); #else - WiFi.persistent(false); // on ES8266 using NVM for wifi config has no benefit of faster connection + WiFi.persistent(false); // on ESP8266 using NVM for wifi config has no benefit of faster connection #endif WiFi.onEvent(WiFiEvent); WiFi.mode(WIFI_STA); // enable scanning @@ -691,6 +695,18 @@ void WLED::initConnection() WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); #endif + char hostname[64] = {'\0'}; + getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name + +#ifdef ARDUINO_ARCH_ESP32 + // Reset mode to NULL to force a full STA mode transition, so that WiFi.mode(WIFI_STA) below actually applies the hostname (and TX power, etc.). + // This is required on reconnects when mode is already WIFI_STA. + WiFi.mode(WIFI_MODE_NULL); + apActive = false; // the AP is physically torn down by WIFI_MODE_NULL + delay(5); // give the WiFi stack time to complete the mode transition + WiFi.setHostname(hostname); +#endif + if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); } else { @@ -718,10 +734,6 @@ void WLED::initConnection() DEBUG_PRINTF_P(PSTR("Connecting to %s...\n"), multiWiFi[selectedWiFi].clientSSID); - // convert the "serverDescription" into a valid DNS hostname (alphanumeric) - char hostname[25]; - prepareHostname(hostname); - #ifdef WLED_ENABLE_WPA_ENTERPRISE if (multiWiFi[selectedWiFi].encryptionType == WIFI_ENCRYPTION_TYPE_PSK) { DEBUG_PRINTLN(F("Using PSK")); @@ -774,8 +786,7 @@ void WLED::initConnection() #ifdef ARDUINO_ARCH_ESP32 WiFi.setTxPower(wifi_power_t(txPower)); WiFi.setSleep(!noWifiSleep); - WiFi.setHostname(hostname); -#else +#else // ESP8266 accepts a hostname set after WiFi interface initialization wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); WiFi.hostname(hostname); #endif