Captive portal на espruino

Тема в разделе "Iskra JS, Espruino, Йодо", создана пользователем ZM_XL, 30 апр 2024.

  1. ZM_XL

    ZM_XL Нерд

    Есть Iskra JS и WiFi (Troyka-модуль).

    Решил сделать устройство, которое можно настроить перед использованием. Для тех, кто не в курсе, что такое Captive portal - это, пока устройство не знает к какой точке доступа цепляться, оно создает свою и ждет, что ты к ней подключишься и настроишь нужные параметры. При этом хочется, чтобы при подключении к точке доступа устройства ты сразу попадал на страницу настройки без перехода в браузер и прочих неудобств.

    На форуме Амперки нашел тему с кодом для Arduino. Есть обсуждение на esp8266.ru. Еще есть хорошая статья Амперкота. Из них я понял, что надо перехватывать DNS-запросы. Пошел искать дальше.
    На гитхабе нашел решение под Espruino с продолжениями (обсуждение, решение 1, решение 2, решение 3, описание 3) на ту же тему. Судя по скринам, они даже когда-то работали. Правда, там подключались с iOS.
    Решение 3 у меня не взлетело, поскольку использует функции, которых уже нет в эспруиновском модуле "Wifi" (сам модуль тоже переименовался). Решение 2 отличается от первого двумя необязательными наворотами, поэтому я решил попробовать решение 1.

    После небольшой доработки получил следующий результат. Точка доступа создается, вешается web-сервер на 80-й порт и UDP-сокет на 53-й порт. (Правда, почему-то UDP-сокет сразу закрывается. Если Web-сервер не создавать, то не закрывается.) К точке доступа подключаюсь, но открытия страницы с "Hello World" не происходит. Пробовал с андроида и с винды. Судя по логам, DNS не отрабатывает переадресацию.

    Логи при запуске Искры:
    Код (Text):
    Wifi setup successfull
    AP created
    HTTP init
    DNS init
    Socket bound - {"type":2,"opt":{},"port":53,"sckt":6,"conn":true}
    Socket closed - no error
    Socket closed - no error
    При подключении с винды ничего не происходит.

    При подключении с андроида пишет следующее:
    Код (Text):
    HTTP request - {
      "type":1,
      "res":{
        "sckt":1,
        "headers":{"Connection":"close"}
      },
      "svr":{
        "type":1,
        "port":80,
        "sckt":6
      },
      "sckt":1,
      "hdrs":true,
      "dRcv":"",
      "headers":{
        "Connection":"close",
        "User-Agent":"Dalvik/2.1.0 (Linux; U; Android 14; SM-A525F Build/UP1A.231005.007)",
        "Host":"192.168.4.1",
        "Accept-Encoding":"gzip"
      },
      "cRcv":0,
      "method":"GET",
      "url":"/"
    }
    HTTP response
    HTTP response - {
      "sckt":1,
      "headers":{"Connection":"close"},
      "dSnd":"HTTP/1.1 200 OK\r\nServer: Espruino 2v13.5586\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n<html><body><H1>Hello World</H1></body></html>",
      "cls":true
    }
    Пробовал комментировать startDNSServer(), результат тот же.

    Кто понимает, в чем может быть проблема? Подскажите, плз.

    Ниже доработанный мною код.
    Код (Javascript):
    // Captive Portal

    PrimarySerial.setup(115200);

    var Wifi = require('@amperka/wifi');
    var http = require('http');
    var dgram = require('dgram');
    var socketUDP = dgram.createSocket('udp4');

    var SSID  = 'CaptivePortalTest';
    var authMode = 'wpa2_psk';
    var password = '1234567890';
    var portHTTP = 80;
    var portDNS  = 53;

    var dnsIPStr = '192.168.4.1';
    var dnsIP    = dnsIPStr.split('.').map(n => String.fromCharCode(parseInt(n, 10))).join('');

    var page = '<html><body><H1>Hello World</H1></body></html>';


    // get Query name out of message
    // offset = 12
    // end \x00
    function dnsQname(msg) {
      var i = 12;
      var qname = '';
      while ( msg[i] !== '\x00' ) {
        qname +=  msg[i];
        i++;
      }
      return qname + '\x00';
    }

    /*
    1. line header
    2. line query
    3. line resource
    */

    function dnsResponse(msg,dns_ip){
      return msg[0]+msg[1] + '\x81\x80'+'\x00\x01'+'\x00\x01'+'\x00\x00\x00\x00' +
             dnsQname(msg) + '\x00\x01' + '\x00\x01' +
             '\xc0\x0c'+'\x00\x01'+'\x00\x01'+'\x00\x00\x00\xf9'+'\x00\x04' + dns_ip  ;
    }

    function startDNSServer(port){
      socketUDP.on('error', (err) => {
        console.log('Socket error - ' + err);
        socketUDP.close();
      });
      socketUDP.on('close', (had_err) => {
        console.log('Socket closed - ' + (had_err ? 'with error' : 'no error'));
      });
      socketUDP.on('message', (msg, info) => {
        console.log('DNS request');
        // we only serve ip4
        if ( msg[msg.length-3] === '\x01') {
          console.log('DNS response');
          socketUDP.send(dnsResponse(msg,dnsIP),info.port,info.address);
        }
      });
      console.log('DNS init');
      socketUDP.bind(port, (res) => {
        console.log('Socket bound - ' + JSON.stringify(res));
      });
    }

    // start http server
    function startHttpServer(port){
      var webServer = http.createServer(function (req, res) {
        console.log('HTTP request - ' +  JSON.stringify(req));
        accept = req.headers.Accept || '';
        if (accept !== '*\/*') {
          console.log('HTTP response');
          res.writeHead(200, {'Content-Type': 'text/html'});
          res.end(page);
        } else { // redirect to the Setup page
          console.log('HTTP redirect');
          res.writeHead(302, {'Location': 'http://192.168.4.1',
                              'Content-Type': 'text/plain'});
          res.end();
        }
        console.log('HTTP response - ' +  JSON.stringify(res));
      });
      console.log('HTTP init');
      webServer.listen(port);
    }

    // start beeing a access point
    function startAccessPoint(ssid,authMode, password, callback){
      //Wifi.startAP(ssid,{authMode: authMode, password: password}, callback);
      var wifi = Wifi.setup(PrimarySerial, function(err) {
        if (err) print('Wifi setup error: ' + err); else print('Wifi setup successfull');
        wifi.createAP(ssid, password, 1, authMode, callback);
      });
    }

    function start(){
      startAccessPoint(SSID,authMode,password,function(err) {
        if (err) console.log('AP creating error - ' + err);
        else {
          console.log('AP created');
          startHttpServer(portHTTP);
          startDNSServer(portDNS);
        }
      });
    }

    setTimeout(start,1000);