feat(example): Added DNS over HTTPS (DoH) example

This commit is contained in:
Abhik Roy
2024-09-30 22:03:37 +10:00
parent 5462240135
commit 967603b5aa
22 changed files with 1392 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "dns_over_https.c" "dns_utils.c"
INCLUDE_DIRS "include" "."
PRIV_REQUIRES nvs_flash lwip esp_event esp-tls esp_http_client)
if(CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM)
target_link_libraries(${COMPONENT_LIB} "-u lwip_hook_netconn_external_resolve")
endif()

View File

@@ -0,0 +1,10 @@
menu "DNS-over-HTTPS Configuration"
config HTTPS_DNS_HTTP_VERSION
string
prompt "DNS-over-HTTPS Server HTTP version"
default "1.1"
help
HTTP version of DNS-over-HTTPS server.
endmenu

View File

@@ -0,0 +1,87 @@
# DNS Over HTTPS (DoH) Component for ESP-IDF
## Overview
This component allows your ESP-IDF device to use DNS over HTTPS (DoH) for DNS resolution. By using DoH, your DNS queries are encrypted, which helps protect your privacy and prevents third parties from spying on or altering your DNS requests.
## Features
- **Secure DNS Queries:** Encrypts DNS requests using TLS for better privacy.
- **Runtime DNS Server Configuration Support:** Configure popular DNS providers like Google or Cloudflare, or set up a custom DNS server directly from your application.
- **Certificate Bundle Support (Default):** Uses an internal certificate bundle by default, simplifying the setup for common DoH servers.
- **Dynamic DNS Request Generation:** Creates DNS queries on-the-fly to handle different hostnames and types.
- **Note on Future Support for DNS over TLS (DoT):** While this component currently supports DNS over HTTPS (DoH), future updates may include support for DNS over TLS (DoT), enabling another secure and privacy-focused method for DNS resolution.
## How It Works
This component utilizes the `CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM` hook to override the core DNS functionality of LWIP and implement custom DNS over HTTPS resolution. To enable this, ensure that the configuration option `Component config → LWIP → Hooks → Netconn external resolve Hook` is set to `Custom implementation`.
Once you add this component to your project, it will replace the default LWIP DNS resolution automatically.
The component must be initialized using the `dns_over_https_init()` function. Security details necessary for server communication must be included in the configuration passed to this function.
**⚠️ Warning:** This component cannot work alongside the OpenThread component, as both components use the CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM hook.
## DNS Resolution Support
This component improves DNS resolution by combining the LWIP DNS module and DoH.
It supports:
### Standard DNS Queries:
Uses the LWIP DNS module to resolve DNS server URLs, localhost, and IP addresses.
Note: Localhost and IP addresses are resolved locally and no actual queries are sent to the DNS server.
### DNS over HTTPS:
For any other DNS queries, it uses mbedTLS to securely send requests over HTTPS, ensuring your queries are encrypted for added privacy.
## Configuration
To set up the DNS over HTTPS component, follow these steps:
1. Go to your project directory.
2. Run `idf.py menuconfig`.
3. Look for the **Component config -> DNS-over-HTTPS Configuration** section.
4. Set the HTTP version for DNS-over-HTTPS (default: 1.1).
### Important Configuration Parameters
To enable custom DNS resolution, configure the `CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM` setting either through menuconfig by navigating to `Component config → LWIP → Hooks → Netconn external resolve Hook` and enabling it, or by adding `CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM=y` to your `sdkconfig.defaults` file to pre-set the configuration during the build process.
## Initialization Requirements
To initialize this module, make sure to call the `init_dns_over_https()` function after these initializations in your main application:
- `nvs_flash_init()`
- `esp_event_loop_create_default()`
- `esp_netif_init()`
## Example Usage
Heres a quick example of how to use this component in your application:
```c
#include "dns_over_https.h" // Include the DoH component header
void app_main()
{
ESP_ERROR_CHECK(esp_netif_init()); /* Initialize the network interface */
ESP_ERROR_CHECK(esp_event_loop_create_default()); /* Create default event loop */
esp_err_t ret = nvs_flash_init(); /* Initialize NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/* Sets up periodic time updates */
setup_periodic_time_updates();
/* Initialize the DOH config */
dns_over_https_config_t config = {
.dns_server = "dns.google",
.dns_service_path = "dns-query",
/* Supply function to attach certificate bundle */
.crt_bundle_attach = esp_crt_bundle_attach;
/* or Supplying certificate */
.cert_pem = server_root_cert_pem_start,
};
ESP_ERROR_CHECK(dns_over_https_init(&config));
// Your application logic here...
}
```
**⚠️ Warning:** The default stack size may not be sufficient for the thread performing DNS resolution. To avoid potential issues, ensure that the stack size allocated to the task handling DNS resolution is increased.

View File

@@ -0,0 +1,296 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_tls.h"
#include "sdkconfig.h"
#include "lwip/prot/dns.h"
#include "lwip/api.h"
#include "lwip/opt.h"
#include "lwip/dns.h"
#include "lwip_default_hooks.h"
#include "esp_http_client.h"
#include "dns_utils.h"
#include "dns_over_https.h"
#define SERVER_URL_MAX_SZ 256
#define BUFFER_SIZE 512
static const char *TAG = "dns_over_https";
static uint8_t buffer_qry_g[BUFFER_SIZE];
/* Buffer structure to store incoming chunks */
typedef struct {
char *buffer;
int length;
} response_buffer_t;
/* Initialize a global or context-specific buffer */
static response_buffer_t dns_response_buffer = { .buffer = NULL, .length = 0 };
static dns_response_t dns_response_g;
/* Global variable to store the configuration */
static dns_over_https_config_t server_config_g;
/* Initializes the DNS over HTTPS (DoH) component with the given configuration. */
esp_err_t dns_over_https_init(const dns_over_https_config_t *config)
{
/* Validate configuration: Either a certificate bundle or a PEM certificate must be provided */
if ((!config->crt_bundle_attach) && (!config->cert_pem)) {
ESP_LOGE(TAG, "Error: Root certificates must be provided when not using internal certificate bundle.");
return ESP_FAIL; /* Return failure code */
}
/*
* Copy the values from the provided configuration structure
* to the global server configuration structure.
*/
server_config_g.dns_server = config->dns_server; /* Set the DNS server address */
server_config_g.dns_service_path = config->dns_service_path; /* Set the DNS service path */
server_config_g.cert_pem = config->cert_pem; /* Set the PEM certificate pointer */
server_config_g.crt_bundle_attach = config->crt_bundle_attach; /* Set the certificate bundle attach function */
return ESP_OK; /* Return success code */
}
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
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 (dns_response_buffer.buffer == NULL) {
dns_response_buffer.buffer = malloc(evt->data_len);
dns_response_buffer.length = evt->data_len;
memcpy(dns_response_buffer.buffer, evt->data, evt->data_len);
} else {
/* Reallocate buffer to hold the new data chunk */
int new_len = dns_response_buffer.length + evt->data_len;
dns_response_buffer.buffer = realloc(dns_response_buffer.buffer, new_len);
memcpy(dns_response_buffer.buffer + dns_response_buffer.length, evt->data, evt->data_len);
dns_response_buffer.length = new_len;
}
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", dns_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 */
parse_dns_response((uint8_t *)dns_response_buffer.buffer, dns_response_buffer.length, &dns_response_g);
} else {
ESP_LOGE(TAG, "HTTP Error: %d", esp_http_client_get_status_code(evt->client));
ESP_LOG_BUFFER_HEXDUMP(TAG, dns_response_buffer.buffer, dns_response_buffer.length, ESP_LOG_ERROR);
dns_response_g.status_code = ERR_VAL;
}
free(dns_response_buffer.buffer);
dns_response_buffer.buffer = NULL;
dns_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 Converts a dns_response_t to an array of IP addresses.
*
* This function iterates over the DNS response and extracts valid
* IPv4 and IPv6 addresses, storing them in the provided array.
*
* @param response The DNS response to process.
* @param ipaddr An array to store the extracted IP addresses.
*
* @return err Status of dns response parsing
*/
static err_t write_ip_addresses_from_dns_response(const dns_response_t *response, ip_addr_t ipaddr[])
{
int count = 0;
memset(ipaddr, 0, DNS_MAX_HOST_IP * sizeof(ip_addr_t));
if (response->status_code != ERR_OK) {
return response->status_code;
}
/* Iterate over the DNS answers */
for (int i = 0; i < response->num_answers && count < DNS_MAX_HOST_IP; i++) {
const dns_answer_storage_t *answer = &response->answers[i];
/* Check if the answer is valid */
if (answer->status != ERR_OK) {
continue;
}
ipaddr[count] = answer->ip;
count++;
}
if (count == 0) {
return ERR_VAL;
}
/* Store the number of valid IP addresses */
return ERR_OK;
}
/**
* @brief Function to handle the HTTPS request for DNS resolution.
*
* This function generates a DNS request, sends it via HTTPS, and processes
* the response to extract IP addresses.
*
* @param name The name to resolve.
* @param addr A pointer to an array to store the resolved IP addresses.
* @param addrtypestr The address RR type (A or AAAA).
*/
static err_t do_https_request(const char *name, ip_addr_t *addr, int addrtype)
{
err_t err = ERR_OK;
const char *prefix = "https://";
size_t url_len = strlen(prefix) + \
strlen(server_config_g.dns_server) + 1 + \
strlen(server_config_g.dns_service_path) + 1; /* 1 for '/' and 1 for '\0' */
char *dns_server_url = malloc(url_len);
if (dns_server_url == NULL) {
ESP_LOGE(TAG, "Memory allocation failed");
return ERR_MEM;
}
snprintf(dns_server_url, url_len, "%s%s/%s", prefix,
server_config_g.dns_server,
server_config_g.dns_service_path);
esp_http_client_config_t config = {
.url = dns_server_url,
.event_handler = _http_event_handler,
.method = HTTP_METHOD_POST,
};
if (server_config_g.crt_bundle_attach) {
config.crt_bundle_attach = server_config_g.crt_bundle_attach;
} else {
config.cert_pem = server_config_g.cert_pem; /* Use the root certificate for dns.google if needed */
}
/* Generate DNS request string */
size_t query_size = create_dns_query(buffer_qry_g, sizeof(buffer_qry_g), name, addrtype, &dns_response_g.id);
if (query_size == -1) {
ESP_LOGE(TAG, "Error: Hostname too big");
err = ERR_MEM;
goto cleanup;
}
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 headers for DoH request */
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 DNS query payload */
ret = esp_http_client_set_post_field(client, (const char *)buffer_qry_g, 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;
}
/* Perform the 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));
/* Check if the response status or buffer indicates an error response */
if ((HttpStatus_Ok != esp_http_client_get_status_code(client)) ||
(dns_response_g.status_code != ERR_OK)) {
err = ERR_ARG;
goto client_cleanup;
}
err = write_ip_addresses_from_dns_response(&dns_response_g, addr);
} else {
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(ret));
err = ERR_VAL;
}
client_cleanup:
esp_http_client_cleanup(client);
cleanup:
free(dns_server_url);
return err;
}
#if defined(CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM)
int lwip_hook_netconn_external_resolve(const char *name, ip_addr_t *addr, u8_t addrtype, err_t *err)
{
LWIP_UNUSED_ARG(name);
LWIP_UNUSED_ARG(addr);
LWIP_UNUSED_ARG(addrtype);
LWIP_UNUSED_ARG(err);
if (server_config_g.dns_server == NULL) {
ESP_LOGE(TAG, "DNS Over HTTPS Server not set. Ensure DNS Over HTTPS module was initialized");
*err = ERR_VAL;
return 1;
}
/* Check if HTTPS_DNS_SERVER is in the dns cache */
if ((strcmp(name, server_config_g.dns_server) == 0) ||
#if LWIP_HAVE_LOOPIF
(strcmp(name, "localhost") == 0) ||
#endif
ipaddr_aton(name, addr)) { /* host name already in octet notation */
return 0;
}
if ((addrtype == NETCONN_DNS_IPV4) || (addrtype == NETCONN_DNS_IPV4_IPV6)) {
*err = do_https_request(name, addr, DNS_RRTYPE_A);
} else if ((addrtype == NETCONN_DNS_IPV6) || (addrtype == NETCONN_DNS_IPV6_IPV4)) {
*err = do_https_request(name, addr, DNS_RRTYPE_AAAA);
} else {
ESP_LOGE(TAG, "Error: Invalid address type");
*err = ERR_VAL;
}
return 1;
}
#endif /* CONFIG_LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE... */

View File

@@ -0,0 +1,167 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <arpa/inet.h>
#include "esp_random.h"
#include "dns_utils.h"
/* Function to create a DNS query */
size_t create_dns_query(uint8_t *buffer, size_t buffer_size, const char *hostname, int addrtype, uint16_t *id_o)
{
/*
DNS Query for example.com (Type A)
0x00, 0x00, // Transaction ID
0x01, 0x00, // Flags: Standard query
0x00, 0x01, // Questions: 1
0x00, 0x00, // Answer RRs: 0
0x00, 0x00, // Authority RRs: 0
0x00, 0x00, // Additional RRs: 0
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', // QNAME: example.com
0x03, 'c', 'o', 'm',
0x00, // End of QNAME
0x00, 0x01, // QTYPE: A (host address)
0x00, 0x01 // QCLASS: IN (internet)
*/
dns_header_t *header = (dns_header_t *)buffer;
memset(buffer, 0, buffer_size);
/* Set header fields */
*id_o = (uint16_t)(esp_random() & 0xFFFF); /* Return the id for response validation */
header->id = htons(*id_o); /* Random transaction ID */
header->flags = htons(0x0100); /* Standard query with recursion */
header->qdcount = htons(1); /* One question */
/* Add the question name */
uint8_t *qname = buffer + sizeof(dns_header_t);
const char *dot = hostname;
while (*dot) {
const char *next_dot = strchr(dot, '.');
if (!next_dot) {
next_dot = dot + strlen(dot);
}
uint8_t len = next_dot - dot;
*qname++ = len;
/* Check for overflow */
if ((qname - buffer) > buffer_size) {
return -1;
}
memcpy(qname, dot, len);
qname += len;
dot = (*next_dot) ? next_dot + 1 : next_dot;
}
*qname++ = 0; /* Null-terminate the question name */
/* Set question fields */
dns_question_t *question = (dns_question_t *)qname;
question->qtype = htons(addrtype);
question->qclass = htons(DNS_RRCLASS_IN);
/* Return the total query size */
return (qname + sizeof(dns_question_t)) - buffer;
}
/**
* Skips over a DNS name in a DNS reply message and returns the offset to the end of the name.
*
* This function handles both uncompressed labels and compression pointers according to RFC 1035.
* Reference: RFC 1035, sections 3.1 (Name Space Definitions) and 4.1.4 (Message Compression).
*
* @param ptr Pointer to the start of the DNS name in the DNS message.
* @return The ptr to the end of the DNS name.
*/
static uint8_t *skip_dns_name(uint8_t *ptr)
{
uint8_t offset = 0;
/* Loop through each part of the name, handling labels and compression pointers */
while (ptr[offset] != 0) {
/* Check if this part is a compression pointer, indicated by the two high bits set to 1 (0xC0) */
/* RFC 1035, Section 4.1.4: Compression pointers */
if ((ptr[offset] & 0xC0) == 0xC0) {
/* Compression pointer is 2 bytes; move offset by 2 and stop */
offset += 2;
return ptr + offset; /* End of name processing due to pointer */
} else {
/* Otherwise, it's a label
RFC 1035, Section 3.1: Labels
- The first byte is the length of this label
- Followed by 'length' bytes of label content */
offset += ptr[offset] + 1; /* Move past this label (1 byte for length + label content) */
}
}
/* RFC 1035, Section 3.1: End of a name is indicated by a zero-length byte (0x00) */
offset += 1; /* Move past the terminating zero byte */
return ptr + offset;
}
void parse_dns_response(uint8_t *buffer, size_t response_size, dns_response_t *dns_response)
{
dns_header_t *header = (dns_header_t *)buffer;
dns_response->status_code = ERR_OK; /* Initialize DNS response code */
/* Check if there are answers and Transaction id matches */
int answer_count = ntohs(header->ancount);
if ((ntohs(header->id) != dns_response->id) || (answer_count == 0)) {
dns_response->status_code = ERR_VAL; /* DNS response code */
return;
}
/* Ensure only MAX_ANSWERS are processed */
dns_response->num_answers = (answer_count < MAX_ANSWERS ? answer_count : MAX_ANSWERS);
/* Skip the header and question section */
uint8_t *ptr = buffer + sizeof(dns_header_t);
/* Skip the question name */
ptr = skip_dns_name(ptr);
/* Skip the question type and class */
ptr += sizeof(dns_question_t);
/* Parse each answer record */
for (int i = 0; i < dns_response->num_answers; i++) {
/* Answer fields */
ptr = skip_dns_name(ptr);
dns_answer_t *answer = (dns_answer_t *)ptr;
uint16_t type = ntohs(answer->type);
uint16_t class = ntohs(answer->class);
uint32_t ttl = ntohl(answer->ttl);
uint16_t data_len = ntohs(answer->data_len);
/* Skip fixed parts of answer (type, class, ttl, data_len) */
ptr += SIZEOF_DNS_ANSWER;
/* Validate RR class and ttl */
if ((class != DNS_RRCLASS_IN) || (ttl > DNS_MAX_TTL)) {
dns_response->answers[i].status = ERR_VAL;
goto next_answer;
}
/* Initialize status for this answer */
dns_response->answers[i].status = ERR_OK;
/* Check the type of answer */
if (type == DNS_RRTYPE_A && data_len == 4) {
/* IPv4 Address (A record) */
memcpy(&dns_response->answers[i].ip, ptr, sizeof(struct in_addr));
IP_SET_TYPE(&dns_response->answers[i].ip, IPADDR_TYPE_V4);
} else if (type == DNS_RRTYPE_AAAA && data_len == 16) {
/* IPv6 Address (AAAA record) */
memcpy(&dns_response->answers[i].ip, ptr, sizeof(struct in6_addr));
IP_SET_TYPE(&dns_response->answers[i].ip, IPADDR_TYPE_V6);
} else {
dns_response->answers[i].status = ERR_VAL;
}
next_answer:
/* Move pointer to next answer */
ptr += data_len;
}
}

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_tls.h"
#include "sdkconfig.h"
#include "lwip/prot/dns.h"
#include "lwip/api.h"
#include "lwip/opt.h"
#include "lwip/dns.h"
#ifdef __cplusplus
extern "C" {
#endif
/* DNS header structure */
typedef struct {
uint16_t id; /* Identification */
uint16_t flags; /* Flags */
uint16_t qdcount; /* Number of questions */
uint16_t ancount; /* Number of answers */
uint16_t nscount; /* Number of authority records */
uint16_t arcount; /* Number of additional records */
} dns_header_t;
/* DNS question structure */
typedef struct {
/* DNS query record starts with either a domain name or a pointer
to a name already present somewhere in the packet. */
uint16_t qtype; /* Question type (e.g., A, MX) */
uint16_t qclass; /* Question class (e.g., IN for internet) */
} dns_question_t;
/** DNS answer message structure.
No packing needed: only used locally on the stack. */
typedef struct {
/* DNS query record starts with either a domain name or a pointer
to a name already present somewhere in the packet. */
uint16_t type; /* Type (e.g., A, MX) */
uint16_t class; /* Class (e.g., IN for internet) */
uint32_t ttl; /* Time-to-live */
uint16_t data_len; /* Length of data */
} dns_answer_t;
#define SIZEOF_DNS_ANSWER 10
/** DNS resource record max. TTL (one week as default) */
#define DNS_MAX_TTL 604800
#define MAX_ANSWERS (CONFIG_LWIP_DNS_MAX_HOST_IP)
typedef struct {
err_t status;
ip_addr_t ip;
} dns_answer_storage_t;
typedef struct {
err_t status_code;
uint16_t id;
int num_answers;
dns_answer_storage_t answers[MAX_ANSWERS];
} dns_response_t;
/* Function to create a DNS query for an A and AAAA records */
size_t create_dns_query(uint8_t *buffer, size_t buffer_size, const char *hostname, int addrtype, uint16_t *id_o);
/* Function to parse a DNS answer for an A and AAAA records */
void parse_dns_response(uint8_t *buffer, size_t response_size, dns_response_t *dns_response);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
version: 0.1.0
dependencies:
idf:
version: ">=5.1"

View File

@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "sdkconfig.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Define the configuration structure */
typedef struct {
const char *dns_server; /*!< DNS server url or IP address */
const char *dns_service_path; /*!< Path to server DNS-over-HTTPS Service */
const char *cert_pem; /*!< SSL server certification, PEM format as string, if the client requires to verify server */
esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification
bundle for server verification, must be enabled in menuconfig */
} dns_over_https_config_t;
/**
* @brief Initializes the DNS over HTTPS (DoH) component with the given configuration.
*
* This function sets up the DNS over HTTPS component by configuring the DNS server,
* service path, and root certificates required for secure communication. It validates
* the provided configuration and ensures necessary parameters are set.
*
* @param[in] config Pointer to the configuration structure containing DNS server details,
* service path, and root certificate options.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*
* @warning This function must be called at least once before making any DNS-over-HTTPS requests
*/
esp_err_t dns_over_https_init(const dns_over_https_config_t *config);
#ifdef __cplusplus
}
#endif