接前一篇文章:ESP32-C3在MQTT访问时出现“Last error code reported from esp-tls: 0x8001”和问题的分析(1)
上一回说明了笔者在调试测试MQTT的过程中遇到的另一个问题:“Last error code reported from esp-tls: 0x8001”。结合log给出了初步分析:
本回继续深入探查。
上一回解析的log都出自于main.c中的MQTT事件回调函数mqtt_event_handler(),那么究竟是什么导致的该回调函数中出现MQTT_EVENT_ERROR这个 case的呢?这就要再往前查找了。从以上log中的“esp-tls: couldn't get hostname for :iot-emqx-pre.nanshe-tech.com: getaddrinfo() returns 202, addrinfo=0x0”开始查起。
这一个打印出自于components\components\esp-tls\esp_tls.c的esp_tls_hostname_to_fd函数,该函数代码如下:
static esp_err_t esp_tls_hostname_to_fd(const char *host, size_t hostlen, int port, esp_tls_addr_family_t addr_family, struct sockaddr_storage *address, int* fd)
{
struct addrinfo *address_info;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
switch (addr_family) {
case ESP_TLS_AF_INET:
hints.ai_family = AF_INET;
break;
case ESP_TLS_AF_INET6:
hints.ai_family = AF_INET6;
break;
default:
hints.ai_family = AF_UNSPEC;
break;
}
hints.ai_socktype = SOCK_STREAM;
char *use_host = strndup(host, hostlen);
if (!use_host) {
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "host:%s: strlen %lu", use_host, (unsigned long)hostlen);
int res = getaddrinfo(use_host, NULL, &hints, &address_info);
if (res != 0 || address_info == NULL) {
ESP_LOGE(TAG, "couldn't get hostname for :%s: "
"getaddrinfo() returns %d, addrinfo=%p", use_host, res, address_info);
free(use_host);
return ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME;
}
free(use_host);
*fd = socket(address_info->ai_family, address_info->ai_socktype, address_info->ai_protocol);
if (*fd < 0) {
ESP_LOGE(TAG, "Failed to create socket (family %d socktype %d protocol %d)", address_info->ai_family, address_info->ai_socktype, address_info->ai_protocol);
freeaddrinfo(address_info);
return ESP_ERR_ESP_TLS_CANNOT_CREATE_SOCKET;
}
#if IPV4_ENABLED
if (address_info->ai_family == AF_INET) {
struct sockaddr_in *p = (struct sockaddr_in *)address_info->ai_addr;
p->sin_port = htons(port);
ESP_LOGD(TAG, "[sock=%d] Resolved IPv4 address: %s", *fd, ipaddr_ntoa((const ip_addr_t*)&p->sin_addr.s_addr));
memcpy(address, p, sizeof(struct sockaddr ));
}
#endif
#if IPV4_ENABLED && IPV6_ENABLED
else
#endif
#if IPV6_ENABLED
if (address_info->ai_family == AF_INET6) {
struct sockaddr_in6 *p = (struct sockaddr_in6 *)address_info->ai_addr;
p->sin6_port = htons(port);
p->sin6_family = AF_INET6;
ESP_LOGD(TAG, "[sock=%d] Resolved IPv6 address: %s", *fd, ip6addr_ntoa((const ip6_addr_t*)&p->sin6_addr));
memcpy(address, p, sizeof(struct sockaddr_in6 ));
}
#endif
else {
ESP_LOGE(TAG, "Unsupported protocol family %d", address_info->ai_family);
close(*fd);
freeaddrinfo(address_info);
return ESP_ERR_ESP_TLS_UNSUPPORTED_PROTOCOL_FAMILY;
}
freeaddrinfo(address_info);
return ESP_OK;
}
打印出自于这一段:
int res = getaddrinfo(use_host, NULL, &hints, &address_info);
if (res != 0 || address_info == NULL) {
ESP_LOGE(TAG, "couldn't get hostname for :%s: "
"getaddrinfo() returns %d, addrinfo=%p", use_host, res, address_info);
free(use_host);
return ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME;
}
出现打印,说明进入了判断体,最终返回ESP_ESP_TLS_CANNOT_RESOVLE_HOSTNAME。ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME宏在components\components\esp-tls\esp_tls_errors.h中定义,代码如下:
#define ESP_ERR_ESP_TLS_BASE 0x8000 /*!< Starting number of ESP-TLS error codes */
/* generic esp-tls error codes */
#define ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME (ESP_ERR_ESP_TLS_BASE + 0x01) /*!< Error if hostname couldn't be resolved upon tls connection */
#define ESP_ERR_ESP_TLS_CANNOT_CREATE_SOCKET (ESP_ERR_ESP_TLS_BASE + 0x02) /*!< Failed to create socket */
#define ESP_ERR_ESP_TLS_UNSUPPORTED_PROTOCOL_FAMILY (ESP_ERR_ESP_TLS_BASE + 0x03) /*!< Unsupported protocol family */
#define ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST (ESP_ERR_ESP_TLS_BASE + 0x04) /*!< Failed to connect to host */
#define ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED (ESP_ERR_ESP_TLS_BASE + 0x05) /*!< failed to set/get socket option */
#define ESP_ERR_ESP_TLS_CONNECTION_TIMEOUT (ESP_ERR_ESP_TLS_BASE + 0x06) /*!< new connection in esp_tls_low_level_conn connection timeouted */
#define ESP_ERR_ESP_TLS_SE_FAILED (ESP_ERR_ESP_TLS_BASE + 0x07) /*< esp-tls use Secure Element returned failed */
#define ESP_ERR_ESP_TLS_TCP_CLOSED_FIN (ESP_ERR_ESP_TLS_BASE + 0x08) /*< esp-tls's TPC transport connection has benn closed (in a clean way) */
根据定义,ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME宏的值为0x8001。这就与上一回讲到的对应起来了:
既然这个0x8001是在getaddrinfo函数出错的时候返回的,那么看一下getaddrinfo返回错误的原因是什么。
函数原型
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
函数功能
getaddrinfo函数根据给定的主机名和服务名,返回一个struct addrinfo结构链表。每个struct addrinfo结构都包含一个互联网地址。getaddrinfo函数将gethostbyname函数和getservname函数提供的功能组合到一个接口中。
函数参数
主机名或IP地址字符串,如果为NULL,则表示本地主机。
一个主机名或地址串(IPv4的点分十进制数串或IPv6的十六进制数串)。如果hints.ai_flags包含AI_NUMERICHOST标志,则此参数必须是IP地址字符串。
服务名称或端口号字符串,如果为NULL,则返回所有可用的套接字地址结构。
一个服务名或十进制端口号数串。如果此参数被设置为一个服务名称,则会将其转换为相应的端口号。如果设置为NULL,则返回的套接字地址的端口号将保持未初始化状态。如果在hints.ai_flags中指定了AI_NUMERICSERV,并且此参数不为NULL,则此参数必须指向包含数字端口号的字符串。
用于指定期望的套接字类型、协议及其它选项的addrinfo结构体指针。
hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。hints参数中,调用者可以设置的字段有:ai_flags、ai_family、ai_socktype、ai_protocol。
用于存储结果的addrinfo结构体指针。
出参,如果本函数返回成功0,则res参数指向的变量已被填入一个指针,它指向的是由其中的 ai_next成员串接起来的addrinfo结构链表。
返回值
成功返回0,失败返回非0,取值如下所示:
代码示例
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
struct addrinfo hints, *result, *p; // 定义addrinfo结构体变量
int status; // getaddrinfo函数返回值
char ipstr[INET6_ADDRSTRLEN]; // 存储IP地址字符串的缓冲区
if (argc != 2) { // 检查命令行参数数量是否正确
fprintf(stderr, "Usage: %s hostname\n", argv[0]);
exit(EXIT_FAILURE);
}
memset(&hints, 0, sizeof(hints)); // 初始化hints结构体
hints.ai_family = AF_UNSPEC; // 不限制IP地址版本
hints.ai_socktype = SOCK_STREAM; // 使用TCP协议
if ((status = getaddrinfo(argv[1], NULL, &hints, &result)) != 0) { // 解析主机名并将结果存储在result指针中
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
exit(EXIT_FAILURE);
}
printf("IP addresses for %s:\n\n", argv[1]);
for (p = result; p != NULL; p = p->ai_next) { // 遍历result指针中的所有套接字地址结构
void *addr;
char *ipver;
if (p->ai_family == AF_INET) { // IPv4地址
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6地址
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr)); // 将套接字地址结构转换为IP地址字符串
printf(" %s: %s\n", ipver, ipstr); // 打印IP地址和版本号
}
freeaddrinfo(result); // 释放由getaddrinfo函数分配的内存
return EXIT_SUCCESS; // 程序正常退出
}
202这一错误对应的宏定义在C:\Espressif\frameworks\esp-idf-v5.2.1\components\lwip\lwip\src\include\lwip\netdb.h中,代码如下:
#if LWIP_DNS_API_DEFINE_ERRORS
/** Errors used by the DNS API functions, h_errno can be one of them */
#define EAI_NONAME 200
#define EAI_SERVICE 201
#define EAI_FAIL 202
#define EAI_MEMORY 203
#define EAI_FAMILY 204
#define HOST_NOT_FOUND 210
#define NO_DATA 211
#define NO_RECOVERY 212
#define TRY_AGAIN 213
#endif /* LWIP_DNS_API_DEFINE_ERRORS */
更多解析请看下回。