/* SPDX-License-Identifier: GPL-3.0-only */ /* Root-level network diagnostics which operate on any active lwIP interface. */ #include "network_console.h" #include #include #include #include #include #include #include "esp_console.h" #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" #include "lwip/ip_addr.h" #include "lwip/netdb.h" #include "lwip/prot/icmp.h" #include "lwip/prot/ip4.h" #include "lwip/sockets.h" #include "ping/ping_sock.h" #define PING_DEFAULT_COUNT 4U #define PING_MIN_COUNT 1U #define PING_MAX_COUNT 20U #define TRACEROUTE_DEFAULT_HOPS 16U #define TRACEROUTE_MIN_HOPS 1U #define TRACEROUTE_MAX_HOPS 30U #define TRACEROUTE_TIMEOUT_US INT64_C(1000000) #define NUMERIC_ADDRESS_CAPACITY 48U /* This covers two maximum-size IPv4 headers plus both required ICMP headers. */ #define TRACEROUTE_REPLY_CAPACITY \ (IP_HLEN_MAX + sizeof(struct icmp_hdr) + IP_HLEN_MAX + sizeof(struct icmp_echo_hdr)) static void print_command_usage(const char *command) { if (command != NULL && strcmp(command, "ping") == 0) { printf("Usage: ping [count] (count: 1..20, default: 4)\n"); } else if (command != NULL && strcmp(command, "nslookup") == 0) { printf("Usage: nslookup \n"); } else if (command != NULL && strcmp(command, "traceroute") == 0) { printf("Usage: traceroute [max-hops] (IPv4 only; 1..30, default: 16)\n"); } else { printf("Network commands: ping, nslookup, traceroute\n"); } } static bool parse_bounded_u32(const char *text, uint32_t minimum, uint32_t maximum, uint32_t *value) { if (text == NULL || value == NULL || *text == '\0') { return false; } for (const char *character = text; *character != '\0'; ++character) { if (*character < '0' || *character > '9') { return false; } } char *end = NULL; errno = 0; unsigned long parsed = strtoul(text, &end, 10); if (errno != 0 || end == text || *end != '\0' || parsed < minimum || parsed > maximum) { return false; } *value = (uint32_t)parsed; return true; } static bool sockaddr_to_numeric(const struct sockaddr *address, socklen_t address_length, char *buffer, size_t buffer_size) { if (address == NULL || buffer == NULL || buffer_size == 0U) { return false; } const void *numeric_address = NULL; if (address->sa_family == AF_INET && address_length >= (socklen_t)sizeof(struct sockaddr_in)) { numeric_address = &((const struct sockaddr_in *)address)->sin_addr; #if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6 } else if (address->sa_family == AF_INET6 && address_length >= (socklen_t)sizeof(struct sockaddr_in6)) { numeric_address = &((const struct sockaddr_in6 *)address)->sin6_addr; #endif } else { return false; } return inet_ntop(address->sa_family, numeric_address, buffer, (socklen_t)buffer_size) != NULL; } static bool addrinfo_to_ip_addr(const struct addrinfo *entry, ip_addr_t *target) { if (entry == NULL || entry->ai_addr == NULL || target == NULL) { return false; } if (entry->ai_family == AF_INET && entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) { const struct sockaddr_in *socket_address = (const struct sockaddr_in *)entry->ai_addr; ip4_addr_t ipv4; inet_addr_to_ip4addr(&ipv4, &socket_address->sin_addr); ip_addr_copy_from_ip4(*target, ipv4); return true; } #if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6 if (entry->ai_family == AF_INET6 && entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6)) { const struct sockaddr_in6 *socket_address = (const struct sockaddr_in6 *)entry->ai_addr; ip6_addr_t ipv6; inet6_addr_to_ip6addr(&ipv6, &socket_address->sin6_addr); ip_addr_copy_from_ip6(*target, ipv6); return true; } #endif return false; } static int resolve_ping_target(const char *host, ip_addr_t *target, char *numeric, size_t numeric_size) { struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_RAW, }; struct addrinfo *results = NULL; int resolver_result = getaddrinfo(host, NULL, &hints, &results); if (resolver_result != 0) { printf("ping: could not resolve '%s' (getaddrinfo error %d)\n", host, resolver_result); return 1; } bool found = false; for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) { if (addrinfo_to_ip_addr(entry, target) && sockaddr_to_numeric(entry->ai_addr, entry->ai_addrlen, numeric, numeric_size)) { found = true; break; } } freeaddrinfo(results); if (!found) { printf("ping: '%s' did not resolve to a supported IPv4 or IPv6 address\n", host); return 1; } return 0; } typedef enum { PING_EVENT_LINE = 0, PING_EVENT_END, } ping_event_kind_t; typedef struct { 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; } 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) { 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] = "?"; 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 { 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) { 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)); } 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 = arguments; ping_event_t event = {.kind = PING_EVENT_END, .profile_error = ESP_OK}; ip_addr_t target_address; 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 (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, event.address, (int)sizeof(event.address)); } event.delete_error = esp_ping_delete_session(handle); (void)xQueueSend(context->queue, &event, 0U); } static int execute_ping(int argc, char **argv) { if ((argc != 2 && argc != 3) || argv[1] == NULL || *argv[1] == '\0') { print_command_usage("ping"); return 1; } uint32_t count = PING_DEFAULT_COUNT; if (argc == 3 && !parse_bounded_u32(argv[2], PING_MIN_COUNT, PING_MAX_COUNT, &count)) { print_command_usage("ping"); return 1; } ip_addr_t target; char numeric[NUMERIC_ADDRESS_CAPACITY]; if (resolve_ping_target(argv[1], &target, numeric, sizeof(numeric)) != 0) { return 1; } 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; config.target_addr = target; const esp_ping_callbacks_t callbacks = { .cb_args = &context, .on_ping_success = ping_on_success, .on_ping_timeout = ping_on_timeout, .on_ping_end = ping_on_end, }; esp_ping_handle_t session = NULL; esp_err_t error = esp_ping_new_session(&config, &callbacks, &session); if (error != ESP_OK) { printf("ping: could not create session: %s\n", esp_err_to_name(error)); return 1; } printf("PING %s (%s): %" PRIu32 " probes\n", argv[1], numeric, count); error = esp_ping_start(session); if (error != ESP_OK) { printf("ping: could not start session: %s\n", esp_err_to_name(error)); (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; } } static bool socket_addresses_equal(const struct addrinfo *left, const struct addrinfo *right) { if (left == NULL || right == NULL || left->ai_addr == NULL || right->ai_addr == NULL || left->ai_family != right->ai_family) { return false; } if (left->ai_family == AF_INET && left->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in) && right->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) { const struct sockaddr_in *left_address = (const struct sockaddr_in *)left->ai_addr; const struct sockaddr_in *right_address = (const struct sockaddr_in *)right->ai_addr; return left_address->sin_addr.s_addr == right_address->sin_addr.s_addr; } #if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6 if (left->ai_family == AF_INET6 && left->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6) && right->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6)) { const struct sockaddr_in6 *left_address = (const struct sockaddr_in6 *)left->ai_addr; const struct sockaddr_in6 *right_address = (const struct sockaddr_in6 *)right->ai_addr; return memcmp(&left_address->sin6_addr, &right_address->sin6_addr, sizeof(left_address->sin6_addr)) == 0; } #endif return false; } static bool address_appeared_earlier(const struct addrinfo *first, const struct addrinfo *current) { for (const struct addrinfo *entry = first; entry != NULL && entry != current; entry = entry->ai_next) { if (socket_addresses_equal(entry, current)) { return true; } } return false; } static int execute_nslookup(int argc, char **argv) { if (argc != 2 || argv[1] == NULL || *argv[1] == '\0') { print_command_usage("nslookup"); return 1; } struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, }; struct addrinfo *results = NULL; int resolver_result = getaddrinfo(argv[1], NULL, &hints, &results); if (resolver_result != 0) { printf("nslookup: could not resolve '%s' (getaddrinfo error %d)\n", argv[1], resolver_result); return 1; } printf("Name: %s\n", argv[1]); size_t printed = 0U; for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) { if (address_appeared_earlier(results, entry)) { continue; } char numeric[NUMERIC_ADDRESS_CAPACITY]; if (!sockaddr_to_numeric(entry->ai_addr, entry->ai_addrlen, numeric, sizeof(numeric))) { continue; } printf("Address: %s (%s)\n", numeric, entry->ai_family == AF_INET ? "IPv4" : "IPv6"); ++printed; } freeaddrinfo(results); if (printed == 0U) { printf("nslookup: no supported IPv4 or IPv6 addresses returned\n"); return 1; } return 0; } static int resolve_traceroute_target(const char *host, struct sockaddr_in *target, char *numeric, size_t numeric_size) { struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_RAW, .ai_protocol = IPPROTO_ICMP, }; struct addrinfo *results = NULL; int resolver_result = getaddrinfo(host, NULL, &hints, &results); if (resolver_result != 0) { printf("traceroute: could not resolve IPv4 host '%s' (getaddrinfo error %d)\n", host, resolver_result); return 1; } const struct addrinfo *selected = NULL; for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) { if (entry->ai_family == AF_INET && entry->ai_addr != NULL && entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) { selected = entry; break; } } if (selected == NULL || !sockaddr_to_numeric(selected->ai_addr, selected->ai_addrlen, numeric, numeric_size)) { freeaddrinfo(results); printf("traceroute: '%s' did not resolve to an IPv4 address\n", host); return 1; } memcpy(target, selected->ai_addr, sizeof(*target)); freeaddrinfo(results); return 0; } typedef enum { TRACE_REPLY_UNRELATED, TRACE_REPLY_HOP, TRACE_REPLY_DESTINATION, TRACE_REPLY_UNREACHABLE, } trace_reply_kind_t; static trace_reply_kind_t parse_trace_reply(const uint8_t *packet, size_t length, uint16_t expected_id, uint16_t expected_sequence, uint32_t expected_destination, uint8_t *unreachable_code) { if (packet == NULL || length < IP_HLEN + sizeof(struct icmp_hdr)) { return TRACE_REPLY_UNRELATED; } const struct ip_hdr *outer_ip = (const struct ip_hdr *)(const void *)packet; size_t outer_header_length = IPH_HL_BYTES(outer_ip); if (IPH_V(outer_ip) != 4U || outer_header_length < IP_HLEN || outer_header_length > length || length - outer_header_length < sizeof(struct icmp_hdr) || IPH_PROTO(outer_ip) != IPPROTO_ICMP) { return TRACE_REPLY_UNRELATED; } uint16_t outer_total_length = lwip_ntohs(IPH_LEN(outer_ip)); if (outer_total_length < outer_header_length + sizeof(struct icmp_hdr)) { return TRACE_REPLY_UNRELATED; } size_t available = length; if ((size_t)outer_total_length < available) { available = outer_total_length; } const uint8_t *outer_icmp_bytes = packet + outer_header_length; const struct icmp_hdr *outer_icmp = (const struct icmp_hdr *)(const void *)outer_icmp_bytes; if (ICMPH_TYPE(outer_icmp) == ICMP_ER) { const struct icmp_echo_hdr *echo_reply = (const struct icmp_echo_hdr *)(const void *)outer_icmp_bytes; if (echo_reply->id == expected_id && echo_reply->seqno == expected_sequence) { return TRACE_REPLY_DESTINATION; } return TRACE_REPLY_UNRELATED; } if (ICMPH_TYPE(outer_icmp) != ICMP_TE && ICMPH_TYPE(outer_icmp) != ICMP_DUR) { return TRACE_REPLY_UNRELATED; } /* ICMP errors quote the original IPv4 header and at least 8 payload bytes. */ size_t inner_offset = outer_header_length + sizeof(struct icmp_hdr); if (inner_offset > available || available - inner_offset < IP_HLEN) { return TRACE_REPLY_UNRELATED; } const struct ip_hdr *inner_ip = (const struct ip_hdr *)(const void *)(packet + inner_offset); size_t inner_header_length = IPH_HL_BYTES(inner_ip); if (IPH_V(inner_ip) != 4U || inner_header_length < IP_HLEN || inner_header_length > available - inner_offset || available - inner_offset - inner_header_length < sizeof(struct icmp_echo_hdr) || IPH_PROTO(inner_ip) != IPPROTO_ICMP || inner_ip->dest.addr != expected_destination || lwip_ntohs(IPH_LEN(inner_ip)) < inner_header_length + sizeof(struct icmp_echo_hdr)) { return TRACE_REPLY_UNRELATED; } const struct icmp_echo_hdr *quoted_echo = (const struct icmp_echo_hdr *)(const void *)( packet + inner_offset + inner_header_length); if (ICMPH_TYPE((const struct icmp_hdr *)quoted_echo) != ICMP_ECHO || quoted_echo->id != expected_id || quoted_echo->seqno != expected_sequence) { return TRACE_REPLY_UNRELATED; } if (ICMPH_TYPE(outer_icmp) == ICMP_DUR) { if (unreachable_code != NULL) { *unreachable_code = ICMPH_CODE(outer_icmp); } return TRACE_REPLY_UNREACHABLE; } return TRACE_REPLY_HOP; } typedef enum { TRACE_WAIT_ERROR = -1, TRACE_WAIT_TIMEOUT = 0, TRACE_WAIT_HOP, TRACE_WAIT_DESTINATION, TRACE_WAIT_UNREACHABLE, } trace_wait_result_t; static trace_wait_result_t wait_for_trace_reply(int socket_fd, const struct sockaddr_in *target, uint16_t expected_id, uint16_t expected_sequence, int64_t sent_at_us, char *source_numeric, size_t source_numeric_size, int64_t *round_trip_us, uint8_t *unreachable_code) { int64_t deadline_us = sent_at_us + TRACEROUTE_TIMEOUT_US; uint8_t reply[TRACEROUTE_REPLY_CAPACITY]; for (;;) { int64_t remaining_us = deadline_us - esp_timer_get_time(); if (remaining_us <= 0) { return TRACE_WAIT_TIMEOUT; } /* Reduce the socket timeout after unrelated traffic to keep one second total. */ struct timeval receive_timeout = { .tv_sec = (long)(remaining_us / INT64_C(1000000)), .tv_usec = (long)(remaining_us % INT64_C(1000000)), }; if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, sizeof(receive_timeout)) != 0) { return TRACE_WAIT_ERROR; } struct sockaddr_in source = {0}; socklen_t source_length = sizeof(source); ssize_t received = recvfrom(socket_fd, reply, sizeof(reply), 0, (struct sockaddr *)&source, &source_length); if (received < 0) { if (errno == EINTR) { continue; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ETIMEDOUT) { return TRACE_WAIT_TIMEOUT; } return TRACE_WAIT_ERROR; } trace_reply_kind_t kind = parse_trace_reply( reply, (size_t)received, expected_id, expected_sequence, target->sin_addr.s_addr, unreachable_code); if (kind == TRACE_REPLY_UNRELATED) { continue; } /* An echo reply is the destination only when it came from our target. */ if (kind == TRACE_REPLY_DESTINATION && source.sin_addr.s_addr != target->sin_addr.s_addr) { continue; } if (!sockaddr_to_numeric((const struct sockaddr *)&source, source_length, source_numeric, source_numeric_size)) { (void)snprintf(source_numeric, source_numeric_size, "?"); } *round_trip_us = esp_timer_get_time() - sent_at_us; switch (kind) { case TRACE_REPLY_HOP: return TRACE_WAIT_HOP; case TRACE_REPLY_DESTINATION: return TRACE_WAIT_DESTINATION; case TRACE_REPLY_UNREACHABLE: return TRACE_WAIT_UNREACHABLE; default: return TRACE_WAIT_ERROR; } } } static void print_trace_rtt(int64_t round_trip_us) { if (round_trip_us < 0) { round_trip_us = 0; } printf("%" PRId64 ".%03" PRId64 " ms", round_trip_us / INT64_C(1000), round_trip_us % INT64_C(1000)); } static int execute_traceroute(int argc, char **argv) { if ((argc != 2 && argc != 3) || argv[1] == NULL || *argv[1] == '\0') { print_command_usage("traceroute"); return 1; } uint32_t max_hops = TRACEROUTE_DEFAULT_HOPS; if (argc == 3 && !parse_bounded_u32(argv[2], TRACEROUTE_MIN_HOPS, TRACEROUTE_MAX_HOPS, &max_hops)) { print_command_usage("traceroute"); return 1; } printf("traceroute: IPv4 only (one ICMP echo probe per hop)\n"); struct sockaddr_in target = {0}; char target_numeric[NUMERIC_ADDRESS_CAPACITY]; if (resolve_traceroute_target(argv[1], &target, target_numeric, sizeof(target_numeric)) != 0) { return 1; } int socket_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (socket_fd < 0) { printf("traceroute: could not create raw ICMP socket: %s\n", strerror(errno)); return 1; } const struct timeval one_second = { .tv_sec = 1, .tv_usec = 0, }; if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &one_second, sizeof(one_second)) != 0) { printf("traceroute: could not set 1 s receive timeout: %s\n", strerror(errno)); close(socket_fd); return 1; } printf("traceroute to %s (%s), %" PRIu32 " hops max\n", argv[1], target_numeric, max_hops); /* A per-run ID plus one sequence per hop rejects other raw-socket traffic. */ uint16_t identifier = lwip_htons((uint16_t)esp_timer_get_time()); bool stopped = false; bool reached = false; uint32_t final_hop = 0U; for (uint32_t hop = 1U; hop <= max_hops; ++hop) { int ttl = (int)hop; if (setsockopt(socket_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) { printf("traceroute: could not set TTL for hop %" PRIu32 ": %s\n", hop, strerror(errno)); close(socket_fd); return 1; } struct icmp_echo_hdr probe = { .type = ICMP_ECHO, .code = 0U, .chksum = 0U, .id = identifier, .seqno = lwip_htons((uint16_t)hop), }; probe.chksum = inet_chksum(&probe, (u16_t)sizeof(probe)); int64_t sent_at_us = esp_timer_get_time(); ssize_t sent = sendto(socket_fd, &probe, sizeof(probe), 0, (const struct sockaddr *)&target, sizeof(target)); if (sent != (ssize_t)sizeof(probe)) { printf("traceroute: probe send failed at hop %" PRIu32 ": %s\n", hop, strerror(errno)); close(socket_fd); return 1; } char source_numeric[NUMERIC_ADDRESS_CAPACITY]; int64_t round_trip_us = 0; uint8_t unreachable_code = 0U; trace_wait_result_t result = wait_for_trace_reply( socket_fd, &target, probe.id, probe.seqno, sent_at_us, source_numeric, sizeof(source_numeric), &round_trip_us, &unreachable_code); if (result == TRACE_WAIT_ERROR) { printf("traceroute: receive failed at hop %" PRIu32 ": %s\n", hop, strerror(errno)); close(socket_fd); return 1; } if (result == TRACE_WAIT_TIMEOUT) { printf("%2" PRIu32 " *\n", hop); continue; } printf("%2" PRIu32 " %-15s ", hop, source_numeric); print_trace_rtt(round_trip_us); if (result == TRACE_WAIT_UNREACHABLE) { printf(" !U (ICMP code %u)", (unsigned int)unreachable_code); } putchar('\n'); if (result == TRACE_WAIT_DESTINATION || result == TRACE_WAIT_UNREACHABLE) { stopped = true; reached = result == TRACE_WAIT_DESTINATION; final_hop = hop; break; } } close(socket_fd); if (reached) { printf("Trace complete: destination reached at hop %" PRIu32 ".\n", final_hop); } else if (stopped) { printf("Trace stopped: destination unreachable at hop %" PRIu32 ".\n", final_hop); } else { printf("Trace complete: destination not reached within %" PRIu32 " hops.\n", max_hops); } return 0; } bool network_console_is_command(const char *name) { return name != NULL && (strcmp(name, "ping") == 0 || strcmp(name, "nslookup") == 0 || strcmp(name, "traceroute") == 0); } int network_console_execute(int argc, char **argv) { if (argc <= 0 || argv == NULL || argv[0] == NULL) { print_command_usage(NULL); return 1; } if (strcmp(argv[0], "ping") == 0) { return execute_ping(argc, argv); } if (strcmp(argv[0], "nslookup") == 0) { return execute_nslookup(argc, argv); } if (strcmp(argv[0], "traceroute") == 0) { return execute_traceroute(argc, argv); } printf("Unknown network command '%s'.\n", argv[0]); print_command_usage(NULL); return 1; } esp_err_t network_console_register_root_commands(void) { /* All aliases intentionally point at the public dispatcher. */ static const esp_console_cmd_t commands[] = { { .command = "ping", .help = "ping [count] (count 1..20, default 4)", .hint = NULL, .func = &network_console_execute, .argtable = NULL, }, { .command = "nslookup", .help = "nslookup (print unique numeric IPv4/IPv6 addresses)", .hint = NULL, .func = &network_console_execute, .argtable = NULL, }, { .command = "traceroute", .help = "traceroute [max-hops] (IPv4 only; 1..30, default 16)", .hint = NULL, .func = &network_console_execute, .argtable = NULL, }, }; for (size_t index = 0U; index < sizeof(commands) / sizeof(commands[0]); ++index) { esp_err_t error = esp_console_cmd_register(&commands[index]); if (error != ESP_OK) { return error; } } return ESP_OK; }