ESPHome 2026.3.3
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_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;
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;
76 }
77 ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
78}
79
81 this->destroy_socket_();
82 ESP_LOGV(TAG, "Stopped");
83}
84
86 // Process one request if socket is valid and data is available
87 if (this->socket_ == nullptr || !this->socket_->ready()) {
88 return;
89 }
90 struct sockaddr_in client_addr;
91 socklen_t client_addr_len = sizeof(client_addr);
92
93 // Receive DNS request using raw fd for recvfrom
94 int fd = this->socket_->get_fd();
95 if (fd < 0) {
96 return;
97 }
98
99 ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr,
100 &client_addr_len);
101
102 if (len < 0) {
103 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
104 ESP_LOGE(TAG, "recvfrom failed: %d", errno);
105 }
106 return;
107 }
108
109 ESP_LOGVV(TAG, "Received %d bytes from %s:%d", len, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
110
111 if (len < static_cast<ssize_t>(sizeof(DNSHeader) + 1)) {
112 ESP_LOGV(TAG, "Request too short: %d", len);
113 return;
114 }
115
116 // Parse DNS header
117 DNSHeader *header = (DNSHeader *) this->buffer_;
118 uint16_t flags = ntohs(header->flags);
119 uint16_t qd_count = ntohs(header->qd_count);
120
121 // Check if it's a standard query
122 if ((flags & DNS_QR_FLAG) || (flags & DNS_OPCODE_MASK) || qd_count != 1) {
123 ESP_LOGV(TAG, "Not a standard query: flags=0x%04X, qd_count=%d", flags, qd_count);
124 return; // Not a standard query
125 }
126
127 // Parse domain name (we don't actually care about it - redirect everything)
128 uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
129 uint8_t *end = this->buffer_ + len;
130
131 while (ptr < end && *ptr != 0) {
132 uint8_t label_len = *ptr;
133 if (label_len > 63) { // Check for invalid label length
134 return;
135 }
136 // Check if we have room for this label plus the length byte
137 if (ptr + label_len + 1 > end) {
138 return; // Would overflow
139 }
140 ptr += label_len + 1;
141 }
142
143 // Check if we reached a proper null terminator
144 if (ptr >= end || *ptr != 0) {
145 return; // Name not terminated or truncated
146 }
147 ptr++; // Skip the null terminator
148
149 // Check we have room for the question
150 if (ptr + sizeof(DNSQuestion) > end) {
151 return; // Request truncated
152 }
153
154 // Parse DNS question
155 DNSQuestion *question = (DNSQuestion *) ptr;
156 uint16_t qtype = ntohs(question->type);
157 uint16_t qclass = ntohs(question->dns_class);
158
159 // We only handle A queries
160 if (qtype != DNS_QTYPE_A || qclass != DNS_QCLASS_IN) {
161 ESP_LOGV(TAG, "Not an A query: type=0x%04X, class=0x%04X", qtype, qclass);
162 return; // Not an A query
163 }
164
165 // Build DNS response by modifying the request in-place
166 header->flags = htons(DNS_QR_FLAG | DNS_AA_FLAG); // Response + Authoritative
167 header->an_count = htons(1); // One answer
168
169 // Add answer section after the question
170 size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader);
171 size_t answer_offset = sizeof(DNSHeader) + question_len;
172
173 // Check if we have room for the answer
174 if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) {
175 ESP_LOGW(TAG, "Response too large");
176 return;
177 }
178
179 DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset);
180
181 // Pointer to name in question (offset from start of packet)
182 answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
183 answer->type = htons(DNS_QTYPE_A);
184 answer->dns_class = htons(DNS_QCLASS_IN);
185 answer->ttl = htonl(DNS_ANSWER_TTL);
186 answer->addr_len = htons(4);
187
188 // Get the raw IP address
189 ip4_addr_t addr = this->server_ip_;
190 answer->ip_addr = addr.addr;
191
192 size_t response_len = answer_offset + sizeof(DNSAnswer);
193
194 // Send response
195 ssize_t sent =
196 this->socket_->sendto(this->buffer_, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len);
197 if (sent < 0) {
198 ESP_LOGV(TAG, "Send failed: %d", errno);
199 } else {
200 ESP_LOGV(TAG, "Sent %d bytes", sent);
201 }
202}
203
204} // namespace esphome::captive_portal
205
206#endif // USE_ESP32
void start(const network::IPAddress &ip)
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)
struct @65::@66 __attribute__
uint16_t flags
uint16_t addr_len
uint16_t qd_count
uint32_t socklen_t
Definition headers.h:99
__int64 ssize_t
Definition httplib.h:178
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().
Definition socket.cpp:143
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:95
std::string size_t len
Definition helpers.h:892
static void uint32_t
struct in_addr sin_addr
Definition headers.h:67
in_port_t sin_port
Definition headers.h:66
uint8_t end[39]
Definition sun_gtil2.cpp:17