2

The captive portal only fails to trigger on Android. When I connect from macOS, the portal opens automatically and loads the root HTML page as expected. On Android, I can see proper DNS and HTTP requests being made, and manually opening the browser redirects correctly to the portal. The only thing that doesn’t work is the automatic popup after connecting.

Can anyone help please?

BTW I'm running this project on esp32c3.

I (539) main_task: Returned from app_main()
I (7099) wifi:new:<1,0>, old:<1,1>, ap:<1,1>, sta:<255,255>, prof:1
I (7109) wifi:station: 7a:fc:fb:13:89:13 join, AID=1, bgn, 20
I (7309) wifi:<ba-add>idx:2 (ifx:1, 7a:fc:fb:13:89:13), tid:0, ssn:0, winSize:64
I (7549) esp_netif_lwip: DHCP server assigned IP to a client, IP is: 192.168.4.2
I (7689) dns_redirect: Received DNS request for: connectivitycheckgstaticcom
I (7689) dns_redirect: Received DNS request for: wwwgooglecom
#include <string.h>
#include <time.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include "lwip/inet.h"

#define EXAMPLE_WIFI_SSID "MyDevice_AP"
#define EXAMPLE_WIFI_PASS "12345678"
static const char *TAG = "dns_redirect";

#define DNS_PORT 53
#define DNS_IP_ADDR "192.168.4.1"
#define DNS_BUF_SIZE 512

static const char *TAG_HTTP = "captive_portal";

typedef struct {
    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)
{
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
        ESP_LOGE(TAG, "Failed to create socket");
        vTaskDelete(NULL);
        return;
    }

    struct sockaddr_in server_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(DNS_PORT),
        .sin_addr.s_addr = INADDR_ANY,
    };

    if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        ESP_LOGE(TAG, "Socket bind failed");
        close(sock);
        vTaskDelete(NULL);
        return;
    }

    uint8_t buf[DNS_BUF_SIZE];
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    while (1) {
        int len = recvfrom(sock, buf, DNS_BUF_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
        if (len < 0) {
            ESP_LOGE(TAG, "Failed to receive data");
            exit(1);
        }

        ESP_LOGI(TAG, "Received DNS request for: %s", &buf[12]);

        if (len < 12) {
            ESP_LOGE(TAG, "Invalid DNS request length: %d, minimal length is 12", len);
            exit(1);
        }

        uint8_t response[DNS_BUF_SIZE];
        memcpy(response, buf, len);

        response[2] = 0x81; // QR=1, Opcode=0, AA=0, TC=0, RD=1
        response[3] = 0x80; // RA=1, Z=0, RCODE=0

        // Answer count = 1
        response[7] = 0x01;

        int pos = len;
        response[pos++] = 0xC0;
        response[pos++] = 0x0C;

        // Type A, Class IN
        response[pos++] = 0x00; response[pos++] = 0x01;
        response[pos++] = 0x00; response[pos++] = 0x01;

        // TTL
        response[pos++] = 0x00; response[pos++] = 0x00;
        response[pos++] = 0x00; response[pos++] = 0x3C;

        // Data length = 4
        response[pos++] = 0x00; response[pos++] = 0x04;

        // IP Address
        struct in_addr ip_resp; 
        inet_aton(DNS_IP_ADDR, &ip_resp); 
        memcpy(&response[pos], &ip_resp.s_addr, 4);
        pos += 4;

        sendto(sock, response, pos, 0, (struct sockaddr *)&client_addr, addr_len);
    }
}

static const char *html_page =
"<!DOCTYPE html><html><head><title>Device Control</title></head>"
"<body><h1>Hello, Captive Portal!</h1><p>Control interface coming soon.</p></body></html>";

esp_err_t root_get_handler(httpd_req_t *req) {
    ESP_LOGI(TAG_HTTP, "Request URI: %s", req->uri);
    httpd_resp_send(req, html_page, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

esp_err_t redirect_handler(httpd_req_t *req) {
    ESP_LOGI(TAG_HTTP, "Redirecting request");
    httpd_resp_set_status(req, "302 Found");
    httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/");
    httpd_resp_send(req, NULL, 0);
    return ESP_OK;
}

httpd_uri_t root = {
    .uri      = "/",
    .method   = HTTP_GET,
    .handler  = root_get_handler,
};

httpd_uri_t catch_all = {
    .uri = "*",
    .method = HTTP_GET,
    .handler = redirect_handler
};


httpd_handle_t start_webserver(void) {

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_uri_handlers = 20;
    httpd_handle_t server = NULL;
    config.uri_match_fn = httpd_uri_match_wildcard;

    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &root);
        httpd_register_uri_handler(server, &catch_all);
    }
    return server;
}

void wifi_init_softap(void) {
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    wifi_config_t wifi_config = {
        .ap = {
            .ssid = EXAMPLE_WIFI_SSID,
            .ssid_len = strlen(EXAMPLE_WIFI_SSID),
            .password = EXAMPLE_WIFI_PASS,
            .channel = 1,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK,
            .max_connection = 4,
            .beacon_interval = 100,
        },
    };

    if (strlen(EXAMPLE_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG_HTTP, "SoftAP started with SSID: %s", EXAMPLE_WIFI_SSID);
}

void app_main(void) {
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_ap();

    wifi_init_softap();

    start_webserver();

    ESP_LOGI(TAG_HTTP, "Captive portal ready.");
    xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
}
2
  • Can you please add your general goal that you want to achieve to give others a bit of context before they start digging through the code? Commented Jul 7 at 15:03
  • Also did you check Androids documentation abou captive portal support? developer.android.com/about/versions/11/features/captive-portal Commented Jul 7 at 15:06

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.