mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-11-02 15:41:38 +01:00
This commit introduces a custom DNS module for ESP32, enabling DNS resolution capabilities over various protocols including UDP, TCP, DNS over TLS (DoT), and DNS over HTTPS (DoH). The module includes initialization and cleanup functionalities, along with protocol-specific implementations for each DNS type.
310 lines
11 KiB
C
310 lines
11 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "esp_event.h"
|
|
#include "esp_log.h"
|
|
#include "sdkconfig.h"
|
|
#include "esp_http_client.h"
|
|
#include "esp_dns_utils.h"
|
|
#include "esp_dns_priv.h"
|
|
#include "esp_dns.h"
|
|
|
|
#define TAG "ESP_DNS_DOH"
|
|
|
|
#define SERVER_URL_MAX_SZ 256
|
|
|
|
/**
|
|
* @brief Initializes the DNS over HTTPS (DoH) module
|
|
*
|
|
* Sets up the DoH service using the provided configuration. Validates the parameters,
|
|
* sets the protocol, and initializes the DNS module. Returns a handle for further use.
|
|
*
|
|
* @param config Pointer to the DNS configuration structure, which must be initialized
|
|
*
|
|
* @return On success, returns a handle to the initialized DoH module; returns NULL on failure
|
|
*/
|
|
esp_dns_handle_t esp_dns_init_doh(esp_dns_config_t *config)
|
|
{
|
|
ESP_LOGD(TAG, "Initializing DNS over HTTPS");
|
|
|
|
/* Validate parameters */
|
|
if (config == NULL) {
|
|
ESP_LOGE(TAG, "Invalid configuration (NULL)");
|
|
return NULL;
|
|
}
|
|
|
|
config->protocol = ESP_DNS_PROTOCOL_DOH;
|
|
|
|
esp_dns_handle_t handle = esp_dns_init(config);
|
|
if (handle == NULL) {
|
|
ESP_LOGE(TAG, "Failed to initialize DNS");
|
|
return NULL;
|
|
}
|
|
|
|
ESP_LOGD(TAG, "DNS module initialized successfully with protocol DNS Over HTTPS(%d)", config->protocol);
|
|
return handle;
|
|
}
|
|
|
|
/**
|
|
* @brief Cleans up the DNS over HTTPS (DoH) module
|
|
*
|
|
* Releases resources allocated for the DoH service. Validates the parameters,
|
|
* checks the protocol, and cleans up the DNS module.
|
|
*
|
|
* @param handle Pointer to the DNS handle to be cleaned up
|
|
*
|
|
* @return 0 on success, or -1 on failure
|
|
*/
|
|
int esp_dns_cleanup_doh(esp_dns_handle_t handle)
|
|
{
|
|
ESP_LOGD(TAG, "Cleaning up DNS over HTTPS");
|
|
|
|
/* Validate parameters */
|
|
if (handle == NULL) {
|
|
ESP_LOGE(TAG, "Invalid handle (NULL)");
|
|
return -1;
|
|
}
|
|
|
|
if (handle->config.protocol != ESP_DNS_PROTOCOL_DOH) {
|
|
ESP_LOGW(TAG, "Unknown protocol during cleanup: %d", handle->config.protocol);
|
|
return -1;
|
|
}
|
|
|
|
int ret = esp_dns_cleanup(handle);
|
|
if (ret != 0) {
|
|
ESP_LOGE(TAG, "Failed to cleanup DNS");
|
|
return ret;
|
|
}
|
|
|
|
/* Empty the handle */
|
|
memset(handle, 0, sizeof(*handle));
|
|
|
|
ESP_LOGD(TAG, "DNS module cleaned up DNS Over HTTPS successfully");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief HTTP event handler for DNS over HTTPS requests
|
|
*
|
|
* Handles HTTP events during DNS over HTTPS communication, including data reception,
|
|
* connection status, and error conditions.
|
|
*
|
|
* @param evt Pointer to the HTTP client event structure
|
|
*
|
|
* @return ESP_OK on success, or an error code on failure
|
|
*/
|
|
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;
|
|
|
|
switch (evt->event_id) {
|
|
case HTTP_EVENT_ERROR:
|
|
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
|
|
break;
|
|
case HTTP_EVENT_ON_CONNECTED:
|
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
|
|
break;
|
|
case HTTP_EVENT_HEADER_SENT:
|
|
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
|
|
break;
|
|
case HTTP_EVENT_ON_HEADER:
|
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
|
|
break;
|
|
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 (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);
|
|
} 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;
|
|
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);
|
|
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;
|
|
} else {
|
|
ESP_LOGE(TAG, "Buffer allocation error");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
}
|
|
break;
|
|
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);
|
|
|
|
/* Check if the buffer indicates an HTTP error response */
|
|
if (HttpStatus_Ok == esp_http_client_get_status_code(evt->client)) {
|
|
/* Parse the DNS response */
|
|
esp_dns_parse_response((uint8_t *)handle->response_buffer.buffer,
|
|
handle->response_buffer.length,
|
|
&handle->response_buffer.dns_response);
|
|
} 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 */
|
|
}
|
|
|
|
free(handle->response_buffer.buffer);
|
|
handle->response_buffer.buffer = NULL;
|
|
handle->response_buffer.length = 0;
|
|
break;
|
|
case HTTP_EVENT_DISCONNECTED:
|
|
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
|
|
break;
|
|
case HTTP_EVENT_REDIRECT:
|
|
ESP_LOGE(TAG, "HTTP_EVENT_REDIRECT: Not supported(%d)", esp_http_client_get_status_code(evt->client));
|
|
break;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Resolves a hostname using DNS over HTTPS
|
|
*
|
|
* This function generates a DNS request, sends it via HTTPS, and processes
|
|
* the response to extract IP addresses.
|
|
*
|
|
* @param handle Pointer to the DNS handle
|
|
* @param name The hostname to resolve
|
|
* @param addr Pointer to store the resolved IP addresses
|
|
* @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)
|
|
{
|
|
uint8_t buffer_qry[ESP_DNS_BUFFER_SIZE];
|
|
|
|
/* Initialize error status */
|
|
err_t err = ERR_OK;
|
|
const char *prefix = "https://";
|
|
|
|
/* Set default values for DoH configuration if not specified */
|
|
const char *url_path = handle->config.protocol_config.doh_config.url_path ?
|
|
handle->config.protocol_config.doh_config.url_path : "dns-query";
|
|
int port = handle->config.port ?
|
|
handle->config.port : ESP_DNS_DEFAULT_DOH_PORT;
|
|
|
|
/* Calculate required URL length: https:// + server + / + path + null terminator */
|
|
size_t url_len = strlen(prefix) + \
|
|
strlen(handle->config.dns_server) + 1 + \
|
|
strlen(url_path) + 1; /* 1 for '/' and 1 for '\0' */
|
|
|
|
/* Allocate memory for the full server URL */
|
|
char *dns_server_url = malloc(url_len);
|
|
if (dns_server_url == NULL) {
|
|
ESP_LOGE(TAG, "Memory allocation failed");
|
|
return ERR_MEM;
|
|
}
|
|
|
|
/* Construct the complete server URL by combining prefix, server and path */
|
|
snprintf(dns_server_url, url_len, "%s%s/%s", prefix,
|
|
handle->config.dns_server,
|
|
url_path);
|
|
|
|
/* Configure the HTTP client with base settings */
|
|
esp_http_client_config_t config = {
|
|
.url = dns_server_url,
|
|
.event_handler = esp_dns_http_event_handler,
|
|
.method = HTTP_METHOD_POST,
|
|
.user_data = handle,
|
|
.port = port,
|
|
};
|
|
|
|
/* Configure TLS certificate settings - either using bundle or PEM cert */
|
|
if (handle->config.tls_config.crt_bundle_attach) {
|
|
config.crt_bundle_attach = handle->config.tls_config.crt_bundle_attach;
|
|
} else {
|
|
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));
|
|
|
|
/* 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);
|
|
if (query_size == -1) {
|
|
ESP_LOGE(TAG, "Error: Hostname too big");
|
|
err = ERR_MEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Initialize HTTP client with the configuration */
|
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
|
if (client == NULL) {
|
|
ESP_LOGE(TAG, "Error initializing HTTP client");
|
|
err = ERR_VAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Set Content-Type header for DNS-over-HTTPS */
|
|
esp_err_t ret = esp_http_client_set_header(client, "Content-Type", "application/dns-message");
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Error setting HTTP header: %s", esp_err_to_name(ret));
|
|
err = ERR_VAL;
|
|
goto client_cleanup;
|
|
}
|
|
|
|
/* Set the DNS query as POST data */
|
|
ret = esp_http_client_set_post_field(client, (const char *)buffer_qry, query_size);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Error setting POST field: %s", esp_err_to_name(ret));
|
|
err = ERR_VAL;
|
|
goto client_cleanup;
|
|
}
|
|
|
|
/* Execute the HTTP request */
|
|
ret = esp_http_client_perform(client);
|
|
if (ret == ESP_OK) {
|
|
ESP_LOGD(TAG, "HTTP POST Status = %d, content_length = %lld",
|
|
esp_http_client_get_status_code(client),
|
|
esp_http_client_get_content_length(client));
|
|
|
|
/* 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)) {
|
|
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);
|
|
} else {
|
|
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(ret));
|
|
err = ERR_VAL;
|
|
}
|
|
|
|
/* Clean up HTTP client */
|
|
client_cleanup:
|
|
esp_http_client_cleanup(client);
|
|
|
|
/* Free allocated memory */
|
|
cleanup:
|
|
free(dns_server_url);
|
|
|
|
return err;
|
|
}
|