-
Notifications
You must be signed in to change notification settings - Fork 25
feat(dns_server): Add DnsServer component enabling captive portal beahvior, udpate Provisioning example to use it
#594
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a7fd364
feat(dns_server): Add `DnsServer` component for implementing basic ca…
finger563 80bfa5c
doc: update
finger563 38d6e0d
update provisioning component / example to act as captive portal
finger563 1786892
Merge branch 'main' into feat/dns-captive-portal
finger563 de80bda
improve API and update examples / docs
finger563 fa4ca3b
update docs
finger563 7cc44fb
add missing sdkconfig defaults and partition for ci build
finger563 31e5d7f
target s3 in ci by default
finger563 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| idf_component_register( | ||
| INCLUDE_DIRS "include" | ||
| SRC_DIRS "src" | ||
| REQUIRES base_component logger socket | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # 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 | ||
| ``` | ||
|
|
||
| ## 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| idf_component_register(SRC_DIRS "." | ||
| INCLUDE_DIRS ".") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| #include <chrono> | ||
| #include <system_error> | ||
| #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{.ip_address = ap_ip, | ||
| .log_level = espp::Logger::Verbosity::INFO}; | ||
|
|
||
| espp::DnsServer dns_server(dns_config); | ||
| std::error_code ec; | ||
| if (!dns_server.start(ec)) { | ||
| logger.error("Failed to start DNS server: {}", ec.message()); | ||
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # ESP-IDF Partition Table | ||
| # Name, Type, SubType, Offset, Size, Flags | ||
| nvs, data, nvs, 0x9000, 0x6000, | ||
| phy_init, data, phy, 0xf000, 0x1000, | ||
| factory, app, factory, 0x10000, 1500K, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Flash size | ||
| CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y | ||
| CONFIG_ESPTOOLPY_FLASHSIZE="4MB" | ||
|
|
||
| # CONFIG_ESP32_WIFI_NVS_ENABLED=n | ||
|
|
||
| CONFIG_PARTITION_TABLE_CUSTOM=y | ||
| CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" | ||
| CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" | ||
| CONFIG_PARTITION_TABLE_OFFSET=0x8000 | ||
| CONFIG_PARTITION_TABLE_MD5=y | ||
|
|
||
| # Common ESP-related | ||
| # | ||
| CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 | ||
| CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| ## 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 <waemfinger@gmail.com> | ||
| documentation: "https://esp-cpp.github.io/espp/network/dns_server.html" | ||
| tags: | ||
| - cpp | ||
| - DNS | ||
| - Captive-Portal | ||
| - provisioning | ||
| - network | ||
| - WiFi | ||
| dependencies: | ||
| idf: | ||
| version: ">=5.0" | ||
| espp/base_component: ">=1.0" | ||
| espp/logger: ">=1.0" | ||
| espp/socket: ">=1.0" | ||
| examples: | ||
| - path: example | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| #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 | ||
| * @param ec Error code set if start fails | ||
| * @return true if server started successfully, false otherwise | ||
| */ | ||
| bool start(std::error_code &ec); | ||
|
|
||
| /** | ||
| * @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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.