/* Use the same PDP 1 socket path as the working WSS client. No raw ICMP * or optional Ping API; select + SO_ERROR distinguish timeout/refusal/success. */ #include #include #include "ql_rtos.h" #include "ql_data_call.h" #include "sockets.h" #include "netdb.h" #include "hal_api.h" #include "net_diag_probe.h" #include "net_diag.h" static unsigned long probe_ms(void) { return (unsigned long)hal_os_up_time_ms(); } void net_diag_probe(const char *host, unsigned short port, net_probe_result *r) { struct addrinfo hints, *addresses = NULL, *address; struct sockaddr_in local; struct ql_data_call_info data; char service[8]; unsigned long started; int fd = -1, nonblocking = 1, ret, connect_error; socklen_t error_size; memset(r, 0, sizeof(*r)); r->outcome = PROBE_LOCAL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; snprintf(service, sizeof(service), "%u", (unsigned int)port); if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; return; } started = probe_ms(); ret = getaddrinfo_with_pcid(host, service, &hints, &addresses, 1); r->dns_ms = probe_ms() - started; r->dns_result = ret; if (ret != 0 || !addresses) { r->outcome = PROBE_DNS; r->error = ret ? ret : -1; if (addresses) freeaddrinfo(addresses); return; } /* One address per round: avoid multiplying timeout/load by DNS answer count. */ for (address = addresses; address; address = address->ai_next) if (address->ai_family == AF_INET && address->ai_addr && address->ai_addrlen >= sizeof(struct sockaddr_in)) break; if (!address) { r->outcome = PROBE_DNS; r->error = -1; goto done; } inet_ntop(AF_INET, &((struct sockaddr_in *)address->ai_addr)->sin_addr, r->address, sizeof(r->address)); if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; goto done; } memset(&data, 0, sizeof(data)); ret = ql_get_data_call_info(1, 0, &data); if (ret != 0 || data.v4.state != 1) { r->error = ret ? ret : -1; goto done; } fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { r->error = ql_get_soc_errno(); goto done; } if (ioctl(fd, FIONBIO, &nonblocking) != 0) { r->error = ql_get_soc_errno(); goto done; } memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(ql_soc_generate_port()); local.sin_addr = data.v4.addr.ip; if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { r->error = ql_get_soc_errno(); goto done; } started = probe_ms(); ret = connect(fd, address->ai_addr, address->ai_addrlen); r->tcp_started = 1; if (ret == 0) { r->outcome = PROBE_OK; goto connected; } connect_error = ql_get_soc_errno(); if (connect_error != EINPROGRESS && connect_error != EWOULDBLOCK && connect_error != EALREADY) { r->outcome = PROBE_CONNECT; r->error = connect_error; goto connected; } /* Short slices allow OTA/sleep/shutdown to cancel within one select slice. */ r->outcome = PROBE_TIMEOUT; while ((unsigned long)(probe_ms() - started) < 3000UL) { fd_set writable, exceptional; struct timeval wait; int error = 0; if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; break; } FD_ZERO(&writable); FD_ZERO(&exceptional); FD_SET(fd, &writable); FD_SET(fd, &exceptional); { unsigned long remaining = 3000UL - (unsigned long)(probe_ms() - started); if (remaining > 3000UL) break; wait.tv_sec = 0; wait.tv_usec = (remaining < 200UL ? remaining : 200UL) * 1000UL; } ret = select(fd + 1, NULL, &writable, &exceptional, &wait); if (ret < 0) { r->outcome = PROBE_LOCAL; r->error = ql_get_soc_errno(); break; } if (ret == 0) continue; if (!FD_ISSET(fd, &writable) && !FD_ISSET(fd, &exceptional)) continue; error_size = sizeof(error); ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_size); if (ret != 0) { r->outcome = PROBE_LOCAL; r->error = ql_get_soc_errno(); } else { r->error = error; r->outcome = error == 0 ? PROBE_OK : PROBE_CONNECT; } break; } connected: r->tcp_ms = probe_ms() - started; done: if (fd >= 0) close(fd); freeaddrinfo(addresses); }