esp-tls: Rework tcp_connect() to use more subroutines

Refactored the esp_tcp_connect() functionality to break it down to
* dns-resolution + socket creation
* set configured socket options
* set/reset non-block mode
* the actual connection in non-blocking mode
This commit is contained in:
David Cermak 2021-02-04 16:50:56 +01:00
parent 1fa0db8d44
commit 2d25252746

View File

@ -133,8 +133,9 @@ esp_tls_t *esp_tls_init(void)
return tls; return tls;
} }
static esp_err_t resolve_host_name(const char *host, size_t hostlen, struct addrinfo **address_info) static esp_err_t esp_tls_hostname_to_fd(const char *host, size_t hostlen, int port, struct sockaddr_storage *address, int* fd)
{ {
struct addrinfo *address_info;
struct addrinfo hints; struct addrinfo hints;
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
@ -146,14 +147,40 @@ static esp_err_t resolve_host_name(const char *host, size_t hostlen, struct addr
} }
ESP_LOGD(TAG, "host:%s: strlen %lu", use_host, (unsigned long)hostlen); ESP_LOGD(TAG, "host:%s: strlen %lu", use_host, (unsigned long)hostlen);
int res = getaddrinfo(use_host, NULL, &hints, address_info); int res = getaddrinfo(use_host, NULL, &hints, &address_info);
if (res != 0 || *address_info == NULL) { if (res != 0 || address_info == NULL) {
ESP_LOGE(TAG, "couldn't get hostname for :%s: " ESP_LOGE(TAG, "couldn't get hostname for :%s: "
"getaddrinfo() returns %d, addrinfo=%p", use_host, res, *address_info); "getaddrinfo() returns %d, addrinfo=%p", use_host, res, address_info);
free(use_host); free(use_host);
return ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME; return ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME;
} }
free(use_host); 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 (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 ));
} else 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 ));
} 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; return ESP_OK;
} }
@ -163,95 +190,93 @@ static void ms_to_timeval(int timeout_ms, struct timeval *tv)
tv->tv_usec = (timeout_ms % 1000) * 1000; tv->tv_usec = (timeout_ms % 1000) * 1000;
} }
static int esp_tls_tcp_enable_keep_alive(int fd, tls_keep_alive_cfg_t *cfg) static esp_err_t esp_tls_set_socket_options(int fd, const esp_tls_cfg_t *cfg)
{ {
int keep_alive_enable = 1; if (cfg && cfg->timeout_ms >= 0) {
int keep_alive_idle = cfg->keep_alive_idle; struct timeval tv;
int keep_alive_interval = cfg->keep_alive_interval; ms_to_timeval(cfg->timeout_ms, &tv);
int keep_alive_count = cfg->keep_alive_count; if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt SO_RCVTIMEO");
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt SO_SNDTIMEO");
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
if (cfg->keep_alive_cfg && cfg->keep_alive_cfg->keep_alive_enable) {
int keep_alive_enable = 1;
int keep_alive_idle = cfg->keep_alive_cfg->keep_alive_idle;
int keep_alive_interval = cfg->keep_alive_cfg->keep_alive_interval;
int keep_alive_count = cfg->keep_alive_cfg->keep_alive_count;
ESP_LOGD(TAG, "Enable TCP keep alive. idle: %d, interval: %d, count: %d", keep_alive_idle, keep_alive_interval, keep_alive_count); ESP_LOGD(TAG, "Enable TCP keep alive. idle: %d, interval: %d, count: %d", keep_alive_idle, keep_alive_interval, keep_alive_count);
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive_enable, sizeof(keep_alive_enable)) != 0) { if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive_enable, sizeof(keep_alive_enable)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt SO_KEEPALIVE"); ESP_LOGE(TAG, "Fail to setsockopt SO_KEEPALIVE");
return -1; return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_alive_enable, sizeof(keep_alive_enable)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPIDLE");
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_alive_interval, sizeof(keep_alive_interval)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPINTVL");
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_alive_count, sizeof(keep_alive_count)) != 0) {
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPCNT");
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
}
} }
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_alive_idle, sizeof(keep_alive_idle)) != 0) { return ESP_OK;
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPIDLE"); }
return -1;
} static esp_err_t esp_tls_set_socket_non_blocking(int fd, bool non_blocking)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_alive_interval, sizeof(keep_alive_interval)) != 0) { {
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPINTVL"); int flags;
return -1; if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {
} ESP_LOGE(TAG, "[sock=%d] get file flags error: %s", fd, strerror(errno));
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_alive_count, sizeof(keep_alive_count)) != 0) { return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPCNT");
return -1;
} }
return 0; if (non_blocking) {
flags |= O_NONBLOCK;
} else {
flags &= ~O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, flags) < 0) {
ESP_LOGE(TAG, "[sock=%d] set blocking/nonblocking error: %s", fd, strerror(errno));
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
return ESP_OK;
} }
static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, const esp_tls_t *tls, const esp_tls_cfg_t *cfg) static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, const esp_tls_t *tls, const esp_tls_cfg_t *cfg)
{ {
esp_err_t ret; struct sockaddr_storage address;
struct addrinfo *addrinfo; int fd;
if ((ret = resolve_host_name(host, hostlen, &addrinfo)) != ESP_OK) { esp_err_t ret = esp_tls_hostname_to_fd(host, hostlen, port, &address, &fd);
if (ret != ESP_OK) {
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
return ret; return ret;
} }
int fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); // Set timeout options and keep-alive options if configured
if (fd < 0) { ret = esp_tls_set_socket_options(fd, cfg);
ESP_LOGE(TAG, "Failed to create socket (family %d socktype %d protocol %d)", addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); if (ret != ESP_OK) {
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno); goto err;
ret = ESP_ERR_ESP_TLS_CANNOT_CREATE_SOCKET;
goto err_freeaddr;
} }
void *addr_ptr; // Set to non block before connecting to better control connection timeout
if (addrinfo->ai_family == AF_INET) { ret = esp_tls_set_socket_non_blocking(fd, true);
struct sockaddr_in *p = (struct sockaddr_in *)addrinfo->ai_addr; if (ret != ESP_OK) {
p->sin_port = htons(port); goto err;
ESP_LOGD(TAG, "[sock=%d] Resolved IPv4 address: %s", fd, ipaddr_ntoa((const ip_addr_t*)&p->sin_addr.s_addr));
addr_ptr = p;
} else if (addrinfo->ai_family == AF_INET6) {
struct sockaddr_in6 *p = (struct sockaddr_in6 *)addrinfo->ai_addr;
ESP_LOGD(TAG, "[sock=%d] Resolved IPv6 address: %s", fd, ip6addr_ntoa((const ip6_addr_t*)&p->sin6_addr));
p->sin6_port = htons(port);
p->sin6_family = AF_INET6;
addr_ptr = p;
} else {
ESP_LOGE(TAG, "Unsupported protocol family %d", addrinfo->ai_family);
ret = ESP_ERR_ESP_TLS_UNSUPPORTED_PROTOCOL_FAMILY;
goto err_freesocket;
}
if (cfg && cfg->timeout_ms >= 0) {
struct timeval tv;
ms_to_timeval(cfg->timeout_ms, &tv);
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (cfg->keep_alive_cfg && cfg->keep_alive_cfg->keep_alive_enable) {
if (esp_tls_tcp_enable_keep_alive(fd, cfg->keep_alive_cfg) < 0) {
ESP_LOGE(TAG, "Error setting keep-alive");
goto err_freesocket;
}
}
}
// Set socket to non-blocking
int flags;
if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {
ESP_LOGE(TAG, "[sock=%d] get file flags error: %s", fd, strerror(errno));
goto err_freesocket;
}
if (fcntl(fd, F_SETFL, flags |= O_NONBLOCK) < 0) {
ESP_LOGE(TAG, "[sock=%d] set nonblocking error: %s", fd, strerror(errno));
goto err_freesocket;
} }
ret = ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST;
ESP_LOGD(TAG, "[sock=%d] Connecting to server. HOST: %s, Port: %d", fd, host, port); ESP_LOGD(TAG, "[sock=%d] Connecting to server. HOST: %s, Port: %d", fd, host, port);
if (connect(fd, (struct sockaddr *)&address, sizeof(struct sockaddr)) < 0) {
if (connect(fd, (struct sockaddr *)(addr_ptr), sizeof(struct sockaddr)) < 0) {
if (errno == EINPROGRESS) { if (errno == EINPROGRESS) {
fd_set fdset; fd_set fdset;
struct timeval tv = { .tv_usec = 0, .tv_sec = 10 }; // Default connection timeout is 10 s struct timeval tv = { .tv_usec = 0, .tv_sec = 10 }; // Default connection timeout is 10 s
@ -259,7 +284,6 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
if (cfg && cfg->non_block) { if (cfg && cfg->non_block) {
// Non-blocking mode -> just return successfully at this stage // Non-blocking mode -> just return successfully at this stage
*sockfd = fd; *sockfd = fd;
freeaddrinfo(addrinfo);
return ESP_OK; return ESP_OK;
} }
@ -273,12 +297,12 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
if (res < 0) { if (res < 0) {
ESP_LOGE(TAG, "[sock=%d] select() error: %s", fd, strerror(errno)); ESP_LOGE(TAG, "[sock=%d] select() error: %s", fd, strerror(errno));
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno); ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, errno);
goto err_freesocket; goto err;
} }
else if (res == 0) { else if (res == 0) {
ESP_LOGE(TAG, "[sock=%d] select() timeout", fd); ESP_LOGE(TAG, "[sock=%d] select() timeout", fd);
ret = ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST; ret = ESP_ERR_ESP_TLS_CONNECTION_TIMEOUT;
goto err_freesocket; goto err;
} else { } else {
int sockerr; int sockerr;
socklen_t len = (socklen_t)sizeof(int); socklen_t len = (socklen_t)sizeof(int);
@ -286,43 +310,33 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)(&sockerr), &len) < 0) { if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)(&sockerr), &len) < 0) {
ESP_LOGE(TAG, "[sock=%d] getsockopt() error: %s", fd, strerror(errno)); ESP_LOGE(TAG, "[sock=%d] getsockopt() error: %s", fd, strerror(errno));
ret = ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED; ret = ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
goto err_freesocket; goto err;
} }
else if (sockerr) { else if (sockerr) {
ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, sockerr); ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_SYSTEM, sockerr);
ESP_LOGE(TAG, "[sock=%d] delayed connect error: %s", fd, strerror(sockerr)); ESP_LOGE(TAG, "[sock=%d] delayed connect error: %s", fd, strerror(sockerr));
ret = ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST; goto err;
goto err_freesocket;
} }
} }
} else { } else {
ESP_LOGE(TAG, "[sock=%d] connect() error: %s", fd, strerror(errno)); ESP_LOGE(TAG, "[sock=%d] connect() error: %s", fd, strerror(errno));
goto err_freesocket; goto err;
} }
} }
if (cfg && cfg->non_block == false) { if (cfg && cfg->non_block == false) {
// Reset socket to blocking (unless non-blocking option set) // reset back to blocking mode (unless non_block configured)
if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) { ret = esp_tls_set_socket_non_blocking(fd, false);
ESP_LOGE(TAG, "[sock=%d] get file flags error: %s", fd, strerror(errno)); if (ret != ESP_OK) {
ret = ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED; goto err;
goto err_freesocket;
}
if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
ESP_LOGE(TAG, "[sock=%d] reset blocking error: %s", fd, strerror(errno));
ret = ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
goto err_freesocket;
} }
} }
*sockfd = fd; *sockfd = fd;
freeaddrinfo(addrinfo);
return ESP_OK; return ESP_OK;
err_freesocket: err:
close(fd); close(fd);
err_freeaddr:
freeaddrinfo(addrinfo);
return ret; return ret;
} }