ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
dns_server_esp32_idf.cpp
Go to the documentation of this file.
2#if defined(USE_ESP32) && (defined(USE_CAPTIVE_PORTAL) || defined(USE_WEBSERVER_CAPTIVE))
7#include <lwip/sockets.h>
8#include <lwip/inet.h>
9
11
12ESPHOME_LOG_TAG(TAG, "web_server_base.dns");
13
14// DNS constants
15static constexpr uint16_t DNS_PORT = 53;
16static constexpr uint16_t DNS_QR_FLAG = 1 << 15;
17static constexpr uint16_t DNS_AA_FLAG = 1 << 10;
18static constexpr uint16_t DNS_OPCODE_MASK = 0x7800;
19static constexpr uint16_t DNS_QTYPE_A = 0x0001;
20static constexpr uint16_t DNS_QCLASS_IN = 0x0001;
21static constexpr uint16_t DNS_ANSWER_TTL = 300;
22
23// DNS Header structure
24struct DNSHeader {
25 uint16_t id;
26 uint16_t flags;
27 uint16_t qd_count;
28 uint16_t an_count;
29 uint16_t ns_count;
30 uint16_t ar_count;
31} __attribute__((packed));
32
33// DNS Question structure
34struct DNSQuestion {
35 uint16_t type;
36 uint16_t dns_class;
37} __attribute__((packed));
38
39// DNS Answer structure
40struct DNSAnswer {
41 uint16_t ptr_offset;
42 uint16_t type;
43 uint16_t dns_class;
44 uint32_t ttl;
45 uint16_t addr_len;
46 uint32_t ip_addr;
47} __attribute__((packed));
48
50 this->server_ip_ = ip;
51#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
52 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
53 ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
54#endif
55
56 // Create loop-monitored UDP socket
57 this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP).release();
58 if (this->socket_ == nullptr) {
59 ESP_LOGE(TAG, "Socket create failed");
60 return false;
61 }
62
63 // Set socket options
64 int enable = 1;
65 this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
66
67 // Bind to port 53
68 struct sockaddr_storage server_addr = {};
69 socklen_t addr_len = socket::set_sockaddr_any((struct sockaddr *) &server_addr, sizeof(server_addr), DNS_PORT);
70
71 int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len);
72 if (err != 0) {
73 ESP_LOGE(TAG, "Bind failed: %d", errno);
74 this->destroy_socket_();
75 return false;
76 }
77 ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
78 return true;
79}
80
82 this->destroy_socket_();
83 ESP_LOGV(TAG, "Stopped");
84}
85
87 // Process one request if socket is valid and data is available
88 if (this->socket_ == nullptr || !this->socket_->ready()) {
89 return;
90 }
91 struct sockaddr_in client_addr;
92 socklen_t client_addr_len = sizeof(client_addr);
93
94 // Receive DNS request using raw fd for recvfrom
95 int fd = this->socket_->get_fd();
96 if (fd < 0) {
97 return;
98 }
99
100 ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr,
101 &client_addr_len);
102
103 if (len < 0) {
104 const int err = errno;
105 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
106 ESP_LOGE(TAG, "recvfrom failed: %d", err);
107 }
108 return;
109 }
110
111 ESP_LOGVV(TAG, "Received %d bytes from %s:%d", len, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
112
113 if (len < static_cast<ssize_t>(sizeof(DNSHeader) + 1)) {
114 ESP_LOGV(TAG, "Request too short: %d", len);
115 return;
116 }
117
118 // Parse DNS header
119 DNSHeader *header = (DNSHeader *) this->buffer_;
120 uint16_t flags = ntohs(header->flags);
121 uint16_t qd_count = ntohs(header->qd_count);
122
123 // Check if it's a standard query
124 if ((flags & DNS_QR_FLAG) || (flags & DNS_OPCODE_MASK) || qd_count != 1) {
125 ESP_LOGV(TAG, "Not a standard query: flags=0x%04X, qd_count=%d", flags, qd_count);
126 return; // Not a standard query
127 }
128
129 // Parse domain name (we don't actually care about it - redirect everything)
130 uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
131 uint8_t *end = this->buffer_ + len;
132
133 while (ptr < end && *ptr != 0) {
134 uint8_t label_len = *ptr;
135 if (label_len > 63) { // Check for invalid label length
136 return;
137 }
138 // Check if we have room for this label plus the length byte
139 if (ptr + label_len + 1 > end) {
140 return; // Would overflow
141 }
142 ptr += label_len + 1;
143 }
144
145 // Check if we reached a proper null terminator
146 if (ptr >= end || *ptr != 0) {
147 return; // Name not terminated or truncated
148 }
149 ptr++; // Skip the null terminator
150
151 // Check we have room for the question
152 if (ptr + sizeof(DNSQuestion) > end) {
153 return; // Request truncated
154 }
155
156 // Parse DNS question
157 DNSQuestion *question = (DNSQuestion *) ptr;
158 uint16_t qtype = ntohs(question->type);
159 uint16_t qclass = ntohs(question->dns_class);
160
161 // We only handle A queries
162 if (qtype != DNS_QTYPE_A || qclass != DNS_QCLASS_IN) {
163 ESP_LOGV(TAG, "Not an A query: type=0x%04X, class=0x%04X", qtype, qclass);
164 return; // Not an A query
165 }
166
167 // Build DNS response by modifying the request in-place
168 header->flags = htons(DNS_QR_FLAG | DNS_AA_FLAG); // Response + Authoritative
169 header->an_count = htons(1); // One answer
170
171 // Add answer section after the question
172 size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader);
173 size_t answer_offset = sizeof(DNSHeader) + question_len;
174
175 // Check if we have room for the answer
176 if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) {
177 ESP_LOGW(TAG, "Response too large");
178 return;
179 }
180
181 DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset);
182
183 // Pointer to name in question (offset from start of packet)
184 answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
185 answer->type = htons(DNS_QTYPE_A);
186 answer->dns_class = htons(DNS_QCLASS_IN);
187 answer->ttl = htonl(DNS_ANSWER_TTL);
188 answer->addr_len = htons(4);
189
190 // Get the raw IP address
191 ip4_addr_t addr = this->server_ip_;
192 answer->ip_addr = addr.addr;
193
194 size_t response_len = answer_offset + sizeof(DNSAnswer);
195
196 // Send response
197 ssize_t sent =
198 this->socket_->sendto(this->buffer_, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len);
199 if (sent < 0) {
200 ESP_LOGV(TAG, "Send failed: %d", errno);
201 } else {
202 ESP_LOGV(TAG, "Sent %d bytes", sent);
203 }
204}
205
206} // namespace esphome::web_server_base
207
208#endif // USE_ESP32 && (USE_CAPTIVE_PORTAL || USE_WEBSERVER_CAPTIVE)
bool ready() const
Check if the socket has buffered data ready to read.
Definition socket.h:106
int bind(const struct sockaddr *addr, socklen_t addrlen)
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)
int setsockopt(int level, int optname, const void *optval, socklen_t optlen)
bool start(const network::IPAddress &ip)
Returns false when the socket could not be created or bound.
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:45
uint16_t flags
uint16_t addr_len
uint16_t qd_count
uint32_t socklen_t
Definition headers.h:109
__int64 ssize_t
Definition httplib.h:178
struct in_addr ip4_addr_t
Definition ip_address.h:23
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.h:175
ESPHOME_LOG_TAG(TAG, "web_server_base.dns")
const void size_t len
Definition hal.h:68
static void uint32_t
char * str_to(char *buf) const
Definition ip_address.h:153
struct in_addr sin_addr
Definition headers.h:77
in_port_t sin_port
Definition headers.h:76
uint8_t end[39]
Definition sun_gtil2.cpp:17