blob: f5a800600e6590049c7caf48a6ff7c2edee07c2a [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Telnet console
*
*
* Telnet console driver.
* Hooks into the printk and fputc (for printf) modules.
*
* Telnet has been standardized in 1983
* RFC 854 - https://tools.ietf.org/html/rfc854
*/
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_TELNET_CONSOLE_LEVEL
#define SYS_LOG_DOMAIN "net/telnet"
#include <logging/sys_log.h>
#include <zephyr.h>
#include <init.h>
#include <misc/printk.h>
#include <console/console.h>
#include <net/buf.h>
#include <net/net_pkt.h>
#include <net/net_ip.h>
#include <net/net_context.h>
#include "telnet_protocol.h"
/* Various definitions mapping the telnet service configuration options */
#define TELNET_PORT CONFIG_TELNET_CONSOLE_PORT
#define TELNET_STACK_SIZE CONFIG_TELNET_CONSOLE_THREAD_STACK
#define TELNET_PRIORITY CONFIG_TELNET_CONSOLE_PRIO
#define TELNET_LINES CONFIG_TELNET_CONSOLE_LINE_BUF_NUMBERS
#define TELNET_LINE_SIZE CONFIG_TELNET_CONSOLE_LINE_BUF_SIZE
#define TELNET_TIMEOUT K_MSEC(CONFIG_TELNET_CONSOLE_SEND_TIMEOUT)
#define TELNET_THRESHOLD CONFIG_TELNET_CONSOLE_SEND_THRESHOLD
#define TELNET_MIN_MSG 2
/* These 2 structures below are used to store the console output
* before sending it to the client. This is done to keep some
* reactivity: the ring buffer is non-protected, if first line has
* not been sent yet, and if next line is reaching the same index in rb,
* the first one will be replaced. In a perfect world, this should
* not happen. However on a loaded system with a lot of debug output
* this is bound to happen eventualy, moreover if it does not have
* the luxury to bufferize as much as it wants to. Just raise
* CONFIG_TELNET_CONSOLE_LINE_BUF_NUMBERS if possible.
*/
struct line_buf {
char buf[TELNET_LINE_SIZE];
u16_t len;
};
struct line_buf_rb {
struct line_buf l_bufs[TELNET_LINES];
u16_t line_in;
u16_t line_out;
};
static struct line_buf_rb telnet_rb;
static K_THREAD_STACK_DEFINE(telnet_stack, TELNET_STACK_SIZE);
static struct k_thread telnet_thread_data;
static K_SEM_DEFINE(send_lock, 0, UINT_MAX);
/* The timer is used to send non-lf terminated output that has
* been around for "tool long". This will prove to be useful
* to send the shell prompt for instance.
* ToDo: raise the time, incrementaly, when no output is coming
* so the timer will kick in less and less.
*/
static void telnet_send_prematurely(struct k_timer *timer);
static K_TIMER_DEFINE(send_timer, telnet_send_prematurely, NULL);
/* For now we handle a unique telnet client connection */
static struct net_context *client_cnx;
static struct net_pkt *out_pkt;
static int (*orig_printk_hook)(int);
static struct k_fifo *avail_queue;
static struct k_fifo *input_queue;
#ifdef CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND
static K_SEM_DEFINE(cmd_lock, 1, 1);
static struct telnet_simple_command telnet_cmd;
#endif /* CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND */
extern void __printk_hook_install(int (*fn)(int));
extern void *__printk_get_hook(void);
static void telnet_rb_init(void)
{
int i;
telnet_rb.line_in = 0;
telnet_rb.line_out = 0;
for (i = 0; i < TELNET_LINES; i++) {
telnet_rb.l_bufs[i].len = 0;
}
}
static void telnet_end_client_connection(void)
{
__printk_hook_install(orig_printk_hook);
orig_printk_hook = NULL;
k_timer_stop(&send_timer);
net_context_put(client_cnx);
client_cnx = NULL;
if (out_pkt) {
net_pkt_unref(out_pkt);
}
telnet_rb_init();
}
static int telnet_setup_out_pkt(struct net_context *client)
{
out_pkt = net_pkt_get_tx(client, K_FOREVER);
if (!out_pkt) {
/* Cannot happen atm, net_pkt waits indefinitely */
return -ENOBUFS;
}
return 0;
}
static void telnet_rb_switch(void)
{
telnet_rb.line_in++;
if (telnet_rb.line_in == TELNET_LINES) {
telnet_rb.line_in = 0;
}
telnet_rb.l_bufs[telnet_rb.line_in].len = 0;
/* Unfortunately, we don't have enough line buffer,
* so we eat the next to be sent.
*/
if (telnet_rb.line_in == telnet_rb.line_out) {
telnet_rb.line_out++;
if (telnet_rb.line_out == TELNET_LINES) {
telnet_rb.line_out = 0;
}
}
k_timer_start(&send_timer, TELNET_TIMEOUT, TELNET_TIMEOUT);
k_sem_give(&send_lock);
}
static inline struct line_buf *telnet_rb_get_line_out(void)
{
u16_t out = telnet_rb.line_out;
telnet_rb.line_out++;
if (telnet_rb.line_out == TELNET_LINES) {
telnet_rb.line_out = 0;
}
if (!telnet_rb.l_bufs[out].len) {
return NULL;
}
return &telnet_rb.l_bufs[out];
}
static inline struct line_buf *telnet_rb_get_line_in(void)
{
return &telnet_rb.l_bufs[telnet_rb.line_in];
}
/* The actual printk hook */
static int telnet_console_out(int c)
{
int key = irq_lock();
struct line_buf *lb = telnet_rb_get_line_in();
bool yield = false;
lb->buf[lb->len++] = (char)c;
if (c == '\n' || lb->len == TELNET_LINE_SIZE - 1) {
lb->buf[lb->len-1] = NVT_CR;
lb->buf[lb->len++] = NVT_LF;
telnet_rb_switch();
yield = true;
}
irq_unlock(key);
#ifdef CONFIG_TELNET_CONSOLE_DEBUG_DEEP
/* This is ugly, but if one wants to debug telnet, it
* will also output the character to original console
*/
orig_printk_hook(c);
#endif
if (yield) {
k_yield();
}
return c;
}
static void telnet_send_prematurely(struct k_timer *timer)
{
struct line_buf *lb = telnet_rb_get_line_in();
if (lb->len >= TELNET_THRESHOLD) {
telnet_rb_switch();
}
}
static void telnet_sent_cb(struct net_context *client,
int status, void *token, void *user_data)
{
if (status) {
telnet_end_client_connection();
SYS_LOG_ERR("Could not sent last packet");
}
}
static inline bool telnet_send(void)
{
struct line_buf *lb = telnet_rb_get_line_out();
if (lb) {
net_pkt_append_all(out_pkt, lb->len, (u8_t *)lb->buf,
K_FOREVER);
/* We reinitialize the line buffer */
lb->len = 0;
if (net_context_send(out_pkt, telnet_sent_cb,
K_NO_WAIT, NULL, NULL) ||
telnet_setup_out_pkt(client_cnx)) {
return false;
}
}
return true;
}
#ifdef CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND
static int telnet_console_out_nothing(int c)
{
return c;
}
static inline void telnet_command_send_reply(u8_t *msg, u16_t len)
{
net_pkt_append_all(out_pkt, len, msg, K_FOREVER);
net_context_send(out_pkt, telnet_sent_cb,
K_NO_WAIT, NULL, NULL);
telnet_setup_out_pkt(client_cnx);
}
static inline void telnet_reply_ay_command(void)
{
static const char alive[24] = "Zephyr at your service\r\n";
telnet_command_send_reply((u8_t *)alive, 24);
}
static inline void telnet_reply_do_command(void)
{
switch (telnet_cmd.opt) {
case NVT_OPT_SUPR_GA:
telnet_cmd.op = NVT_CMD_WILL;
break;
default:
telnet_cmd.op = NVT_CMD_WONT;
break;
}
telnet_command_send_reply((u8_t *)&telnet_cmd,
sizeof(struct telnet_simple_command));
}
static inline void telnet_reply_command(void)
{
if (k_sem_take(&cmd_lock, K_NO_WAIT)) {
return;
}
if (!telnet_cmd.iac) {
goto out;
}
switch (telnet_cmd.op) {
case NVT_CMD_AO:
/* OK, no output then */
__printk_hook_install(telnet_console_out_nothing);
telnet_rb_init();
break;
case NVT_CMD_AYT:
telnet_reply_ay_command();
break;
case NVT_CMD_DO:
telnet_reply_do_command();
break;
default:
SYS_LOG_DBG("Operation %u not handled",
telnet_cmd.op);
break;
}
telnet_cmd.iac = NVT_NUL;
telnet_cmd.op = NVT_NUL;
telnet_cmd.opt = NVT_NUL;
out:
k_sem_give(&cmd_lock);
}
#else
#define telnet_reply_command()
#endif /* CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND */
static inline bool telnet_handle_command(struct net_pkt *pkt)
{
struct telnet_simple_command *cmd =
(struct telnet_simple_command *)net_pkt_appdata(pkt);
if (cmd->iac != NVT_CMD_IAC) {
return false;
}
#ifdef CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND
cmd = (struct telnet_simple_command *)l_start;
SYS_LOG_DBG("Got a command %u/%u/%u", cmd->iac, cmd->op, cmd->opt);
if (!k_sem_take(&cmd_lock, K_NO_WAIT)) {
telnet_command_cpy(&telnet_cmd, cmd);
k_sem_give(&cmd_lock);
k_sem_give(&send_lock);
}
#endif /* CONFIG_TELNET_CONSOLE_SUPPORT_COMMAND */
return true;
}
static inline void telnet_handle_input(struct net_pkt *pkt)
{
struct console_input *input;
u16_t len, offset, pos;
len = net_pkt_appdatalen(pkt);
if (len > CONSOLE_MAX_LINE_LEN || len < TELNET_MIN_MSG) {
return;
}
if (telnet_handle_command(pkt)) {
return;
}
if (!avail_queue || !input_queue) {
return;
}
input = k_fifo_get(avail_queue, K_NO_WAIT);
if (!input) {
return;
}
offset = net_pkt_get_len(pkt) - len;
net_frag_read(pkt->frags, offset, &pos, len, (u8_t *)input->line);
/* LF/CR will be removed if only the line is not NUL terminated */
if (input->line[len-1] != NVT_NUL) {
if (input->line[len-1] == NVT_LF) {
input->line[len-1] = NVT_NUL;
}
if (input->line[len-2] == NVT_CR) {
input->line[len-2] = NVT_NUL;
}
}
k_fifo_put(input_queue, input);
}
static void telnet_recv(struct net_context *client,
struct net_pkt *pkt,
int status,
void *user_data)
{
if (!pkt || status) {
telnet_end_client_connection();
SYS_LOG_DBG("Telnet client dropped (AF_INET%s) status %d",
net_context_get_family(client) == AF_INET ?
"" : "6", status);
return;
}
telnet_handle_input(pkt);
net_pkt_unref(pkt);
}
/* Telnet server loop, used to send buffered output in the RB */
static void telnet_run(void)
{
while (true) {
k_sem_take(&send_lock, K_FOREVER);
if (!telnet_send()) {
telnet_end_client_connection();
}
telnet_reply_command();
}
}
static void telnet_accept(struct net_context *client,
struct sockaddr *addr,
socklen_t addrlen,
int error,
void *user_data)
{
if (error) {
SYS_LOG_ERR("Error %d", error);
goto error;
}
if (client_cnx) {
SYS_LOG_WRN("A telnet client is already in.");
goto error;
}
if (net_context_recv(client, telnet_recv, 0, NULL)) {
SYS_LOG_ERR("Unable to setup reception (family %u)",
net_context_get_family(client));
goto error;
}
if (telnet_setup_out_pkt(client)) {
goto error;
}
SYS_LOG_DBG("Telnet client connected (family AF_INET%s)",
net_context_get_family(client) == AF_INET ? "" : "6");
orig_printk_hook = __printk_get_hook();
__printk_hook_install(telnet_console_out);
client_cnx = client;
k_timer_start(&send_timer, TELNET_TIMEOUT, TELNET_TIMEOUT);
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)) {
SYS_LOG_ERR("No context available");
goto error;
}
if (net_context_bind(*ctx, addr, addrlen)) {
SYS_LOG_ERR("Cannot bind on family AF_INET%s",
family == AF_INET ? "" : "6");
goto error;
}
if (net_context_listen(*ctx, 0)) {
SYS_LOG_ERR("Cannot listen on");
goto error;
}
if (net_context_accept(*ctx, telnet_accept, K_NO_WAIT, NULL)) {
SYS_LOG_ERR("Cannot accept");
goto error;
}
SYS_LOG_DBG("Telnet console enabled on AF_INET%s",
family == AF_INET ? "" : "6");
return;
error:
SYS_LOG_ERR("Unable to start telnet on AF_INET%s",
family == AF_INET ? "" : "6");
if (*ctx) {
net_context_put(*ctx);
*ctx = NULL;
}
}
void telnet_register_input(struct k_fifo *avail, struct k_fifo *lines,
u8_t (*completion)(char *str, u8_t len))
{
ARG_UNUSED(completion);
avail_queue = avail;
input_queue = lines;
}
static int telnet_console_init(struct device *arg)
{
#ifdef 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;
#endif
#ifdef 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;
#endif
#ifdef CONFIG_NET_IPV4
telnet_setup_server(&ctx4, AF_INET,
(struct sockaddr *)&any_addr4,
sizeof(any_addr4));
#endif
#ifdef CONFIG_NET_IPV6
telnet_setup_server(&ctx6, AF_INET6,
(struct sockaddr *)&any_addr6,
sizeof(any_addr6));
#endif
k_thread_create(&telnet_thread_data, telnet_stack,
TELNET_STACK_SIZE,
(k_thread_entry_t)telnet_run,
NULL, NULL, NULL,
K_PRIO_COOP(TELNET_PRIORITY), 0, K_MSEC(10));
SYS_LOG_INF("Telnet console initialized");
return 0;
}
/* Telnet is initialized as an application directly, as it requires
* the whole network stack to be ready.
*/
SYS_INIT(telnet_console_init, APPLICATION, CONFIG_TELNET_CONSOLE_INIT_PRIORITY);