blob: 1d161f9fe9ae2169dd3a936af78040707a2a18f6 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2019 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/shell/shell_telnet.h>
#include "shell_telnet_protocol.h"
SHELL_TELNET_DEFINE(shell_transport_telnet);
SHELL_DEFINE(shell_telnet, CONFIG_SHELL_PROMPT_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 CONFIG_SHELL_TELNET_SEND_TIMEOUT
#define TELNET_MIN_COMMAND_LEN 2
#define TELNET_WILL_DO_COMMAND_LEN 3
/* Basic TELNET implementation. */
static void telnet_end_client_connection(void)
{
struct net_pkt *pkt;
(void)net_context_put(sh_telnet->client_ctx);
sh_telnet->client_ctx = NULL;
sh_telnet->output_lock = false;
k_work_cancel_delayable_sync(&sh_telnet->send_work,
&sh_telnet->work_sync);
/* 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(uint8_t *msg, uint16_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((uint8_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((uint8_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_work_cancel_delayable_sync(&sh_telnet->send_work,
&sh_telnet->work_sync);
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_work *work)
{
(void)telnet_send();
}
static inline int telnet_handle_command(struct net_pkt *pkt)
{
/* Commands are two or three bytes. */
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(cmd_access, uint16_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 0;
}
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);
}
if (cmd->op == NVT_CMD_SB) {
/* TODO Add subnegotiation support. */
return -EOPNOTSUPP;
}
if (cmd->op == NVT_CMD_WILL || cmd->op == NVT_CMD_WONT ||
cmd->op == NVT_CMD_DO || cmd->op == NVT_CMD_DONT) {
return TELNET_WILL_DO_COMMAND_LEN;
}
return TELNET_MIN_COMMAND_LEN;
}
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;
int ret;
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);
(void)net_context_update_recv_wnd(client, len);
while (len >= TELNET_MIN_COMMAND_LEN) {
ret = telnet_handle_command(pkt);
if (ret > 0) {
LOG_DBG("Handled command");
ret = net_pkt_skip(pkt, ret);
if (ret < 0) {
goto unref;
}
} else if (ret < 0) {
goto unref;
} else {
break;
}
len = net_pkt_remaining_data(pkt);
}
if (len == 0) {
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)
{
int ret;
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, K_NO_WAIT, NULL)) {
LOG_ERR("Unable to setup reception (family %u)",
net_context_get_family(client));
goto error;
}
net_context_set_accepting(client, false);
LOG_DBG("Telnet client connected (family AF_INET%s)",
net_context_get_family(client) == AF_INET ? "" : "6");
sh_telnet->client_ctx = client;
/* Disable echo - if command handling is enabled we reply that we don't
* support echo.
*/
ret = shell_echo_set(sh_telnet->shell_context, false);
if (ret < 0) {
LOG_ERR("Failed to disable echo, err: %d", ret);
}
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) {
(void)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_work_init_delayable(&sh_telnet->send_work, telnet_send_prematurely);
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;
uint32_t timeout;
bool was_running;
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_ticks_to_ms_ceil32(
k_work_delayable_remaining_get(&sh_telnet->send_work));
was_running = k_work_cancel_delayable_sync(&sh_telnet->send_work,
&sh_telnet->work_sync);
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, (uint8_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 = was_running ? timeout : TELNET_TIMEOUT;
k_work_reschedule(&sh_telnet->send_work, K_MSEC(timeout));
}
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;
if (net_pkt_read(pkt, data, read_len) < 0) {
/* Failed to read, get rid of the faulty packet. */
LOG_ERR("Failed to read net packet.");
*cnt = 0;
flush = true;
}
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(const struct device *arg)
{
ARG_UNUSED(arg);
bool log_backend = CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > 0;
uint32_t level = (CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > LOG_LEVEL_DBG) ?
CONFIG_LOG_MAX_LEVEL : CONFIG_SHELL_TELNET_INIT_LOG_LEVEL;
static const struct shell_backend_config_flags cfg_flags =
SHELL_DEFAULT_BACKEND_CONFIG_FLAGS;
return shell_init(&shell_telnet, NULL, cfg_flags, log_backend, level);
}
SYS_INIT(enable_shell_telnet, APPLICATION, 0);
const struct shell *shell_backend_telnet_get_ptr(void)
{
return &shell_telnet;
}