Your new topic does not fit any of the above??? Check first. Then post here. Thanks.

Moderator: igrr

User avatar
By blackie
#21883 The code I posted above for replyWithIP() fixes the issue with every platform I have tested so far. Windows, OSX, Android

There is still an issue with the DNS response packet created. Dig reports the answer coming back in the "additional", but it should be the "answer" section.
Code: Select allC:\tmp\BIND9.10.2-P1.x64>dig test.com

; <<>> DiG 9.10.2-P1 <<>> test.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53101
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test.com.                      IN      A

;; ADDITIONAL SECTION:
test.com.               300     IN      A       192.168.4.1

;; Query time: 165 msec
;; SERVER: 192.168.4.1#53(192.168.4.1)
;; WHEN: Fri Jun 26 15:21:46 Eastern Daylight Time 2015
;; MSG SIZE  rcvd: 53
User avatar
By SwiCago
#21907
blackie wrote:i think the question is in the buffer, so it would be a part of the response.

_udp.write(_buffer, _currentPacketSize);

Edit:
I have it working on Windows.

Code: Select allvoid DNSServer::replyWithIP()
{
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->ANCount = _dnsHeader->QDCount;
//  _dnsHeader->QDCount = 0;
  _dnsHeader->RA = 1;                                      //added

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, _currentPacketSize);

  _udp.write((uint8_t)192);     //  answer name is a pointer
   _udp.write((uint8_t)12);      // pointer to offset at 0x00c


      _udp.write((uint8_t)0);   // 0x0001  answer is type A query (host address)
  _udp.write((uint8_t)1);

    _udp.write((uint8_t)0);   //0x0001 answer is class IN (internet address)
  _udp.write((uint8_t)1);
 
  _udp.write((unsigned char*)&_ttl, 4);

    // Length of RData is 4 bytes (because, in this case, RData is IPv4)
 _udp.write((uint8_t)0);
  _udp.write((uint8_t)4);
  _udp.write(_resolvedIP, sizeof(_resolvedIP));
  _udp.endPacket();
}


Blackies above code fixes invalid DNS response for Android!!!

blackie wrote:The code I posted above for replyWithIP() fixes the issue with every platform I have tested so far. Windows, OSX, Android

There is still an issue with the DNS response packet created. Dig reports the answer coming back in the "additional", but it should be the "answer" section.
Code: Select allC:\tmp\BIND9.10.2-P1.x64>dig test.com

; <<>> DiG 9.10.2-P1 <<>> test.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53101
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;test.com.                      IN      A

;; ADDITIONAL SECTION:
test.com.               300     IN      A       192.168.4.1

;; Query time: 165 msec
;; SERVER: 192.168.4.1#53(192.168.4.1)
;; WHEN: Fri Jun 26 15:21:46 Eastern Daylight Time 2015
;; MSG SIZE  rcvd: 53



Blackie, apologies, I did not see your edit with new code change...Thank you very much for this!!!
Burkmurry and others, I can confirm that Blackies code change, which I quoted above works for Android and iOS and returns correct DNS response. In order to make Android go to Captive portal login I added Blackies code change plus the following in setup

server.on("/generate_204", handle_root); for the webserver

I am posting my full code again, with android captive portal addition (works on all OS woot)
Code: Select all#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
 
const char* ssid     = "esp8266";
boolean LEDstate[] = {LOW, false, LOW};

const char* html = "<html><head><title>Success</title><style>.bt{display:block;width:250px;height:100px;padding:10px;margin:10px;"
                    "text-align:center;border-radius:5px;color:white;font-weight:bold;font-size:70px;text-decoration:none;} "
                    "body{background:#000;} .r{background:#933;} .g{background:#363;} .y{background:#EE0;height:100px;"
                    "width:100px;border-radius:50px;} .b{background:#000;height:100px;width:100px;border-radius:50px;} "
                    ".a{font-size:35px;} td{vertical-align:middle;}</style>"
                    "</head><body><table><tr><td><div class='TGT0'></div></td><td><a class='bt g' href='/L0?v=1'>ON</a></td>"
                    "<td><a class='bt r' href='/L0?v=0'>OFF</a></td></tr><tr><td><div class='TGT2'></div></td><td>"
                    "<a class='bt g' href='/L2?v=1'>ON</a></td><td><a class='bt r' href='/L2?v=0'>OFF</a></td></tr>"
                    "<tr><td>&nbsp;</td><td><a class='bt g a' href='/ALL?v=1'><br/>ALL ON</a></td><td>"
                    "<a class='bt r a' href='/ALL?v=0'><br/>ALL OFF</a></td></tr></body></html>";

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
IPAddress netMsk(255, 255, 255, 0);
DNSServer dnsServer;
ESP8266WebServer server(80);

void setup() {
  pinMode(0, OUTPUT);
  pinMode(2, OUTPUT);
  digitalWrite(2, LEDstate[0]);
  digitalWrite(2, LEDstate[2]);
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, netMsk);
  WiFi.softAP(ssid);
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());
  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
  dnsServer.start(DNS_PORT, "*", apIP);
  Serial.println("USP Server started");
  server.on("/", handle_root);
  server.on("/generate_204", handle_root);  //Android captive portal
  server.on("/L0", handle_L0);
  server.on("/L2", handle_L2);
  server.on("/ALL", handle_ALL);
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");

}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}

void handleNotFound() {
  Serial.print("\t\t\t\t URI Not Found: ");
  Serial.println(server.uri());
  server.send ( 200, "text/plain", "URI Not Found" );
}

void handle_root() {
  Serial.println("Page served");
  String toSend = html;
  toSend.replace("TGT0", LEDstate[0] ? "y" : "b");
  toSend.replace("TGT2", LEDstate[2] ? "y" : "b");
  server.send(200, "text/html", toSend);
  delay(100);
}

void handle_L0() {
  change_states(0);
  handle_root();
}

void handle_L2() {
  change_states(2);
  handle_root();
}

void handle_ALL() {
  change_states(0);
  change_states(2);
  handle_root();
}

void change_states(int tgt) {
  if (server.hasArg("v")) {
    int state = server.arg("v").toInt() == 1;
    Serial.print("LED");
    Serial.print(tgt);
    Serial.print("=");
    Serial.println(state);
    LEDstate[tgt] = state ? HIGH : LOW;
    digitalWrite(tgt, LEDstate[tgt]);
  }
}


Some tweaks may still be needed. I noticed repeated page loads under andriod, makes it think sign in failed...So I guess adding some sort of confirm as Burkmurry mentioned would fix that...But as it stands, it will auto open captive portal login for all OS
User avatar
By SwiCago
#23900
cdmsjtr wrote:I am getting an FFatal Error, can you post the full code with the DNSServer.h and DNSServer.cpp latest code please


Here is what I am currently using
DNSServier.ccp
Code: Select all#include "DNSServer.h"
#include <lwip/def.h>
#include <Arduino.h>

#define DEBUG
#define DEBUG_OUTPUT Serial

DNSServer::DNSServer()
{
  _ttl = htonl(60);
  _errorReplyCode = DNSReplyCode::NonExistentDomain;
}

bool DNSServer::start(const uint16_t &port, const String &domainName,
                     const IPAddress &resolvedIP)
{
  _port = port;
  _domainName = domainName;
  _resolvedIP[0] = resolvedIP[0];
  _resolvedIP[1] = resolvedIP[1];
  _resolvedIP[2] = resolvedIP[2];
  _resolvedIP[3] = resolvedIP[3];
  downcaseAndRemoveWwwPrefix(_domainName);
  return _udp.begin(_port) == 1;
}

void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode)
{
  _errorReplyCode = replyCode;
}

void DNSServer::setTTL(const uint32_t &ttl)
{
  _ttl = htonl(ttl);
}

void DNSServer::stop()
{
  _udp.stop();
}

void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
{
  domainName.toLowerCase();
  domainName.replace("www.", "");
}

void DNSServer::processNextRequest()
{
  _currentPacketSize = _udp.parsePacket();
  if (_currentPacketSize)
  {
    _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char));
    _udp.read(_buffer, _currentPacketSize);
    _dnsHeader = (DNSHeader*) _buffer;

    if (_dnsHeader->QR == DNS_QR_QUERY &&
        _dnsHeader->OPCode == DNS_OPCODE_QUERY &&
        requestIncludesOnlyOneQuestion() &&
        (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
       )
    {
      replyWithIP();
    }
    else if (_dnsHeader->QR == DNS_QR_QUERY)
    {
      replyWithCustomCode();
    }

    free(_buffer);
  }
}

bool DNSServer::requestIncludesOnlyOneQuestion()
{
  return ntohs(_dnsHeader->QDCount) == 1 &&
         _dnsHeader->ANCount == 0 &&
         _dnsHeader->NSCount == 0 &&
         _dnsHeader->ARCount == 0;
}

String DNSServer::getDomainNameWithoutWwwPrefix()
{
  String parsedDomainName = "";
  unsigned char *start = _buffer + 12;
  if (*start == 0)
  {
    return parsedDomainName;
  }
  int pos = 0;
  while(true)
  {
    unsigned char labelLength = *(start + pos);
    for(int i = 0; i < labelLength; i++)
    {
      pos++;
      parsedDomainName += (char)*(start + pos);
    }
    pos++;
    if (*(start + pos) == 0)
    {
      downcaseAndRemoveWwwPrefix(parsedDomainName);
      return parsedDomainName;
    }
    else
    {
      parsedDomainName += ".";
    }
  }
}

void DNSServer::replyWithIP()
{
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->ANCount = _dnsHeader->QDCount;
  _dnsHeader->QDCount = _dnsHeader->QDCount;
  //_dnsHeader->RA = 1; 

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, _currentPacketSize);

  _udp.write((uint8_t)192); //  answer name is a pointer
  _udp.write((uint8_t)12);  // pointer to offset at 0x00c

  _udp.write((uint8_t)0);   // 0x0001  answer is type A query (host address)
  _udp.write((uint8_t)1);

  _udp.write((uint8_t)0);   //0x0001 answer is class IN (internet address)
  _udp.write((uint8_t)1);
 
  _udp.write((unsigned char*)&_ttl, 4);

  // Length of RData is 4 bytes (because, in this case, RData is IPv4)
  _udp.write((uint8_t)0);
  _udp.write((uint8_t)4);
  _udp.write(_resolvedIP, sizeof(_resolvedIP));
  _udp.endPacket();



  #ifdef DEBUG
    DEBUG_OUTPUT.print("DNS responds: ");
    DEBUG_OUTPUT.print(_resolvedIP[0]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[1]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[2]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[3]);
    DEBUG_OUTPUT.print(" for ");
    DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix());
  #endif
}

void DNSServer::replyWithCustomCode()
{
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->RCode = (unsigned char)_errorReplyCode;
  _dnsHeader->QDCount = 0;

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, sizeof(DNSHeader));
  _udp.endPacket();
}

DNSServer.h
Code: Select all#ifndef DNSServer_h
#define DNSServer_h
#include <WiFiUdp.h>

#define DNS_QR_QUERY 0
#define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0

enum class DNSReplyCode
{
  NoError = 0,
  FormError = 1,
  ServerFailure = 2,
  NonExistentDomain = 3,
  NotImplemented = 4,
  Refused = 5,
  YXDomain = 6,
  YXRRSet = 7,
  NXRRSet = 8
};

struct DNSHeader
{
  uint16_t ID;               // identification number
  unsigned char RD : 1;      // recursion desired
  unsigned char TC : 1;      // truncated message
  unsigned char AA : 1;      // authoritive answer
  unsigned char OPCode : 4;  // message_type
  unsigned char QR : 1;      // query/response flag
  unsigned char RCode : 4;   // response code
  unsigned char Z : 3;       // its z! reserved
  unsigned char RA : 1;      // recursion available
  uint16_t QDCount;          // number of question entries
  uint16_t ANCount;          // number of answer entries
  uint16_t NSCount;          // number of authority entries
  uint16_t ARCount;          // number of resource entries
};

class DNSServer
{
  public:
    DNSServer();
    void processNextRequest();
    void setErrorReplyCode(const DNSReplyCode &replyCode);
    void setTTL(const uint32_t &ttl);

    // Returns true if successful, false if there are no sockets available
    bool start(const uint16_t &port,
              const String &domainName,
              const IPAddress &resolvedIP);
    // stops the DNS server
    void stop();

  private:
    WiFiUDP _udp;
    uint16_t _port;
    String _domainName;
    unsigned char _resolvedIP[4];
    int _currentPacketSize;
    unsigned char* _buffer;
    DNSHeader* _dnsHeader;
    uint32_t _ttl;
    DNSReplyCode _errorReplyCode;

    void downcaseAndRemoveWwwPrefix(String &domainName);
    String getDomainNameWithoutWwwPrefix();
    bool requestIncludesOnlyOneQuestion();
    void replyWithIP();
    void replyWithCustomCode();
};
#endif


My demo sketch
Code: Select all#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
 
const char* ssid     = "esp8266";
boolean LEDstate[] = {LOW, false, LOW};

const char* html = "<html><head><title>Success</title><style>.bt{display:block;width:250px;height:100px;padding:10px;margin:10px;"
                    "text-align:center;border-radius:5px;color:white;font-weight:bold;font-size:70px;text-decoration:none;} "
                    "body{background:#000;} .r{background:#933;} .g{background:#363;} .y{background:#EE0;height:100px;"
                    "width:100px;border-radius:50px;} .b{background:#000;height:100px;width:100px;border-radius:50px;} "
                    ".a{font-size:35px;} td{vertical-align:middle;}</style>"
                    "</head><body><table><tr><td><div class='TGT0'></div></td><td><a class='bt g' href='/L0?v=1'>ON</a></td>"
                    "<td><a class='bt r' href='/L0?v=0'>OFF</a></td></tr><tr><td><div class='TGT2'></div></td><td>"
                    "<a class='bt g' href='/L2?v=1'>ON</a></td><td><a class='bt r' href='/L2?v=0'>OFF</a></td></tr>"
                    "<tr><td>&nbsp;</td><td><a class='bt g a' href='/ALL?v=1'><br/>ALL ON</a></td><td>"
                    "<a class='bt r a' href='/ALL?v=0'><br/>ALL OFF</a></td></tr></body></html>";

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
IPAddress netMsk(255, 255, 255, 0);
DNSServer dnsServer;
ESP8266WebServer server(80);

void setup() {
  pinMode(0, OUTPUT);
  pinMode(2, OUTPUT);
  digitalWrite(2, LEDstate[0]);
  digitalWrite(2, LEDstate[2]);
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, netMsk);
  WiFi.softAP(ssid);
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());
  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
  dnsServer.start(DNS_PORT, "*", apIP);
  Serial.println("USP Server started");
  server.on("/", handle_root);
  server.on("/generate_204", handle_root);  //Android captive portal
  server.on("/L0", handle_L0);
  server.on("/L2", handle_L2);
  server.on("/ALL", handle_ALL);
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");

}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}

void handleNotFound() {
  Serial.print("\t\t\t\t URI Not Found: ");
  Serial.println(server.uri());
  server.send ( 200, "text/plain", "URI Not Found" );
}

void handle_root() {
  Serial.println("Page served");
  String toSend = html;
  toSend.replace("TGT0", LEDstate[0] ? "y" : "b");
  toSend.replace("TGT2", LEDstate[2] ? "y" : "b");
  server.send(200, "text/html", toSend);
  delay(100);
}

void handle_L0() {
  change_states(0);
  handle_root();
}

void handle_L2() {
  change_states(2);
  handle_root();
}

void handle_ALL() {
  change_states(0);
  change_states(2);
  handle_root();
}

void change_states(int tgt) {
  if (server.hasArg("v")) {
    int state = server.arg("v").toInt() == 1;
    Serial.print("LED");
    Serial.print(tgt);
    Serial.print("=");
    Serial.println(state);
    LEDstate[tgt] = state ? HIGH : LOW;
    digitalWrite(tgt, LEDstate[tgt]);
  }
}