From 9be43c364a4e2f3b5460000c0157cfcd7dd3d6bf Mon Sep 17 00:00:00 2001 From: Niek van der Maas Date: Sat, 13 Jul 2019 01:56:31 +0200 Subject: [PATCH] Telnet/WiFiServer: alternative to ESPAsyncTCP (#1799) * Telnet rewrite, no longer using ESPAsyncTCP * Support two telnet server implementations * Clean up diff * Update types, no free() calls, cleanup * Use std::unique_ptr * Fix nullptr checks * Fix double log with async server * Common new connection method * use same connected() check as sync variant * fixup! use same connected() check as sync variant * Fix compile issue, close connection in case of timeout and when max connections reached * Use AyncsClient* in function call * Allow overriding telnet port + max clients * Do not allocate 512 bytes by default * Use TERMINAL_BUFFER_SIZE --- code/espurna/config/general.h | 9 ++ code/espurna/config/types.h | 6 ++ code/espurna/telnet.ino | 181 ++++++++++++++++++++++++---------- 3 files changed, 145 insertions(+), 51 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index a3f5f097..7d7f77c6 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -118,8 +118,17 @@ #define TELNET_AUTHENTICATION 1 // Request password to start telnet session by default #endif +#ifndef TELNET_PORT #define TELNET_PORT 23 // Port to listen to telnet clients +#endif + +#ifndef TELNET_MAX_CLIENTS #define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients +#endif + +#ifndef TELNET_SERVER +#define TELNET_SERVER TELNET_SERVER_ASYNC // Can be either TELNET_SERVER_ASYNC (using ESPAsyncTCP) or TELNET_SERVER_WIFISERVER (using WiFiServer) +#endif //------------------------------------------------------------------------------ // TERMINAL diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index 62381260..cf67b02a 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -343,3 +343,9 @@ #define MAGNITUDE_PH 31 #define MAGNITUDE_MAX 32 + +//------------------------------------------------------------------------------ +// Telnet server +//------------------------------------------------------------------------------ +#define TELNET_SERVER_ASYNC 0 +#define TELNET_SERVER_WIFISERVER 1 \ No newline at end of file diff --git a/code/espurna/telnet.ino b/code/espurna/telnet.ino index b7ad357f..f5551584 100644 --- a/code/espurna/telnet.ino +++ b/code/espurna/telnet.ino @@ -10,10 +10,16 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer #if TELNET_SUPPORT -#include +#if TELNET_SERVER == TELNET_SERVER_WIFISERVER + #include + WiFiServer _telnetServer = WiFiServer(TELNET_PORT); + std::unique_ptr _telnetClients[TELNET_MAX_CLIENTS]; +#else + #include + AsyncServer _telnetServer = AsyncServer(TELNET_PORT); + std::unique_ptr _telnetClients[TELNET_MAX_CLIENTS]; +#endif -AsyncServer * _telnetServer; -AsyncClient * _telnetClients[TELNET_MAX_CLIENTS]; bool _telnetFirst = true; bool _telnetAuth = TELNET_AUTHENTICATION; @@ -38,9 +44,11 @@ void _telnetWebSocketOnSend(JsonObject& root) { #endif void _telnetDisconnect(unsigned char clientId) { - _telnetClients[clientId]->free(); - delete _telnetClients[clientId]; - _telnetClients[clientId] = NULL; + // ref: we are called from onDisconnect, async is already stopped + #if TELNET_SERVER == TELNET_SERVER_WIFISERVER + _telnetClients[clientId]->stop(); + #endif + _telnetClients[clientId] = nullptr; wifiReconnectCheck(); DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId); } @@ -74,7 +82,6 @@ bool _telnetWrite(unsigned char clientId, const char * message) { } void _telnetData(unsigned char clientId, void *data, size_t len) { - // Skip first message since it's always garbage if (_telnetFirst) { _telnetFirst = false; @@ -87,13 +94,13 @@ void _telnetData(unsigned char clientId, void *data, size_t len) { // C-d is sent as two bytes (sometimes repeating) if (len >= 2) { if ((p[0] == 0xFF) && (p[1] == 0xEC)) { - _telnetClients[clientId]->close(true); + _telnetDisconnect(clientId); return; } } if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) { - _telnetClients[clientId]->close(); + _telnetDisconnect(clientId); return; } @@ -108,10 +115,10 @@ void _telnetData(unsigned char clientId, void *data, size_t len) { String password = getAdminPass(); if (strncmp(p, password.c_str(), password.length()) == 0) { DEBUG_MSG_P(PSTR("[TELNET] Client #%d authenticated\n"), clientId); - _telnetWrite(clientId, "Welcome!\n"); + _telnetWrite(clientId, "Password correct, welcome!\n"); _telnetClientsAuth[clientId] = true; } else { - _telnetWrite(clientId, "Password: "); + _telnetWrite(clientId, "Password (try again): "); } return; } @@ -120,13 +127,101 @@ void _telnetData(unsigned char clientId, void *data, size_t len) { #if TERMINAL_SUPPORT terminalInject(data, len); #endif +} + +void _telnetNotifyConnected(unsigned char i) { + + DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i); + + // If there is no terminal support automatically dump info and crash data + #if TERMINAL_SUPPORT == 0 + info(); + wifiDebug(); + crashDump(); + crashClear(); + #endif + + #ifdef ESPURNA_CORE + _telnetClientsAuth[i] = true; + #else + _telnetClientsAuth[i] = !_telnetAuth; + if (_telnetAuth) { + if (getAdminPass().length()) { + _telnetWrite(i, "Password: "); + } else { + _telnetClientsAuth[i] = true; + } + } + #endif + + _telnetFirst = true; + wifiReconnectCheck(); } -void _telnetNewClient(AsyncClient *client) { +#if TELNET_SERVER == TELNET_SERVER_WIFISERVER - if (client->localIP() != WiFi.softAPIP()) { +void _telnetLoop() { + if (_telnetServer.hasClient()) { + int i; + + for (i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (!_telnetClients[i] || !_telnetClients[i]->connected()) { + + _telnetClients[i] = std::unique_ptr(new WiFiClient(_telnetServer.available())); + + if (_telnetClients[i]->localIP() != WiFi.softAPIP()) { + // Telnet is always available for the ESPurna Core image + #ifdef ESPURNA_CORE + bool telnetSTA = true; + #else + bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1; + #endif + + if (!telnetSTA) { + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n")); + _telnetDisconnect(i); + return; + } + } + + _telnetNotifyConnected(i); + + break; + } + } + //no free/disconnected spot so reject + if (i == TELNET_MAX_CLIENTS) { + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n")); + _telnetServer.available().stop(); + return; + } + } + + for (int i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (_telnetClients[i]) { + // Handle client timeouts + if (!_telnetClients[i]->connected()) { + _telnetDisconnect(i); + } else { + // Read data from clients + while (_telnetClients[i] && _telnetClients[i]->available()) { + char data[TERMINAL_BUFFER_SIZE]; + size_t len = _telnetClients[i]->available(); + unsigned int r = _telnetClients[i]->readBytes(data, min(sizeof(data), len)); + + _telnetData(i, data, r); + } + } + } + } +} + +#else // TELNET_SERVER_ASYNC + +void _telnetNewClient(AsyncClient* client) { + if (client->localIP() != WiFi.softAPIP()) { // Telnet is always available for the ESPurna Core image #ifdef ESPURNA_CORE bool telnetSTA = true; @@ -137,76 +232,54 @@ void _telnetNewClient(AsyncClient *client) { if (!telnetSTA) { DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n")); client->onDisconnect([](void *s, AsyncClient *c) { - c->free(); delete c; }); client->close(true); return; } - } for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { if (!_telnetClients[i] || !_telnetClients[i]->connected()) { - _telnetClients[i] = client; + _telnetClients[i] = std::unique_ptr(client); - client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { + _telnetClients[i]->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { }, 0); - client->onData([i](void *s, AsyncClient *c, void *data, size_t len) { + _telnetClients[i]->onData([i](void *s, AsyncClient *c, void *data, size_t len) { _telnetData(i, data, len); }, 0); - client->onDisconnect([i](void *s, AsyncClient *c) { + _telnetClients[i]->onDisconnect([i](void *s, AsyncClient *c) { _telnetDisconnect(i); }, 0); - client->onError([i](void *s, AsyncClient *c, int8_t error) { + _telnetClients[i]->onError([i](void *s, AsyncClient *c, int8_t error) { DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%u\n"), c->errorToString(error), error, i); }, 0); - client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { + _telnetClients[i]->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%u at %lu\n"), i, time); c->close(); }, 0); - DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i); - - // If there is no terminal support automatically dump info and crash data - #if TERMINAL_SUPPORT == 0 - info(); - wifiDebug(); - crashDump(); - crashClear(); - #endif - - #ifdef ESPURNA_CORE - _telnetClientsAuth[i] = true; - #else - _telnetClientsAuth[i] = !_telnetAuth; - if (_telnetAuth) _telnetWrite(i, "Password: "); - #endif - - _telnetFirst = true; - wifiReconnectCheck(); - + _telnetNotifyConnected(i); return; - } } DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n")); client->onDisconnect([](void *s, AsyncClient *c) { - c->free(); delete c; }); client->close(true); - } +#endif // TELNET_SERVER == TELNET_SERVER_WIFISERVER + // ----------------------------------------------------------------------------- // Public API // ----------------------------------------------------------------------------- @@ -228,12 +301,16 @@ void _telnetConfigure() { } void telnetSetup() { - - _telnetServer = new AsyncServer(TELNET_PORT); - _telnetServer->onClient([](void *s, AsyncClient* c) { - _telnetNewClient(c); - }, 0); - _telnetServer->begin(); + #if TELNET_SERVER == TELNET_SERVER_WIFISERVER + espurnaRegisterLoop(_telnetLoop); + _telnetServer.setNoDelay(true); + _telnetServer.begin(); + #else + _telnetServer.onClient([](void *s, AsyncClient* c) { + _telnetNewClient(c); + }, 0); + _telnetServer.begin(); + #endif #if WEB_SUPPORT wsOnSendRegister(_telnetWebSocketOnSend); @@ -243,7 +320,9 @@ void telnetSetup() { espurnaRegisterReload(_telnetConfigure); _telnetConfigure(); - DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT); + DEBUG_MSG_P(PSTR("[TELNET] %s server, Listening on port %d\n"), + (TELNET_SERVER == TELNET_SERVER_WIFISERVER) ? "Sync" : "Async", + TELNET_PORT); }