mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(esp_http_server): Adds support to bind an interface to server
Closes https://github.com/espressif/esp-idf/issues/17859
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
#include <esp_err.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_event_base.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -78,6 +79,7 @@ initializer that should be kept in sync
|
||||
.keep_alive_idle = 0, \
|
||||
.keep_alive_interval = 0, \
|
||||
.keep_alive_count = 0, \
|
||||
.if_name = NULL, \
|
||||
.open_fn = NULL, \
|
||||
.close_fn = NULL, \
|
||||
.uri_match_fn = NULL \
|
||||
@@ -239,6 +241,10 @@ typedef struct httpd_config {
|
||||
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; /*!< Bind server to a specific network interface.
|
||||
If NULL, server listens on all interfaces (INADDR_ANY).
|
||||
The pointer only needs to remain valid for the duration of
|
||||
httpd_start() -- it is not referenced after that call returns. */
|
||||
/**
|
||||
* Custom session opening callback.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <esp_err.h>
|
||||
#include <assert.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
@@ -386,6 +387,26 @@ static esp_err_t httpd_server_init(struct httpd_data *hd)
|
||||
ESP_LOGW(TAG, LOG_FMT("error in setsockopt SO_REUSEADDR (%d)"), errno);
|
||||
}
|
||||
|
||||
/* Bind to specific network interface if configured */
|
||||
if (hd->config.if_name && hd->config.if_name->ifr_name[0] != 0) {
|
||||
ESP_LOGI(TAG, LOG_FMT("Binding server to interface: %s"), hd->config.if_name->ifr_name);
|
||||
#ifndef __APPLE__
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, hd->config.if_name, sizeof(struct ifreq)) < 0) {
|
||||
#else
|
||||
int idx = if_nametoindex(hd->config.if_name->ifr_name);
|
||||
if (idx == 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("Invalid interface name %s"), hd->config.if_name->ifr_name);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) < 0) {
|
||||
#endif
|
||||
ESP_LOGE(TAG, LOG_FMT("Failed to bind to interface %s (%d)"), hd->config.if_name->ifr_name, errno);
|
||||
close(fd);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <esp_system.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <net/if.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
@@ -16,6 +18,8 @@
|
||||
int pre_start_mem, post_stop_mem, post_stop_min_mem;
|
||||
bool basic_sanity = true;
|
||||
|
||||
#define TAG "test_http_server"
|
||||
|
||||
esp_err_t null_func(httpd_req_t *req)
|
||||
{
|
||||
return ESP_OK;
|
||||
@@ -160,7 +164,7 @@ TEST_CASE("Leak Test", "[HTTP SERVER]")
|
||||
test_case_uses_tcpip();
|
||||
|
||||
task_count = uxTaskGetNumberOfTasks();
|
||||
printf("Initial task count: %d\n", task_count);
|
||||
ESP_LOGI(TAG, "Initial task count: %d\n", task_count);
|
||||
|
||||
pre_start_mem = esp_get_free_heap_size();
|
||||
|
||||
@@ -170,7 +174,7 @@ TEST_CASE("Leak Test", "[HTTP SERVER]")
|
||||
unsigned num_tasks = uxTaskGetNumberOfTasks();
|
||||
task_count++;
|
||||
if (num_tasks != task_count) {
|
||||
printf("Incorrect task count (starting): %d expected %d\n",
|
||||
ESP_LOGE(TAG, "Incorrect task count (starting): %d expected %d\n",
|
||||
num_tasks, task_count);
|
||||
res = false;
|
||||
}
|
||||
@@ -178,14 +182,14 @@ TEST_CASE("Leak Test", "[HTTP SERVER]")
|
||||
|
||||
for (int i = 0; i < SERVER_INSTANCES; i++) {
|
||||
if (httpd_stop(hd[i]) != ESP_OK) {
|
||||
printf("Failed to stop httpd task %d\n", i);
|
||||
ESP_LOGE(TAG, "Failed to stop httpd task %d\n", i);
|
||||
res = false;
|
||||
}
|
||||
vTaskDelay(10);
|
||||
unsigned num_tasks = uxTaskGetNumberOfTasks();
|
||||
task_count--;
|
||||
if (num_tasks != task_count) {
|
||||
printf("Incorrect task count (stopping): %d expected %d\n",
|
||||
ESP_LOGE(TAG, "Incorrect task count (stopping): %d expected %d\n",
|
||||
num_tasks, task_count);
|
||||
res = false;
|
||||
}
|
||||
@@ -290,6 +294,79 @@ TEST_CASE("Max Allowed Sockets Test", "[HTTP SERVER]")
|
||||
TEST_ASSERT(httpd_start(&hd, &config) != ESP_OK);
|
||||
}
|
||||
|
||||
TEST_CASE("Interface Binding Test", "[HTTP SERVER]")
|
||||
{
|
||||
test_case_uses_tcpip();
|
||||
|
||||
httpd_handle_t hd;
|
||||
struct ifreq ifr;
|
||||
|
||||
/* Test 1: Server starts successfully with NULL if_name (default behavior - INADDR_ANY) */
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = 8080;
|
||||
TEST_ASSERT(config.if_name == NULL);
|
||||
TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK);
|
||||
TEST_ASSERT(httpd_stop(hd) == ESP_OK);
|
||||
|
||||
/* Test 2: Server starts successfully with loopback interface binding */
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strcpy(ifr.ifr_name, "lo0"); // Loopback interface
|
||||
config.server_port = 8081;
|
||||
config.if_name = 𝔦
|
||||
|
||||
/* On embedded systems, loopback may not exist, so we test both success and expected failure */
|
||||
esp_err_t ret = httpd_start(&hd, &config);
|
||||
if (ret == ESP_OK) {
|
||||
/* If loopback exists and binding succeeds, verify we can stop cleanly */
|
||||
TEST_ASSERT(httpd_stop(hd) == ESP_OK);
|
||||
} else {
|
||||
/* If loopback doesn't exist or binding fails, that's also acceptable */
|
||||
/* The important part is that the server doesn't crash */
|
||||
ESP_LOGI(TAG, "Loopback binding failed as expected on this platform\n");
|
||||
}
|
||||
|
||||
/* Test 3: Server handles empty interface name (should bind to all interfaces) */
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_name[0] = '\0'; // Empty interface name
|
||||
config.server_port = 8082;
|
||||
config.if_name = 𝔦
|
||||
|
||||
/* Empty interface name should be ignored and server should start normally */
|
||||
TEST_ASSERT(httpd_start(&hd, &config) == ESP_OK);
|
||||
TEST_ASSERT(httpd_stop(hd) == ESP_OK);
|
||||
|
||||
/* Test 4: Server handles invalid interface name gracefully */
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strcpy(ifr.ifr_name, "nonex"); // Invalid interface
|
||||
config.server_port = 8083;
|
||||
config.if_name = 𝔦
|
||||
|
||||
/* Starting with invalid interface should fail gracefully */
|
||||
ret = httpd_start(&hd, &config);
|
||||
TEST_ASSERT(ret != ESP_OK);
|
||||
if (ret != ESP_OK) {
|
||||
/* Expected failure - invalid interface */
|
||||
ESP_LOGI(TAG, "Invalid interface binding failed as expected\n");
|
||||
} else {
|
||||
/* On some platforms, the check might not happen until actual use */
|
||||
httpd_stop(hd);
|
||||
}
|
||||
|
||||
/* Test 5: Verify backward compatibility - multiple servers without interface binding */
|
||||
httpd_handle_t hd1, hd2;
|
||||
httpd_config_t config1 = HTTPD_DEFAULT_CONFIG();
|
||||
httpd_config_t config2 = HTTPD_DEFAULT_CONFIG();
|
||||
config1.server_port = 8084;
|
||||
config1.ctrl_port = ESP_HTTPD_DEF_CTRL_PORT + 1;
|
||||
config2.server_port = 8085;
|
||||
config2.ctrl_port = ESP_HTTPD_DEF_CTRL_PORT + 2;
|
||||
|
||||
TEST_ASSERT(httpd_start(&hd1, &config1) == ESP_OK);
|
||||
TEST_ASSERT(httpd_start(&hd2, &config2) == ESP_OK);
|
||||
TEST_ASSERT(httpd_stop(hd1) == ESP_OK);
|
||||
TEST_ASSERT(httpd_stop(hd2) == ESP_OK);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
|
||||
@@ -207,6 +207,7 @@ typedef struct httpd_ssl_config httpd_ssl_config_t;
|
||||
.keep_alive_idle = 0, \
|
||||
.keep_alive_interval = 0, \
|
||||
.keep_alive_count = 0, \
|
||||
.if_name = NULL, \
|
||||
.open_fn = NULL, \
|
||||
.close_fn = NULL, \
|
||||
.uri_match_fn = NULL \
|
||||
|
||||
@@ -21,6 +21,33 @@ Application Examples
|
||||
|
||||
- :example:`protocols/http_server/advanced_tests` demonstrates how to use the HTTP server for advanced testing.
|
||||
|
||||
Interface Binding
|
||||
-----------------
|
||||
|
||||
By default, the server listens on all available interfaces (``INADDR_ANY``). This is the behavior when ``httpd_config_t.if_name`` is ``NULL``.
|
||||
|
||||
To bind the HTTP server to a specific network interface, set ``httpd_config_t.if_name`` to point to a ``struct ifreq`` with ``ifr_name`` populated (for example ``"eth0"``, ``"en0"``, or ``"lo"`` depending on platform).
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
struct ifreq ifr = {0};
|
||||
strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name) - 1);
|
||||
ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
|
||||
|
||||
config.if_name = 𝔦
|
||||
config.server_port = 80;
|
||||
|
||||
ESP_ERROR_CHECK(httpd_start(&server, &config));
|
||||
|
||||
Notes:
|
||||
|
||||
- ``if_name`` is only used during ``httpd_start()``. The ``ifreq`` object only needs to stay valid for the duration of that call.
|
||||
|
||||
Persistent Connections
|
||||
----------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user