Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ jobs:
target: esp32
- path: 'components/display_drivers/example'
target: esp32
- path: 'components/dns_server/example'
target: esp32
- path: 'components/drv2605/example'
target: esp32
- path: 'components/encoder/example'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/upload_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
components/csv
components/display
components/display_drivers
components/dns_server
components/drv2605
components/encoder
components/esp-box
Expand Down
5 changes: 5 additions & 0 deletions components/dns_server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
SRCS "src/dns_server.cpp"
INCLUDE_DIRS "include"
REQUIRES base_component logger socket
)
67 changes: 67 additions & 0 deletions components/dns_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# DNS Server

Simple DNS server component for implementing captive portals on ESP32 devices.

## Features

- Responds to all DNS queries with a configured IP address
- Lightweight implementation suitable for embedded systems
- Built on top of espp::UdpSocket for efficient UDP communication
- Useful for captive portal implementations where all domains should resolve to the device

## Usage

```cpp
#include "dns_server.hpp"

// Create DNS server that responds with the AP's IP
espp::DnsServer::Config dns_config{
.ip_address = "192.168.4.1",
.log_level = espp::Logger::Verbosity::INFO
};
espp::DnsServer dns_server(dns_config);

// Start the DNS server
if (dns_server.start()) {
fmt::print("DNS server started\n");
} else {
fmt::print("Failed to start DNS server\n");
}

// ... server runs in background ...

// Stop when done
dns_server.stop();
```

## How It Works

The DNS server listens on UDP port 53 (the standard DNS port) and responds to all A record queries with the configured IP address. This creates a "captive portal" effect where:

1. When a device connects to your WiFi AP, it tries to reach the internet
2. All DNS queries are answered with your device's IP address
3. The device's captive portal detection triggers
4. The user is directed to your web interface

## Integration with Provisioning

This component is designed to work seamlessly with the `espp::Provisioning` component to create a complete captive portal experience for WiFi provisioning.

## API

### Configuration

- `ip_address`: The IP address to respond with for all DNS queries (typically your AP's IP)
- `log_level`: Logging verbosity level

### Methods

- `start()`: Start the DNS server
- `stop()`: Stop the DNS server
- `is_running()`: Check if the server is currently running

## Limitations

- Only responds to A record queries (IPv4)
- Does not support AAAA records (IPv6)
- Minimal DNS implementation focused on captive portal use case
22 changes: 22 additions & 0 deletions components/dns_server/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# The following 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.20)

set(ENV{IDF_COMPONENT_MANAGER} "0")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py nvs_flash dns_server wifi"
CACHE STRING
"List of components to include"
)

project(dns_server_example)

set(CMAKE_CXX_STANDARD 20)
45 changes: 45 additions & 0 deletions components/dns_server/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# DNS Server Example

This example demonstrates the use of the `dns_server` component to create a simple DNS server that responds to all queries with a single IP address. This is commonly used for captive portal implementations.

## How to use example

### Hardware Required

This example can be run on any ESP32 development board.

### 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.)

## Example Output

```
[DNS Server Example/I][0.739]: Starting DNS Server Example
[DNS Server Example/I][0.739]: Starting WiFi AP: ESP-DNS-Test
[DNS Server Example/I][0.889]: WiFi AP started successfully
[DNS Server Example/I][0.889]: Connect to SSID: ESP-DNS-Test with password: testpassword
[DNS Server Example/I][0.899]: AP IP Address: 192.168.4.1
[DNS Server Example/I][0.899]: Starting DNS server on 192.168.4.1:53
[DNS Server Example/I][0.909]: DNS server started successfully
[DNS Server Example/I][0.909]: All DNS queries will resolve to: 192.168.4.1
```

When a client connects and makes DNS queries, you'll see output like:

```
[DnsServer/I][15.234]: Resolved 'www.google.com' to 192.168.4.1
[DnsServer/I][15.456]: Resolved 'www.apple.com' to 192.168.4.1
```

## Testing

1. Connect your phone or computer to the WiFi network "ESP-DNS-Test" with password "testpassword"
2. Try to ping any domain: `ping google.com` - all domains should resolve to 192.168.4.1
3. The captive portal detection should trigger automatically on most devices
2 changes: 2 additions & 0 deletions components/dns_server/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")
68 changes: 68 additions & 0 deletions components/dns_server/example/main/dns_server_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <chrono>
#include <vector>

#include "dns_server.hpp"
#include "logger.hpp"
#include "wifi.hpp"

using namespace std::chrono_literals;

extern "C" void app_main(void) {
espp::Logger logger({.tag = "DNS Server Example", .level = espp::Logger::Verbosity::INFO});

logger.info("Starting DNS Server Example");

#if CONFIG_ESP32_WIFI_NVS_ENABLED
// Initialize NVS
esp_err_t ret = nvs_flash_init();
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);
#endif

// Initialize WiFi in AP mode
std::string ap_ssid = "ESP-DNS-Test";
std::string ap_password = "testpassword";

logger.info("Starting WiFi AP: {}", ap_ssid);

espp::WifiAp ap{espp::WifiAp::Config{
.ssid = ap_ssid,
.password = ap_password,
.channel = 1,
.max_number_of_stations = 4,
}};

logger.info("WiFi AP started successfully");
logger.info("Connect to SSID: {} with password: {}", ap_ssid, ap_password);

// Get the AP IP address
std::string ap_ip = ap.get_ip_address();
logger.info("AP IP Address: {}", ap_ip);

// Create and start DNS server
logger.info("Starting DNS server on {}:53", ap_ip);

espp::DnsServer::Config dns_config{.log_level = espp::Logger::Verbosity::INFO};

espp::DnsServer dns_server(dns_config);
if (!dns_server.start()) {
logger.error("Failed to start DNS server");
return;
}

logger.info("DNS server started successfully");
logger.info("All DNS queries will resolve to: {}", ap_ip);
logger.info("");
logger.info("To test:");
logger.info("1. Connect your device to WiFi network '{}'", ap_ssid);
logger.info("2. Try pinging any domain (e.g., ping google.com)");
logger.info("3. All domains should resolve to {}", ap_ip);

// Run forever
while (true) {
std::this_thread::sleep_for(1s);
}
}
20 changes: 20 additions & 0 deletions components/dns_server/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## IDF Component Manager Manifest File
license: "MIT"
description: "DNS server component for captive portal functionality - responds to all DNS queries with a configured IP address"
url: "https://github.com/esp-cpp/espp/tree/main/components/dns_server"
repository: "git://github.com/esp-cpp/espp.git"
maintainers:
- William Emfinger <[email protected]>
documentation: "https://esp-cpp.github.io/espp/network/dns_server.html"
tas:
- cpp
- DNS
- Captive Portal
dependencies:
idf:
version: ">=5.0"
espp/base_component: ">=1.0"
espp/logger: ">=1.0"
espp/socket: ">=1.0"
examples:
- path: example
78 changes: 78 additions & 0 deletions components/dns_server/include/dns_server.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

#include <functional>
#include <memory>
#include <string>

#include "base_component.hpp"
#include "udp_socket.hpp"

namespace espp {
/**
* @brief Simple DNS server for captive portal support.
*
* This component implements a minimal DNS server that responds to all DNS queries
* with a configured IP address. This is useful for captive portals where you want
* all DNS requests to resolve to the ESP32's IP address.
*
* The server listens on UDP port 53 (standard DNS port) and responds to A record
* queries with the configured IP address.
*
* \section dns_server_ex1 DNS Server Example
* \snippet dns_server_example.cpp dns server example
*/
class DnsServer : public BaseComponent {
public:
/**
* @brief Configuration for the DNS server
*/
struct Config {
std::string ip_address; /**< IP address to respond with for all DNS queries */
espp::Logger::Verbosity log_level = espp::Logger::Verbosity::WARN; /**< Log verbosity */
};

/**
* @brief Construct a new DNS Server
* @param config Configuration for the DNS server
*/
explicit DnsServer(const Config &config);

/**
* @brief Destroy the DNS Server
*/
~DnsServer();

/**
* @brief Start the DNS server
* @return true if server started successfully, false otherwise
*/
bool start();

/**
* @brief Stop the DNS server
*/
void stop();

/**
* @brief Check if the server is running
* @return true if running, false otherwise
*/
bool is_running() const;

protected:
/**
* @brief Parse DNS query and generate response
* @param query The DNS query packet
* @param query_len Length of the query
* @param response Buffer to write the response to
* @param response_len Length of the response buffer
* @return Number of bytes written to response buffer
*/
size_t process_dns_query(const uint8_t *query, size_t query_len, uint8_t *response,
size_t response_len);

std::string ip_address_;
std::unique_ptr<UdpSocket> socket_;
bool running_{false};
};
} // namespace espp
Loading
Loading