Merge branch 'feature/transport_support_dev_bind' into 'master'

transport: Support bind socket to specified interface

Closes IDFGH-4232

See merge request espressif/esp-idf!11961
This commit is contained in:
Mahavir Jain 2021-03-10 06:34:39 +00:00
commit b8cd8cc2df
14 changed files with 155 additions and 21 deletions

View File

@ -196,16 +196,18 @@ static void ms_to_timeval(int timeout_ms, struct timeval *tv)
static esp_err_t esp_tls_set_socket_options(int fd, const esp_tls_cfg_t *cfg)
{
if (cfg && cfg->timeout_ms >= 0) {
struct timeval tv;
ms_to_timeval(cfg->timeout_ms, &tv);
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) {
if (cfg->timeout_ms >= 0) {
struct timeval tv;
ms_to_timeval(cfg->timeout_ms, &tv);
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;
@ -231,6 +233,15 @@ static esp_err_t esp_tls_set_socket_options(int fd, const esp_tls_cfg_t *cfg)
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
}
if (cfg->if_name) {
if (cfg->if_name->ifr_name[0] != 0) {
ESP_LOGD(TAG, "Bind [sock=%d] to interface %s", fd, cfg->if_name->ifr_name);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, cfg->if_name, sizeof(struct ifreq)) != 0) {
ESP_LOGE(TAG, "Bind [sock=%d] to interface %s fail", fd, cfg->if_name->ifr_name);
return ESP_ERR_ESP_TLS_SOCKET_SETOPT_FAILED;
}
}
}
}
return ESP_OK;
}
@ -266,7 +277,7 @@ static esp_err_t esp_tcp_connect(const char *host, int hostlen, int port, int *s
return ret;
}
// Set timeout options and keep-alive options if configured
// Set timeout options, keep-alive options and bind device options if configured
ret = esp_tls_set_socket_options(fd, cfg);
if (ret != ESP_OK) {
goto err;

View File

@ -172,6 +172,7 @@ typedef struct esp_tls_cfg {
void *ds_data; /*!< Pointer for digital signature peripheral context */
bool is_plain_tcp; /*!< Use non-TLS connection: When set to true, the esp-tls uses
plain TCP transport rather then TLS/SSL connection */
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
} esp_tls_cfg_t;
#ifdef CONFIG_ESP_TLS_SERVER

View File

@ -122,6 +122,7 @@ struct esp_http_client {
int header_index;
bool is_async;
esp_transport_keep_alive_t keep_alive_cfg;
struct ifreq *if_name;
};
typedef struct esp_http_client esp_http_client_t;
@ -576,6 +577,7 @@ 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;
@ -583,6 +585,14 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
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);
}
if (config->if_name) {
client->if_name = calloc(1, sizeof(struct ifreq) + 1);
HTTP_MEM_CHECK(TAG, client->if_name, goto error);
memcpy(client->if_name, config->if_name, sizeof(struct ifreq));
esp_transport_tcp_set_interface_name(tcp, client->if_name);
}
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
esp_transport_handle_t ssl = NULL;
_success = (
@ -613,10 +623,6 @@ 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) {
@ -719,7 +725,9 @@ esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
free(client->response->buffer);
free(client->response);
}
if (client->if_name) {
free(client->if_name);
}
free(client->parser);
free(client->parser_settings);
_clear_connection_info(client);

View File

@ -19,6 +19,7 @@
#include "http_parser.h"
#include "sdkconfig.h"
#include "esp_err.h"
#include <sys/socket.h>
#ifdef __cplusplus
extern "C" {
@ -135,6 +136,7 @@ typedef struct {
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 */
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
} esp_http_client_config_t;
/**

View File

@ -121,6 +121,7 @@ struct esp_websocket_client {
int payload_len;
int payload_offset;
esp_transport_keep_alive_t keep_alive_cfg;
struct ifreq *if_name;
};
static uint64_t _tick_get_ms(void)
@ -316,6 +317,12 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count;
}
if (config->if_name) {
client->if_name = calloc(1, sizeof(struct ifreq) + 1);
ESP_WS_CLIENT_MEM_CHECK(TAG, client->if_name, goto _websocket_init_fail);
memcpy(client->if_name, config->if_name, sizeof(struct ifreq));
}
client->lock = xSemaphoreCreateRecursiveMutex();
ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail);
@ -331,7 +338,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT);
esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup
esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg);
esp_transport_tcp_set_interface_name(tcp, client->if_name);
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail);
@ -374,7 +381,6 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
if (config->skip_cert_common_name_check) {
esp_transport_ssl_skip_common_name_check(ssl);
}
esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg);
esp_transport_handle_t wss = esp_transport_ws_init(ssl);
ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail);
@ -448,6 +454,9 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
if (client->event_handle) {
esp_event_loop_delete(client->event_handle);
}
if (client->if_name) {
free(client->if_name);
}
esp_websocket_client_destroy_config(client);
esp_transport_list_destroy(client->transport_list);
vQueueDelete(client->lock);

View File

@ -22,6 +22,7 @@
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
#include "esp_event.h"
#include <sys/socket.h>
#ifdef __cplusplus
extern "C" {
@ -100,6 +101,7 @@ typedef struct {
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 */
size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
} esp_websocket_client_config_t;
/**

View File

@ -173,6 +173,15 @@ void esp_transport_ssl_set_psk_key_hint(esp_transport_handle_t t, const psk_hint
*/
void esp_transport_ssl_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg);
/**
* @brief Set name of interface that socket can be binded on
* So the data can transport on this interface
*
* @param[in] t The transport handle
* @param[in] if_name The interface name
*/
void esp_transport_ssl_set_interface_name(esp_transport_handle_t t, struct ifreq *if_name);
#ifdef __cplusplus
}
#endif

View File

@ -16,6 +16,7 @@
#define _ESP_TRANSPORT_TCP_H_
#include "esp_transport.h"
#include <sys/socket.h>
#ifdef __cplusplus
extern "C" {
@ -30,6 +31,15 @@ extern "C" {
*/
void esp_transport_tcp_set_keep_alive(esp_transport_handle_t t, esp_transport_keep_alive_t *keep_alive_cfg);
/**
* @brief Set name of interface that socket can be binded on
* So the data can transport on this interface
*
* @param[in] t The transport handle
* @param[in] if_name The interface name
*/
void esp_transport_tcp_set_interface_name(esp_transport_handle_t t, struct ifreq *if_name);
/**
* @brief Create TCP transport, the transport handle must be release esp_transport_destroy callback
*

View File

@ -32,6 +32,12 @@ struct tcp_connect_task_params {
bool consume_sock_backlog;
};
#define TEST_TRANSPORT_BIND_IFNAME() \
struct ifreq ifr; \
ifr.ifr_name[0] = 'l'; \
ifr.ifr_name[1] = 'o'; \
ifr.ifr_name[2] = '\0';
/**
* @brief Recursively connects with a new socket to loopback interface until the last one blocks.
* The last socket is closed upon test teardown, that initiates recursive cleanup (close) for all
@ -350,6 +356,10 @@ TEST_CASE("tcp_transport: Keep alive test", "[tcp_transport]")
.keep_alive_count = 3 };
esp_transport_tcp_set_keep_alive(tcp, &keep_alive_cfg);
// Bind device interface to loopback
TEST_TRANSPORT_BIND_IFNAME();
esp_transport_tcp_set_interface_name(tcp, &ifr);
tcp_transport_keepalive_test(tcp, &keep_alive_cfg);
// Cleanup
@ -374,6 +384,10 @@ TEST_CASE("ssl_transport: Keep alive test", "[tcp_transport]")
.keep_alive_count = 4 };
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
// Bind device interface to loopback
TEST_TRANSPORT_BIND_IFNAME();
esp_transport_ssl_set_interface_name(ssl, &ifr);
tcp_transport_keepalive_test(ssl, &keep_alive_cfg);
// Cleanup
@ -400,6 +414,10 @@ TEST_CASE("ws_transport: Keep alive test", "[tcp_transport]")
.keep_alive_count = 3 };
esp_transport_tcp_set_keep_alive(ssl, &keep_alive_cfg);
// Bind device interface to loopback
TEST_TRANSPORT_BIND_IFNAME();
esp_transport_ssl_set_interface_name(ws, &ifr);
tcp_transport_keepalive_test(ws, &keep_alive_cfg);
// Cleanup
@ -424,6 +442,10 @@ TEST_CASE("ssl_transport: Check that parameters (keepalive) are set independentl
.keep_alive_count = 3 };
esp_transport_ssl_set_keep_alive(ssl, &keep_alive_cfg);
// Bind device interface to loopback
TEST_TRANSPORT_BIND_IFNAME();
esp_transport_ssl_set_interface_name(ssl, &ifr);
tcp_transport_keepalive_test(ssl, &keep_alive_cfg);
// Cleanup

View File

@ -345,6 +345,12 @@ void esp_transport_ssl_set_keep_alive(esp_transport_handle_t t, esp_transport_ke
ssl->cfg.keep_alive_cfg = (tls_keep_alive_cfg_t *) keep_alive_cfg;
}
void esp_transport_ssl_set_interface_name(esp_transport_handle_t t, struct ifreq *if_name)
{
GET_SSL_FROM_TRANSPORT_OR_RETURN(ssl, t);
ssl->cfg.if_name = if_name;
}
esp_transport_handle_t esp_transport_ssl_init(void)
{
esp_transport_handle_t t = esp_transport_init();
@ -378,3 +384,8 @@ void esp_transport_tcp_set_keep_alive(esp_transport_handle_t t, esp_transport_ke
{
return esp_transport_ssl_set_keep_alive(t, keep_alive_cfg);
}
void esp_transport_tcp_set_interface_name(esp_transport_handle_t t, struct ifreq *if_name)
{
return esp_transport_ssl_set_interface_name(t, if_name);
}

View File

@ -4,4 +4,6 @@ This example is based on `esp_https_ota` component's APIs.
## Configuration
Refer README.md in the parent directory for setup details
Refer README.md in the parent directory for setup details.
Example also supports binding to specific interface (either "Ethernet" or "WiFi Station"), which will allow firmware upgrade to happen through specific interface (in case multiple networking interfaces are enabled). Please see more on this through example configuration in `idf.py menuconfig -> Example Configuration -> Support firmware upgrade bind specificied interface->Choose OTA data bind interface`.

View File

@ -17,4 +17,29 @@ menu "Example Configuration"
help
This allows you to skip the validation of OTA server certificate CN field.
config EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
bool "Support firmware upgrade bind specified interface"
default n
help
This allows you to bind specified interface in OTA example.
choice EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_TYPE
prompt "Choose OTA data bind interface"
default EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_STA
depends on EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
help
Select which interface type of OTA data go through.
config EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_STA
bool "Bind wifi station interface"
depends on EXAMPLE_CONNECT_WIFI
help
Select wifi station interface to pass the OTA data.
config EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_ETH
bool "Bind ethernet interface"
depends on EXAMPLE_CONNECT_ETHERNET
help
Select ethernet interface to pass the OTA data.
endchoice
endmenu

View File

@ -20,11 +20,20 @@
#include "nvs.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include <sys/socket.h>
#if CONFIG_EXAMPLE_CONNECT_WIFI
#include "esp_wifi.h"
#endif
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
/* The interface name value can refer to if_desc in esp_netif_defaults.h */
#if CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_ETH
static const char *bind_interface_name = "eth";
#elif CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_STA
static const char *bind_interface_name = "sta";
#endif
#endif
static const char *TAG = "simple_ota_example";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
@ -62,12 +71,24 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
void simple_ota_example_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting OTA example");
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
esp_netif_t *netif = get_example_netif_from_desc(bind_interface_name);
if (netif == NULL) {
ESP_LOGE(TAG, "Can't find netif from interface description");
abort();
}
struct ifreq ifr;
esp_netif_get_netif_impl_name(netif, ifr.ifr_name);
ESP_LOGI(TAG, "Bind interface name is %s", ifr.ifr_name);
#endif
esp_http_client_config_t config = {
.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_BIND_IF
.if_name = &ifr,
#endif
};
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN

View File

@ -1,2 +1,3 @@
CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF=y