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:
+108
-87
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user