Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/esp_dns__build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
idf_ver: ["latest", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: esp_dns_basic, path: "components/esp_dns/examples"}]
test:
- app: esp_dns_basic
path: components/esp_dns/examples
ci_relpath: ../../../ci/build_apps.py
- app: esp_dns_concurrent
path: components/esp_dns/tests/test_apps
ci_relpath: ../../../../ci/build_apps.py
include:
- idf_ver: "latest"
warning: "the choice symbol ETHERNET_PHY_LAN867X\nis deprecated: Please use smi_gpio instead"
Expand All @@ -31,11 +37,12 @@ jobs:
env:
EXPECTED_WARNING: ${{ matrix.warning }}
shell: bash
working-directory: ${{matrix.test.path}}
working-directory: ${{ matrix.test.path }}
run: |
if [[ "${{ matrix.idf_ver }}" == "release-v5.3" || "${{ matrix.idf_ver }}" == "release-v5.4" ]]; then
export EXPECTED_WARNING="unknown kconfig symbol 'LWIP_USE_ESP_GETADDRINFO'"
if [[ "${{ matrix.idf_ver }}" == "release-v5.4" || "${{ matrix.idf_ver }}" == "release-v5.5" ]]; then
export EXPECTED_WARNING="unknown kconfig symbol 'LWIP_USE_ESP_GETADDRINFO'
unknown kconfig symbol 'MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY'"
fi
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
python ${{ matrix.test.ci_relpath }} ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ test_multi_heap_host
# VS Code Settings
.vscode/

# clangd / LSP cache
.cache/

# VIM files
*.swp
*.swo
Expand Down
27 changes: 27 additions & 0 deletions components/esp_dns/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
menu "ESP DNS"

config ESP_DNS_MAX_ANSWER_SCAN
int "Maximum resource records to scan per DNS response"
range 1 128
default 16
help
Maximum number of answer-section resource records to walk when parsing
a DNS response. This limit is independent of MAX_ANSWERS (fixed at 1
until the lwIP external-resolve hook passes addr_cnt).

Increase this value for long CNAME chains or responses with many
non-address records before the desired A/AAAA records.

config ESP_DNS_BUFFER_SIZE
int "DNS response/query buffer size in bytes"
range 512 4096
default 512
help
Size of stack buffers used for DNS queries and responses in TCP,
DNS-over-TLS, and DNS-over-HTTPS paths. The default of 512 bytes
matches the traditional DNS UDP message size limit from RFC 1035.

Increase this value if you expect larger responses (for example
with EDNS0), keeping in mind the impact on task stack usage.

endmenu
12 changes: 11 additions & 1 deletion components/esp_dns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ if (ret != 0) {
| `server_cert` | SSL server certificate in PEM format |
| `alpn_protos` | ALPN protocols for DoH (typically `"h2"`) |

When using the certificate bundle with public DoT/DoH resolvers (e.g. Google, Cloudflare), enable cross-signed chain verification in your project `sdkconfig.defaults`:

```
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y
```

This is an IDF mbedTLS option (not an esp_dns Kconfig). The bundled example and concurrent test ship with it enabled. Disable it in menuconfig (**Component config → mbedTLS → Certificate Bundle → Support cross-signed certificate verification**) only if you use PEM pinning or a resolver that does not present cross-signed chains.

### Protocol-Specific Options

#### DoH Options
Expand All @@ -181,8 +189,9 @@ When using secure DNS protocols (DoT and DoH), you have two certificate options:

- The UDP DNS protocol implementation relies on the native LWIP DNS resolver.
- Transport protocol selection must be configured through `esp_dns_init_xxx()` rather than `getaddrinfo()` parameters due to LWIP resolver hook limitations.
- Maximum response size is limited by the buffer size (default: 512 bytes) for DNS over TLS (DOT) and TCP protocols.
- Maximum response size is limited by `CONFIG_ESP_DNS_BUFFER_SIZE` (default: 512 bytes) for DNS over TLS (DOT) and TCP protocols.
- Only one DNS protocol can be active at a time.
- **Multi-IP results (TCP/DoT/DoH):** `dns_resolve_*` accepts `addr_cnt` and clamps it via `esp_dns_clamp_addr_cnt()` to `MAX_ANSWERS` (1). The lwIP hook passes `MAX_ANSWERS` because `LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE` does not yet forward the caller's `addr_cnt`. When lwIP adds that parameter, update `lwip_hook_netconn_external_resolve` to pass it through.

- **Resolution Speed**:
- UDP DNS is fastest but least secure
Expand Down Expand Up @@ -213,6 +222,7 @@ Once you add this component to your project, it will replace the default LWIP DN
- **Certificate Errors**:
- Verify that the correct certificate is provided for secure protocols
- For public DNS servers, use the certificate bundle approach
- If you see `No matching trusted root certificate found` when using the certificate bundle (e.g. with `dns.google` or Cloudflare), the server likely uses a cross-signed chain. Add `CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y` to your project `sdkconfig.defaults` (see **TLS Configuration** above). The esp_dns examples enable this by default.

- **Timeout Errors**:
- Increase the timeout value for slow network connections
Expand Down
67 changes: 38 additions & 29 deletions components/esp_dns/esp_dns_doh.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ esp_err_t esp_dns_http_event_handler(esp_http_client_event_t *evt)
{
char *temp_buff = NULL;
size_t temp_buff_len = 0;
esp_dns_handle_t handle = (esp_dns_handle_t)evt->user_data;
response_buffer_t *response_buffer = (response_buffer_t *)evt->user_data;

switch (evt->event_id) {
case HTTP_EVENT_ERROR:
Expand All @@ -117,32 +117,32 @@ esp_err_t esp_dns_http_event_handler(esp_http_client_event_t *evt)
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
/* Check if buffer is null, if yes, initialize it */
if (handle->response_buffer.buffer == NULL) {
if (response_buffer->buffer == NULL) {
if (evt->data_len == 0) {
ESP_LOGW(TAG, "Received empty HTTP data");
return ESP_ERR_INVALID_ARG;
}
temp_buff = malloc(evt->data_len);
if (temp_buff) {
handle->response_buffer.buffer = temp_buff;
handle->response_buffer.length = evt->data_len;
memcpy(handle->response_buffer.buffer, evt->data, evt->data_len);
response_buffer->buffer = temp_buff;
response_buffer->length = evt->data_len;
memcpy(response_buffer->buffer, evt->data, evt->data_len);
} else {
ESP_LOGE(TAG, "Buffer allocation error");
return ESP_ERR_NO_MEM;
}
} else {
/* Reallocate buffer to hold the new data chunk */
int new_len = handle->response_buffer.length + evt->data_len;
int new_len = response_buffer->length + evt->data_len;
if (new_len == 0) {
ESP_LOGW(TAG, "New data length is zero after receiving HTTP data");
return ESP_ERR_INVALID_ARG;
}
temp_buff = realloc(handle->response_buffer.buffer, new_len);
temp_buff = realloc(response_buffer->buffer, new_len);
if (temp_buff) {
handle->response_buffer.buffer = temp_buff;
memcpy(handle->response_buffer.buffer + handle->response_buffer.length, evt->data, evt->data_len);
handle->response_buffer.length = new_len;
response_buffer->buffer = temp_buff;
memcpy(response_buffer->buffer + response_buffer->length, evt->data, evt->data_len);
response_buffer->length = new_len;
} else {
ESP_LOGE(TAG, "Buffer allocation error");
return ESP_ERR_NO_MEM;
Expand All @@ -152,29 +152,30 @@ esp_err_t esp_dns_http_event_handler(esp_http_client_event_t *evt)
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
/* Entire response received, process it here */
ESP_LOGD(TAG, "Received full response, length: %d", handle->response_buffer.length);
ESP_LOGD(TAG, "Received full response, length: %d", response_buffer->length);

/* Check if the buffer indicates an HTTP error response */
if (HttpStatus_Ok == esp_http_client_get_status_code(evt->client)) {
if (handle->response_buffer.length >= sizeof(dns_header_t)) {
if (response_buffer->length >= sizeof(dns_header_t)) {
/* Parse the DNS response */
esp_dns_parse_response((uint8_t *)handle->response_buffer.buffer,
handle->response_buffer.length,
&handle->response_buffer.dns_response);
esp_dns_parse_response((uint8_t *)response_buffer->buffer,
response_buffer->length,
&response_buffer->dns_response,
response_buffer->max_ips);
} else {
ESP_LOGE(TAG, "DNS response too small");
handle->response_buffer.dns_response.status_code = ERR_VAL;
response_buffer->dns_response.status_code = ERR_VAL;
}
} else {
ESP_LOGE(TAG, "HTTP Error: %d", esp_http_client_get_status_code(evt->client));
temp_buff_len = handle->response_buffer.length > ESP_DNS_BUFFER_SIZE ? ESP_DNS_BUFFER_SIZE : handle->response_buffer.length;
ESP_LOG_BUFFER_HEXDUMP(TAG, handle->response_buffer.buffer, temp_buff_len, ESP_LOG_ERROR);
handle->response_buffer.dns_response.status_code = ERR_VAL; /* TBD: Not handled properly yet */
temp_buff_len = response_buffer->length > ESP_DNS_BUFFER_SIZE ? ESP_DNS_BUFFER_SIZE : response_buffer->length;
ESP_LOG_BUFFER_HEXDUMP(TAG, response_buffer->buffer, temp_buff_len, ESP_LOG_ERROR);
response_buffer->dns_response.status_code = ERR_VAL; /* TBD: Not handled properly yet */
}

free(handle->response_buffer.buffer);
handle->response_buffer.buffer = NULL;
handle->response_buffer.length = 0;
free(response_buffer->buffer);
response_buffer->buffer = NULL;
response_buffer->length = 0;
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
Expand All @@ -198,17 +199,24 @@ esp_err_t esp_dns_http_event_handler(esp_http_client_event_t *evt)
* @param handle Pointer to the DNS handle
* @param name The hostname to resolve
* @param addr Pointer to store the resolved IP addresses
* @param addr_cnt Number of address slots in addr (must be > 0)
* @param rrtype The address RR type (A or AAAA)
*
* @return ERR_OK on success, or an error code on failure
*/
err_t dns_resolve_doh(const esp_dns_handle_t handle, const char *name, ip_addr_t *addr, u8_t rrtype)
err_t dns_resolve_doh(const esp_dns_handle_t handle, const char *name, ip_addr_t *addr, u8_t addr_cnt, u8_t rrtype)
{
uint8_t buffer_qry[ESP_DNS_BUFFER_SIZE];

/* Initialize error status */
err_t err = ERR_OK;
const char *prefix = "https://";
response_buffer_t response_buffer;
u8_t max_ips = esp_dns_clamp_addr_cnt(addr_cnt);

if (addr == NULL || max_ips == 0) {
return ERR_ARG;
}

/* Set default values for DoH configuration if not specified */
const char *url_path = handle->config.protocol_config.doh_config.url_path ?
Expand Down Expand Up @@ -238,7 +246,7 @@ err_t dns_resolve_doh(const esp_dns_handle_t handle, const char *name, ip_addr_t
.url = dns_server_url,
.event_handler = esp_dns_http_event_handler,
.method = HTTP_METHOD_POST,
.user_data = handle,
.user_data = &response_buffer,
.port = port,
};

Expand All @@ -249,11 +257,12 @@ err_t dns_resolve_doh(const esp_dns_handle_t handle, const char *name, ip_addr_t
config.cert_pem = handle->config.tls_config.cert_pem; /* Use the root certificate for dns.google if needed */
}

/* Clear the response buffer to ensure no residual data remains */
memset(&handle->response_buffer, 0, sizeof(response_buffer_t));
/* Keep response state local to this lookup so concurrent queries do not collide. */
memset(&response_buffer, 0, sizeof(response_buffer_t));
response_buffer.max_ips = max_ips;

/* Create DNS query in wire format */
size_t query_size = esp_dns_create_query(buffer_qry, sizeof(buffer_qry), name, rrtype, &handle->response_buffer.dns_response.id);
size_t query_size = esp_dns_create_query(buffer_qry, sizeof(buffer_qry), name, rrtype, &response_buffer.dns_response.id);
if (query_size == -1) {
ESP_LOGE(TAG, "Error: Hostname too big");
err = ERR_MEM;
Expand Down Expand Up @@ -293,13 +302,13 @@ err_t dns_resolve_doh(const esp_dns_handle_t handle, const char *name, ip_addr_t

/* Verify HTTP status code and DNS response status */
if ((HttpStatus_Ok != esp_http_client_get_status_code(client)) ||
(handle->response_buffer.dns_response.status_code != ERR_OK)) {
(response_buffer.dns_response.status_code != ERR_OK)) {
err = ERR_ARG;
goto client_cleanup;
}

/* Extract IP addresses from DNS response */
err = esp_dns_extract_ip_addresses_from_response(&handle->response_buffer.dns_response, addr);
err = esp_dns_get_ips_from_response(&response_buffer.dns_response, addr, max_ips);
} else {
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(ret));
err = ERR_VAL;
Expand Down
25 changes: 14 additions & 11 deletions components/esp_dns/esp_dns_dot.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ int esp_dns_cleanup_dot(esp_dns_handle_t handle)
*
* @return ERR_OK on success, or an error code on failure
*/
err_t dns_resolve_dot(const esp_dns_handle_t handle, const char *name, ip_addr_t *addr, u8_t rrtype)
err_t dns_resolve_dot(const esp_dns_handle_t handle, const char *name, ip_addr_t *addr, u8_t addr_cnt, u8_t rrtype)
{
int err = ERR_OK;
esp_transport_handle_t transport = NULL;
Expand All @@ -105,22 +105,24 @@ err_t dns_resolve_dot(const esp_dns_handle_t handle, const char *name, ip_addr_t
size_t query_size;
int timeout_ms;
int dot_port;
response_buffer_t response_buffer;
u8_t max_ips = esp_dns_clamp_addr_cnt(addr_cnt);

if (addr == NULL) {
if (addr == NULL || max_ips == 0) {
return ERR_ARG;
}

/* Set timeout and port values, using defaults if not specified in config */
timeout_ms = handle->config.timeout_ms ? : ESP_DNS_DEFAULT_TIMEOUT_MS;
dot_port = handle->config.port ? : ESP_DNS_DEFAULT_DOT_PORT;

/* Clear the response buffer to ensure no residual data remains */
memset(&handle->response_buffer, 0, sizeof(response_buffer_t));
/* Keep response state local to this lookup so concurrent queries do not collide. */
memset(&response_buffer, 0, sizeof(response_buffer_t));

/* Create DNS query in wire format, leaving 2 bytes at start for length prefix as required by RFC 7858 */
memset(dot_buffer, 0, ESP_DNS_BUFFER_SIZE);
query_size = esp_dns_create_query((uint8_t *)(dot_buffer + 2), sizeof(dot_buffer) - 2,
name, rrtype, &handle->response_buffer.dns_response.id);
name, rrtype, &response_buffer.dns_response.id);
if (query_size == -1) {
ESP_LOGE(TAG, "Error: Hostname too big");
return ERR_MEM;
Expand Down Expand Up @@ -182,16 +184,17 @@ err_t dns_resolve_dot(const esp_dns_handle_t handle, const char *name, ip_addr_t
}

/* Skip the 2-byte length field that prepends DNS messages as required by RFC 7858 */
handle->response_buffer.buffer = dot_buffer + 2;
handle->response_buffer.length = len - 2;
response_buffer.buffer = dot_buffer + 2;
response_buffer.length = len - 2;

/* Parse the DNS response */
esp_dns_parse_response((uint8_t *)handle->response_buffer.buffer,
handle->response_buffer.length,
&handle->response_buffer.dns_response);
esp_dns_parse_response((uint8_t *)response_buffer.buffer,
response_buffer.length,
&response_buffer.dns_response,
max_ips);

/* Extract IP addresses from DNS response */
err = esp_dns_extract_ip_addresses_from_response(&handle->response_buffer.dns_response, addr);
err = esp_dns_get_ips_from_response(&response_buffer.dns_response, addr, max_ips);
if (err != ERR_OK) {
ESP_LOGE(TAG, "Failed to extract IP address from DNS response");
goto cleanup;
Expand Down
14 changes: 10 additions & 4 deletions components/esp_dns/esp_dns_lwip.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ extern esp_dns_handle_t g_dns_handle;
/**
* @brief Custom DNS resolution hook for lwIP network connections
*
* LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE passes ip_addr_t *addr but not addr_cnt.
* Callers may provide a single-element buffer (e.g. netconn_gethostbyname with
* addr_cnt == 1) even when CONFIG_LWIP_DNS_MAX_HOST_IP > 1, so dns_resolve_*
* must not write more than MAX_ANSWERS (1) addresses.
*
* @param name Hostname to resolve
* @param addr Pointer to store resolved IP address
* @param addr Pointer to store resolved IP address(es)
* @param addrtype Type of address to resolve (IPv4/IPv6)
* @param err Pointer to store error code
*
* @return int 0 if resolution should be handled by lwIP, 1 if handled by this module
*/
int lwip_hook_netconn_external_resolve(const char *name, ip_addr_t *addr, u8_t addrtype, err_t *err)
{
const u8_t addr_cnt = MAX_ANSWERS;
if (g_dns_handle == NULL) {
ESP_LOGD(TAG, "ESP_DNS module not initialized, resolving through native DNS");
*err = ERR_OK;
Expand Down Expand Up @@ -91,13 +97,13 @@ int lwip_hook_netconn_external_resolve(const char *name, ip_addr_t *addr, u8_t a
/* Return zero as lwIP DNS can handle UDP DNS */
return 0;
case ESP_DNS_PROTOCOL_TCP:
*err = dns_resolve_tcp(g_dns_handle, name, addr, rrtype);
*err = dns_resolve_tcp(g_dns_handle, name, addr, addr_cnt, rrtype);
break;
case ESP_DNS_PROTOCOL_DOT:
*err = dns_resolve_dot(g_dns_handle, name, addr, rrtype);
*err = dns_resolve_dot(g_dns_handle, name, addr, addr_cnt, rrtype);
break;
case ESP_DNS_PROTOCOL_DOH:
*err = dns_resolve_doh(g_dns_handle, name, addr, rrtype);
*err = dns_resolve_doh(g_dns_handle, name, addr, addr_cnt, rrtype);
break;
default:
ESP_LOGE(TAG, "Invalid transport type");
Expand Down
Loading
Loading