/* * Copyright (c) 2017 Intel Corporation * Copyright (c) 2019 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #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 #define SOCK_ID_IPV4_LISTEN 0 #define SOCK_ID_IPV6_LISTEN 1 #define SOCK_ID_CLIENT 2 #define SOCK_ID_MAX 3 /* Basic TELNET implementation. */ static void telnet_server_cb(struct k_work *work); static int telnet_init(struct shell_telnet *ctx); NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(telnet_server, NULL, telnet_server_cb, SHELL_TELNET_POLLFD_COUNT); static void telnet_end_client_connection(void) { int ret; (void)zsock_close(sh_telnet->fds[SOCK_ID_CLIENT].fd); sh_telnet->fds[SOCK_ID_CLIENT].fd = -1; sh_telnet->output_lock = false; k_work_cancel_delayable_sync(&sh_telnet->send_work, &sh_telnet->work_sync); ret = net_socket_service_register(&telnet_server, sh_telnet->fds, ARRAY_SIZE(sh_telnet->fds), NULL); if (ret < 0) { LOG_ERR("Failed to register socket service, %d", ret); } } static void telnet_command_send_reply(uint8_t *msg, uint16_t len) { if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) { return; } while (len > 0) { int ret; ret = zsock_send(sh_telnet->fds[SOCK_ID_CLIENT].fd, msg, len, 0); if (ret < 0) { LOG_ERR("Failed to send command %d, shutting down", ret); telnet_end_client_connection(); break; } msg += ret; len -= ret; } } 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 int telnet_echo_set(const struct shell *sh, bool val) { int ret = shell_echo_set(sh_telnet->shell_context, val); if (ret < 0) { LOG_ERR("Failed to set echo to: %d, err: %d", val, ret); } return ret; } static void telnet_reply_dont_command(struct telnet_simple_command *cmd) { switch (cmd->opt) { case NVT_OPT_ECHO: { int ret = telnet_echo_set(sh_telnet->shell_context, false); if (ret >= 0) { cmd->op = NVT_CMD_WILL_NOT; } else { cmd->op = NVT_CMD_WILL; } break; } default: cmd->op = NVT_CMD_WILL_NOT; break; } telnet_command_send_reply((uint8_t *)cmd, sizeof(struct telnet_simple_command)); } 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; case NVT_OPT_ECHO: { int ret = telnet_echo_set(sh_telnet->shell_context, true); if (ret >= 0) { cmd->op = NVT_CMD_WILL; } else { cmd->op = NVT_CMD_WILL_NOT; } break; } default: cmd->op = NVT_CMD_WILL_NOT; 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; case NVT_CMD_DO_NOT: telnet_reply_dont_command(cmd); break; default: LOG_DBG("Operation %u not handled", cmd->op); break; } } static int telnet_send(bool block) { int ret; uint8_t *msg = sh_telnet->line_out.buf; uint16_t len = sh_telnet->line_out.len; if (sh_telnet->line_out.len == 0) { return 0; } if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) { return -ENOTCONN; } while (len > 0) { ret = zsock_send(sh_telnet->fds[SOCK_ID_CLIENT].fd, msg, len, block ? 0 : ZSOCK_MSG_DONTWAIT); if (!block && (ret < 0) && (errno == EAGAIN)) { /* Not all data was sent - move the remaining data and * update length. */ memmove(sh_telnet->line_out.buf, msg, len); sh_telnet->line_out.len = len; return -EAGAIN; } if (ret < 0) { ret = -errno; LOG_ERR("Failed to send %d, shutting down", -ret); telnet_end_client_connection(); return ret; } msg += ret; len -= ret; } /* We reinitialize the line buffer */ sh_telnet->line_out.len = 0; return 0; } static void telnet_send_prematurely(struct k_work *work) { int ret; /* Use non-blocking send to prevent system workqueue blocking. */ ret = telnet_send(false); if (ret == -EAGAIN) { /* Not all data was sent, reschedule the work. */ k_work_reschedule(&sh_telnet->send_work, K_MSEC(TELNET_TIMEOUT)); } } static int telnet_command_length(uint8_t op) { if (op == NVT_CMD_SB || op == NVT_CMD_WILL || op == NVT_CMD_WILL_NOT || op == NVT_CMD_DO || op == NVT_CMD_DO_NOT) { return TELNET_WILL_DO_COMMAND_LEN; } return TELNET_MIN_COMMAND_LEN; } static inline int telnet_handle_command(struct telnet_simple_command *cmd) { /* Commands are two or three bytes. */ if (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; } return 0; } static void telnet_recv(struct zsock_pollfd *pollfd) { struct telnet_simple_command *cmd = (struct telnet_simple_command *)sh_telnet->cmd_buf; size_t len, off, buf_left, cmd_total_len; uint8_t *buf; int ret; k_mutex_lock(&sh_telnet->rx_lock, K_FOREVER); buf_left = sizeof(sh_telnet->rx_buf) - sh_telnet->rx_len; if (buf_left == 0) { /* No space left to read TCP stream, try again later. */ k_mutex_unlock(&sh_telnet->rx_lock); k_msleep(10); return; } buf = sh_telnet->rx_buf + sh_telnet->rx_len; ret = zsock_recv(pollfd->fd, buf, buf_left, 0); if (ret < 0) { LOG_DBG("Telnet client error %d", ret); goto error; } else if (ret == 0) { LOG_DBG("Telnet client closed connection"); goto error; } off = 0; len = ret; cmd_total_len = 0; /* Filter out and process commands from the data buffer. */ while (off < len) { if (sh_telnet->cmd_len > 0) { /* Command mode */ if (sh_telnet->cmd_len == 1) { /* Operation */ cmd->op = *(buf + off); sh_telnet->cmd_len++; cmd_total_len++; off++; if (telnet_command_length(cmd->op) > TELNET_MIN_COMMAND_LEN) { continue; } } else if (sh_telnet->cmd_len == 2) { /* Option */ cmd->opt = *(buf + off); sh_telnet->cmd_len++; cmd_total_len++; off++; } ret = telnet_handle_command(cmd); if (ret < 0) { goto error; } else { LOG_DBG("Handled command"); } memset(cmd, 0, sizeof(*cmd)); sh_telnet->cmd_len = 0; continue; } if (*(buf + off) == NVT_CMD_IAC) { cmd->iac = *(buf + off); sh_telnet->cmd_len++; cmd_total_len++; off++; continue; } /* Data byte, remove command bytes from the buffer, if any. */ if (cmd_total_len > 0) { size_t data_off = off; off -= cmd_total_len; len -= cmd_total_len; cmd_total_len = 0; memmove(buf + off, buf + data_off, len); } off++; } if (cmd_total_len > 0) { /* In case the buffer ended with command, trim the buffer size * here. */ len -= cmd_total_len; } if (len == 0) { k_mutex_unlock(&sh_telnet->rx_lock); return; } sh_telnet->rx_len += len; k_mutex_unlock(&sh_telnet->rx_lock); sh_telnet->shell_handler(SHELL_TRANSPORT_EVT_RX_RDY, sh_telnet->shell_context); return; error: k_mutex_unlock(&sh_telnet->rx_lock); telnet_end_client_connection(); } static void telnet_restart_server(void) { int ret; if (sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd >= 0) { (void)zsock_close(sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd); sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd = -1; } if (sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd >= 0) { (void)zsock_close(sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd); sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd = -1; } if (sh_telnet->fds[SOCK_ID_CLIENT].fd >= 0) { (void)zsock_close(sh_telnet->fds[SOCK_ID_CLIENT].fd); sh_telnet->fds[SOCK_ID_CLIENT].fd = -1; } ret = telnet_init(sh_telnet); if (ret < 0) { LOG_ERR("Telnet fatal error, failed to restart server (%d)", ret); (void)net_socket_service_unregister(&telnet_server); } } static void telnet_accept(struct zsock_pollfd *pollfd) { int sock, ret = 0; struct sockaddr addr; socklen_t addrlen = sizeof(struct sockaddr); sock = zsock_accept(pollfd->fd, &addr, &addrlen); if (sock < 0) { ret = -errno; NET_ERR("Telnet accept error (%d)", ret); return; } if (sh_telnet->fds[SOCK_ID_CLIENT].fd > 0) { /* Too many connections. */ ret = 0; NET_ERR("Telnet client already connected."); goto error; } sh_telnet->fds[SOCK_ID_CLIENT].fd = sock; sh_telnet->fds[SOCK_ID_CLIENT].events = ZSOCK_POLLIN; sh_telnet->rx_len = 0; sh_telnet->cmd_len = 0; sh_telnet->line_out.len = 0; ret = net_socket_service_register(&telnet_server, sh_telnet->fds, ARRAY_SIZE(sh_telnet->fds), NULL); if (ret < 0) { LOG_ERR("Failed to register socket service, (%d)", ret); sh_telnet->fds[SOCK_ID_CLIENT].fd = -1; goto error; } LOG_DBG("Telnet client connected (family AF_INET%s)", addr.sa_family == AF_INET ? "" : "6"); /* Disable echo - if command handling is enabled we reply that we * support echo. */ (void)telnet_echo_set(sh_telnet->shell_context, false); return; error: if (sock > 0) { (void)zsock_close(sock); } if (ret < 0) { telnet_restart_server(); } } static void telnet_server_cb(struct k_work *work) { struct net_socket_service_event *evt = CONTAINER_OF(work, struct net_socket_service_event, work); int sock_error; socklen_t optlen = sizeof(int); if (sh_telnet == NULL) { return; } if ((evt->event.revents & ZSOCK_POLLERR) || (evt->event.revents & ZSOCK_POLLNVAL)) { (void)zsock_getsockopt(evt->event.fd, SOL_SOCKET, SO_ERROR, &sock_error, &optlen); NET_ERR("Telnet socket %d error (%d)", evt->event.fd, sock_error); if (evt->event.fd == sh_telnet->fds[SOCK_ID_CLIENT].fd) { return telnet_end_client_connection(); } goto error; } if (!(evt->event.revents & ZSOCK_POLLIN)) { return; } if (evt->event.fd == sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd) { return telnet_accept(&sh_telnet->fds[SOCK_ID_IPV4_LISTEN]); } else if (evt->event.fd == sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd) { return telnet_accept(&sh_telnet->fds[SOCK_ID_IPV6_LISTEN]); } else if (evt->event.fd == sh_telnet->fds[SOCK_ID_CLIENT].fd) { return telnet_recv(&sh_telnet->fds[SOCK_ID_CLIENT]); } NET_ERR("Unexpected FD received for telnet, restarting service."); error: telnet_restart_server(); } static int telnet_setup_server(struct zsock_pollfd *pollfd, sa_family_t family, struct sockaddr *addr, socklen_t addrlen) { int ret = 0; pollfd->fd = zsock_socket(family, SOCK_STREAM, IPPROTO_TCP); if (pollfd->fd < 0) { ret = -errno; LOG_ERR("Failed to create telnet AF_INET%s socket", family == AF_INET ? "" : "6"); goto error; } if (zsock_bind(pollfd->fd, addr, addrlen) < 0) { ret = -errno; LOG_ERR("Cannot bind on family AF_INET%s (%d)", family == AF_INET ? "" : "6", ret); goto error; } if (zsock_listen(pollfd->fd, 1)) { ret = -errno; LOG_ERR("Cannot listen on family AF_INET%s (%d)", family == AF_INET ? "" : "6", ret); goto error; } pollfd->events = ZSOCK_POLLIN; LOG_DBG("Telnet console enabled on AF_INET%s", family == AF_INET ? "" : "6"); return 0; error: LOG_ERR("Unable to start telnet on AF_INET%s (%d)", family == AF_INET ? "" : "6", ret); if (pollfd->fd >= 0) { (void)zsock_close(pollfd->fd); pollfd->fd = -1; } return ret; } static int telnet_init(struct shell_telnet *ctx) { int ret; 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 }; ret = telnet_setup_server(&ctx->fds[SOCK_ID_IPV4_LISTEN], AF_INET, (struct sockaddr *)&any_addr4, sizeof(any_addr4)); if (ret < 0) { goto error; } } 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 }; ret = telnet_setup_server(&ctx->fds[SOCK_ID_IPV6_LISTEN], AF_INET6, (struct sockaddr *)&any_addr6, sizeof(any_addr6)); if (ret < 0) { goto error; } } ret = net_socket_service_register(&telnet_server, ctx->fds, ARRAY_SIZE(ctx->fds), NULL); if (ret < 0) { LOG_ERR("Failed to register socket service, %d", ret); goto error; } LOG_INF("Telnet shell backend initialized"); return 0; error: if (ctx->fds[SOCK_ID_IPV4_LISTEN].fd >= 0) { (void)zsock_close(ctx->fds[SOCK_ID_IPV4_LISTEN].fd); ctx->fds[SOCK_ID_IPV4_LISTEN].fd = -1; } if (ctx->fds[SOCK_ID_IPV6_LISTEN].fd >= 0) { (void)zsock_close(ctx->fds[SOCK_ID_IPV6_LISTEN].fd); ctx->fds[SOCK_ID_IPV6_LISTEN].fd = -1; } return ret; } /* 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; memset(sh_telnet, 0, sizeof(struct shell_telnet)); for (int i = 0; i < ARRAY_SIZE(sh_telnet->fds); i++) { sh_telnet->fds[i].fd = -1; } sh_telnet->shell_handler = evt_handler; sh_telnet->shell_context = context; err = telnet_init(sh_telnet); if (err != 0) { return err; } k_work_init_delayable(&sh_telnet->send_work, telnet_send_prematurely); k_mutex_init(&sh_telnet->rx_lock); 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 telnet_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->fds[SOCK_ID_CLIENT].fd < 0 || 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(true); 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 telnet_read(const struct shell_transport *transport, void *data, size_t length, size_t *cnt) { size_t read_len; if (sh_telnet == NULL) { return -ENODEV; } if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) { goto no_data; } k_mutex_lock(&sh_telnet->rx_lock, K_FOREVER); if (sh_telnet->rx_len == 0) { k_mutex_unlock(&sh_telnet->rx_lock); goto no_data; } read_len = sh_telnet->rx_len; if (read_len > length) { read_len = length; } memcpy(data, sh_telnet->rx_buf, read_len); *cnt = read_len; sh_telnet->rx_len -= read_len; if (sh_telnet->rx_len > 0) { memmove(sh_telnet->rx_buf, sh_telnet->rx_buf + read_len, sh_telnet->rx_len); } k_mutex_unlock(&sh_telnet->rx_lock); return 0; no_data: *cnt = 0; return 0; } const struct shell_transport_api shell_telnet_transport_api = { .init = init, .uninit = uninit, .enable = enable, .write = telnet_write, .read = telnet_read }; static int enable_shell_telnet(void) { 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, CONFIG_SHELL_TELNET_INIT_PRIORITY); const struct shell *shell_backend_telnet_get_ptr(void) { return &shell_telnet; }