Files
MTGcompanion/main/dns_server.c
Rasmus d691febfe4
All checks were successful
ESP32 Build & Release / build (push) Successful in 6m41s
on device wifi
2026-09-12 13:09:47 +02:00

164 lines
4.2 KiB
C

/*
* 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;
}