My Project
telnet.cpp
1 /*
2  * uuid-telnet - Telnet service
3  * Copyright 2019,2022 Simon Arlott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "uuid/telnet.h"
20 
21 #include <Arduino.h>
22 #ifdef ARDUINO_ARCH_ESP8266
23 # include <ESP8266WiFi.h>
24 #else
25 # include <WiFi.h>
26 #endif
27 #include <WiFiUdp.h>
28 
29 #include <algorithm>
30 #include <list>
31 #include <memory>
32 #include <string>
33 
34 #include <uuid/common.h>
35 #include <uuid/log.h>
36 
37 #ifndef UUID_TELNET_HAVE_WIFICLIENT_REMOTE
38 # if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
39 # define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 1
40 # else
41 # define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 0
42 # endif
43 #endif
44 
45 #ifndef UUID_TELNET_HAVE_WIFICLIENT_NODELAY
46 # if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
47 # define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 1
48 # else
49 # define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 0
50 # endif
51 #endif
52 
53 #ifndef UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
54 # if defined(ARDUINO_ARCH_ESP8266)
55 # define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 1
56 # else
57 # define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 0
58 # endif
59 #endif
60 
61 #ifndef PSTR_ALIGN
62 # define PSTR_ALIGN 4
63 #endif
64 
65 static const char __pstr__logger_name[] __attribute__((__aligned__(PSTR_ALIGN))) PROGMEM = "telnet";
66 
67 namespace uuid {
68 
69 namespace telnet {
70 
71 uuid::log::Logger TelnetService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::DAEMON};
72 
73 TelnetService::TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
74  : TelnetService(DEFAULT_PORT, commands, context, flags) {
75 
76 }
77 
78 TelnetService::TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
79  : TelnetService(port,
80  [commands, context, flags] (Stream &stream, const IPAddress &addr __attribute__((unused)), uint16_t port __attribute__((unused))) -> std::shared_ptr<uuid::console::Shell> {
81  return std::make_shared<uuid::console::Shell>(stream, commands, context, flags);
82 }) {
83 
84 }
85 
87  : TelnetService(DEFAULT_PORT, shell_factory) {
88 
89 }
90 
92  : server_(port), shell_factory_(shell_factory) {
93 
94 }
95 
97  server_.begin();
98 }
99 
101  while (!connections_.empty()) {
102  connections_.front().stop();
103  connections_.pop_front();
104  }
105 }
106 
108  server_.stop();
109 }
110 
112  return maximum_connections_;
113 }
114 
116  maximum_connections_ = std::max((size_t)1, count);
117 
118  if (connections_.size() > maximum_connections_) {
119  size_t stop = connections_.size() - maximum_connections_;
120 
121  for (auto it = connections_.begin(); it != connections_.end(); ) {
122  if (it->stop()) {
123  if (--stop == 0)
124  break;
125  }
126  }
127  }
128 }
129 
130 unsigned long TelnetService::initial_idle_timeout() const {
131  return initial_idle_timeout_;
132 }
133 
134 void TelnetService::initial_idle_timeout(unsigned long timeout) {
135  initial_idle_timeout_ = timeout;
136 }
137 
139  return write_timeout_;
140 }
141 
142 void TelnetService::default_write_timeout(unsigned long timeout) {
143  write_timeout_ = timeout;
144 }
145 
147  for (auto it = connections_.begin(); it != connections_.end(); ) {
148  if (!it->loop()) {
149  it = connections_.erase(it);
150  } else {
151  it++;
152  }
153  }
154 
155  WiFiClient client = server_.available();
156  if (client) {
157  if (connections_.size() >= maximum_connections_) {
158 #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
159  logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort());
160 #else
161  logger_.info(F("New connection rejected (connection limit reached)"));
162 #endif
163  client.println(F("Maximum connection limit reached."));
164  client.stop();
165  } else {
166 #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
167  logger_.info(F("New connection from [%s]:%u accepted"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort());
168 #endif
169  connections_.emplace_back(shell_factory_, std::move(client), initial_idle_timeout_, write_timeout_);
170 #if !(UUID_TELNET_HAVE_WIFICLIENT_REMOTE)
171  logger_.info(F("New connection %p accepted"), &connections_.back());
172 #endif
173  }
174  }
175 }
176 
177 TelnetService::Connection::Connection(shell_factory_function &shell_factory, WiFiClient &&client, unsigned long idle_timeout, unsigned long write_timeout)
178  : client_(std::move(client)), stream_(client_) {
179 #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
180  // These have to be copied because they're not accessible on closed connections
181  addr_ = client_.remoteIP();
182  port_ = client_.remotePort();
183 #else
184  port_ = 0;
185 #endif
186 
187 #if UUID_TELNET_HAVE_WIFICLIENT_NODELAY
188  client_.setNoDelay(true);
189 #endif
190 
191 #if UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
192  // Disconnect after 30 seconds without a response
193  client_.keepAlive(5, 5, 5);
194 #endif
195 
196  if (write_timeout > 0) {
197  client_.setTimeout(write_timeout);
198  }
199 
200  stream_.start();
201 
202  if (client_.connected()) {
203  std::shared_ptr<uuid::console::Shell> shell = shell_factory(stream_, addr_, port_);
204  shell->idle_timeout(idle_timeout);
205  shell->start();
206  shell_ = shell;
207  }
208 }
209 
211  if (!shell_.expired()) {
212  if (!client_.connected()) {
213  auto shell = shell_.lock();
214 
215  if (shell) {
216  shell->stop();
217  }
218  }
219  return true;
220  } else {
221 #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
222  logger_.info(F("Connection from [%s]:%u closed"), uuid::printable_to_string(addr_).c_str(), port_);
223 #else
224  logger_.info(F("Connection %p closed"), this);
225 #endif
226  return false;
227  }
228 }
229 
231  auto shell = shell_.lock();
232 
233  if (shell) {
234  shell->stop();
235  return true;
236  } else {
237  return false;
238  }
239 }
240 
241 } // namespace telnet
242 
243 } // namespace uuid
uuid::telnet::TelnetService::initial_idle_timeout
unsigned long initial_idle_timeout() const
Get the initial idle timeout for new connections.
Definition: telnet.cpp:130
uuid::log::Logger
Logger instance used to make log messages.
Definition: log.h:347
uuid::telnet::TelnetService::Connection::port_
uint16_t port_
Definition: telnet.h:415
uuid::telnet::TelnetService::server_
WiFiServer server_
Definition: telnet.h:423
uuid::telnet::TelnetService::TelnetService
TelnetService(std::shared_ptr< uuid::console::Commands > commands, unsigned int context=0, unsigned int flags=0)
Create a new telnet service listening on the default port.
Definition: telnet.cpp:73
uuid::telnet::TelnetService::logger_
static uuid::log::Logger logger_
Definition: telnet.h:421
uuid::telnet::TelnetService::close_all
void close_all()
Close all connections.
Definition: telnet.cpp:100
uuid::telnet::TelnetService::stop
void stop()
Stop listening for connections.
Definition: telnet.cpp:107
uuid::telnet::TelnetService::start
void start()
Start listening for connections on the configured port.
Definition: telnet.cpp:96
uuid::telnet::TelnetService::initial_idle_timeout_
unsigned long initial_idle_timeout_
Definition: telnet.h:427
uuid::telnet::TelnetService::default_write_timeout
unsigned long default_write_timeout() const
Get the default socket write timeout for new connections.
Definition: telnet.cpp:138
uuid::log::Logger::info
void info(const char *format,...) const
Log a message at level Level::INFO.
Definition: log.cpp:230
uuid::telnet::TelnetService::maximum_connections_
size_t maximum_connections_
Definition: telnet.h:424
uuid::telnet::TelnetService
Provides access to a console shell as a telnet server.
Definition: telnet.h:230
uuid::telnet::TelnetService::Connection::addr_
IPAddress addr_
Definition: telnet.h:414
uuid::telnet::TelnetService::connections_
std::list< Connection > connections_
Definition: telnet.h:425
uuid::telnet::TelnetService::maximum_connections
size_t maximum_connections() const
Get the maximum number of concurrent open connections.
Definition: telnet.cpp:111
uuid::printable_to_string
std::string printable_to_string(const Printable &printable)
Create a std::string from a Printable object.
Definition: printable_to_string.cpp:55
uuid::telnet::TelnetService::write_timeout_
unsigned long write_timeout_
Definition: telnet.h:428
uuid
Common utilities.
Definition: get_uptime_ms.cpp:28
uuid::telnet::TelnetService::shell_factory_function
std::function< std::shared_ptr< uuid::console::Shell >(Stream &stream, const IPAddress &addr, uint16_t port)> shell_factory_function
Function to handle the creation of a shell.
Definition: telnet.h:245
uuid::telnet::TelnetService::Connection::stop
bool stop()
Stop the shell.
Definition: telnet.cpp:230
uuid::telnet::TelnetService::shell_factory_
shell_factory_function shell_factory_
Definition: telnet.h:426
uuid::console::Shell::start
void start()
Perform startup process for this shell.
Definition: shell.cpp:54
uuid::telnet::TelnetService::Connection::stream_
TelnetStream stream_
Definition: telnet.h:412
uuid::console::Shell::idle_timeout
unsigned long idle_timeout() const
Get the idle timeout.
Definition: shell.cpp:498
uuid::telnet::TelnetService::Connection::shell_
std::weak_ptr< uuid::console::Shell > shell_
Definition: telnet.h:413
uuid::telnet::TelnetService::Connection::client_
WiFiClient client_
Definition: telnet.h:411
uuid::telnet::TelnetStream::start
void start()
Perform initial negotiation.
Definition: stream.cpp:36
uuid::telnet::TelnetService::Connection::Connection
Connection(shell_factory_function &shell_factory, WiFiClient &&client, unsigned long idle_timeout, unsigned long write_timeout)
Create a telnet connection shell.
Definition: telnet.cpp:177
uuid::telnet::TelnetService::loop
void loop()
Accept new connections.
Definition: telnet.cpp:146
uuid::telnet::TelnetService::Connection::loop
bool loop()
Stop the shell if the client is not connected.
Definition: telnet.cpp:210