Examples/http-server: Make DNS server a simple configurable component

This commit is contained in:
David Cermak 2023-05-30 17:30:06 +02:00
parent dc016f5987
commit 6edd1be973
6 changed files with 148 additions and 51 deletions

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS dns_server.c
INCLUDE_DIRS include
PRIV_REQUIRES esp_netif)

View File

@ -1,10 +1,7 @@
/* Captive Portal Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <sys/param.h>
@ -12,12 +9,14 @@
#include "esp_log.h"
#include "esp_system.h"
#include "esp_check.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "dns_server.h"
#define DNS_PORT (53)
#define DNS_MAX_LEN (256)
@ -57,6 +56,14 @@ typedef struct __attribute__((__packed__))
uint32_t ip_addr;
} dns_answer_t;
// DNS server handle
struct dns_server_handle {
bool started;
TaskHandle_t task;
int num_of_entries;
dns_entry_pair_t entry[];
};
/*
Parse the name from the packet from the DNS name format to a regular .-seperated name
returns the pointer to the next part of the packet
@ -90,7 +97,7 @@ static char *parse_dns_name(char *raw_name, char *parsed_name, size_t parsed_nam
}
// Parses the DNS request and prepares a DNS response with the IP of the softAP
static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len)
static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len, dns_server_handle_t h)
{
if (req_len > dns_reply_max_len) {
return -1;
@ -126,8 +133,8 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
char *cur_qd_ptr = dns_reply + sizeof(dns_header_t);
char name[128];
// Respond to all questions with the ESP32's IP address
for (int i = 0; i < qd_count; i++) {
// Respond to all questions based on configured rules
for (int qd_i = 0; qd_i < qd_count; qd_i++) {
char *name_end_ptr = parse_dns_name(cur_qd_ptr, name, sizeof(name));
if (name_end_ptr == NULL) {
ESP_LOGE(TAG, "Failed to parse DNS question: %s", cur_qd_ptr);
@ -141,6 +148,25 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
ESP_LOGD(TAG, "Received type: %d | Class: %d | Question for: %s", qd_type, qd_class, name);
if (qd_type == QD_TYPE_A) {
esp_ip4_addr_t ip = { .addr = IPADDR_ANY };
// Check the configured rules to decide whether to answer this question or not
for (int i = 0; i < h->num_of_entries; ++i) {
// check if the name either corresponds to the entry, or if we should answer to all queries ("*")
if (strcmp(h->entry[i].name, "*") == 0 || strcmp(h->entry[i].name, name) == 0) {
if (h->entry[i].if_key) {
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey(h->entry[i].if_key), &ip_info);
ip.addr = ip_info.ip.addr;
break;
} else if (h->entry->ip.addr != IPADDR_ANY) {
ip.addr = h->entry[i].ip.addr;
break;
}
}
}
if (ip.addr == IPADDR_ANY) { // no rule applies, continue with another question
continue;
}
dns_answer_t *answer = (dns_answer_t *)cur_ans_ptr;
answer->ptr_offset = htons(0xC000 | (cur_qd_ptr - dns_reply));
@ -148,12 +174,10 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
answer->class = htons(qd_class);
answer->ttl = htonl(ANS_TTL_SEC);
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
ESP_LOGD(TAG, "Answer with PTR offset: 0x%" PRIX16 " and IP 0x%" PRIX32, ntohs(answer->ptr_offset), ip_info.ip.addr);
ESP_LOGD(TAG, "Answer with PTR offset: 0x%" PRIX16 " and IP 0x%" PRIX32, ntohs(answer->ptr_offset), ip.addr);
answer->addr_len = htons(sizeof(ip_info.ip.addr));
answer->ip_addr = ip_info.ip.addr;
answer->addr_len = htons(sizeof(ip.addr));
answer->ip_addr = ip.addr;
}
}
return reply_len;
@ -169,8 +193,9 @@ void dns_server_task(void *pvParameters)
char addr_str[128];
int addr_family;
int ip_protocol;
dns_server_handle_t handle = pvParameters;
while (1) {
while (handle->started) {
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
@ -193,7 +218,7 @@ void dns_server_task(void *pvParameters)
}
ESP_LOGI(TAG, "Socket bound, port %d", DNS_PORT);
while (1) {
while (handle->started) {
ESP_LOGI(TAG, "Waiting for data");
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
@ -218,7 +243,7 @@ void dns_server_task(void *pvParameters)
rx_buffer[len] = 0;
char reply[DNS_MAX_LEN];
int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN);
int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN, handle);
ESP_LOGI(TAG, "Received %d bytes from %s | DNS reply with len: %d", len, addr_str, reply_len);
if (reply_len <= 0) {
@ -242,7 +267,24 @@ void dns_server_task(void *pvParameters)
vTaskDelete(NULL);
}
void start_dns_server(void)
dns_server_handle_t start_dns_server(dns_server_config_t *config)
{
xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
dns_server_handle_t handle = calloc(1, sizeof(struct dns_server_handle) + config->num_of_entries * sizeof(dns_entry_pair_t));
ESP_RETURN_ON_FALSE(handle, NULL, TAG, "Failed to allocate dns server handle");
handle->started = true;
handle->num_of_entries = config->num_of_entries;
memcpy(handle->entry, config->item, config->num_of_entries * sizeof(dns_entry_pair_t));
xTaskCreate(dns_server_task, "dns_server", 4096, handle, 5, &handle->task);
return handle;
}
void stop_dns_server(dns_server_handle_t handle)
{
if (handle) {
handle->started = false;
vTaskDelete(handle->task);
free(handle);
}
}

View File

@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DNS_SERVER_MAX_ITEMS
#define DNS_SERVER_MAX_ITEMS 1
#endif
#define DNS_SERVER_CONFIG_SINGLE(queried_name, netif_key) { \
.num_of_entries = 1, \
.item = { { .name = queried_name, .if_key = netif_key } } \
}
/**
* @brief Definition of one DNS entry: NAME - IP (or the netif whose IP to answer)
*
* @note Please use string literals (or ensure they are valid during dns_server lifetime) as names, since
* we don't take copies of the config values `name` and `if_key`
*/
typedef struct dns_entry_pair {
const char* name; /**<! Exact match of the name field of the DNS query to answer */
const char* if_key; /**<! Use this network interface IP to answer, only if NULL, use the static IP below */
esp_ip4_addr_t ip; /**<! Constant IP address to answer this query, if "if_key==NULL" */
} dns_entry_pair_t;
/**
* @brief DNS server config struct defining the rules for answering DNS (A type) queries
*
* @note If you want to define more rules, you can set `DNS_SERVER_MAX_ITEMS` before including this header
* Example of using 2 entries with constant IP addresses
* \code{.c}
* #define DNS_SERVER_MAX_ITEMS 2
* #include "dns_server.h"
*
* dns_server_config_t config = {
* .num_of_entries = 2,
* .item = { {.name = "my-esp32.com", .ip = { .addr = ESP_IP4TOADDR( 192, 168, 4, 1) } } ,
* {.name = "my-utils.com", .ip = { .addr = ESP_IP4TOADDR( 192, 168, 4, 100) } } } };
* start_dns_server(&config);
* \endcode
*/
typedef struct dns_server_config {
int num_of_entries; /**<! Number of rules specified in the config struct */
dns_entry_pair_t item[DNS_SERVER_MAX_ITEMS]; /**<! Array of pairs */
} dns_server_config_t;
/**
* @brief DNS server handle
*/
typedef struct dns_server_handle *dns_server_handle_t;
/**
* @brief Set ups and starts a simple DNS server that will respond to all A queries (IPv4)
* based on configured rules, pairs of name and either IPv4 address or a netif ID (to respond by it's IPv4 add)
*
* @param config Configuration structure listing the pairs of (name, IP/netif-id)
* @return dns_server's handle on success, NULL on failure
*/
dns_server_handle_t start_dns_server(dns_server_config_t *config);
/**
* @brief Stops and destroys DNS server's task and structs
* @param handle DNS server's handle to destroy
*/
void stop_dns_server(dns_server_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -1,3 +1,2 @@
idf_component_register(SRCS "main.c" "dns_server.c"
INCLUDE_DIRS "include"
idf_component_register(SRCS main.c
EMBED_FILES root.html)

View File

@ -1,26 +0,0 @@
/* Captive Portal Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set ups and starts a simple DNS server that will respond to all queries
* with the soft AP's IP address
*
*/
void start_dns_server(void);
#ifdef __cplusplus
}
#endif

View File

@ -159,5 +159,6 @@ void app_main(void)
start_webserver();
// Start the DNS server that will redirect all queries to the softAP IP
start_dns_server();
dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
start_dns_server(&config);
}