Add Dual-Stack Network Diagnostics

This commit is contained in:
2026-09-21 10:01:55 +02:00
parent 60c1e279d6
commit 06df47c934
18 changed files with 1488 additions and 270 deletions
@@ -0,0 +1,53 @@
static void expect_matches(const char *prefix, const char *expected)
{
char output[CONSOLE_COMPLETION_OUTPUT_CAPACITY]={0}; size_t length=999;
assert(console_completion_format_matches(prefix,output,sizeof(output),&length));
assert(length==strlen(expected) && !memcmp(output,expected,length));
}
int main(void)
{
const char *verbs[]={"ping","nslookup","traceroute"};
for (unsigned alias=0;alias<2;++alias)
for (unsigned verb=0;verb<3;++verb) {
char base[64], prefix[128], expected[256], expanded[128];
snprintf(base,sizeof(base),"%s%s",alias ? "wifi " : "",verbs[verb]);
snprintf(prefix,sizeof(prefix),"%s ",base);
snprintf(expected,sizeof(expected),"%s -4\r\n%s -6\r\n",base,base);
expect_matches(prefix,expected);
assert(console_completion_expand(prefix,expanded,sizeof(expanded)));
snprintf(prefix,sizeof(prefix),"%s -",base);
assert(!strcmp(expanded,prefix));
expect_matches(prefix,expected);
assert(!console_completion_expand(prefix,expanded,sizeof(expanded)));
snprintf(prefix,sizeof(prefix),"%s -6",base);
expect_matches(prefix,"");
assert(!console_completion_expand(prefix,expanded,sizeof(expanded)));
snprintf(prefix,sizeof(prefix),"%s -4 ",base); expect_matches(prefix,"");
snprintf(prefix,sizeof(prefix),"%s -6 ",base); expect_matches(prefix,"");
snprintf(prefix,sizeof(prefix),"%s private-host.example -",base); expect_matches(prefix,"");
snprintf(prefix,sizeof(prefix),"%s 2001:db8::1 ",base); expect_matches(prefix,"");
snprintf(prefix,sizeof(prefix),"%s -",base);
size_t length=123;
assert(!console_completion_format_matches(prefix,expanded,1,&length));
assert(length==123);
snprintf(prefix,sizeof(prefix),"%s ",base);
assert(!console_completion_expand(prefix,expanded,strlen(prefix)+1));
}
expect_matches("wifi profile secret 0 ","");
expect_matches("wifi ap secret ","");
expect_matches("user password admin ","");
expect_matches("nslook","nslookup\r\n");
expect_matches("wifi p","wifi profiles\r\nwifi profile\r\nwifi profile set\r\nwifi profile secret\r\nwifi profile enable\r\nwifi profile disable\r\nwifi profile delete\r\nwifi ping\r\nwifi ping -4\r\nwifi ping -6\r\n");
/* Every candidate prefix must still fit the shared bounded list buffer. */
for (size_t i=0;i<sizeof(s_completion_candidates)/sizeof(s_completion_candidates[0]);++i) {
char prefix[257], output[CONSOLE_COMPLETION_OUTPUT_CAPACITY]; size_t length;
strcpy(prefix,s_completion_candidates[i]);
for (size_t n=0;n<=strlen(s_completion_candidates[i]);++n) {
char saved=prefix[n]; prefix[n]='\0';
assert(console_completion_format_matches(prefix,output,sizeof(output),&length));
prefix[n]=saved;
}
}
puts("PASS: actual shared completion matcher, six diagnostic prefixes, no host/secret guessing, expansion and bounded output");
}
@@ -0,0 +1,80 @@
static bool owner_live=true;
static bool is_current(const admin_ssh_console_token_t *token, const user_principal_t *principal)
{ (void)token; assert(principal->role==USER_ROLE_ADMIN); return owner_live; }
static bool drained(const admin_ssh_console_token_t *token) { (void)token; return true; }
static esp_err_t perform(const admin_ssh_console_token_t *token,
admin_ssh_deferred_action_type_t action, uint32_t argument)
{ (void)token; (void)action; (void)argument; assert(false); return ESP_FAIL; }
static const admin_console_owner_t owner={.is_current=is_current,.drained=drained,.perform=perform};
static void pump(void) { if (!setjmp(loop_done)) worker_task(NULL); }
static void feed(const admin_ssh_console_token_t *token, const char *line)
{
char input[257]; snprintf(input,sizeof(input),"%s\r",line);
size_t used=0;
assert(admin_ssh_console_feed_input(token,(const uint8_t *)input,strlen(input),&used));
assert(used==strlen(input));
}
static void discard_output(const admin_ssh_console_token_t *token)
{
uint8_t output[4096]; size_t used;
assert(admin_ssh_console_read_output(token,output,sizeof(output),&used)==ESP_OK);
}
int main(void)
{
assert(admin_ssh_console_init()==ESP_OK);
assert(admin_ssh_console_start_uart_frontend()==ESP_OK);
const user_principal_t admin={.role=USER_ROLE_ADMIN};
const user_principal_t ordinary={.role=USER_ROLE_USER};
const char *verbs[]={"ping","nslookup","traceroute"};
const char *hosts[]={"example.org","192.0.2.1","2001:db8::1","fe80::1%st1"};
unsigned cases=0;
for (unsigned transport=0;transport<2;++transport) {
admin_ssh_console_token_t token={.slot_index=0,.session_id=7,.slot_generation=1,
.transport=transport ? ADMIN_CONSOLE_TRANSPORT_WEB : ADMIN_CONSOLE_TRANSPORT_SSH};
assert(admin_ssh_console_open_owned(&token,&ordinary,&owner)!=ESP_OK);
assert(!s_sessions[0].active && !s_request_queue->count);
assert(admin_ssh_console_open_owned(&token,&admin,&owner)==ESP_OK);
for (unsigned alias=0;alias<2;++alias)
for (unsigned verb=0;verb<3;++verb)
for (unsigned host=0;host<4;++host)
for (unsigned family=0;family<3;++family)
for (unsigned position=0;position<3;++position) {
const char *flag=family==0 ? "" : family==1 ? "-4 " : "-6 ";
const char *count=verb==0 ? "20 " : verb==2 ? "30 " : "";
char line[257];
snprintf(line,sizeof(line),"%s%s %s%s %s%s%s",alias ? "wifi " : "",verbs[verb],
position==0 ? flag : "",hosts[host],position==1 ? flag : "",count,
position==2 ? flag : "");
unsigned before=runs;
discard_output(&token);
feed(&token,line); pump();
assert(runs==before+1 && !strcmp(dispatched,line));
/* Trusted UART0 arrives at exactly the same registry boundary. */
admin_request_t uart={.origin=ADMIN_REQUEST_UART0};
memcpy(uart.line,line,strlen(line)+1);
assert(xQueueSend(s_request_queue,&uart,0)); pump();
assert(runs==before+2 && !strcmp(dispatched,line));
++cases;
}
const char *extra[]={"ping example.org", "wifi ping example.org", "traceroute example.org",
"wifi traceroute example.org", "nslookup example.org", "wifi nslookup example.org",
" \"wifi\" \"ping\" \"2001:db8::1\" \"2\" \"-6\" ",
"\"nslookup\" \"-6\" \"example.org\""};
for (unsigned i=0;i<sizeof(extra)/sizeof(extra[0]);++i) {
discard_output(&token); unsigned before=runs;
feed(&token,extra[i]); pump();
assert(runs==before+1 && !strcmp(dispatched,extra[i]));
}
/* Existing currentness guards deny diagnostics after revocation. */
unsigned before=runs;
feed(&token,"wifi ping -6 2001:db8::1"); principal_current=false; pump();
assert(runs==before && !s_sessions[0].active);
principal_current=true; ++token.slot_generation;
assert(admin_ssh_console_open_owned(&token,&admin,&owner)==ESP_OK);
feed(&token,"nslookup -6 example.org"); owner_live=false; pump();
assert(runs==before && !s_sessions[0].active);
owner_live=true;
}
printf("PASS: %u SSH/WEB root+Wi-Fi family/position/host cases and UART0 parity; defaults/quotes, ordinary-role and revocation guards\n",cases);
}
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Host surface regressions; real dispatcher/matcher, fake RTOS and console sink.
No sockets, diagnostic core execution, target scheduler or hardware are exercised.
"""
import os
from pathlib import Path
import re
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[2]
IDF = Path(os.environ.get("IDF_PATH", str(Path.home() / ".platformio/packages/framework-espidf")))
def read(path):
return (ROOT / path).read_text()
def stripped(text):
return "\n".join(line for line in text.splitlines()
if not line.startswith(("#include", "#pragma once")))
# Guard canonical registry and unchanged argv forwarding, including UART0 wiring.
network = read("src/network_console.c")
wifi = read("src/wifi_console.c")
main = read("src/main.c")
admin = read("src/admin_ssh_console.c")
registry = network[network.index("esp_err_t network_console_register_root_commands("):]
for name in ("ping", "nslookup", "traceroute"):
assert re.search(r'\.command\s*=\s*"' + name + r'"[^}]*\.func\s*=\s*&network_console_execute', registry)
assert f'strcmp(name, "{name}") == 0' in network
assert re.search(r'if \(argc >= 2 && network_console_is_command\(argv\[1\]\)\)\s*\{\s*'
r'/\*.*?\*/\s*return network_console_execute\(argc - 1, argv \+ 1\);\s*\}', wifi, re.S)
assert re.search(r'\.command\s*=\s*"wifi"[^}]*\.func\s*=\s*&command_wifi', wifi)
for registration in ("wifi_console_register_commands", "network_console_register_root_commands"):
assert f"ESP_ERROR_CHECK({registration}());" in main
uart = admin[admin.index("static void uart_frontend_task("):]
assert ".origin = ADMIN_REQUEST_UART0" in uart
assert "request.line" in uart and "xQueueSend(s_request_queue, &request" in uart
assert "dispatch_registered_command(&request);" in admin
assert "esp_console_run((const char *)request->line, &command_result)" in admin
print("PASS: root/Wi-Fi registry, exact shared argv forwarding and UART0 queue/dispatcher source contracts")
with tempfile.TemporaryDirectory(prefix="network-surfaces-") as directory:
path = Path(directory)
# Observe the actual dispatcher output at the ESP-IDF registry boundary.
fakes = read("tests/admin_console_boundary/fakes.h")
old = "{ (void)s; ++runs; if (command_hook) command_hook(); *r=0; return ESP_OK; }"
assert old in fakes
fakes = fakes.replace("static esp_err_t esp_console_run(",
"static char dispatched[257];\nstatic esp_err_t esp_console_run(")
fakes = fakes.replace(old, "{ assert(strlen(s)<sizeof(dispatched)); strcpy(dispatched,s); ++runs; if (command_hook) command_hook(); *r=0; return ESP_OK; }")
unit = (fakes + stripped(read("src/admin_ssh_console.h")) + "\n" + stripped(admin)
+ read("tests/network_diagnostics_surfaces/routing.c"))
(path / "routing.c").write_text(unit)
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-Wno-unused-variable",
str(path / "routing.c"), str(IDF / "components/console/split_argv.c"),
"-o", str(path / "routing")], check=True, timeout=30)
subprocess.run([str(path / "routing")], check=True, timeout=10)
completion = read("src/console_completion.c")
# All shared matching functions, excluding only UART read/linenoise adapters.
completion = completion[:completion.index("static ssize_t console_read_with_late_terminal_upgrade(")]
unit = ("#include <assert.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <string.h>\n"
+ stripped(read("src/console_completion.h")) + "\n" + stripped(completion)
+ read("tests/network_diagnostics_surfaces/completion.c"))
(path / "completion.c").write_text(unit)
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror",
str(path / "completion.c"), "-o", str(path / "completion")], check=True, timeout=30)
subprocess.run([str(path / "completion")], check=True, timeout=10)