/* 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=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=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>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=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; }