ESPHome 2026.2.2
Loading...
Searching...
No Matches
dns_server_esp32_idf.cpp
Go to the documentation of this file.
2#ifdef USE_ESP32
7#include <lwip/sockets.h>
8#include <lwip/inet.h>
9
11
12static const char *const TAG = "captive_portal.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_OPCODE_MASK = 0x7800;
18static constexpr uint16_t DNS_QTYPE_A = 0x0001;
19static constexpr uint16_t DNS_QCLASS_IN = 0x0001;
20static constexpr uint16_t DNS_ANSWER_TTL = 300;
21
22// DNS Header structure
23struct DNSHeader {
24 uint16_t id;
25 uint16_t flags;
26 uint16_t qd_count;
27 uint16_t an_count;
28 uint16_t ns_count;
29 uint16_t ar_count;
30} __attribute__((packed));
31
32// DNS Question structure
33struct DNSQuestion {
34 uint16_t type;
35 uint16_t dns_class;
36} __attribute__((packed));
37
38// DNS Answer structure
39struct DNSAnswer {
40 uint16_t ptr_offset;
41 uint16_t type;
42 uint16_t dns_class;
43 uint32_t ttl;
44 uint16_t addr_len;
45 uint32_t ip_addr;
46} __attribute__((packed));
47
49 this->server_ip_ = ip;
50#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
51 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
52 ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
53#endif
54
55 // Create loop-monitored UDP socket
56 this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
57 if (this->socket_ == nullptr) {
58 ESP_LOGE(TAG, "Socket create failed");
59 return;
60 }
61
62 // Set socket options
63 int enable = 1;
64 this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
65
66 // Bind to port 53
67 struct sockaddr_storage server_addr = {};
68 socklen_t addr_len = socket::set_sockaddr_any((struct sockaddr *) &server_addr, sizeof(server_addr), DNS_PORT);
69
70 int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len);
71 if (err != 0) {
72 ESP_LOGE(TAG, "Bind failed: %d", errno);
73 this->socket_ = nullptr;
74 return;
75 }
76 ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
77}
78
80 if (this->socket_ != nullptr) {
81 this->socket_->close();
82 this->socket_ = nullptr;
83 }
84 ESP_LOGV(TAG, "Stopped");
85}
86
88 // Process one request if socket is valid and data is available
89 if (this->socket_ == nullptr || !this->socket_->ready()) {
90 return;
91 }
92 struct sockaddr_in client_addr;
93 socklen_t client_addr_len = sizeof(client_addr);
94
95 // Receive DNS request using raw fd for recvfrom
96 int fd = this->socket_->get_fd();
97 if (fd < 0) {
98 return;
99 }
100
101 ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr,
102 &client_addr_len);
103
104 if (len < 0) {
105 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
106 ESP_LOGE(TAG, "recvfrom failed: %d", errno);
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 | 0x8000); // 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::captive_portal
207
208#endif // USE_ESP32
std::unique_ptr< socket::Socket > socket_
void start(const network::IPAddress &ip)
struct @65::@66 __attribute__
uint16_t flags
uint16_t addr_len
uint16_t qd_count
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:178
in_addr ip4_addr_t
Definition ip_address.h:23
std::unique_ptr< Socket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:112
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().
Definition socket.cpp:159
std::string size_t len
Definition helpers.h:692
struct in_addr sin_addr
Definition headers.h:65
in_port_t sin_port
Definition headers.h:64
uint8_t end[39]
Definition sun_gtil2.cpp:17