Expand admin SSH command capabilities

Add per-session history, tab completion, interactive prompts, and
bounded input handling. Support deferred lifecycle and host-key actions
after output drains, and document the expanded administration workflow.
This commit is contained in:
2026-08-30 18:34:01 +02:00
parent 0a1bbd6782
commit c2c11fee4e
14 changed files with 699 additions and 224 deletions
+108 -87
View File
@@ -14,6 +14,7 @@
#include "esp_err.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "lwip/inet.h"
#include "lwip/inet_chksum.h"
@@ -163,109 +164,109 @@ static int resolve_ping_target(const char *host, ip_addr_t *target,
return 0;
}
typedef enum {
PING_EVENT_LINE = 0,
PING_EVENT_END,
} ping_event_kind_t;
typedef struct {
TaskHandle_t waiting_task;
ping_event_kind_t kind;
char line[128];
char address[NUMERIC_ADDRESS_CAPACITY];
uint32_t transmitted;
uint32_t received;
uint32_t duration_ms;
esp_err_t profile_error;
esp_err_t delete_error;
bool received_reply;
bool summary_valid;
} ping_event_t;
typedef struct {
QueueHandle_t queue;
} ping_wait_context_t;
#define PING_EVENT_QUEUE_LENGTH (PING_MAX_COUNT + 1U)
static StaticQueue_t s_ping_queue_storage;
static uint8_t s_ping_queue_bytes[PING_EVENT_QUEUE_LENGTH * sizeof(ping_event_t)];
static QueueHandle_t s_ping_queue;
static void ping_on_success(esp_ping_handle_t handle, void *arguments)
{
(void)arguments;
ping_wait_context_t *context = arguments;
ping_event_t event = {.kind = PING_EVENT_LINE};
uint16_t sequence = 0U;
uint8_t ttl = 0U;
uint32_t reply_size = 0U;
uint32_t elapsed_ms = 0U;
ip_addr_t reply_address;
char numeric[NUMERIC_ADDRESS_CAPACITY];
if (esp_ping_get_profile(handle, ESP_PING_PROF_SEQNO,
&sequence, sizeof(sequence)) != ESP_OK ||
esp_ping_get_profile(handle, ESP_PING_PROF_SIZE,
&reply_size, sizeof(reply_size)) != ESP_OK ||
esp_ping_get_profile(handle, ESP_PING_PROF_TIMEGAP,
&elapsed_ms, sizeof(elapsed_ms)) != ESP_OK ||
esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
&reply_address, sizeof(reply_address)) != ESP_OK ||
ipaddr_ntoa_r(&reply_address, numeric, (int)sizeof(numeric)) == NULL) {
printf("ping: received a reply but could not read its profile\n");
return;
}
if (IP_IS_V4(&reply_address) &&
esp_ping_get_profile(handle, ESP_PING_PROF_TTL,
&ttl, sizeof(ttl)) == ESP_OK) {
printf("%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
" ttl=%u time=%" PRIu32 " ms\n",
reply_size, numeric, sequence, (unsigned int)ttl, elapsed_ms);
char numeric[NUMERIC_ADDRESS_CAPACITY] = "?";
bool valid = esp_ping_get_profile(handle, ESP_PING_PROF_SEQNO,
&sequence, sizeof(sequence)) == ESP_OK &&
esp_ping_get_profile(handle, ESP_PING_PROF_SIZE,
&reply_size, sizeof(reply_size)) == ESP_OK &&
esp_ping_get_profile(handle, ESP_PING_PROF_TIMEGAP,
&elapsed_ms, sizeof(elapsed_ms)) == ESP_OK &&
esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
&reply_address, sizeof(reply_address)) == ESP_OK &&
ipaddr_ntoa_r(&reply_address, numeric, (int)sizeof(numeric)) != NULL;
if (!valid) {
strlcpy(event.line, "ping: received a reply but could not read its profile",
sizeof(event.line));
} else if (IP_IS_V4(&reply_address) &&
esp_ping_get_profile(handle, ESP_PING_PROF_TTL,
&ttl, sizeof(ttl)) == ESP_OK) {
snprintf(event.line, sizeof(event.line),
"%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
" ttl=%u time=%" PRIu32 " ms",
reply_size, numeric, sequence, (unsigned int)ttl, elapsed_ms);
} else {
printf("%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
" time=%" PRIu32 " ms\n",
reply_size, numeric, sequence, elapsed_ms);
snprintf(event.line, sizeof(event.line),
"%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
" time=%" PRIu32 " ms",
reply_size, numeric, sequence, elapsed_ms);
}
(void)xQueueSend(context->queue, &event, 0U);
}
static void ping_on_timeout(esp_ping_handle_t handle, void *arguments)
{
(void)arguments;
ping_wait_context_t *context = arguments;
ping_event_t event = {.kind = PING_EVENT_LINE};
uint16_t sequence = 0U;
ip_addr_t target_address;
char numeric[NUMERIC_ADDRESS_CAPACITY] = "?";
if (esp_ping_get_profile(handle, ESP_PING_PROF_SEQNO,
&sequence, sizeof(sequence)) == ESP_OK &&
esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
&target_address, sizeof(target_address)) == ESP_OK) {
(void)ipaddr_ntoa_r(&target_address, numeric, (int)sizeof(numeric));
}
printf("From %s: icmp_seq=%" PRIu16 " timeout\n", numeric, sequence);
snprintf(event.line, sizeof(event.line), "From %s: icmp_seq=%" PRIu16 " timeout",
numeric, sequence);
(void)xQueueSend(context->queue, &event, 0U);
}
static void ping_on_end(esp_ping_handle_t handle, void *arguments)
{
ping_wait_context_t *context = (ping_wait_context_t *)arguments;
uint32_t transmitted = 0U;
uint32_t received = 0U;
uint32_t duration_ms = 0U;
ping_wait_context_t *context = arguments;
ping_event_t event = {.kind = PING_EVENT_END, .profile_error = ESP_OK};
ip_addr_t target_address;
char numeric[NUMERIC_ADDRESS_CAPACITY] = "?";
esp_err_t profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
if (profile_error == ESP_OK) {
profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_REPLY, &received, sizeof(received));
strlcpy(event.address, "?", sizeof(event.address));
event.profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_REQUEST, &event.transmitted, sizeof(event.transmitted));
if (event.profile_error == ESP_OK) {
event.profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_REPLY, &event.received, sizeof(event.received));
}
if (profile_error == ESP_OK) {
profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_DURATION, &duration_ms, sizeof(duration_ms));
if (event.profile_error == ESP_OK) {
event.profile_error = esp_ping_get_profile(
handle, ESP_PING_PROF_DURATION, &event.duration_ms, sizeof(event.duration_ms));
}
if (esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
&target_address, sizeof(target_address)) == ESP_OK) {
(void)ipaddr_ntoa_r(&target_address, numeric, (int)sizeof(numeric));
(void)ipaddr_ntoa_r(&target_address, event.address, (int)sizeof(event.address));
}
if (profile_error == ESP_OK) {
context->received_reply = received > 0U;
context->summary_valid = true;
uint32_t loss_percent = transmitted == 0U
? 0U
: ((transmitted - received) * 100U) / transmitted;
printf("\n--- %s ping statistics ---\n", numeric);
printf("%" PRIu32 " packets transmitted, %" PRIu32
" received, %" PRIu32 "%% packet loss, time %" PRIu32 " ms\n",
transmitted, received, loss_percent, duration_ms);
} else {
printf("ping: session ended, but summary profile retrieval failed: %s\n",
esp_err_to_name(profile_error));
}
/* Stop ping_sock's task before waking the higher-priority console caller. */
context->delete_error = esp_ping_delete_session(handle);
xTaskNotifyGive(context->waiting_task);
event.delete_error = esp_ping_delete_session(handle);
(void)xQueueSend(context->queue, &event, 0U);
}
static int execute_ping(int argc, char **argv)
@@ -288,12 +289,17 @@ static int execute_ping(int argc, char **argv)
return 1;
}
ping_wait_context_t context = {
.waiting_task = xTaskGetCurrentTaskHandle(),
.delete_error = ESP_FAIL,
};
/* Remove any unrelated notification before this command begins waiting. */
(void)ulTaskNotifyTake(pdTRUE, 0U);
if (s_ping_queue == NULL) {
s_ping_queue = xQueueCreateStatic(PING_EVENT_QUEUE_LENGTH, sizeof(ping_event_t),
s_ping_queue_bytes, &s_ping_queue_storage);
} else {
(void)xQueueReset(s_ping_queue);
}
if (s_ping_queue == NULL) {
printf("ping: could not allocate event queue\n");
return 1;
}
ping_wait_context_t context = {.queue = s_ping_queue};
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
config.count = count;
@@ -321,21 +327,36 @@ static int execute_ping(int argc, char **argv)
return 1;
}
/* Finite count guarantees on_ping_end; blocking keeps console output ordered. */
if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) == 0U) {
printf("ping: wait for session completion failed\n");
(void)esp_ping_stop(session);
(void)esp_ping_delete_session(session);
return 1;
for (;;) {
ping_event_t event;
if (xQueueReceive(s_ping_queue, &event, portMAX_DELAY) != pdTRUE) {
printf("ping: wait for session completion failed\n");
return 1;
}
if (event.kind == PING_EVENT_LINE) {
printf("%s\n", event.line);
continue;
}
if (event.profile_error != ESP_OK) {
printf("ping: session ended, but summary profile retrieval failed: %s\n",
esp_err_to_name(event.profile_error));
return 1;
}
uint32_t loss_percent = event.transmitted == 0U
? 0U
: ((event.transmitted - event.received) * 100U) /
event.transmitted;
printf("\n--- %s ping statistics ---\n", event.address);
printf("%" PRIu32 " packets transmitted, %" PRIu32
" received, %" PRIu32 "%% packet loss, time %" PRIu32 " ms\n",
event.transmitted, event.received, loss_percent, event.duration_ms);
if (event.delete_error != ESP_OK) {
printf("ping: could not delete session: %s\n",
esp_err_to_name(event.delete_error));
return 1;
}
return event.received > 0U ? 0 : 1;
}
bool command_succeeded = context.summary_valid && context.received_reply;
if (context.delete_error != ESP_OK) {
printf("ping: could not delete session: %s\n",
esp_err_to_name(context.delete_error));
return 1;
}
return command_succeeded ? 0 : 1;
}
static bool socket_addresses_equal(const struct addrinfo *left,