mirror of
https://github.com/espressif/esp-idf
synced 2025-03-10 01:29:21 -04:00
Examples/http-server: Make DNS server a simple configurable component
This commit is contained in:
parent
dc016f5987
commit
6edd1be973
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS dns_server.c
|
||||
INCLUDE_DIRS include
|
||||
PRIV_REQUIRES esp_netif)
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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)
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user