mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
sys/net/application_layer: add telnet server module
This commit is contained in:
parent
1b52dee2d6
commit
2034fa5101
@ -210,6 +210,7 @@ PSEUDOMODULES += stdio_cdc_acm
|
||||
PSEUDOMODULES += stdio_ethos
|
||||
PSEUDOMODULES += stdio_nimble_debug
|
||||
PSEUDOMODULES += stdio_uart_rx
|
||||
PSEUDOMODULES += stdio_telnet
|
||||
PSEUDOMODULES += stm32_eth
|
||||
PSEUDOMODULES += stm32_eth_auto
|
||||
PSEUDOMODULES += stm32_eth_link_up
|
||||
|
@ -7,6 +7,7 @@ STDIO_MODULES = \
|
||||
stdio_rtt \
|
||||
stdio_semihosting \
|
||||
stdio_uart \
|
||||
stdio_telnet \
|
||||
#
|
||||
|
||||
ifneq (,$(filter newlib picolibc,$(USEMODULE)))
|
||||
@ -63,6 +64,11 @@ ifneq (,$(filter stdio_semihosting,$(USEMODULE)))
|
||||
FEATURES_REQUIRED_ANY += cpu_core_cortexm|arch_riscv
|
||||
endif
|
||||
|
||||
ifneq (,$(filter stdio_telnet,$(USEMODULE)))
|
||||
DEFAULT_MODULE += auto_init_telnet
|
||||
USEMODULE += telnet
|
||||
endif
|
||||
|
||||
# enable stdout buffering for modules that benefit from sending out buffers in larger chunks
|
||||
ifneq (,$(filter picolibc,$(USEMODULE)))
|
||||
ifneq (,$(filter stdio_cdc_acm stdio_ethos slipdev_stdio stdio_semihosting,$(USEMODULE)))
|
||||
|
@ -11,6 +11,9 @@ endif
|
||||
ifneq (,$(filter cipher_modes,$(USEMODULE)))
|
||||
DIRS += crypto/modes
|
||||
endif
|
||||
ifneq (,$(filter telnet,$(USEMODULE)))
|
||||
DIRS += net/application_layer/telnet
|
||||
endif
|
||||
ifneq (,$(filter constfs,$(USEMODULE)))
|
||||
DIRS += fs/constfs
|
||||
endif
|
||||
|
@ -324,6 +324,11 @@ ifneq (,$(filter sema_inv,$(USEMODULE)))
|
||||
USEMODULE += atomic_utils
|
||||
endif
|
||||
|
||||
ifneq (,$(filter telnet,$(USEMODULE)))
|
||||
USEMODULE += pipe
|
||||
USEMODULE += sock_tcp
|
||||
endif
|
||||
|
||||
ifneq (,$(filter luid,$(USEMODULE)))
|
||||
FEATURES_OPTIONAL += periph_cpuid
|
||||
endif
|
||||
|
@ -305,6 +305,12 @@ void auto_init(void)
|
||||
gnrc_ipv6_auto_subnets_init();
|
||||
}
|
||||
|
||||
if (IS_USED(MODULE_AUTO_INIT_TELNET)) {
|
||||
LOG_DEBUG("auto_init TELNET server\n");
|
||||
extern void telnet_server_start(void);
|
||||
telnet_server_start();
|
||||
}
|
||||
|
||||
if (IS_USED(MODULE_AUTO_INIT_MULTIMEDIA)) {
|
||||
LOG_DEBUG("auto_init MULTIMEDIA\n");
|
||||
if (IS_USED(MODULE_DFPLAYER)) {
|
||||
|
95
sys/include/net/telnet.h
Normal file
95
sys/include/net/telnet.h
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2021 ML!PA Consulting GmbH
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup net_telnet basic Telnet server implementation
|
||||
* @ingroup net_ipv6
|
||||
* @brief Telnet server functions
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief minimal Telnet server ([RFC 854](https://tools.ietf.org/html/rfc854)) implementation
|
||||
* @note This implementation only supports a single client and no options.
|
||||
*
|
||||
* @warning Telnet is entirely unencrypted! Do not use it on public networks.
|
||||
* This is intended to aid debugging on networks that are isolated from the Internet.
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*/
|
||||
#ifndef NET_TELNET_H
|
||||
#define NET_TELNET_H
|
||||
|
||||
#include "net/sock/tcp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The port for the Telnet server to listen on
|
||||
*/
|
||||
#ifndef CONFIG_TELNET_PORT
|
||||
#define CONFIG_TELNET_PORT (23)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Start the Telnet server thread
|
||||
*
|
||||
* @return 0 on success, error otherwise
|
||||
*/
|
||||
int telnet_server_start(void);
|
||||
|
||||
/**
|
||||
* @brief Write data to the telnet client
|
||||
*
|
||||
* @param[in] buffer The buffer to send to the client
|
||||
* @param[in] len The length of the buffer
|
||||
*
|
||||
* @return 0 on success, error otherwise
|
||||
*/
|
||||
int telnet_server_write(const void* buffer, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Read data from the telnet client, will block until data is available.
|
||||
*
|
||||
* @param[out] buffer The buffer to write data from the client
|
||||
* @param[in] count Number of bytes to read
|
||||
*
|
||||
* @return number of bytes read, error otherwise
|
||||
*/
|
||||
int telnet_server_read(void* buffer, size_t count);
|
||||
|
||||
/**
|
||||
* @brief Callback function that gets called when a telnet client connects
|
||||
* but before stdio is redirected.
|
||||
*
|
||||
* @param[in] sock Socket of the client that just connected
|
||||
* only use with @ref sock_tcp_get_local
|
||||
*/
|
||||
void telnet_cb_pre_connected(sock_tcp_t *sock);
|
||||
|
||||
/**
|
||||
* @brief Callback function that gets called when a telnet client connects
|
||||
* after stdio is redirected.
|
||||
*
|
||||
* @param[in] sock Socket of the client that just connected
|
||||
* only use with @ref sock_tcp_get_local
|
||||
*/
|
||||
void telnet_cb_connected(sock_tcp_t *sock);
|
||||
|
||||
/**
|
||||
* @brief Callback function that gets called after a telnet client disconnected.
|
||||
*/
|
||||
void telnet_cb_disconneced(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NET_TELNET_H */
|
||||
/** @} */
|
@ -21,3 +21,9 @@ rsource "asymcute/Kconfig"
|
||||
rsource "emcute/Kconfig"
|
||||
|
||||
endmenu # MQTT-SN
|
||||
|
||||
menu "Telnet"
|
||||
|
||||
rsource "telnet/Kconfig"
|
||||
|
||||
endmenu # Telnet
|
||||
|
28
sys/net/application_layer/telnet/Kconfig
Normal file
28
sys/net/application_layer/telnet/Kconfig
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2021 ML!PA Consulting GmbH
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
#
|
||||
menuconfig KCONFIG_USEMODULE_TELNET
|
||||
bool "Configure telnet server"
|
||||
depends on USEMODULE_TELNET
|
||||
help
|
||||
Configure telnet module using Kconfig. If not set default values and
|
||||
CFLAGS will be used.
|
||||
|
||||
if KCONFIG_USEMODULE_TELNET
|
||||
|
||||
config TELNET_PORT
|
||||
int "Server port"
|
||||
default 23
|
||||
help
|
||||
Server port, the default is the one specified in RFC 855.
|
||||
|
||||
config TELNET_TCP_QUEUE_SIZE
|
||||
int "TCP Queue Size"
|
||||
default 1
|
||||
help
|
||||
Maximum number of incoming TCP connections.
|
||||
|
||||
endif # KCONFIG_USEMODULE_TELNET
|
1
sys/net/application_layer/telnet/Makefile
Normal file
1
sys/net/application_layer/telnet/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
86
sys/net/application_layer/telnet/stdio_telnet.c
Normal file
86
sys/net/application_layer/telnet/stdio_telnet.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 ML!PA Consulting GmbH
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup sys
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief STDIO over Telnet implementation
|
||||
*
|
||||
* This file implements STDIO via a Telnet server with fallback UART output
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "board.h"
|
||||
#include "kernel_defines.h"
|
||||
#include "net/telnet.h"
|
||||
#if IS_USED(MODULE_PERIPH_UART)
|
||||
#include "stdio_uart.h"
|
||||
#include "periph/uart.h"
|
||||
#endif
|
||||
#if IS_USED(MODULE_VFS)
|
||||
#include "vfs.h"
|
||||
#endif
|
||||
#ifdef CPU_NATIVE
|
||||
#include "native_internal.h"
|
||||
#endif
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
static inline void _init_fallback(void)
|
||||
{
|
||||
#if defined(STDIO_UART_DEV) && IS_USED(MODULE_PERIPH_UART)
|
||||
uart_init(STDIO_UART_DEV, STDIO_UART_BAUDRATE, NULL, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int _write_fallback(const void* buffer, size_t len)
|
||||
{
|
||||
#if defined(CPU_NATIVE)
|
||||
real_write(STDOUT_FILENO, buffer, len);
|
||||
#elif defined(STDIO_UART_DEV) && IS_USED(MODULE_PERIPH_UART)
|
||||
uart_write(STDIO_UART_DEV, buffer, len);
|
||||
#else
|
||||
(void)buffer;
|
||||
#endif
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void stdio_init(void)
|
||||
{
|
||||
_init_fallback();
|
||||
|
||||
#if IS_USED(MODULE_VFS)
|
||||
vfs_bind_stdio();
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t stdio_read(void* buffer, size_t count)
|
||||
{
|
||||
return telnet_server_read(buffer, count);
|
||||
}
|
||||
|
||||
ssize_t stdio_write(const void* buffer, size_t len)
|
||||
{
|
||||
int res = telnet_server_write(buffer, len);
|
||||
|
||||
/* write to UART if no client is connected */
|
||||
if (res == -ENOTCONN) {
|
||||
return _write_fallback(buffer, len);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
303
sys/net/application_layer/telnet/telnet_server.c
Normal file
303
sys/net/application_layer/telnet/telnet_server.c
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright (C) 2021 ML!PA Consulting GmbH
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Telnet server implementation
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include "net/sock/tcp.h"
|
||||
#include "net/telnet.h"
|
||||
#include "pipe.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
#ifndef CONFIG_TELNET_TCP_QUEUE_SIZE
|
||||
#define CONFIG_TELNET_TCP_QUEUE_SIZE (1)
|
||||
#endif
|
||||
|
||||
static char _stdin_pipe_buf[16];
|
||||
static ringbuffer_t _stdin_ringbuffer;
|
||||
static pipe_t _stdin_pipe;
|
||||
|
||||
static sock_tcp_queue_t sock_queue;
|
||||
static sock_tcp_t socks[CONFIG_TELNET_TCP_QUEUE_SIZE];
|
||||
static sock_tcp_t *client;
|
||||
|
||||
static char telnet_stack[THREAD_STACKSIZE_DEFAULT];
|
||||
|
||||
#define SOCK_TCP_TIMEOUT_MS 50
|
||||
|
||||
enum {
|
||||
TELNET_CMD_EOF = 236,
|
||||
TELNET_CMD_SE = 240,
|
||||
TELNET_CMD_NOP,
|
||||
TELNET_CMD_DATA_MARK,
|
||||
TELNET_CMD_BRK,
|
||||
TELNET_CMD_IP,
|
||||
TELNET_CMD_AO,
|
||||
TELNET_CMD_AYT,
|
||||
TELNET_CMD_EC,
|
||||
TELNET_CMD_EL,
|
||||
TELNET_CMD_GA,
|
||||
TELNET_CMD_SB = 250,
|
||||
TELNET_CMD_WILL,
|
||||
TELNET_CMD_WONT,
|
||||
TELNET_CMD_DO,
|
||||
TELNET_CMD_DONT,
|
||||
TELNET_CMD_IAC
|
||||
};
|
||||
|
||||
enum {
|
||||
TELNET_OPT_BINARY = 0,
|
||||
TELNET_OPT_ECHO = 1,
|
||||
TELNET_OPT_SUP_GO_AHEAD = 3,
|
||||
TELNET_OPT_STATUS = 5,
|
||||
};
|
||||
|
||||
static bool connected;
|
||||
static mutex_t connected_mutex = MUTEX_INIT_LOCKED;
|
||||
static mutex_t sock_mutex;
|
||||
|
||||
__attribute__((weak))
|
||||
void telnet_cb_pre_connected(sock_tcp_t *sock)
|
||||
{
|
||||
(void)sock;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void telnet_cb_connected(sock_tcp_t *sock)
|
||||
{
|
||||
(void)sock;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void telnet_cb_disconneced(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void _acquire(void)
|
||||
{
|
||||
mutex_lock(&sock_mutex);
|
||||
}
|
||||
|
||||
static void _release(void)
|
||||
{
|
||||
mutex_unlock(&sock_mutex);
|
||||
}
|
||||
|
||||
static void _connected(void)
|
||||
{
|
||||
telnet_cb_pre_connected(client);
|
||||
|
||||
connected = true;
|
||||
mutex_unlock(&connected_mutex);
|
||||
|
||||
telnet_cb_connected(client);
|
||||
}
|
||||
|
||||
static void _disconnect(void)
|
||||
{
|
||||
mutex_trylock(&connected_mutex);
|
||||
connected = false;
|
||||
|
||||
telnet_cb_disconneced();
|
||||
}
|
||||
|
||||
static int _write_buffer(const void* buffer, size_t len)
|
||||
{
|
||||
int res = 0;
|
||||
const char *buf = buffer;
|
||||
_acquire();
|
||||
|
||||
while (len) {
|
||||
/* telnet expects \r\n line endings */
|
||||
/* https://datatracker.ietf.org/doc/html/rfc5198#appendix-C */
|
||||
const char *nl = memchr(buf, '\n', len);
|
||||
if (nl) {
|
||||
const char cr = '\r';
|
||||
size_t before_nl = nl - buf;
|
||||
|
||||
/* write string before \n */
|
||||
res = sock_tcp_write(client, buf, before_nl);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* insert \r */
|
||||
res = sock_tcp_write(client, &cr, 1);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf = nl;
|
||||
len -= before_nl;
|
||||
}
|
||||
|
||||
res = sock_tcp_write(client, buf, len);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
len -= res;
|
||||
buf += res;
|
||||
}
|
||||
_release();
|
||||
|
||||
return res < 0 ? res : 0;
|
||||
}
|
||||
|
||||
static uint8_t _will(uint8_t option)
|
||||
{
|
||||
switch (option) {
|
||||
/* agree to suppress go-ahead packets */
|
||||
/* see RFC 858 */
|
||||
case TELNET_OPT_SUP_GO_AHEAD: return TELNET_CMD_DO;
|
||||
}
|
||||
|
||||
return TELNET_CMD_WONT;
|
||||
}
|
||||
|
||||
static void _process_cmd(uint8_t cmd, uint8_t option)
|
||||
{
|
||||
DEBUG("cmd: %u, option: %u\n", cmd, option);
|
||||
switch (cmd) {
|
||||
case TELNET_CMD_WILL:
|
||||
{
|
||||
uint8_t reply[] = {TELNET_CMD_IAC, _will(option), option};
|
||||
_write_buffer(reply, sizeof(reply));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *telnet_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
static uint8_t rx_buf[64];
|
||||
|
||||
while (1) {
|
||||
ssize_t res = sock_tcp_accept(&sock_queue, &client, SOCK_NO_TIMEOUT);
|
||||
if (res < 0) {
|
||||
DEBUG("accept error: %s\n", strerror(-res));
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG("connected\n");
|
||||
_connected();
|
||||
|
||||
bool is_cmd = false;
|
||||
uint8_t is_option = 0;
|
||||
while (1) {
|
||||
_acquire();
|
||||
res = sock_tcp_read(client, rx_buf, sizeof(rx_buf), SOCK_TCP_TIMEOUT_MS);
|
||||
_release();
|
||||
if (res == -ETIMEDOUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < res; ++i) {
|
||||
uint8_t c = rx_buf[i];
|
||||
|
||||
if (is_cmd) {
|
||||
is_cmd = false;
|
||||
switch (c) {
|
||||
case TELNET_CMD_IAC:
|
||||
goto write;
|
||||
case TELNET_CMD_EOF:
|
||||
goto disco;
|
||||
default:
|
||||
is_option = c;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_option) {
|
||||
_process_cmd(is_option, c);
|
||||
is_option = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == TELNET_CMD_IAC) {
|
||||
is_cmd = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == 0) {
|
||||
continue;
|
||||
}
|
||||
write:
|
||||
pipe_write(&_stdin_pipe, &c, 1);
|
||||
}
|
||||
}
|
||||
disco:
|
||||
_disconnect();
|
||||
sock_tcp_disconnect(client);
|
||||
|
||||
if (res < 0) {
|
||||
DEBUG("telnet: read: %s\n", strerror(res));
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int telnet_server_write(const void* buffer, size_t len)
|
||||
{
|
||||
if (connected) {
|
||||
int res = _write_buffer(buffer, len);
|
||||
return res ? res : (int)len;
|
||||
}
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
int telnet_server_read(void* buffer, size_t count)
|
||||
{
|
||||
/* block until a connection is established */
|
||||
mutex_lock(&connected_mutex);
|
||||
int res = pipe_read(&_stdin_pipe, buffer, count);
|
||||
if (connected) {
|
||||
mutex_unlock(&connected_mutex);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int telnet_server_start(void)
|
||||
{
|
||||
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
|
||||
ep.port = CONFIG_TELNET_PORT;
|
||||
|
||||
int res = sock_tcp_listen(&sock_queue, &ep, socks, ARRAY_SIZE(socks), 0);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* init RX ringbuffer */
|
||||
ringbuffer_init(&_stdin_ringbuffer, _stdin_pipe_buf, sizeof(_stdin_pipe_buf));
|
||||
pipe_init(&_stdin_pipe, &_stdin_ringbuffer, NULL);
|
||||
|
||||
/* initiate telnet server */
|
||||
thread_create(telnet_stack, sizeof(telnet_stack),
|
||||
THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST,
|
||||
telnet_thread, NULL, "telnet");
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user