diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index b6d971d6ff..8d1cb0cddf 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -82,6 +82,34 @@ static void ms_to_timeval(int timeout_ms, struct timeval *tv) tv->tv_usec = (timeout_ms % 1000) * 1000; } +static int esp_tls_tcp_enable_keep_alive(int fd, tls_keep_alive_cfg_t *cfg) +{ + int keep_alive_enable = 1; + int keep_alive_idle = cfg->keep_alive_idle; + int keep_alive_interval = cfg->keep_alive_interval; + int keep_alive_count = 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); + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive_enable, sizeof(keep_alive_enable)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt SO_KEEPALIVE"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_alive_idle, sizeof(keep_alive_idle)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPIDLE"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_alive_interval, sizeof(keep_alive_interval)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPINTVL"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_alive_count, sizeof(keep_alive_count)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPCNT"); + return -1; + } + + return 0; +} + static int esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, const esp_tls_cfg_t *cfg) { int ret = -1; @@ -117,6 +145,12 @@ static int esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, 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; + } + } } if (cfg->non_block) { int flags = fcntl(fd, F_GETFL, 0); diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index b48c1e42d1..499e06679a 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -43,6 +43,16 @@ typedef enum esp_tls_conn_state { ESP_TLS_DONE, } esp_tls_conn_state_t; +/** + * @brief Keep alive parameters structure + */ +typedef struct tls_keep_alive_cfg { + bool keep_alive_enable; /*!< Enable keep-alive timeout */ + int keep_alive_idle; /*!< Keep-alive idle time (second) */ + int keep_alive_interval; /*!< Keep-alive interval time (second) */ + int keep_alive_count; /*!< Keep-alive packet retry send count */ +} tls_keep_alive_cfg_t; + /** * @brief ESP-TLS configuration parameters */ @@ -93,6 +103,8 @@ typedef struct esp_tls_cfg { If NULL, server certificate CN must match hostname. */ bool skip_common_name; /*!< Skip any validation of server certificate CN field */ + + tls_keep_alive_cfg_t *keep_alive_cfg; /*!< Enable TCP keep-alive timeout for SSL connection */ } esp_tls_cfg_t; /** diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 49919b24f8..535dc82bed 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -118,6 +118,7 @@ struct esp_http_client { bool first_line_prepared; int header_index; bool is_async; + esp_transport_keep_alive_t keep_alive_cfg; }; typedef struct esp_http_client esp_http_client_t; @@ -138,6 +139,9 @@ static const char *DEFAULT_HTTP_PROTOCOL = "HTTP/1.1"; static const char *DEFAULT_HTTP_PATH = "/"; static int DEFAULT_MAX_REDIRECT = 10; static int DEFAULT_TIMEOUT_MS = 5000; +static const int DEFAULT_KEEP_ALIVE_IDLE = 5; +static const int DEFAULT_KEEP_ALIVE_INTERVAL= 5; +static const int DEFAULT_KEEP_ALIVE_COUNT= 3; static const char *HTTP_METHOD_MAPPING[] = { "GET", @@ -480,7 +484,7 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co { esp_http_client_handle_t client; - esp_transport_handle_t tcp; + esp_transport_handle_t tcp = NULL; bool _success; _success = ( @@ -511,8 +515,15 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co ESP_LOGE(TAG, "Error initialize transport"); goto error; } + if (config->keep_alive_enable == true) { + client->keep_alive_cfg.keep_alive_enable = true; + client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? DEFAULT_KEEP_ALIVE_IDLE : config->keep_alive_idle; + client->keep_alive_cfg.keep_alive_interval = (config->keep_alive_interval == 0) ? DEFAULT_KEEP_ALIVE_INTERVAL : config->keep_alive_interval; + client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? DEFAULT_KEEP_ALIVE_COUNT : config->keep_alive_count; + esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); + } #ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS - esp_transport_handle_t ssl; + esp_transport_handle_t ssl = NULL; _success = ( (ssl = esp_transport_ssl_init()) && (esp_transport_set_default_port(ssl, DEFAULT_HTTPS_PORT) == ESP_OK) && @@ -541,6 +552,10 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co if (config->skip_cert_common_name_check) { esp_transport_ssl_skip_common_name_check(ssl); } + + if (config->keep_alive_enable == true) { + esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg); + } #endif if (_set_config(client, config) != ESP_OK) { diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index 881fcd4b44..fddbf58a52 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -120,6 +120,10 @@ typedef struct { bool is_async; /*!< Set asynchronous mode, only supported with HTTPS for now */ bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */ bool skip_cert_common_name_check; /*!< Skip any validation of server certificate CN field */ + bool keep_alive_enable; /*!< Enable keep-alive timeout */ + int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */ + int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ + int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ } esp_http_client_config_t; /** diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 87b4a7ce49..97663d19ad 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -39,6 +39,9 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) #define WEBSOCKET_PING_TIMEOUT_MS (10*1000) #define WEBSOCKET_EVENT_QUEUE_SIZE (1) +#define WEBSOCKET_KEEP_ALIVE_IDLE (5) +#define WEBSOCKET_KEEP_ALIVE_INTERVAL (5) +#define WEBSOCKET_KEEP_ALIVE_COUNT (3) #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \ @@ -104,6 +107,7 @@ struct esp_websocket_client { ws_transport_opcodes_t last_opcode; int payload_len; int payload_offset; + esp_transport_keep_alive_t keep_alive_cfg; }; static uint64_t _tick_get_ms(void) @@ -280,6 +284,13 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie return NULL; } + if (config->keep_alive_enable == true) { + client->keep_alive_cfg.keep_alive_enable = true; + client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? WEBSOCKET_KEEP_ALIVE_IDLE : config->keep_alive_idle; + client->keep_alive_cfg.keep_alive_interval = (config->keep_alive_interval == 0) ? WEBSOCKET_KEEP_ALIVE_INTERVAL : config->keep_alive_interval; + client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count; + } + client->lock = xSemaphoreCreateRecursiveMutex(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); @@ -293,6 +304,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); + esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup @@ -313,6 +325,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie if (config->cert_pem) { esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); } + esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg); esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup esp_transport_handle_t wss = esp_transport_ws_init(ssl); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 6a8bca2b97..43857f552b 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -86,6 +86,10 @@ typedef struct { char *user_agent; /*!< Websocket user-agent */ char *headers; /*!< Websocket additional headers */ int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received, disabled if value is 0 */ + bool keep_alive_enable; /*!< Enable keep-alive timeout */ + int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */ + int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ + int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ } esp_websocket_client_config_t; /** diff --git a/components/tcp_transport/include/esp_transport.h b/components/tcp_transport/include/esp_transport.h index e163a010cd..1499e41fa3 100644 --- a/components/tcp_transport/include/esp_transport.h +++ b/components/tcp_transport/include/esp_transport.h @@ -16,11 +16,21 @@ #define _ESP_TRANSPORT_H_ #include +#include #ifdef __cplusplus extern "C" { #endif +/** +* @brief Keep alive parameters structure +*/ +typedef struct esp_transport_keepalive { + bool keep_alive_enable; /*!< Enable keep-alive timeout */ + int keep_alive_idle; /*!< Keep-alive idle time (second) */ + int keep_alive_interval; /*!< Keep-alive interval time (second) */ + int keep_alive_count; /*!< Keep-alive packet retry send count */ +} esp_transport_keep_alive_t; typedef struct esp_transport_list_t* esp_transport_list_handle_t; typedef struct esp_transport_item_t* esp_transport_handle_t; @@ -298,6 +308,22 @@ esp_err_t esp_transport_set_async_connect_func(esp_transport_handle_t t, connect */ esp_err_t esp_transport_set_parent_transport_func(esp_transport_handle_t t, payload_transfer_func _parent_transport); +/** + * @brief Set keep-alive configuration + * + * @param[in] t The transport handle + * @param[in] keep_alive_cfg The keep-alive config + */ +void esp_transport_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg); + +/** + * @brief Get keep-alive config of this transport + * + * @param[in] t The transport handle + * + * @return The keep-alive configuration + */ +void *esp_transport_get_keep_alive(esp_transport_handle_t t); #ifdef __cplusplus } #endif diff --git a/components/tcp_transport/include/esp_transport_ssl.h b/components/tcp_transport/include/esp_transport_ssl.h index 0f83c1d6e4..fedd1c46b2 100644 --- a/components/tcp_transport/include/esp_transport_ssl.h +++ b/components/tcp_transport/include/esp_transport_ssl.h @@ -78,6 +78,14 @@ void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char */ void esp_transport_ssl_skip_common_name_check(esp_transport_handle_t t); +/** + * @brief Set keep-alive status in current ssl context + * + * @param[in] t ssl transport + * @param[in] keep_alive_cfg The handle for keep-alive configuration + */ +void esp_transport_ssl_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg); + #ifdef __cplusplus } #endif diff --git a/components/tcp_transport/include/esp_transport_tcp.h b/components/tcp_transport/include/esp_transport_tcp.h index 57ad453309..b78ecc1d1e 100644 --- a/components/tcp_transport/include/esp_transport_tcp.h +++ b/components/tcp_transport/include/esp_transport_tcp.h @@ -21,6 +21,14 @@ extern "C" { #endif +/** + * @brief Set TCP keep-alive configuration + * + * @param[in] t The transport handle + * @param[in] keep_alive_cfg The keep-alive config + */ +void esp_transport_tcp_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg); + /** * @brief Create TCP transport, the transport handle must be release esp_transport_destroy callback * diff --git a/components/tcp_transport/transport.c b/components/tcp_transport/transport.c index f6fa311ae7..63501e6d5d 100644 --- a/components/tcp_transport/transport.c +++ b/components/tcp_transport/transport.c @@ -42,7 +42,7 @@ struct esp_transport_item_t { trans_func _destroy; /*!< Destroy and free transport */ connect_async_func _connect_async; /*!< non-blocking connect function of this transport */ payload_transfer_func _parent_transfer; /*!< Function returning underlying transport layer */ - + esp_transport_keep_alive_t *keep_alive_cfg; /*!< TCP keep-alive config */ STAILQ_ENTRY(esp_transport_item_t) next; }; @@ -277,3 +277,18 @@ esp_err_t esp_transport_set_parent_transport_func(esp_transport_handle_t t, payl t->_parent_transfer = _parent_transport; return ESP_OK; } + +void esp_transport_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg) +{ + if (t && keep_alive_cfg) { + t->keep_alive_cfg = keep_alive_cfg; + } +} + +void *esp_transport_get_keep_alive(esp_transport_handle_t t) +{ + if (t) { + return t->keep_alive_cfg; + } + return NULL; +} diff --git a/components/tcp_transport/transport_ssl.c b/components/tcp_transport/transport_ssl.c index 4d4f72b8b6..40549c46e8 100644 --- a/components/tcp_transport/transport_ssl.c +++ b/components/tcp_transport/transport_ssl.c @@ -227,6 +227,14 @@ void esp_transport_ssl_skip_common_name_check(esp_transport_handle_t t) } } +void esp_transport_ssl_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg) +{ + transport_ssl_t *ssl = esp_transport_get_context_data(t); + if (t && ssl) { + ssl->cfg.keep_alive_cfg = (tls_keep_alive_cfg_t *)keep_alive_cfg; + } +} + esp_transport_handle_t esp_transport_ssl_init() { esp_transport_handle_t t = esp_transport_init(); diff --git a/components/tcp_transport/transport_tcp.c b/components/tcp_transport/transport_tcp.c index 8bd429eab1..3d5e7cb71b 100644 --- a/components/tcp_transport/transport_tcp.c +++ b/components/tcp_transport/transport_tcp.c @@ -51,11 +51,40 @@ static int resolve_dns(const char *host, struct sockaddr_in *ip) { return ESP_OK; } +static int tcp_enable_keep_alive(int fd, esp_transport_keep_alive_t *keep_alive_cfg) +{ + int keep_alive_enable = 1; + int keep_alive_idle = keep_alive_cfg->keep_alive_idle; + int keep_alive_interval = keep_alive_cfg->keep_alive_interval; + int keep_alive_count = 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); + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive_enable, sizeof(keep_alive_enable)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt SO_KEEPALIVE"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_alive_idle, sizeof(keep_alive_idle)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPIDLE"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_alive_interval, sizeof(keep_alive_interval)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPINTVL"); + return -1; + } + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_alive_count, sizeof(keep_alive_count)) != 0) { + ESP_LOGE(TAG, "Fail to setsockopt TCP_KEEPCNT"); + return -1; + } + + return 0; +} + static int tcp_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms) { struct sockaddr_in remote_ip; struct timeval tv; transport_tcp_t *tcp = esp_transport_get_context_data(t); + esp_transport_keep_alive_t *keep_alive_cfg = esp_transport_get_keep_alive(t); bzero(&remote_ip, sizeof(struct sockaddr_in)); @@ -80,7 +109,15 @@ static int tcp_connect(esp_transport_handle_t t, const char *host, int port, int setsockopt(tcp->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(tcp->sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - + // Set socket keep-alive option + if (keep_alive_cfg && keep_alive_cfg->keep_alive_enable) { + if (tcp_enable_keep_alive(tcp->sock, keep_alive_cfg) < 0) { + ESP_LOGE(TAG, "Error to set tcp [socket=%d] keep-alive", tcp->sock); + close(tcp->sock); + tcp->sock = -1; + return -1; + } + } ESP_LOGD(TAG, "[sock=%d],connecting to server IP:%s,Port:%d...", tcp->sock, ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr), port); if (connect(tcp->sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0) { @@ -180,6 +217,11 @@ static esp_err_t tcp_destroy(esp_transport_handle_t t) return 0; } +void esp_transport_tcp_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg) +{ + esp_transport_set_keep_alive(t, keep_alive_cfg); +} + esp_transport_handle_t esp_transport_tcp_init() { esp_transport_handle_t t = esp_transport_init(); diff --git a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c index 6850d78a38..d0d993a944 100644 --- a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c +++ b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c @@ -138,6 +138,7 @@ void advanced_ota_example_task(void * pvParameter) .url = CONFIG_FIRMWARE_UPGRADE_URL, .cert_pem = (char *)server_cert_pem_start, .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT, + .keep_alive_enable = true, }; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN diff --git a/examples/system/ota/native_ota_example/main/native_ota_example.c b/examples/system/ota/native_ota_example/main/native_ota_example.c index a2c732f981..b2c664f32d 100644 --- a/examples/system/ota/native_ota_example/main/native_ota_example.c +++ b/examples/system/ota/native_ota_example/main/native_ota_example.c @@ -182,6 +182,7 @@ static void ota_example_task(void *pvParameter) .url = EXAMPLE_SERVER_URL, .cert_pem = (char *)server_cert_pem_start, .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT, + .keep_alive_enable = true, }; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN diff --git a/examples/system/ota/simple_ota_example/main/simple_ota_example.c b/examples/system/ota/simple_ota_example/main/simple_ota_example.c index ffcec6cd67..ec6ed5a722 100644 --- a/examples/system/ota/simple_ota_example/main/simple_ota_example.c +++ b/examples/system/ota/simple_ota_example/main/simple_ota_example.c @@ -145,6 +145,7 @@ void simple_ota_example_task(void * pvParameter) .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL, .cert_pem = (char *)server_cert_pem_start, .event_handler = _http_event_handler, + .keep_alive_enable = true, }; #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN