mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-11-03 08:01:42 +01:00
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;
|
||
|
|
}
|