shell: Add TELNET backend

Add TELNET backed for shell module. The TELNET implementation is based
on the telnet_console driver.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2019-03-28 11:54:14 +01:00 committed by Anas Nashif
commit 658a08a4cb
5 changed files with 791 additions and 0 deletions

View file

@ -23,6 +23,11 @@ zephyr_sources_ifdef(
shell_rtt.c
)
zephyr_sources_ifdef(
CONFIG_SHELL_BACKEND_TELNET
shell_telnet.c
)
zephyr_sources_ifdef(
CONFIG_SHELL_HELP
shell_help.c

View file

@ -167,6 +167,94 @@ source "subsys/logging/Kconfig.template.log_config"
endif #SHELL_BACKEND_RTT
config SHELL_BACKEND_TELNET
bool "Enable TELNET backend."
depends on NET_TCP
depends on NET_IPV4 || NET_IPV6
help
Enable TELNET backend.
if SHELL_BACKEND_TELNET
config SHELL_TELNET_PORT
int "Telnet port number"
default 23
help
This option is used to configure on which port telnet is going
to be bound.
config SHELL_TELNET_LINE_BUF_SIZE
int "Telnet line buffer size"
default 80
help
This option can be used to modify the size of the buffer storing
shell output line, prior to sending it through the network.
Of course an output line can be longer than such size, it just
means sending it will start as soon as it reaches this size.
It really depends on what type of output is expected.
A lot of short lines: better reduce this value. On the contrary,
raise it.
config SHELL_TELNET_SEND_TIMEOUT
int "Telnet line send timeout"
default 100
help
This option can be used to modify the duration of the timer that kick
in when a line buffer is not empty but did not yet meet the line feed.
config SHELL_TELNET_SUPPORT_COMMAND
bool "Add support for telnet commands (IAC) [Experimental]"
help
Current support is so limited it's not interesting to enable it.
However, if proven to be needed at some point, it will be possible
to extend such support.
module = SHELL_TELNET
default-timeout = 100
source "subsys/shell/Kconfig.template.shell_log_queue_timeout"
default-size = 10
source "subsys/shell/Kconfig.template.shell_log_queue_size"
choice
prompt "Initial log level limit"
default SHELL_TELNET_INIT_LOG_LEVEL_DEFAULT
config SHELL_TELNET_INIT_LOG_LEVEL_DEFAULT
bool "System limit (LOG_MAX_LEVEL)"
config SHELL_TELNET_INIT_LOG_LEVEL_DBG
bool "Debug"
config SHELL_TELNET_INIT_LOG_LEVEL_INF
bool "Info"
config SHELL_TELNET_INIT_LOG_LEVEL_WRN
bool "Warning"
config SHELL_TELNET_INIT_LOG_LEVEL_ERR
bool "Error"
config SHELL_TELNET_INIT_LOG_LEVEL_NONE
bool "None"
endchoice
config SHELL_TELNET_INIT_LOG_LEVEL
int
default 0 if SHELL_TELNET_INIT_LOG_LEVEL_NONE
default 1 if SHELL_TELNET_INIT_LOG_LEVEL_ERR
default 2 if SHELL_TELNET_INIT_LOG_LEVEL_WRN
default 3 if SHELL_TELNET_INIT_LOG_LEVEL_INF
default 4 if SHELL_TELNET_INIT_LOG_LEVEL_DBG
default 5 if SHELL_TELNET_INIT_LOG_LEVEL_DEFAULT
module = SHELL_TELNET
module-str = TELNET shell backend
source "subsys/logging/Kconfig.template.log_config"
endif # SHELL_TELNET_BACKEND
config SHELL_BACKEND_DUMMY
bool "Enable dummy backend."
help

498
subsys/shell/shell_telnet.c Normal file
View file

@ -0,0 +1,498 @@
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2019 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <init.h>
#include <logging/log.h>
#include <net/net_context.h>
#include <net/net_ip.h>
#include <net/net_pkt.h>
#include <shell/shell_telnet.h>
#include "shell_telnet_protocol.h"
SHELL_TELNET_DEFINE(shell_transport_telnet);
SHELL_DEFINE(shell_telnet, "~$ ", &shell_transport_telnet,
CONFIG_SHELL_TELNET_LOG_MESSAGE_QUEUE_SIZE,
CONFIG_SHELL_TELNET_LOG_MESSAGE_QUEUE_TIMEOUT,
SHELL_FLAG_OLF_CRLF);
LOG_MODULE_REGISTER(shell_telnet, CONFIG_SHELL_TELNET_LOG_LEVEL);
struct shell_telnet *sh_telnet;
/* Various definitions mapping the TELNET service configuration options */
#define TELNET_PORT CONFIG_SHELL_TELNET_PORT
#define TELNET_LINE_SIZE CONFIG_SHELL_TELNET_LINE_BUF_SIZE
#define TELNET_TIMEOUT K_MSEC(CONFIG_SHELL_TELNET_SEND_TIMEOUT)
#define TELNET_MIN_MSG 2
/* Basic TELNET implmentation. */
static void telnet_end_client_connection(void)
{
struct net_pkt *pkt;
net_context_put(sh_telnet->client_ctx);
sh_telnet->client_ctx = NULL;
sh_telnet->output_lock = false;
k_timer_stop(&sh_telnet->send_timer);
/* Flush the RX FIFO */
while ((pkt = k_fifo_get(&sh_telnet->rx_fifo, K_NO_WAIT)) != NULL) {
net_pkt_unref(pkt);
}
}
static void telnet_sent_cb(struct net_context *client,
int status, void *user_data)
{
if (status < 0) {
telnet_end_client_connection();
LOG_ERR("Could not send packet %d", status);
}
}
static void telnet_command_send_reply(u8_t *msg, u16_t len)
{
int err;
if (sh_telnet->client_ctx == NULL) {
return;
}
err = net_context_send(sh_telnet->client_ctx, msg, len, telnet_sent_cb,
K_FOREVER, NULL);
if (err < 0) {
LOG_ERR("Failed to send command %d, shutting down", err);
telnet_end_client_connection();
}
}
static void telnet_reply_ay_command(void)
{
static const char alive[] = "Zephyr at your service\r\n";
telnet_command_send_reply((u8_t *)alive, strlen(alive));
}
static void telnet_reply_do_command(struct telnet_simple_command *cmd)
{
switch (cmd->opt) {
case NVT_OPT_SUPR_GA:
cmd->op = NVT_CMD_WILL;
break;
default:
cmd->op = NVT_CMD_WONT;
break;
}
telnet_command_send_reply((u8_t *)cmd,
sizeof(struct telnet_simple_command));
}
static void telnet_reply_command(struct telnet_simple_command *cmd)
{
if (!cmd->iac) {
return;
}
switch (cmd->op) {
case NVT_CMD_AO:
/* OK, no output then */
sh_telnet->output_lock = true;
sh_telnet->line_out.len = 0;
k_timer_stop(&sh_telnet->send_timer);
break;
case NVT_CMD_AYT:
telnet_reply_ay_command();
break;
case NVT_CMD_DO:
telnet_reply_do_command(cmd);
break;
default:
LOG_DBG("Operation %u not handled", cmd->op);
break;
}
}
static int telnet_send(void)
{
int err;
if (sh_telnet->line_out.len == 0) {
return 0;
}
if (sh_telnet->client_ctx == NULL) {
return -ENOTCONN;
}
err = net_context_send(sh_telnet->client_ctx, sh_telnet->line_out.buf,
sh_telnet->line_out.len, telnet_sent_cb,
K_FOREVER, NULL);
if (err < 0) {
LOG_ERR("Failed to send %d, shutting down", err);
telnet_end_client_connection();
return err;
}
/* We reinitialize the line buffer */
sh_telnet->line_out.len = 0;
return 0;
}
static void telnet_send_prematurely(struct k_timer *timer)
{
(void)telnet_send();
}
static inline bool telnet_handle_command(struct net_pkt *pkt)
{
/* Commands are two or three bytes. */
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(cmd_access, u16_t);
struct telnet_simple_command *cmd;
cmd = (struct telnet_simple_command *)net_pkt_get_data(pkt,
&cmd_access);
if (!cmd || cmd->iac != NVT_CMD_IAC) {
return false;
}
if (IS_ENABLED(CONFIG_SHELL_TELNET_SUPPORT_COMMAND)) {
LOG_DBG("Got a command %u/%u/%u", cmd->iac, cmd->op, cmd->opt);
telnet_reply_command(cmd);
}
return true;
}
static void telnet_recv(struct net_context *client,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
union net_proto_header *proto_hdr,
int status,
void *user_data)
{
size_t len;
if (!pkt || status) {
telnet_end_client_connection();
LOG_DBG("Telnet client dropped (AF_INET%s) status %d",
net_context_get_family(client) == AF_INET ?
"" : "6", status);
return;
}
len = net_pkt_remaining_data(pkt);
if (len < TELNET_MIN_MSG) {
LOG_DBG("Packet smaller than minimum length");
goto unref;
}
if (telnet_handle_command(pkt)) {
LOG_DBG("Handled command");
goto unref;
}
/* Fifo add */
k_fifo_put(&sh_telnet->rx_fifo, pkt);
sh_telnet->shell_handler(SHELL_TRANSPORT_EVT_RX_RDY,
sh_telnet->shell_context);
return;
unref:
net_pkt_unref(pkt);
}
static void telnet_accept(struct net_context *client,
struct sockaddr *addr,
socklen_t addrlen,
int error,
void *user_data)
{
if (error) {
LOG_ERR("Error %d", error);
goto error;
}
if (sh_telnet->client_ctx) {
LOG_INF("A telnet client is already in.");
goto error;
}
if (net_context_recv(client, telnet_recv, 0, NULL)) {
LOG_ERR("Unable to setup reception (family %u)",
net_context_get_family(client));
goto error;
}
LOG_DBG("Telnet client connected (family AF_INET%s)",
net_context_get_family(client) == AF_INET ? "" : "6");
sh_telnet->client_ctx = client;
return;
error:
net_context_put(client);
}
static void telnet_setup_server(struct net_context **ctx, sa_family_t family,
struct sockaddr *addr, socklen_t addrlen)
{
if (net_context_get(family, SOCK_STREAM, IPPROTO_TCP, ctx)) {
LOG_ERR("No context available");
goto error;
}
if (net_context_bind(*ctx, addr, addrlen)) {
LOG_ERR("Cannot bind on family AF_INET%s",
family == AF_INET ? "" : "6");
goto error;
}
if (net_context_listen(*ctx, 0)) {
LOG_ERR("Cannot listen on");
goto error;
}
if (net_context_accept(*ctx, telnet_accept, K_NO_WAIT, NULL)) {
LOG_ERR("Cannot accept");
goto error;
}
LOG_DBG("Telnet console enabled on AF_INET%s",
family == AF_INET ? "" : "6");
return;
error:
LOG_ERR("Unable to start telnet on AF_INET%s",
family == AF_INET ? "" : "6");
if (*ctx) {
net_context_put(*ctx);
*ctx = NULL;
}
}
static int telnet_init(void)
{
if (IS_ENABLED(CONFIG_NET_IPV4)) {
struct sockaddr_in any_addr4 = {
.sin_family = AF_INET,
.sin_port = htons(TELNET_PORT),
.sin_addr = INADDR_ANY_INIT
};
static struct net_context *ctx4;
telnet_setup_server(&ctx4, AF_INET,
(struct sockaddr *)&any_addr4,
sizeof(any_addr4));
}
if (IS_ENABLED(CONFIG_NET_IPV6)) {
struct sockaddr_in6 any_addr6 = {
.sin6_family = AF_INET6,
.sin6_port = htons(TELNET_PORT),
.sin6_addr = IN6ADDR_ANY_INIT
};
static struct net_context *ctx6;
telnet_setup_server(&ctx6, AF_INET6,
(struct sockaddr *)&any_addr6,
sizeof(any_addr6));
}
LOG_INF("Telnet shell backend initialized");
return 0;
}
/* Shell API */
static int init(const struct shell_transport *transport,
const void *config,
shell_transport_handler_t evt_handler,
void *context)
{
int err;
sh_telnet = (struct shell_telnet *)transport->ctx;
err = telnet_init();
if (err != 0) {
return err;
}
memset(sh_telnet, 0, sizeof(struct shell_telnet));
sh_telnet->shell_handler = evt_handler;
sh_telnet->shell_context = context;
k_fifo_init(&sh_telnet->rx_fifo);
k_timer_init(&sh_telnet->send_timer, telnet_send_prematurely, NULL);
return 0;
}
static int uninit(const struct shell_transport *transport)
{
if (sh_telnet == NULL) {
return -ENODEV;
}
return 0;
}
static int enable(const struct shell_transport *transport, bool blocking)
{
if (sh_telnet == NULL) {
return -ENODEV;
}
return 0;
}
static int write(const struct shell_transport *transport,
const void *data, size_t length, size_t *cnt)
{
struct shell_telnet_line_buf *lb;
size_t copy_len;
int err;
u32_t timeout;
if (sh_telnet == NULL) {
*cnt = 0;
return -ENODEV;
}
if (sh_telnet->client_ctx == NULL || sh_telnet->output_lock) {
*cnt = length;
return 0;
}
*cnt = 0;
lb = &sh_telnet->line_out;
/* Stop the transmission timer, so it does not interrupt the operation.
*/
timeout = k_timer_remaining_get(&sh_telnet->send_timer);
k_timer_stop(&sh_telnet->send_timer);
do {
if (lb->len + length - *cnt > TELNET_LINE_SIZE) {
copy_len = TELNET_LINE_SIZE - lb->len;
} else {
copy_len = length - *cnt;
}
memcpy(lb->buf + lb->len, (u8_t *)data + *cnt, copy_len);
lb->len += copy_len;
/* Send the data immediately if the buffer is full or line feed
* is recognized.
*/
if (lb->buf[lb->len - 1] == '\n' ||
lb->len == TELNET_LINE_SIZE) {
err = telnet_send();
if (err != 0) {
*cnt = length;
return err;
}
}
*cnt += copy_len;
} while (*cnt < length);
if (lb->len > 0) {
/* Check if the timer was already running, initialize otherwise.
*/
timeout = (timeout == 0) ? TELNET_TIMEOUT : timeout;
k_timer_start(&sh_telnet->send_timer, timeout, 0);
}
sh_telnet->shell_handler(SHELL_TRANSPORT_EVT_TX_RDY,
sh_telnet->shell_context);
return 0;
}
static int read(const struct shell_transport *transport,
void *data, size_t length, size_t *cnt)
{
struct net_pkt *pkt;
size_t read_len;
bool flush = true;
if (sh_telnet == NULL) {
return -ENODEV;
}
if (sh_telnet->client_ctx == NULL) {
goto no_data;
}
pkt = k_fifo_peek_head(&sh_telnet->rx_fifo);
if (pkt == NULL) {
goto no_data;
}
read_len = net_pkt_remaining_data(pkt);
if (read_len > length) {
read_len = length;
flush = false;
}
*cnt = read_len;
net_pkt_read(pkt, data, read_len);
if (flush) {
(void)k_fifo_get(&sh_telnet->rx_fifo, K_NO_WAIT);
net_pkt_unref(pkt);
}
return 0;
no_data:
*cnt = 0;
return 0;
}
const struct shell_transport_api shell_telnet_transport_api = {
.init = init,
.uninit = uninit,
.enable = enable,
.write = write,
.read = read
};
static int enable_shell_telnet(struct device *arg)
{
ARG_UNUSED(arg);
bool log_backend = CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > 0;
u32_t level = (CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > LOG_LEVEL_DBG) ?
CONFIG_LOG_MAX_LEVEL : CONFIG_SHELL_TELNET_INIT_LOG_LEVEL;
shell_init(&shell_telnet, NULL, true, log_backend, level);
return 0;
}
SYS_INIT(enable_shell_telnet, APPLICATION, 0);
const struct shell *shell_backend_telnet_get_ptr(void)
{
return &shell_telnet;
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Telnet console protocol specific defines
*
*
* This defines the Telnet codes, all prefixed as NVT_
* (NVT: Network Virtual Terminal, see rfc854)
*/
#ifndef SHELL_TELNET_PROTOCOL_H__
#define SHELL_TELNET_PROTOCOL_H__
/** Printer/Keyboard codes */
/* Mandatory ones */
#define NVT_NUL 0
#define NVT_LF 10
#define NVT_CR 13
/* Optional ones */
#define NVT_BEL 7
#define NVT_BS 8
#define NVT_HT 9
#define NVT_VT 11
#define NVT_FF 12
/* Telnet commands */
#define NVT_CMD_SE 240
#define NVT_CMD_NOP 241
#define NVT_CMD_DM 242
#define NVT_CMD_BRK 243
#define NVT_CMD_IP 244
#define NVT_CMD_AO 245
#define NVT_CMD_AYT 246
#define NVT_CMD_EC 247
#define NVT_CMD_EL 248
#define NVT_CMD_GA 249
#define NVT_CMD_SB 250
#define NVT_CMD_WILL 251
#define NVT_CMD_WONT 252
#define NVT_CMD_DO 253
#define NVT_CMD_DONT 254
#define NVT_CMD_IAC 255
/* Telnet options */
#define NVT_OPT_TX_BIN 0
#define NVT_OPT_ECHO 1
#define NVT_OPT_RECONNECT 2
#define NVT_OPT_SUPR_GA 3
#define NVT_OPT_MSG_SZ_NEG 4
#define NVT_OPT_STATUS 5
#define NVT_OPT_TIMING_MARK 6
#define NVT_OPT_REMOTE_CTRL_TRANS_ECHO 7
#define NVT_OPT_OUT_LINE_WIDTH 8
#define NVT_OPT_OUT_PAGE_SZ 9
#define NVT_OPT_NEG_CR 10
#define NVT_OPT_NEG_HT 11
#define NVT_OPT_NAOHTD 12
#define NVT_OPT_NEG_OUT_FF 13
#define NVT_OPT_NEG_VT 14
#define NVT_OPT_NEG_OUT_VT 15
#define NVT_OPT_NET_OUT_LF 16
#define NVT_OPT_EXT_ASCII 17
#define NVT_OPT_LOGOUT 18
#define NVT_OPT_BYTE_MACRO 19
#define NVT_OPT_DATA_ENTRY 20
#define NVT_OPT_SUPDUP 21
#define NVT_OPT_SUPDUP_OUT 22
#define NVT_OPT_SEND_LOC 23
#define NVT_OPT_TERM_TYPE 24
#define NVT_OPT_EOR 25
#define NVT_OPT_TACACS_UID 26
#define NVT_OPT_OUT_MARK 27
#define NVT_OPT_TTYLOC 28
#define NVT_OPT_3270 29
#define NVT_OPT_X_3_PAD 30
#define NVT_OPT_NAWS 31
#define NVT_OPT_TERM_SPEED 32
#define NVT_OPT_REMOTE_FC 33
#define NVT_OPT_LINEMODE 34
#define NVT_OPT_X_LOC 35
#define NVT_OPT_ENV 36
#define NVT_OPT_AUTH 37
#define NVT_OPT_ENCRYPT_OPT 38
#define NVT_OPT_NEW_ENV 39
#define NVT_OPT_TN3270E 40
#define NVT_OPT_XAUTH 41
#define NVT_OPT_CHARSET 42
#define NVT_OPT_RSP 43
#define NVT_OPT_COM_PORT_CTRL 44
#define NVT_OPT_SUPR_LOCAL_ECHO 45
#define NVT_OPT_START_TLS 46
#define NVT_OPT_KERMIT 47
#define NVT_OPT_SEND_URL 48
#define NVT_OPT_FORWARD_X 49
#define NVT_OPT_PRAGMA_LOGON 138
#define NVT_OPT_SSPI_LOGON 139
#define NVT_OPT_PRAGMA_HB 140
#define NVT_OPT_EXT_OPT_LIST 255
/** Describes a telnet command */
struct telnet_simple_command {
/** Mandatory IAC code */
u8_t iac;
/** Type of operation (see Telnet commands above) */
u8_t op;
/** Option code */
u8_t opt;
};
static inline void telnet_command_cpy(struct telnet_simple_command *dst,
struct telnet_simple_command *src)
{
dst->iac = src->iac;
dst->op = src->op;
dst->opt = src->opt;
}
#endif /* SHELL_TELNET_PROTOCOL_H__ */