mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	make it captive
This commit is contained in:
		| @@ -56,10 +56,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | ||||
| } | ||||
|  | ||||
| void CaptivePortal::setup() { | ||||
| #ifndef USE_ARDUINO | ||||
|   // Disable loop for non-Arduino frameworks (DNS runs in its own task on ESP-IDF) | ||||
|   // Disable loop by default - will be enabled when captive portal starts | ||||
|   this->disable_loop(); | ||||
| #endif | ||||
| } | ||||
| void CaptivePortal::start() { | ||||
|   this->base_->init(); | ||||
| @@ -79,12 +77,14 @@ void CaptivePortal::start() { | ||||
|   this->dns_server_ = make_unique<DNSServer>(); | ||||
|   this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); | ||||
|   this->dns_server_->start(53, F("*"), ip); | ||||
|   // Re-enable loop() when DNS server is started | ||||
|   this->enable_loop(); | ||||
| #endif | ||||
|  | ||||
|   this->initialized_ = true; | ||||
|   this->active_ = true; | ||||
|  | ||||
|   // Enable loop() now that captive portal is active | ||||
|   this->enable_loop(); | ||||
|  | ||||
|   ESP_LOGV(TAG, "Captive portal started"); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -22,20 +22,24 @@ class CaptivePortal : public AsyncWebHandler, public Component { | ||||
|   CaptivePortal(web_server_base::WebServerBase *base); | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| #ifdef USE_ARDUINO | ||||
|   void loop() override { | ||||
| #ifdef USE_ARDUINO | ||||
|     if (this->dns_server_ != nullptr) { | ||||
|       this->dns_server_->processNextRequest(); | ||||
|     } else { | ||||
|       this->disable_loop(); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_ESP_IDF | ||||
|     if (this->dns_server_ != nullptr) { | ||||
|       this->dns_server_->process_next_request(); | ||||
|     } | ||||
| #endif | ||||
|   } | ||||
|   float get_setup_priority() const override; | ||||
|   void start(); | ||||
|   bool is_active() const { return this->active_; } | ||||
|   void end() { | ||||
|     this->active_ = false; | ||||
|     this->disable_loop();  // Stop processing DNS requests | ||||
|     this->base_->deinit(); | ||||
|     if (this->dns_server_ != nullptr) { | ||||
|       this->dns_server_->stop(); | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include <lwip/sockets.h> | ||||
| #include <lwip/netdb.h> | ||||
| #include <lwip/inet.h> | ||||
|  | ||||
| namespace esphome::captive_portal { | ||||
| @@ -51,83 +51,57 @@ void DNSServer::start(const network::IPAddress &ip) { | ||||
|   this->server_ip_ = ip; | ||||
|   ESP_LOGI(TAG, "Starting DNS server on %s", ip.str().c_str()); | ||||
|  | ||||
|   // Create socket | ||||
|   this->dns_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | ||||
|   if (this->dns_socket_ < 0) { | ||||
|     ESP_LOGE(TAG, "Socket create failed: %d", errno); | ||||
|   // Create loop-monitored UDP socket | ||||
|   this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP); | ||||
|   if (this->socket_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Socket create failed"); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGD(TAG, "Socket created: %d", this->dns_socket_); | ||||
|  | ||||
|   // Set socket options | ||||
|   int enable = 1; | ||||
|   if (setsockopt(this->dns_socket_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { | ||||
|     ESP_LOGW(TAG, "SO_REUSEADDR failed: %d", errno); | ||||
|   } | ||||
|   this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); | ||||
|  | ||||
|   // Bind to port 53 | ||||
|   struct sockaddr_in server_addr = {}; | ||||
|   server_addr.sin_family = AF_INET; | ||||
|   server_addr.sin_addr.s_addr = htonl(INADDR_ANY); | ||||
|   server_addr.sin_port = htons(DNS_PORT); | ||||
|   struct sockaddr_storage server_addr = {}; | ||||
|   socklen_t addr_len = socket::set_sockaddr_any((struct sockaddr *) &server_addr, sizeof(server_addr), DNS_PORT); | ||||
|  | ||||
|   if (bind(this->dns_socket_, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { | ||||
|   int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len); | ||||
|   if (err != 0) { | ||||
|     ESP_LOGE(TAG, "Bind failed: %d", errno); | ||||
|     close(this->dns_socket_); | ||||
|     this->dns_socket_ = -1; | ||||
|     this->socket_ = nullptr; | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGD(TAG, "Bound to port %d", DNS_PORT); | ||||
|  | ||||
|   // Create task | ||||
|   BaseType_t task_result = | ||||
|       xTaskCreate(&DNSServer::dns_server_task, "dns_server", DNS_TASK_STACK_SIZE, this, 1, &this->dns_task_handle_); | ||||
|   if (task_result != pdPASS) { | ||||
|     ESP_LOGE(TAG, "Task create failed"); | ||||
|     close(this->dns_socket_); | ||||
|     this->dns_socket_ = -1; | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void DNSServer::stop() { | ||||
|   if (this->dns_task_handle_) { | ||||
|     vTaskDelete(this->dns_task_handle_); | ||||
|     this->dns_task_handle_ = nullptr; | ||||
|   if (this->socket_ != nullptr) { | ||||
|     this->socket_->close(); | ||||
|     this->socket_ = nullptr; | ||||
|   } | ||||
|  | ||||
|   if (this->dns_socket_ >= 0) { | ||||
|     close(this->dns_socket_); | ||||
|     this->dns_socket_ = -1; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "Stopped"); | ||||
| } | ||||
|  | ||||
| void DNSServer::dns_server_task(void *pvParameters) { | ||||
|   DNSServer *server = static_cast<DNSServer *>(pvParameters); | ||||
|   ESP_LOGV(TAG, "Task started, socket: %d", server->dns_socket_); | ||||
|  | ||||
|   // Set socket timeout to prevent blocking forever | ||||
|   struct timeval timeout; | ||||
|   timeout.tv_sec = 1; | ||||
|   timeout.tv_usec = 0; | ||||
|   if (setsockopt(server->dns_socket_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { | ||||
|     ESP_LOGW(TAG, "SO_RCVTIMEO failed: %d", errno); | ||||
|   } | ||||
|  | ||||
|   while (true) { | ||||
|     server->process_dns_request(server->dns_socket_); | ||||
| void DNSServer::process_next_request() { | ||||
|   // Process one request if socket is valid and data is available | ||||
|   if (this->socket_ != nullptr && this->socket_->ready()) { | ||||
|     this->process_dns_request(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void DNSServer::process_dns_request(int sock) { | ||||
| void DNSServer::process_dns_request() { | ||||
|   struct sockaddr_in client_addr; | ||||
|   socklen_t client_addr_len = sizeof(client_addr); | ||||
|   uint8_t buffer[DNS_MAX_LEN]; | ||||
|  | ||||
|   // Receive DNS request | ||||
|   int len = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &client_addr, &client_addr_len); | ||||
|   // Receive DNS request using raw fd for recvfrom | ||||
|   int fd = this->socket_->get_fd(); | ||||
|   if (fd < 0) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr, | ||||
|                          &client_addr_len); | ||||
|  | ||||
|   if (len < 0) { | ||||
|     if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { | ||||
| @@ -144,7 +118,7 @@ void DNSServer::process_dns_request(int sock) { | ||||
|   } | ||||
|  | ||||
|   // Parse DNS header | ||||
|   DNSHeader *header = (DNSHeader *) buffer; | ||||
|   DNSHeader *header = (DNSHeader *) this->buffer_; | ||||
|   uint16_t flags = ntohs(header->flags); | ||||
|   uint16_t qd_count = ntohs(header->qd_count); | ||||
|  | ||||
| @@ -155,8 +129,8 @@ void DNSServer::process_dns_request(int sock) { | ||||
|   } | ||||
|  | ||||
|   // Parse domain name (we don't actually care about it - redirect everything) | ||||
|   uint8_t *ptr = buffer + sizeof(DNSHeader); | ||||
|   uint8_t *end = buffer + len; | ||||
|   uint8_t *ptr = this->buffer_ + sizeof(DNSHeader); | ||||
|   uint8_t *end = this->buffer_ + len; | ||||
|  | ||||
|   while (ptr < end && *ptr != 0) { | ||||
|     uint8_t label_len = *ptr; | ||||
| @@ -197,16 +171,16 @@ void DNSServer::process_dns_request(int sock) { | ||||
|   header->an_count = htons(1);                  // One answer | ||||
|  | ||||
|   // Add answer section after the question | ||||
|   size_t question_len = (ptr + sizeof(DNSQuestion)) - buffer - sizeof(DNSHeader); | ||||
|   size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader); | ||||
|   size_t answer_offset = sizeof(DNSHeader) + question_len; | ||||
|  | ||||
|   // Check if we have room for the answer | ||||
|   if (answer_offset + sizeof(DNSAnswer) > sizeof(buffer)) { | ||||
|   if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) { | ||||
|     ESP_LOGW(TAG, "Response too large"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   DNSAnswer *answer = (DNSAnswer *) (buffer + answer_offset); | ||||
|   DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset); | ||||
|  | ||||
|   // Pointer to name in question (offset from start of packet) | ||||
|   answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader)); | ||||
| @@ -222,7 +196,8 @@ void DNSServer::process_dns_request(int sock) { | ||||
|   size_t response_len = answer_offset + sizeof(DNSAnswer); | ||||
|  | ||||
|   // Send response | ||||
|   int sent = sendto(sock, buffer, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len); | ||||
|   ssize_t sent = | ||||
|       this->socket_->sendto(this->buffer_, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len); | ||||
|   if (sent < 0) { | ||||
|     ESP_LOGV(TAG, "Send failed: %d", errno); | ||||
|   } else { | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| #pragma once | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include <memory> | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/network/ip_address.h" | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/task.h> | ||||
| #include "esphome/components/socket/socket.h" | ||||
|  | ||||
| namespace esphome::captive_portal { | ||||
|  | ||||
| @@ -12,14 +12,14 @@ class DNSServer { | ||||
|  public: | ||||
|   void start(const network::IPAddress &ip); | ||||
|   void stop(); | ||||
|   void process_next_request(); | ||||
|  | ||||
|  protected: | ||||
|   static void dns_server_task(void *pvParameters); | ||||
|   void process_dns_request(int sock); | ||||
|   void process_dns_request(); | ||||
|  | ||||
|   TaskHandle_t dns_task_handle_{nullptr}; | ||||
|   int dns_socket_{-1}; | ||||
|   std::unique_ptr<socket::Socket> socket_{nullptr}; | ||||
|   network::IPAddress server_ip_; | ||||
|   uint8_t buffer_[256];  // DNS_MAX_LEN | ||||
| }; | ||||
|  | ||||
| }  // namespace esphome::captive_portal | ||||
|   | ||||
		Reference in New Issue
	
	Block a user