ESPHome 2025.10.0-dev
Loading...
Searching...
No Matches
dns_server_esp32_idf.cpp
Go to the documentation of this file.
2#ifdef USE_ESP_IDF
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 ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
51
52 // Create loop-monitored UDP socket
53 this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
54 if (this->socket_ == nullptr) {
55 ESP_LOGE(TAG, "Socket create failed");
56 return;
57 }
58
59 // Set socket options
60 int enable = 1;
61 this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
62
63 // Bind to port 53
64 struct sockaddr_storage server_addr = {};
65 socklen_t addr_len = socket::set_sockaddr_any((struct sockaddr *) &server_addr, sizeof(server_addr), DNS_PORT);
66
67 int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len);
68 if (err != 0) {
69 ESP_LOGE(TAG, "Bind failed: %d", errno);
70 this->socket_ = nullptr;
71 return;
72 }
73 ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
74}
75
77 if (this->socket_ != nullptr) {
78 this->socket_->close();
79 this->socket_ = nullptr;
80 }
81 ESP_LOGV(TAG, "Stopped");
82}
83
85 // Process one request if socket is valid and data is available
86 if (this->socket_ == nullptr || !this->socket_->ready()) {
87 return;
88 }
89 struct sockaddr_in client_addr;
90 socklen_t client_addr_len = sizeof(client_addr);
91
92 // Receive DNS request using raw fd for recvfrom
93 int fd = this->socket_->get_fd();
94 if (fd < 0) {
95 return;
96 }
97
98 ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr,
99 &client_addr_len);
100
101 if (len < 0) {
102 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
103 ESP_LOGE(TAG, "recvfrom failed: %d", errno);
104 }
105 return;
106 }
107
108 ESP_LOGVV(TAG, "Received %d bytes from %s:%d", len, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
109
110 if (len < static_cast<ssize_t>(sizeof(DNSHeader) + 1)) {
111 ESP_LOGV(TAG, "Request too short: %d", len);
112 return;
113 }
114
115 // Parse DNS header
116 DNSHeader *header = (DNSHeader *) this->buffer_;
117 uint16_t flags = ntohs(header->flags);
118 uint16_t qd_count = ntohs(header->qd_count);
119
120 // Check if it's a standard query
121 if ((flags & DNS_QR_FLAG) || (flags & DNS_OPCODE_MASK) || qd_count != 1) {
122 ESP_LOGV(TAG, "Not a standard query: flags=0x%04X, qd_count=%d", flags, qd_count);
123 return; // Not a standard query
124 }
125
126 // Parse domain name (we don't actually care about it - redirect everything)
127 uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
128 uint8_t *end = this->buffer_ + len;
129
130 while (ptr < end && *ptr != 0) {
131 uint8_t label_len = *ptr;
132 if (label_len > 63) { // Check for invalid label length
133 return;
134 }
135 // Check if we have room for this label plus the length byte
136 if (ptr + label_len + 1 > end) {
137 return; // Would overflow
138 }
139 ptr += label_len + 1;
140 }
141
142 // Check if we reached a proper null terminator
143 if (ptr >= end || *ptr != 0) {
144 return; // Name not terminated or truncated
145 }
146 ptr++; // Skip the null terminator
147
148 // Check we have room for the question
149 if (ptr + sizeof(DNSQuestion) > end) {
150 return; // Request truncated
151 }
152
153 // Parse DNS question
154 DNSQuestion *question = (DNSQuestion *) ptr;
155 uint16_t qtype = ntohs(question->type);
156 uint16_t qclass = ntohs(question->dns_class);
157
158 // We only handle A queries
159 if (qtype != DNS_QTYPE_A || qclass != DNS_QCLASS_IN) {
160 ESP_LOGV(TAG, "Not an A query: type=0x%04X, class=0x%04X", qtype, qclass);
161 return; // Not an A query
162 }
163
164 // Build DNS response by modifying the request in-place
165 header->flags = htons(DNS_QR_FLAG | 0x8000); // Response + Authoritative
166 header->an_count = htons(1); // One answer
167
168 // Add answer section after the question
169 size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader);
170 size_t answer_offset = sizeof(DNSHeader) + question_len;
171
172 // Check if we have room for the answer
173 if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) {
174 ESP_LOGW(TAG, "Response too large");
175 return;
176 }
177
178 DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset);
179
180 // Pointer to name in question (offset from start of packet)
181 answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
182 answer->type = htons(DNS_QTYPE_A);
183 answer->dns_class = htons(DNS_QCLASS_IN);
184 answer->ttl = htonl(DNS_ANSWER_TTL);
185 answer->addr_len = htons(4);
186
187 // Get the raw IP address
188 ip4_addr_t addr = this->server_ip_;
189 answer->ip_addr = addr.addr;
190
191 size_t response_len = answer_offset + sizeof(DNSAnswer);
192
193 // Send response
194 ssize_t sent =
195 this->socket_->sendto(this->buffer_, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len);
196 if (sent < 0) {
197 ESP_LOGV(TAG, "Send failed: %d", errno);
198 } else {
199 ESP_LOGV(TAG, "Sent %d bytes", sent);
200 }
201}
202
203} // namespace esphome::captive_portal
204
205#endif // USE_ESP_IDF
std::unique_ptr< socket::Socket > socket_
void start(const network::IPAddress &ip)
struct @63::@64 __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:44
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:82
std::string size_t len
Definition helpers.h:291
std::string str() const
Definition ip_address.h:52
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