1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 06:23:53 +01:00

Merge pull request #16723 from benpicco/gnrc_sock_tcp-telnet

sys/net/application_layer: add telnet server module & example
This commit is contained in:
Marian Buschsieweke 2022-03-02 07:28:48 +01:00 committed by GitHub
commit fbe73b7c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 747 additions and 0 deletions

View File

@ -0,0 +1,51 @@
# name of your application
APPLICATION = telnet_server
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Include packages that pull up and auto-init the link layer.
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
USEMODULE += netdev_default
USEMODULE += auto_init_gnrc_netif
# Activate ICMPv6 error messages
USEMODULE += gnrc_icmpv6_error
# Specify the mandatory networking modules for IPv6
USEMODULE += gnrc_ipv6_default
# Additional networking modules that can be dropped if not needed
USEMODULE += gnrc_icmpv6_echo
USEMODULE += netutils
# Add also the shell, some shell commands
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
# Include the telnet server
USEMODULE += stdio_telnet
# Enable faster re-connects
CFLAGS += -DCONFIG_GNRC_TCP_EXPERIMENTAL_DYN_MSL_EN=1
# Acknowledge that telnet is bad
I_UNDERSTAND_THAT_TELNET_IS_INSECURE = 1
# enable debug output via UART
FEATURES_OPTIONAL += periph_uart
# Optionally include DNS support. This includes resolution of names at an
# upstream DNS server and the handling of RDNSS options in Router Advertisements
# to auto-configure that upstream DNS server.
# USEMODULE += sock_dns # include DNS client
# USEMODULE += gnrc_ipv6_nib_dns # include RDNSS option handling
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
include $(RIOTBASE)/Makefile.include
# Set a custom channel if needed
include $(RIOTMAKE)/default-radio-settings.inc.mk

View File

@ -0,0 +1,37 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-mega2560 \
arduino-nano \
arduino-uno \
atmega1284p \
atmega328p \
atmega328p-xplained-mini \
atxmega-a3bu-xplained \
bluepill-stm32f030c8 \
derfmega128 \
i-nucleo-lrwan1 \
mega-xplained \
microduino-corerf \
msb-430 \
msb-430h \
nucleo-f030r8 \
nucleo-f031k6 \
nucleo-f042k6 \
nucleo-f303k8 \
nucleo-f334r8 \
nucleo-l011k4 \
nucleo-l031k6 \
nucleo-l053r8 \
samd10-xmini \
stk3200 \
slstk3400a \
stm32f030f4-demo \
stm32f0discovery \
stm32g0316-disco \
stm32l0538-disco \
telosb \
waspmote-pro \
z1 \
zigduino \
#

View File

@ -0,0 +1,104 @@
/*
* 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 examples
* @{
*
* @file
* @brief Example application for demonstrating the RIOT telnet server
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
#include <stdio.h>
#include "net/ipv6/addr.h"
#include "net/gnrc.h"
#include "net/gnrc/netif.h"
#include "net/telnet.h"
#include "shell.h"
#include "msg.h"
#define MAIN_QUEUE_SIZE (8)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
static void _print_addr(void)
{
gnrc_netif_t *netif = NULL;
while ((netif = gnrc_netif_iter(netif))) {
ipv6_addr_t ipv6_addrs[CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF];
int res = gnrc_netapi_get(netif->pid, NETOPT_IPV6_ADDR, 0, ipv6_addrs,
sizeof(ipv6_addrs));
if (res < 0) {
continue;
}
for (unsigned i = 0; i < (unsigned)(res / sizeof(ipv6_addr_t)); i++) {
char ipv6_addr[IPV6_ADDR_MAX_STR_LEN];
ipv6_addr_to_str(ipv6_addr, &ipv6_addrs[i], IPV6_ADDR_MAX_STR_LEN);
printf("My address is %s\n", ipv6_addr);
}
}
}
static void _print_motd(void)
{
puts("RIOT telnet example application");
puts("╔═══════════════════════════════════════════════════╗");
puts("║telnet is entirely unencrypted and unauthenticated.║");
puts("║Do not use this on public networks. ║");
puts("╚═══════════════════════════════════════════════════╝");
}
void telnet_cb_pre_connected(sock_tcp_t *sock)
{
sock_tcp_ep_t ep;
char addr_str[IPV6_ADDR_MAX_STR_LEN];
sock_tcp_get_local(sock, &ep);
ipv6_addr_to_str(addr_str, (ipv6_addr_t *)ep.addr.ipv6, sizeof(addr_str));
printf("%s connected\n", addr_str);
}
void telnet_cb_disconneced(void)
{
puts("disconnected");
}
void telnet_cb_connected(sock_tcp_t *sock)
{
(void)sock;
_print_motd();
}
int main(void)
{
/* we need a message queue for the thread running the shell in order to
* receive potentially fast incoming networking packets */
msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
_print_motd();
/* print address so we can connect to it */
_print_addr();
/* start shell */
printf("All up, awaiting connection on port %u\n", CONFIG_TELNET_PORT);
printf("Local shell disabled");
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE);
/* should be never reached */
return 0;
}

View File

@ -77,4 +77,17 @@ else
$(shell $(COLOR_ECHO) "$(COLOR_RED)Deprecated modules are in use:$(COLOR_RESET)"\
"$(DEPRECATED_MODULES_USED)" 1>&2)
endif
# Warn about telnet
ifneq (,$(filter auto_init_telnet,$(USEMODULE)))
ifneq (1,$(I_UNDERSTAND_THAT_TELNET_IS_INSECURE))
$(shell $(COLOR_ECHO) "$(COLOR_RED)Telnet will be started automatically, "\
"make sure you understand why this almost certainly "\
"is a REALLY BAD idea before proceeding!$(COLOR_RESET)" 1>&2)
$(error I_UNDERSTAND_THAT_TELNET_IS_INSECURE must be set to 1 to proceed)
else
$(shell $(COLOR_ECHO) "$(COLOR_YELLOW)Telnet will be started automatically,"\
"don't run this on public networks!$(COLOR_RESET)" 1>&2)
endif
endif
endif

View File

@ -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

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -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
View 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 */
/** @} */

View File

@ -21,3 +21,9 @@ rsource "asymcute/Kconfig"
rsource "emcute/Kconfig"
endmenu # MQTT-SN
menu "Telnet"
rsource "telnet/Kconfig"
endmenu # Telnet

View 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

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View 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;
}

View 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;
}

View File

@ -27,6 +27,8 @@
#include "mbox.h"
#include "net/af.h"
#include "net/gnrc.h"
#include "net/gnrc/tcp.h"
#include "net/gnrc/netreg.h"
#ifdef SOCK_HAS_ASYNC
#include "net/sock/async/types.h"