Skip to content

Instantly share code, notes, and snippets.

@Cyclenerd
Created July 26, 2017 19:46
Show Gist options
  • Save Cyclenerd/7c9cba13360ec1ec9d2ea36e50c7ff77 to your computer and use it in GitHub Desktop.
Save Cyclenerd/7c9cba13360ec1ec9d2ea36e50c7ff77 to your computer and use it in GitHub Desktop.

Revisions

  1. Cyclenerd renamed this gist Jul 26, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. Cyclenerd created this gist Jul 26, 2017.
    165 changes: 165 additions & 0 deletions DNSServer.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    #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();
    }

    72 changes: 72 additions & 0 deletions DNSServer.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    #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

    52 changes: 52 additions & 0 deletions WiFiAccessPoint.ino
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,52 @@
    /* Create a WiFi access point and provide a web server on it. */

    #include <ESP8266WiFi.h>
    #include "./DNSServer.h" // Patched lib
    #include <ESP8266WebServer.h>

    const byte DNS_PORT = 53; // Capture DNS requests on port 53
    IPAddress apIP(10, 10, 10, 1); // Private network for server
    DNSServer dnsServer; // Create the DNS object
    ESP8266WebServer webServer(80); // HTTP server

    String responseHTML = "<!DOCTYPE html>"
    "<html lang=\"en\">"
    "<head>"
    "<meta charset=\"utf-8\">"
    "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
    "<title>Internet of Bottles</title>"
    "</head>"
    "<body>"
    "<p>I'm just a stupid bottle with WiFi.</p>"
    "</body>"
    "</html>";


    void setup() {
    // turn the LED on (HIGH is the voltage level)
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);

    // configure access point
    WiFi.mode(WIFI_AP);
    WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
    WiFi.softAP("IoT --- Free WiFi"); // WiFi name

    // if DNSServer is started with "*" for domain name, it will reply with
    // provided IP to all DNS request
    dnsServer.start(DNS_PORT, "*", apIP);

    // replay to all requests with same HTML
    webServer.onNotFound([]() {
    webServer.send(200, "text/html", responseHTML);
    });
    webServer.begin();
    }

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