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,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(dns_over_https)

View File

@ -0,0 +1,109 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
# DNS over HTTPS Example
This example demonstrates how to use the DNS over HTTPS (DoH) component in an ESP32 application. The example resolves domain names securely via HTTPS using the configured DNS over HTTPS provider (Google(default), Cloudflare, or a custom server).
## Features
- **DNS over HTTPS**: Resolves domain names securely using HTTPS.
## Certificate Options
This example provides two certificate options for DNS over HTTPS:
1. Internal Certificate Bundle (Default): By default, the example uses an internal certificate bundle, making it easy to get started with popular DoH providers like Google and Cloudflare.
2. Custom Certificate: If you prefer to use your own DoH server and certificate, you can configure the server name in menuconfig and place your custom certificate in the `cert_custom_root.pem` file. This option provides flexibility if you have specific security or server requirements.
To configure the certificate option in menuconfig, navigate to `Example DNS-over-HTTPS Configuration → Use internal certificate bundle` to enable or disable the internal bundle.
## Configuration
Before building and running the example, you need to configure the DNS over HTTPS provider in `menuconfig`:
1. Run `idf.py menuconfig`.
2. Look for the **Example DNS-over-HTTPS Configuration** section.
3. Choose your preferred DNS server:
* Google DNS (default: dns.google)
* Cloudflare DNS (cloudflare-dns.com)
* Custom DNS-over-HTTPS Server
4. For custom DNS configuration enter the custom DNS-over-HTTPS server URL.
5. Specify a custom DNS-over-HTTPS query path (default: dns-query).
6. Toggle whether to use the internal certificate bundle:
* If disabled, specify the DNS certificate (cert_custom_root.pem).
7. Save your changes and exit the configuration menu.
8. Configure Wi-Fi or Ethernet to join a network. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
## How It Works
1. **Network Initialization**: The application initializes the network interfaces (Wi-Fi or Ethernet) and establishes a connection.
2. **NVS Initialization**: Non-Volatile Storage (NVS) is initialized to store and retrieve system time across reboots.
3. **DNS over HTTPS Initialization**: The `init_dns_over_https()` function initializes the DNS over HTTPS resolver, which securely handles DNS queries using HTTPS.
4. **Performing getaddrinfo**: The application executes the getaddrinfo operation for several domain names.
## How to use example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
### Hardware Required
* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for power supply and programming
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Troubleshooting Tips
* **Connectivity**:
Ensure that the network connection details are accurate. For example, verify the Wi-Fi SSID and password or check that the Ethernet connection is secure and not faulty.
* **Stack Overflow Errors**:
If you encounter a stack overflow, it might indicate that the DNS server is returning a lengthy error message. To diagnose the issue, try increasing the stack size to retrieve more detailed information.
* **Incorrect Path Issues**:
Occasionally, stack overflow error is caused by an incorrect server path. Verify the server details and ensure the server path is correctly configured.
## Example Output
```
I (4652) esp_netif_handlers: example_netif_sta ip: 192.168.50.136, mask: 255.255.255.0, gw: 192.168.50.1
I (4652) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.50.136
I (5552) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:5abf:25ff:fee0:4100, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5552) example_common: Connected to example_netif_sta
I (5562) example_common: - IPv4 address: 192.168.50.136,
I (5562) example_common: - IPv6 address: fe80:0000:0000:0000:5abf:25ff:fee0:4100, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5572) time_sync: Updating time from NVS
I (5582) main_task: Returned from app_main()
I (5592) wifi:<ba-add>idx:1 (ifx:0, a0:36:bc:0e:c4:f0), tid:7, ssn:3, winSize:64
I (5622) wifi:<ba-del>idx:0, tid:6
I (5622) wifi:<ba-add>idx:0 (ifx:0, a0:36:bc:0e:c4:f0), tid:0, ssn:1, winSize:64
I (5962) esp-x509-crt-bundle: Certificate validated
I (6772) example_dns_over_https: Using DNS Over HTTPS server: dns.google
I (6772) example_dns_over_https: Resolving IP addresses for yahoo.com:
I (6772) example_dns_over_https: IPv4: 74.6.143.26
I (6782) example_dns_over_https: IPv4: 74.6.231.21
I (6782) example_dns_over_https: IPv4: 98.137.11.163
I (6792) example_dns_over_https: IPv4: 74.6.231.20
I (7192) esp-x509-crt-bundle: Certificate validated
I (8382) example_dns_over_https: Using DNS Over HTTPS server: dns.google
I (8382) example_dns_over_https: Resolving IP addresses for www.google.com:
I (8382) example_dns_over_https: IPv6: 2404:6800:4015:803::2004
I (9032) esp-x509-crt-bundle: Certificate validated
I (9862) example_dns_over_https: Using DNS Over HTTPS server: dns.google
I (9862) example_dns_over_https: Resolving IP addresses for www.google.com:
I (9862) example_dns_over_https: IPv4: 142.250.70.228
```

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

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "time_sync.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash esp_event esp_netif esp_timer)

View File

@ -0,0 +1,18 @@
menu "Time Sync Configuration"
config TIME_SYNC_NTP_SERVER
string "NTP Server"
default "pool.ntp.org"
help
Configure the NTP server for time synchronization.
The default is set to "pool.ntp.org".
config TIME_SYNC_NTP_UPDATE_PERIOD
int "NTP Time Update Interval (in hours)"
default 24
range 1 24
help
The time interval in hours for updating the system time using the NTP server.
For example, if set to 12, the system will update the time from the NTP server every 12 hours.
endmenu

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,46 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Update system time from NVS or SNTP if not available.
*
* This function retrieves the stored system time from NVS. If the time is not found,
* it synchronizes with the SNTP server and updates the time in NVS.
*
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure.
*/
esp_err_t update_time_from_nvs(void);
/**
* @brief Fetch the current system time and store it in NVS.
*
* This function initializes SNTP, retrieves the current system time, and stores
* it in Non-Volatile Storage (NVS). In case of failure, an error message is logged.
*
* @param[in] args Unused argument placeholder.
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure.
*/
esp_err_t fetch_and_store_time_in_nvs(void*);
/**
* @brief Sets up periodic time updates.
*
* If the reset reason is power-on, this function updates the system time from NVS.
* It also creates a periodic timer to regularly fetch and store the time in NVS
* using `fetch_and_store_time_in_nvs` as the callback.
*
* @return void
*/
void setup_periodic_time_updates(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,192 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_netif_sntp.h"
#include "esp_timer.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "time_sync.h"
static const char *TAG = "time_sync";
#define STORAGE_NAMESPACE "storage"
/* Timer interval once every day (24 Hours) */
#define SECONDS_PER_HOUR 3600ULL
#define MICROSECONDS_PER_SECOND 1000000ULL
#define TIME_PERIOD ((uint64_t)CONFIG_TIME_SYNC_NTP_UPDATE_PERIOD * SECONDS_PER_HOUR * MICROSECONDS_PER_SECOND) // Convert hours to microseconds
/**
* @brief Initialize SNTP service with predefined servers.
*
* This function sets up the SNTP service to sync time.
*/
void initialize_sntp(void)
{
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(1,
ESP_SNTP_SERVER_LIST(CONFIG_TIME_SYNC_NTP_SERVER));
esp_netif_sntp_init(&config);
}
/**
* @brief Obtain system time from SNTP service.
*
* This function attempts to synchronize the system time using SNTP, retrying
* up to 10 times if necessary. If the synchronization is successful, it returns
* ESP_OK, otherwise ESP_FAIL.
*
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure.
*/
static esp_err_t obtain_time(void)
{
/* wait for time to be set */
int retry = 0;
const int retry_count = 10;
while (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(2000)) != ESP_OK && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
}
if (retry == retry_count) {
return ESP_FAIL;
}
return ESP_OK;
}
/**
* @brief Fetch the current system time and store it in NVS.
*
* This function initializes SNTP, retrieves the current system time, and stores
* it in Non-Volatile Storage (NVS). In case of failure, an error message is logged.
*
* @param[in] args Unused argument placeholder.
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure.
*/
esp_err_t fetch_and_store_time_in_nvs(void *args)
{
nvs_handle_t my_handle = 0;
esp_err_t err;
initialize_sntp();
if (obtain_time() != ESP_OK) {
err = ESP_FAIL;
goto exit;
}
time_t now;
time(&now);
/* Open */
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) {
goto exit;
}
/* Write */
err = nvs_set_i64(my_handle, "timestamp", now);
if (err != ESP_OK) {
goto exit;
}
err = nvs_commit(my_handle);
if (err != ESP_OK) {
goto exit;
}
exit:
if (my_handle != 0) {
nvs_close(my_handle);
}
esp_netif_sntp_deinit();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error updating time in nvs");
} else {
ESP_LOGI(TAG, "Updated time in NVS");
}
return err;
}
/**
* @brief Update system time from NVS or SNTP if not available.
*
* This function retrieves the stored system time from NVS. If the time is not found,
* it synchronizes with the SNTP server and updates the time in NVS.
*
* @return esp_err_t ESP_OK on success, ESP_FAIL on failure.
*/
esp_err_t update_time_from_nvs(void)
{
nvs_handle_t my_handle = 0;
esp_err_t err;
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error opening NVS");
goto exit;
}
int64_t timestamp = 0;
err = nvs_get_i64(my_handle, "timestamp", &timestamp);
if (err == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGI(TAG, "Time not found in NVS. Syncing time from SNTP server.");
if (fetch_and_store_time_in_nvs(NULL) != ESP_OK) {
err = ESP_FAIL;
} else {
err = ESP_OK;
}
} else if (err == ESP_OK) {
struct timeval get_nvs_time;
get_nvs_time.tv_sec = timestamp;
settimeofday(&get_nvs_time, NULL);
}
exit:
if (my_handle != 0) {
nvs_close(my_handle);
}
return err;
}
/**
* @brief Sets up periodic time updates.
*
* If the reset reason is power-on, this function updates the system time from NVS.
* It also creates a periodic timer to regularly fetch and store the time in NVS
* using `fetch_and_store_time_in_nvs` as the callback.
*
* @return void
*/
void setup_periodic_time_updates(void)
{
/* Check if the reset reason is power-on, and update time from NVS */
if (esp_reset_reason() == ESP_RST_POWERON) {
ESP_LOGI(TAG, "Updating time from NVS");
ESP_ERROR_CHECK(update_time_from_nvs());
}
/* Set up a periodic timer to fetch and store the time in NVS */
const esp_timer_create_args_t nvs_update_timer_args = {
.callback = (void *) &fetch_and_store_time_in_nvs,
};
esp_timer_handle_t nvs_update_timer;
ESP_ERROR_CHECK(esp_timer_create(&nvs_update_timer_args, &nvs_update_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(nvs_update_timer, TIME_PERIOD));
}

View File

@ -0,0 +1,10 @@
# Check for configuration options and set the EMBED_TXTFILES accordingly
if(DEFINED CONFIG_HTTPS_DNS_CERT_GOOGLE_HIDDEN)
set(cert_file "${CONFIG_HTTPS_DNS_CERT_GOOGLE_HIDDEN}")
elseif(DEFINED CONFIG_HTTPS_DNS_CERT_CUSTOM_HIDDEN)
set(cert_file "${CONFIG_HTTPS_DNS_CERT_CUSTOM_HIDDEN}")
endif()
idf_component_register(SRCS "example_dns_over_https.c"
INCLUDE_DIRS "."
EMBED_TXTFILES ${cert_file})

View File

@ -0,0 +1,102 @@
menu "Example DNS-over-HTTPS Configuration"
choice HTTPS_DNS_SERVER
prompt "Choose DNS-over-HTTPS Server"
default HTTPS_DNS_SERVER_GOOGLE
config HTTPS_DNS_SERVER_GOOGLE
bool "Google DNS (dns.google)"
help
Use Google's DNS-over-HTTPS server (dns.google) with its corresponding root certificate.
config HTTPS_DNS_SERVER_CLOUDFLARE
bool "Cloudflare DNS (cloudflare-dns.com)"
help
Use Cloudflare's DNS-over-HTTPS server (cloudflare-dns.com) with its corresponding root certificate.
config HTTPS_DNS_SERVER_CUSTOM
bool "Custom DNS-over-HTTPS Server"
help
Use a custom DNS-over-HTTPS server. You must specify both the server URL and certificate manually.
endchoice
config HTTPS_DNS_SERVER_URL_GOOGLE
string
prompt "Google DNS-over-HTTPS Server URL"
default "dns.google"
depends on HTTPS_DNS_SERVER_GOOGLE
help
Google DNS-over-HTTPS server URL.
config HTTPS_DNS_SERVICE_PATH_GOOGLE
string
prompt "Path to Google DNS-over-HTTPS Service"
default "dns-query"
depends on HTTPS_DNS_SERVER_GOOGLE
help
Path to Google DNS-over-HTTPS Service.
config HTTPS_DNS_SERVER_URL_CLOUDFLARE
string
prompt "Cloudflare DNS-over-HTTPS Server URL"
default "cloudflare-dns.com"
depends on HTTPS_DNS_SERVER_CLOUDFLARE
help
Cloudflare DNS-over-HTTPS server URL.
config HTTPS_DNS_SERVICE_PATH_CLOUDFLARE
string
prompt "Path to Cloudflare DNS-over-HTTPS Service"
default "dns-query"
depends on HTTPS_DNS_SERVER_CLOUDFLARE
help
Path to Cloudflare DNS-over-HTTPS Service.
config HTTPS_DNS_SERVER_URL_CUSTOM
string
prompt "Custom DNS-over-HTTPS Server URL"
depends on HTTPS_DNS_SERVER_CUSTOM
help
Specify your custom DNS-over-HTTPS server URL here.
config HTTPS_DNS_SERVICE_PATH_CUSTOM
string
prompt "Path to the Custom DNS-over-HTTPS Service"
default "dns-query"
depends on HTTPS_DNS_SERVER_CUSTOM
help
Path to the Custom DNS-over-HTTPS Service.
config HTTPS_DNS_ESP_CERT_BUNDLE
bool "Use internal certificate bundle"
default y
help
Enable this option to use the internal certificate bundle for DNS-over-HTTPS.
config HTTPS_DNS_CERT_GOOGLE_HIDDEN
string
default "cert_google_root.pem"
depends on HTTPS_DNS_SERVER_GOOGLE && !HTTPS_DNS_ESP_CERT_BUNDLE
config HTTPS_DNS_CERT_GOOGLE
string
prompt "Google DNS Certificate (readonly)"
default HTTPS_DNS_CERT_GOOGLE_HIDDEN
depends on HTTPS_DNS_SERVER_GOOGLE && !HTTPS_DNS_ESP_CERT_BUNDLE
help
Google DNS root certificate in PEM format. This option is read-only.
config HTTPS_DNS_CERT_CUSTOM_HIDDEN
string
default "cert_custom_root.pem"
depends on HTTPS_DNS_SERVER_CUSTOM && !HTTPS_DNS_ESP_CERT_BUNDLE
config HTTPS_DNS_CERT_CUSTOM
string
prompt "Custom DNS Certificate (readonly)"
default HTTPS_DNS_CERT_CUSTOM_HIDDEN
depends on HTTPS_DNS_SERVER_CUSTOM && !HTTPS_DNS_ESP_CERT_BUNDLE
help
Specify the certificate file for the custom DNS server in PEM format. This option is read-only.
endmenu

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----

View File

@ -0,0 +1,160 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_timer.h"
#include "lwip/opt.h"
#include "protocol_examples_common.h"
#include "time_sync.h"
#include "dns_over_https.h"
#if defined(CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE) && defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
#include "esp_crt_bundle.h"
#endif
#if defined(CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE) && !defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
#error "CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE is enabled, but CONFIG_MBEDTLS_CERTIFICATE_BUNDLE is not enabled. Please enable CONFIG_MBEDTLS_CERTIFICATE_BUNDLE."
#endif
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN INET_ADDRSTRLEN
#endif
#ifdef CONFIG_HTTPS_DNS_SERVER_GOOGLE
#define HTTPS_DNS_SERVER CONFIG_HTTPS_DNS_SERVER_URL_GOOGLE
#define HTTPS_DNS_SERVICE_PATH CONFIG_HTTPS_DNS_SERVICE_PATH_GOOGLE
#if !defined(CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE)
extern const char server_root_cert_pem_start[] asm("_binary_cert_google_root_pem_start");
extern const char server_root_cert_pem_end[] asm("_binary_cert_google_root_pem_end");
#endif /* CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE */
#elif CONFIG_HTTPS_DNS_SERVER_CLOUDFLARE
#define HTTPS_DNS_SERVER CONFIG_HTTPS_DNS_SERVER_URL_CLOUDFLARE
#define HTTPS_DNS_SERVICE_PATH CONFIG_HTTPS_DNS_SERVICE_PATH_CLOUDFLARE
const char *server_root_cert_pem_start = NULL;
const char *server_root_cert_pem_end = NULL;
#elif CONFIG_HTTPS_DNS_SERVER_CUSTOM
#define HTTPS_DNS_SERVER CONFIG_HTTPS_DNS_SERVER_URL_CUSTOM
#define HTTPS_DNS_SERVICE_PATH CONFIG_HTTPS_DNS_SERVICE_PATH_CUSTOM
#if !defined(CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE)
extern const char server_root_cert_pem_start[] asm("_binary_cert_custom_root_pem_start");
extern const char server_root_cert_pem_end[] asm("_binary_cert_custom_root_pem_end");
#endif /* CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE */
#endif
static const char *TAG = "example_dns_over_https";
static void do_getaddrinfo(char *hostname, int family)
{
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];
void *addr = NULL;
char *ipver = NULL;
/* Initialize the hints structure */
memset(&hints, 0, sizeof hints);
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM; /* TCP stream sockets */
/* Get address information */
if ((status = getaddrinfo(hostname, NULL, &hints, &res)) != 0) {
ESP_LOGE(TAG, "getaddrinfo error: %d", status);
goto cleanup;
}
ESP_LOGI(TAG, "Using DNS Over HTTPS server: %s", HTTPS_DNS_SERVER);
ESP_LOGI(TAG, "Resolving IP addresses for %s:", hostname);
/* Loop through all the results */
for (p = res; p != NULL; p = p->ai_next) {
/* Get pointer to the address itself */
#if defined(CONFIG_LWIP_IPV4)
if (p->ai_family == AF_INET) { /* IPv4 */
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
/* Convert the IP to a string and print it */
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
ESP_LOGI(TAG, "%s: %s", ipver, ipstr);
}
#endif
#if defined(CONFIG_LWIP_IPV6)
if (p->ai_family == AF_INET6) { /* IPv6 */
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
/* Convert the IP to a string and print it */
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
ESP_LOGI(TAG, "%s: %s", ipver, ipstr);
}
#endif
}
printf("\n");
cleanup:
freeaddrinfo(res); /* Free the linked list */
}
static void addr_info_task(void *pvParameters)
{
do_getaddrinfo("yahoo.com", AF_INET);
do_getaddrinfo("yahoo.com", AF_UNSPEC);
do_getaddrinfo("www.google.com", AF_INET6);
do_getaddrinfo("www.google.com", AF_UNSPEC);
do_getaddrinfo("0.0.0.0", AF_UNSPEC);
do_getaddrinfo("fe80:0000:0000:0000:5abf:25ff:fee0:4100", AF_UNSPEC);
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
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);
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
/* Enables periodic time synchronization required for certificate expiry validation */
#ifdef CONFIG_MBEDTLS_HAVE_TIME_DATE
setup_periodic_time_updates();
#endif
/* Initialize the DOH config */
dns_over_https_config_t config = {
.dns_server = HTTPS_DNS_SERVER,
.dns_service_path = HTTPS_DNS_SERVICE_PATH,
#if defined(CONFIG_HTTPS_DNS_ESP_CERT_BUNDLE)
.crt_bundle_attach = esp_crt_bundle_attach,
#else
.cert_pem = server_root_cert_pem_start,
#endif
};
ESP_ERROR_CHECK(dns_over_https_init(&config));
xTaskCreate(addr_info_task, "AddressInfo", 4 * 1024, NULL, 5, NULL);
}

View File

@ -0,0 +1,10 @@
## IDF Component Manager Manifest File
dependencies:
idf:
version: ">=5.1"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
dns_over_https:
path: ${IDF_PATH}/examples/protocols/dns_over_https/components/dns_over_https
time_sync:
path: ${IDF_PATH}/examples/protocols/dns_over_https/components/time_sync

View File

@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration
#
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_LWIP_DNS_MAX_HOST_IP=4
CONFIG_LWIP_USE_ESP_GETADDRINFO=y
CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM=y
CONFIG_MBEDTLS_HAVE_TIME_DATE=y