This commit is contained in:
163
main/dns_server.c
Normal file
163
main/dns_server.c
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* MTG RFID Companion - Captive Portal DNS Server
|
||||
*
|
||||
* Lightweight UDP DNS server on port 53. Resolves every domain query
|
||||
* to 192.168.4.1 to trigger automatic captive portal detection on smartphones.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "dns_server.h"
|
||||
|
||||
static const char *TAG = "dns";
|
||||
|
||||
#define DNS_PORT 53
|
||||
#define DNS_MAX_LEN 512
|
||||
|
||||
static TaskHandle_t s_dns_task = NULL;
|
||||
static int s_sock = -1;
|
||||
static volatile bool s_running = false;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qdcount;
|
||||
uint16_t ancount;
|
||||
uint16_t nscount;
|
||||
uint16_t arcount;
|
||||
} dns_header_t;
|
||||
|
||||
static void dns_server_task(void *pvParameters)
|
||||
{
|
||||
uint8_t rx_buffer[DNS_MAX_LEN];
|
||||
uint8_t tx_buffer[DNS_MAX_LEN];
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
|
||||
struct sockaddr_in server_addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_ANY),
|
||||
.sin_port = htons(DNS_PORT),
|
||||
};
|
||||
|
||||
s_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (s_sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to create DNS socket: errno %d", errno);
|
||||
s_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
struct timeval timeout = {
|
||||
.tv_sec = 1,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
setsockopt(s_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
if (bind(s_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
||||
ESP_LOGE(TAG, "DNS socket bind failed: errno %d", errno);
|
||||
close(s_sock);
|
||||
s_sock = -1;
|
||||
s_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Captive portal DNS server listening on UDP port 53");
|
||||
|
||||
while (s_running) {
|
||||
int len = recvfrom(s_sock, rx_buffer, sizeof(rx_buffer), 0,
|
||||
(struct sockaddr *)&client_addr, &addr_len);
|
||||
|
||||
if (len < (int)sizeof(dns_header_t)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dns_header_t *req_hdr = (dns_header_t *)rx_buffer;
|
||||
int qdcount = ntohs(req_hdr->qdcount);
|
||||
if (qdcount < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Parse Question section to find end of question */
|
||||
int idx = sizeof(dns_header_t);
|
||||
while (idx < len && rx_buffer[idx] != 0) {
|
||||
idx += 1 + rx_buffer[idx];
|
||||
}
|
||||
idx += 1; /* null terminator */
|
||||
idx += 4; /* QTYPE + QCLASS */
|
||||
|
||||
if (idx > len || idx + 16 > DNS_MAX_LEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Build Response Packet */
|
||||
memcpy(tx_buffer, rx_buffer, idx);
|
||||
dns_header_t *resp_hdr = (dns_header_t *)tx_buffer;
|
||||
resp_hdr->flags = htons(0x8180); /* Standard response, recursion available, no error */
|
||||
resp_hdr->ancount = htons(1);
|
||||
resp_hdr->nscount = 0;
|
||||
resp_hdr->arcount = 0;
|
||||
|
||||
/* Answer Section */
|
||||
uint8_t *ans = tx_buffer + idx;
|
||||
/* Name pointer to question at offset 12 (0xC00C) */
|
||||
*ans++ = 0xC0;
|
||||
*ans++ = 0x0C;
|
||||
/* Type: A (IPv4) */
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x01;
|
||||
/* Class: IN */
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x01;
|
||||
/* TTL: 60 seconds */
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x3C;
|
||||
/* Data length: 4 bytes */
|
||||
*ans++ = 0x00;
|
||||
*ans++ = 0x04;
|
||||
/* IP Address: 192.168.4.1 */
|
||||
*ans++ = 192;
|
||||
*ans++ = 168;
|
||||
*ans++ = 4;
|
||||
*ans++ = 1;
|
||||
|
||||
int tx_len = ans - tx_buffer;
|
||||
sendto(s_sock, tx_buffer, tx_len, 0,
|
||||
(struct sockaddr *)&client_addr, addr_len);
|
||||
}
|
||||
|
||||
if (s_sock >= 0) {
|
||||
close(s_sock);
|
||||
s_sock = -1;
|
||||
}
|
||||
ESP_LOGI(TAG, "DNS server stopped");
|
||||
s_dns_task = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t dns_server_start(void)
|
||||
{
|
||||
if (s_running) {
|
||||
return ESP_OK;
|
||||
}
|
||||
s_running = true;
|
||||
BaseType_t ret = xTaskCreatePinnedToCore(
|
||||
dns_server_task, "dns_server", 3072, NULL, 4, &s_dns_task, 0);
|
||||
if (ret != pdPASS) {
|
||||
s_running = false;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void dns_server_stop(void)
|
||||
{
|
||||
s_running = false;
|
||||
}
|
||||
Reference in New Issue
Block a user