Add Dual-Stack Network Diagnostics
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
#include "../../src/network_console.c"
|
||||
#undef close /* Output-capture descriptors are real host files, not fake sockets. */
|
||||
|
||||
static unsigned checks;
|
||||
#define CHECK(x) do { ++checks; if (!(x)) { fprintf(stderr,"FAIL %s:%d: %s\n",__FILE__,__LINE__,#x); abort(); } } while (0)
|
||||
static int queries[8], query_count, resolver_error4, resolver_error6, freed;
|
||||
static bool resolver_linklocal;
|
||||
static long socket_timeout_ms;
|
||||
static unsigned recv_calls, ping_created;
|
||||
static struct addrinfo ai;
|
||||
static struct sockaddr_storage answer;
|
||||
static int sockets, closed, options, fail_option, send_fail, recv_error, socket_fail;
|
||||
static int socket_family, socket_protocol, sent_count, ttl_options, v6only_options, bind_options;
|
||||
static uint8_t incoming[1280];
|
||||
static size_t incoming_length;
|
||||
static struct sockaddr_storage incoming_source;
|
||||
static int64_t now;
|
||||
static esp_ping_config_t ping_config;
|
||||
static esp_ping_callbacks_t ping_callbacks;
|
||||
static int ping_create_error, ping_start_error, ping_deleted;
|
||||
static bool ping_delayed;
|
||||
static uint8_t *queue_bytes;
|
||||
static unsigned queue_size, queue_read, queue_write;
|
||||
static bool allocation_fail;
|
||||
|
||||
static void reset(void)
|
||||
{
|
||||
query_count = freed = resolver_error4 = resolver_error6 = 0;
|
||||
resolver_linklocal = false;
|
||||
socket_timeout_ms = 0;
|
||||
recv_calls = ping_created = 0;
|
||||
sockets = closed = options = fail_option = send_fail = recv_error = socket_fail = 0;
|
||||
sent_count = ttl_options = v6only_options = bind_options = 0;
|
||||
incoming_length = 0;
|
||||
now = 0;
|
||||
ping_create_error = ping_start_error = ping_deleted = 0;
|
||||
ping_delayed = false;
|
||||
allocation_fail = false;
|
||||
}
|
||||
|
||||
int fake_getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **out)
|
||||
{
|
||||
(void)host; CHECK(service == NULL); CHECK(query_count < 8);
|
||||
queries[query_count++] = hints->ai_family;
|
||||
CHECK(hints->ai_flags == 0);
|
||||
int error = hints->ai_family == AF_INET ? resolver_error4 : resolver_error6;
|
||||
*out = NULL;
|
||||
if (error) return error;
|
||||
memset(&answer,0,sizeof(answer));
|
||||
if (hints->ai_family == AF_INET) {
|
||||
struct sockaddr_in *v4 = (void *)&answer;
|
||||
v4->sin_family = AF_INET;
|
||||
inet_pton(AF_INET,"192.0.2.1",&v4->sin_addr);
|
||||
} else {
|
||||
struct sockaddr_in6 *v6 = (void *)&answer;
|
||||
v6->sin6_family = AF_INET6;
|
||||
inet_pton(AF_INET6,resolver_linklocal?"fe80::1":"2001:db8::1",&v6->sin6_addr);
|
||||
CHECK(v6->sin6_scope_id==0); /* DNS does not supply an interface zone. */
|
||||
}
|
||||
ai = (struct addrinfo){.ai_family=hints->ai_family,.ai_addr=(void *)&answer,.ai_addrlen=sizeof(answer)};
|
||||
*out = &ai;
|
||||
return 0;
|
||||
}
|
||||
void fake_freeaddrinfo(struct addrinfo *p) { CHECK(p == &ai); ++freed; }
|
||||
int fake_socket(int family,int type,int protocol)
|
||||
{
|
||||
CHECK(type == SOCK_RAW); socket_family=family; socket_protocol=protocol;
|
||||
if (socket_fail) { errno=ENOMEM; return -1; }
|
||||
++sockets; return 0; /* fd zero is valid and must be closed */
|
||||
}
|
||||
int fake_setsockopt(int fd,int level,int option,const void *value,socklen_t length)
|
||||
{
|
||||
CHECK(fd==0); ++options;
|
||||
if (fail_option==options) { errno=EINVAL; return -1; }
|
||||
if (level==IPPROTO_IP && option==IP_TTL) {
|
||||
CHECK(length==sizeof(int)); CHECK(*(const int *)value>=1 && *(const int *)value<=30); ++ttl_options;
|
||||
} else if (level==IPPROTO_IPV6 && option==IPV6_V6ONLY) {
|
||||
CHECK(*(const int *)value==1); ++v6only_options;
|
||||
} else if (level==SOL_SOCKET && option==SO_BINDTODEVICE) {
|
||||
CHECK(length==sizeof(struct ifreq)); CHECK(strcmp(((const struct ifreq *)value)->ifr_name,"st1")==0); ++bind_options;
|
||||
} else {
|
||||
CHECK(level==SOL_SOCKET && option==SO_RCVTIMEO);
|
||||
CHECK(length==sizeof(struct timeval));
|
||||
const struct timeval *timeout=value;
|
||||
CHECK(timeout->tv_sec<=1 && timeout->tv_usec<1000000);
|
||||
/* sdk_contract.py also compiles the installed SDK's actual conversion. */
|
||||
socket_timeout_ms=timeout->tv_sec*1000+timeout->tv_usec/1000;
|
||||
CHECK(socket_timeout_ms>0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
ssize_t fake_sendto(int fd,const void *data,size_t length,int flags,const struct sockaddr *address,socklen_t address_length)
|
||||
{
|
||||
(void)flags; CHECK(fd==0); CHECK(length==8); ++sent_count;
|
||||
CHECK(address->sa_family==socket_family);
|
||||
const struct icmp_echo_hdr *echo=data;
|
||||
if (socket_family==AF_INET6) {
|
||||
CHECK(socket_protocol==IPPROTO_ICMPV6); CHECK(echo->type==128); CHECK(echo->chksum==0);
|
||||
CHECK(address_length==sizeof(struct sockaddr_in6));
|
||||
} else {
|
||||
CHECK(socket_protocol==IPPROTO_ICMP); CHECK(echo->type==ICMP_ECHO); CHECK(inet_chksum(data,length)==0);
|
||||
CHECK(address_length==sizeof(struct sockaddr_in));
|
||||
}
|
||||
if (send_fail) { errno=ENETUNREACH; return -1; }
|
||||
return length;
|
||||
}
|
||||
ssize_t fake_recvfrom(int fd,void *data,size_t length,int flags,struct sockaddr *source,socklen_t *source_length)
|
||||
{
|
||||
(void)flags; CHECK(fd==0);
|
||||
++recv_calls;
|
||||
CHECK(socket_timeout_ms>0); /* zero would enter portMAX_DELAY in IDF */
|
||||
if (incoming_length) {
|
||||
CHECK(length>=incoming_length); memcpy(data,incoming,incoming_length);
|
||||
memcpy(source,&incoming_source,sizeof(incoming_source)); *source_length=sizeof(incoming_source);
|
||||
size_t result=incoming_length; incoming_length=0; return result;
|
||||
}
|
||||
errno=recv_error ? recv_error : EAGAIN; return -1;
|
||||
}
|
||||
int fake_close(int fd) { CHECK(fd==0); ++closed; return 0; }
|
||||
unsigned int fake_if_nametoindex(const char *name) { return strcmp(name,"st1")==0 ? 2U : 0U; }
|
||||
char *fake_if_indextoname(unsigned int index,char *name) { if (index!=2 && index!=3) return NULL; strcpy(name,index==2?"st1":"ap2"); return name; }
|
||||
esp_netif_t *esp_netif_get_handle_from_ifkey(const char *key) { static int sta=2,ap=3; return strcmp(key,"WIFI_STA_DEF")==0?&sta:≈ }
|
||||
int esp_netif_get_netif_impl_index(esp_netif_t *p) { return *p; }
|
||||
size_t strlcpy(char *dest,const char *source,size_t size) { size_t n=strlen(source); if (size) snprintf(dest,size,"%s",source); return n; }
|
||||
const char *esp_err_to_name(esp_err_t error) { (void)error; return "fake error"; }
|
||||
void *heap_caps_malloc(size_t size,int caps) { CHECK(caps==(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT)); return allocation_fail?NULL:malloc(size); }
|
||||
QueueHandle_t xQueueCreateStatic(unsigned int count,unsigned int size,uint8_t *bytes,StaticQueue_t *control) { (void)control; CHECK(count==21); queue_bytes=bytes; queue_size=size; queue_read=queue_write=0; return bytes; }
|
||||
int xQueueReset(QueueHandle_t q) { CHECK(q==queue_bytes); queue_read=queue_write=0; return pdTRUE; }
|
||||
int xQueueSend(QueueHandle_t q,const void *event,unsigned int timeout) { CHECK(q==queue_bytes && timeout==0 && queue_write<21); memcpy(queue_bytes+queue_size*queue_write++,event,queue_size); return pdTRUE; }
|
||||
int xQueueReceive(QueueHandle_t q,void *event,unsigned int timeout) { (void)timeout; CHECK(q==queue_bytes); if (queue_read==queue_write) return 0; memcpy(event,queue_bytes+queue_size*queue_read++,queue_size); return pdTRUE; }
|
||||
const char *ipaddr_ntoa_r(const ip_addr_t *ip,char *out,int capacity) { return inet_ntop(ip->family,ip->family==AF_INET?(const void *)&ip->v4:(const void *)&ip->v6.addr,out,capacity); }
|
||||
esp_err_t esp_ping_get_profile(esp_ping_handle_t h,int profile,void *out,size_t size) { CHECK(h==&ping_config); memset(out,0,size); if (profile==ESP_PING_PROF_IPADDR) memcpy(out,&ping_config.target_addr,size); if (profile==ESP_PING_PROF_REQUEST || profile==ESP_PING_PROF_REPLY) *(uint32_t *)out=ping_config.count; return ESP_OK; }
|
||||
esp_err_t esp_ping_new_session(const esp_ping_config_t *config,const esp_ping_callbacks_t *callbacks,esp_ping_handle_t *out) { ++ping_created; ping_config=*config; ping_callbacks=*callbacks; if(ping_create_error) return -1; *out=&ping_config; return ESP_OK; }
|
||||
esp_err_t esp_ping_start(esp_ping_handle_t h) { if(ping_start_error) return -1; if(ping_delayed) return ESP_OK; ping_callbacks.on_ping_success(h,ping_callbacks.cb_args); ping_callbacks.on_ping_end(h,ping_callbacks.cb_args); return ESP_OK; }
|
||||
esp_err_t esp_ping_delete_session(esp_ping_handle_t h) { CHECK(h==&ping_config); ++ping_deleted; return ESP_OK; }
|
||||
esp_err_t esp_ping_stop(esp_ping_handle_t h) { CHECK(h==&ping_config); return ESP_OK; }
|
||||
int64_t esp_timer_get_time(void) { now+=1000; return now; }
|
||||
uint16_t inet_chksum(const void *data,u16_t length) { uint32_t sum=checksum_sum(data,length,0); while(sum>>16) sum=(sum&65535)+(sum>>16); return htons((uint16_t)~sum); }
|
||||
esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd) { CHECK(cmd->func==network_console_execute); return ESP_OK; }
|
||||
|
||||
static void test_arguments(void)
|
||||
{
|
||||
diagnostic_arguments_t a;
|
||||
char *good[]={"ping","-6","::1","20"};
|
||||
CHECK(parse_arguments(4,good,4,20,&a) && a.family==AF_INET6 && a.limit==20);
|
||||
char *after[]={"ping","::1","20","-6"};
|
||||
CHECK(parse_arguments(4,after,4,20,&a));
|
||||
char *middle[]={"ping","::1","-6","1"};
|
||||
CHECK(parse_arguments(4,middle,4,20,&a) && a.limit==1);
|
||||
char *bad[][5]={{"ping","-6","::1","-6",NULL},{"ping","-4","::1","-6",NULL},
|
||||
{"ping","host","0",NULL},{"ping","host","21",NULL},{"ping","host","-1",NULL},
|
||||
{"ping","host","42949672960",NULL},{"ping","host","1x",NULL},{"ping","",NULL},
|
||||
{"ping","-6",NULL},{"ping","host","1","2",NULL},{"ping","--", "host",NULL}};
|
||||
for(size_t i=0;i<sizeof(bad)/sizeof(bad[0]);++i) { int n=0; while(bad[i][n])++n; CHECK(!parse_arguments(n,bad[i],4,20,&a)); }
|
||||
char *lookup[]={"nslookup","host","1"}; CHECK(!parse_arguments(3,lookup,0,0,&a));
|
||||
char *trace[]={"traceroute","host","30"}; CHECK(parse_arguments(3,trace,16,30,&a)); trace[2]="31"; CHECK(!parse_arguments(3,trace,16,30,&a));
|
||||
}
|
||||
|
||||
static diagnostic_target_t target_for(const char *text)
|
||||
{
|
||||
diagnostic_target_t target; CHECK(parse_literal(text,AF_UNSPEC,&target)==1); return target;
|
||||
}
|
||||
static void test_resolution(void)
|
||||
{
|
||||
diagnostic_target_t target; char numeric[48];
|
||||
const char *valid[]={"fe80::1%sta","fe80::1%ap","fe80::1%2","fe80::1%st1","fe80::1","::1","192.0.2.1"};
|
||||
for(size_t i=0;i<sizeof(valid)/sizeof(valid[0]);++i) CHECK(parse_literal(valid[i],AF_UNSPEC,&target)==1);
|
||||
const char *bad[]={"fe80::1%0","fe80::1%256","fe80::1%9","fe80::1%","fe80::1%eth0","fe80::1%sta%2","example%sta","192.0.2.1%sta","::ffff:192.0.2.1","fe80:garbage"};
|
||||
for(size_t i=0;i<sizeof(bad)/sizeof(bad[0]);++i) CHECK(parse_literal(bad[i],AF_UNSPEC,&target)==-1);
|
||||
CHECK(parse_literal("::1",AF_INET,&target)==-1); CHECK(parse_literal("192.0.2.1",AF_INET6,&target)==-1);
|
||||
target=target_for("fe80::1%sta"); CHECK(sockaddr_to_numeric((void *)&target.address,target.length,numeric,sizeof(numeric))); CHECK(strcmp(numeric,"fe80::1%2")==0);
|
||||
CHECK(!sockaddr_to_numeric((void *)&target.address,target.length,numeric,4));
|
||||
ip_addr_t ip; struct addrinfo entry={.ai_family=AF_INET6,.ai_addr=(void *)&target.address,.ai_addrlen=target.length};
|
||||
CHECK(addrinfo_to_ip_addr(&entry,&ip)); CHECK(ip.v6.zone==2);
|
||||
diagnostic_arguments_t args={.host="example.test",.family=AF_UNSPEC};
|
||||
reset(); CHECK(resolve_target(&args,&target,numeric,sizeof(numeric))==0); CHECK(query_count==1 && queries[0]==AF_INET && freed==1);
|
||||
reset(); resolver_error4=EAI_FAIL; CHECK(resolve_target(&args,&target,numeric,sizeof(numeric))==0); CHECK(query_count==2 && queries[1]==AF_INET6 && freed==1);
|
||||
reset(); resolver_error4=EAI_MEMORY; CHECK(resolve_target(&args,&target,numeric,sizeof(numeric))!=0); CHECK(query_count==1);
|
||||
reset(); args.family=AF_INET6; resolver_error6=EAI_FAIL; CHECK(resolve_target(&args,&target,numeric,sizeof(numeric))!=0); CHECK(query_count==1 && queries[0]==AF_INET6);
|
||||
char *lookup[]={"nslookup","example.test"}; reset(); CHECK(network_console_execute(2,lookup)==0); CHECK(query_count==2 && freed==2);
|
||||
char *lookup6[]={"nslookup","example.test","-6"}; reset(); CHECK(network_console_execute(3,lookup6)==0); CHECK(query_count==1 && queries[0]==AF_INET6);
|
||||
char *lookup4[]={"nslookup","-4","example.test"}; reset(); CHECK(network_console_execute(3,lookup4)==0); CHECK(query_count==1 && queries[0]==AF_INET);
|
||||
reset(); resolver_error4=EAI_NONAME; CHECK(network_console_execute(2,lookup)==0); CHECK(query_count==2 && queries[1]==AF_INET6);
|
||||
reset(); resolver_error4=resolver_error6=EAI_FAIL; CHECK(network_console_execute(2,lookup)!=0); CHECK(query_count==2);
|
||||
}
|
||||
|
||||
static int capture_command(int argc, char **argv, char *output, size_t capacity)
|
||||
{
|
||||
fflush(stdout);
|
||||
int saved=dup(STDOUT_FILENO);
|
||||
FILE *capture=tmpfile();
|
||||
CHECK(saved>=0 && capture!=NULL);
|
||||
CHECK(dup2(fileno(capture),STDOUT_FILENO)>=0);
|
||||
int result=network_console_execute(argc,argv);
|
||||
fflush(stdout);
|
||||
CHECK(fseek(capture,0,SEEK_SET)==0);
|
||||
size_t size=fread(output,1,capacity-1,capture);
|
||||
CHECK(size<capacity-1 && !ferror(capture));
|
||||
output[size]='\0';
|
||||
CHECK(dup2(saved,STDOUT_FILENO)>=0);
|
||||
CHECK(close(saved)==0);
|
||||
CHECK(fclose(capture)==0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void test_unscoped_linklocal(void)
|
||||
{
|
||||
char output[1024];
|
||||
diagnostic_target_t target;
|
||||
reset(); resolver_linklocal=true;
|
||||
CHECK(resolve_family("ll.example",AF_INET6,&target)==0);
|
||||
CHECK(target_needs_scope(&target) && freed==1);
|
||||
const char *hosts[]={"ll.example","fe80::1"};
|
||||
for(size_t h=0;h<2;++h) {
|
||||
for(int explicit6=0;explicit6<=1;++explicit6) {
|
||||
char *lookup[]={"nslookup",(char *)hosts[h],"-6"};
|
||||
reset(); resolver_linklocal=true;
|
||||
CHECK(capture_command(explicit6?3:2,lookup,output,sizeof(output))==0);
|
||||
CHECK(strstr(output,h==0?"AAAA: fe80::1":"Address: fe80::1 (numeric literal; no DNS query)")!=NULL);
|
||||
CHECK(strstr(output,"explicit device scope")!=NULL && strstr(output,"fe80::1%sta")!=NULL);
|
||||
CHECK(query_count==(h==0?(explicit6?1:2):0));
|
||||
if(h==0) CHECK(queries[query_count-1]==AF_INET6);
|
||||
CHECK(sockets==0 && ping_created==0);
|
||||
const char *commands[]={"ping","traceroute"};
|
||||
for(size_t c=0;c<2;++c) {
|
||||
char *probe[]={(char *)commands[c],(char *)hosts[h],"1","-6"};
|
||||
reset(); resolver_linklocal=true; resolver_error4=EAI_NONAME;
|
||||
CHECK(capture_command(explicit6?4:3,probe,output,sizeof(output))!=0);
|
||||
CHECK(strstr(output,"Link-local fe80::1")!=NULL && strstr(output,"fe80::1%sta")!=NULL);
|
||||
CHECK(strstr(output,"device interface, not the SSH/browser client's")!=NULL);
|
||||
CHECK(sockets==0 && sent_count==0 && ping_created==0);
|
||||
CHECK(query_count==(h==0?(explicit6?1:2):0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void put16(uint8_t *p,uint16_t n) { p[0]=n>>8; p[1]=n; }
|
||||
static void fix_packet(uint8_t *p,size_t n,bool v6)
|
||||
{
|
||||
size_t h=v6?40:20;
|
||||
p[h+2]=p[h+3]=0;
|
||||
if(v6) {
|
||||
uint32_t sum=checksum_sum(p+8,32,checksum_sum(p+h,n-h,(uint32_t)(n-h)+58));
|
||||
while(sum>>16) sum=(sum&65535)+(sum>>16);
|
||||
put16(p+h+2,(uint16_t)~sum);
|
||||
} else {
|
||||
p[10]=p[11]=0; uint16_t c=inet_chksum(p,20); memcpy(p+10,&c,2);
|
||||
c=inet_chksum(p+h,n-h); memcpy(p+h+2,&c,2);
|
||||
}
|
||||
}
|
||||
static size_t packet_for(uint8_t *p,bool v6,bool error)
|
||||
{
|
||||
size_t h=v6?40:20, n=h+8+(error?h+8:0);
|
||||
memset(p,0,1280); p[0]=v6?0x60:0x45;
|
||||
if(v6) { put16(p+4,n-h); p[6]=58; inet_pton(AF_INET6,"2001:db8::1",p+8); inet_pton(AF_INET6,"2001:db8::2",p+24); }
|
||||
else { put16(p+2,n); p[9]=1; inet_pton(AF_INET,"192.0.2.1",p+12); inet_pton(AF_INET,"192.0.2.2",p+16); }
|
||||
uint8_t *echo=p+h;
|
||||
echo[0]=error?(v6?3:11):(v6?129:0);
|
||||
if(error) {
|
||||
uint8_t *inner=echo+8; inner[0]=v6?0x60:0x45;
|
||||
if(v6) { put16(inner+4,8); inner[6]=58; inet_pton(AF_INET6,"2001:db8::1",inner+24); }
|
||||
else { put16(inner+2,28); inner[9]=1; inet_pton(AF_INET,"192.0.2.1",inner+16); uint16_t c=inet_chksum(inner,20); memcpy(inner+10,&c,2); }
|
||||
echo=inner+h; echo[0]=v6?128:8;
|
||||
}
|
||||
put16(echo+4,0x1234); put16(echo+6,7); fix_packet(p,n,v6); return n;
|
||||
}
|
||||
static void test_packets(void)
|
||||
{
|
||||
uint8_t packet[1280],code; uint16_t id=htons(0x1234),seq=htons(7);
|
||||
for(int v6=0;v6<=1;++v6) {
|
||||
diagnostic_target_t target=target_for(v6?"2001:db8::1":"192.0.2.1");
|
||||
for(int error=0;error<=1;++error) {
|
||||
size_t n=packet_for(packet,v6,error),h=v6?40:20;
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==(error?TRACE_REPLY_HOP:TRACE_REPLY_DESTINATION));
|
||||
for(size_t truncated=0;truncated<n;++truncated) CHECK(parse_trace_reply(packet,truncated,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id+1,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq+1,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet[n-1]^=1; CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED); packet[n-1]^=1;
|
||||
packet[h+1]=255; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error);
|
||||
if(error) {
|
||||
packet[n-7]=1; fix_packet(packet,n,v6); /* quoted echo code */
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error);
|
||||
packet[n-8]=0xff; fix_packet(packet,n,v6); /* quoted echo type */
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error);
|
||||
put16(packet+h+8+(v6?4:2),v6?7:27); fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error);
|
||||
packet[h]=v6?1:3; packet[h+1]=1; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNREACHABLE && code==1);
|
||||
packet[h]=v6?3:11; packet[h+1]=1; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error); packet[h+8+(v6?24:16)]^=1; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,v6,error); packet[h+8+(v6?6:9)]=v6?44:17; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
} else {
|
||||
packet[v6?8:12]^=1; fix_packet(packet,n,v6);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
diagnostic_target_t target=target_for("2001:db8::1");
|
||||
for(unsigned i=0;i<6;++i) {
|
||||
const uint8_t unsupported[]={0,43,44,50,51,60};
|
||||
size_t n=packet_for(packet,true,true); packet[6]=unsupported[i]; fix_packet(packet,n,true);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
packet_for(packet,true,true); packet[48+6]=unsupported[i]; fix_packet(packet,n,true);
|
||||
CHECK(parse_trace_reply(packet,n,&target,id,seq,&code)==TRACE_REPLY_UNRELATED);
|
||||
}
|
||||
/* Packet fuzz lengths and bytes exercise actual parser bounds deterministically. */
|
||||
uint32_t random=7;
|
||||
for(unsigned i=0;i<10000;++i) { random=random*1664525U+1013904223U; size_t n=random%sizeof(packet); for(size_t j=0;j<n;++j) { random=random*1664525U+1013904223U; packet[j]=random>>24; } (void)parse_trace_reply(packet,n,&target,id,seq,&code); }
|
||||
}
|
||||
|
||||
static void test_submillisecond_deadline(void)
|
||||
{
|
||||
const int remaining[]={1,500,999,1000};
|
||||
for(int v6=0;v6<=1;++v6) {
|
||||
diagnostic_target_t target=target_for(v6?"2001:db8::1":"192.0.2.1");
|
||||
char source[48]; int64_t rtt; uint8_t code;
|
||||
for(size_t i=0;i<sizeof(remaining)/sizeof(remaining[0]);++i) {
|
||||
reset(); now=TRACEROUTE_TIMEOUT_US-remaining[i]-1000;
|
||||
CHECK(wait_for_trace_reply(0,&target,0,0,0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_TIMEOUT);
|
||||
CHECK(recv_calls==(remaining[i]>=1000?1U:0U));
|
||||
CHECK(options==(remaining[i]>=1000?1:0));
|
||||
if(remaining[i]>=1000) CHECK(socket_timeout_ms==1);
|
||||
}
|
||||
/* One unrelated packet leaves exactly 500 us; no second recv allowed. */
|
||||
reset(); now=TRACEROUTE_TIMEOUT_US-1500-1000;
|
||||
incoming_length=packet_for(incoming,v6,false); incoming_source=target.address;
|
||||
CHECK(wait_for_trace_reply(0,&target,0,0,0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_TIMEOUT);
|
||||
CHECK(recv_calls==1 && options==1 && socket_timeout_ms==1);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_lifetimes(void)
|
||||
{
|
||||
for(int v6=0;v6<=1;++v6) {
|
||||
char *argv[]={"traceroute",v6?"2001:db8::1":"192.0.2.1","1"};
|
||||
reset(); CHECK(network_console_execute(3,argv)==0); CHECK(sockets==1 && closed==1 && sent_count==1 && ttl_options==1); CHECK(v6only_options==v6);
|
||||
int option_count=options;
|
||||
for(int fail=1;fail<=option_count;++fail) { reset(); fail_option=fail; CHECK(network_console_execute(3,argv)!=0); CHECK(closed==1); }
|
||||
reset(); send_fail=1; CHECK(network_console_execute(3,argv)!=0); CHECK(closed==1);
|
||||
reset(); recv_error=EIO; CHECK(network_console_execute(3,argv)!=0); CHECK(closed==1);
|
||||
reset(); socket_fail=1; CHECK(network_console_execute(3,argv)!=0); CHECK(closed==0);
|
||||
}
|
||||
char *scoped[]={"traceroute","fe80::1%sta","-6","1"}; reset(); CHECK(network_console_execute(4,scoped)==0); CHECK(bind_options==1 && closed==1);
|
||||
reset(); fail_option=2; CHECK(network_console_execute(4,scoped)!=0); CHECK(closed==1);
|
||||
char *hostname[]={"traceroute","example.test","1"}; reset(); send_fail=1; CHECK(network_console_execute(3,hostname)!=0); CHECK(query_count==1 && queries[0]==AF_INET && closed==1);
|
||||
diagnostic_target_t target=target_for("2001:db8::1"); char source[48]; int64_t rtt; uint8_t code;
|
||||
reset(); now=2000000; CHECK(wait_for_trace_reply(0,&target,htons(0x1234),htons(7),0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_TIMEOUT); CHECK(options==0);
|
||||
reset(); incoming_length=packet_for(incoming,true,false); incoming_source=target.address;
|
||||
CHECK(wait_for_trace_reply(0,&target,htons(0x1234),htons(7),0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_DESTINATION);
|
||||
reset(); incoming_length=packet_for(incoming,true,false); incoming_source=target_for("2001:db8::9").address;
|
||||
CHECK(wait_for_trace_reply(0,&target,htons(0x1234),htons(7),0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_TIMEOUT);
|
||||
char *ping[]={"ping","fe80::1%sta","-6","1"};
|
||||
reset(); allocation_fail=true; CHECK(network_console_execute(4,ping)!=0); CHECK(s_ping_queue_bytes==NULL);
|
||||
reset(); CHECK(network_console_execute(4,ping)==0); CHECK(ping_config.interface==2 && ping_config.target_addr.v6.zone==2 && ping_deleted==1);
|
||||
reset(); ping_create_error=1; CHECK(network_console_execute(4,ping)!=0); CHECK(ping_deleted==0);
|
||||
reset(); ping_start_error=1; CHECK(network_console_execute(4,ping)!=0); CHECK(ping_deleted==1);
|
||||
reset(); ping_delayed=true; CHECK(network_console_execute(4,ping)!=0); CHECK(s_ping_pending && ping_deleted==0);
|
||||
CHECK(ping_callbacks.cb_args==&s_ping_context);
|
||||
CHECK(network_console_execute(4,ping)!=0); CHECK(s_ping_pending);
|
||||
ping_callbacks.on_ping_end(&ping_config,ping_callbacks.cb_args);
|
||||
ping_delayed=false; CHECK(network_console_execute(4,ping)==0); CHECK(!s_ping_pending && ping_deleted==2);
|
||||
reset(); recv_error=EINTR;
|
||||
CHECK(wait_for_trace_reply(0,&target,0,0,0,source,sizeof(source),&rtt,&code)==TRACE_WAIT_TIMEOUT);
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
test_arguments(); test_resolution(); test_unscoped_linklocal();
|
||||
test_packets(); test_submillisecond_deadline(); test_lifetimes();
|
||||
CHECK(network_console_register_root_commands()==ESP_OK);
|
||||
free(s_ping_queue_bytes);
|
||||
printf("network diagnostics: %u checks + 10000 packet fuzz inputs PASS\n",checks);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user