From 615c9279f79f9f721eedc3d48b3506b3c3054492 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Thu, 18 Jan 2018 13:54:51 +0200 Subject: [PATCH] Add DNS Server --- .../examples/CaptivePortal/CaptivePortal.ino | 52 +++++++ libraries/DNSServer/library.properties | 9 ++ libraries/DNSServer/src/DNSServer.cpp | 147 ++++++++++++++++++ libraries/DNSServer/src/DNSServer.h | 76 +++++++++ 4 files changed, 284 insertions(+) create mode 100644 libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino create mode 100644 libraries/DNSServer/library.properties create mode 100644 libraries/DNSServer/src/DNSServer.cpp create mode 100644 libraries/DNSServer/src/DNSServer.h diff --git a/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino b/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino new file mode 100644 index 00000000..ceab0da9 --- /dev/null +++ b/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino @@ -0,0 +1,52 @@ +#include +#include + +const byte DNS_PORT = 53; +IPAddress apIP(192, 168, 1, 1); +DNSServer dnsServer; +WiFiServer server(80); + +String responseHTML = "" + "CaptivePortal" + "

Hello World!

This is a captive portal example. All requests will " + "be redirected here.

"; + +void setup() { + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); + WiFi.softAP("DNSServer CaptivePortal example"); + + // if DNSServer is started with "*" for domain name, it will reply with + // provided IP to all DNS request + dnsServer.start(DNS_PORT, "*", apIP); + + server.begin(); +} + +void loop() { + dnsServer.processNextRequest(); + WiFiClient client = server.available(); // listen for incoming clients + + if (client) { + String currentLine = ""; + while (client.connected()) { + if (client.available()) { + char c = client.read(); + if (c == '\n') { + if (currentLine.length() == 0) { + client.println("HTTP/1.1 200 OK"); + client.println("Content-type:text/html"); + client.println(); + client.print(responseHTML); + break; + } else { + currentLine = ""; + } + } else if (c != '\r') { + currentLine += c; + } + } + } + client.stop(); + } +} diff --git a/libraries/DNSServer/library.properties b/libraries/DNSServer/library.properties new file mode 100644 index 00000000..8da8e6b3 --- /dev/null +++ b/libraries/DNSServer/library.properties @@ -0,0 +1,9 @@ +name=DNSServer +version=1.1.0 +author=Kristijan Novoselić +maintainer=Kristijan Novoselić, +sentence=A simple DNS server for ESP32. +paragraph=This library implements a simple DNS server. +category=Communication +url= +architectures=esp32 diff --git a/libraries/DNSServer/src/DNSServer.cpp b/libraries/DNSServer/src/DNSServer.cpp new file mode 100644 index 00000000..12cd3e50 --- /dev/null +++ b/libraries/DNSServer/src/DNSServer.cpp @@ -0,0 +1,147 @@ +#include "DNSServer.h" +#include +#include + + +DNSServer::DNSServer() +{ + _ttl = htonl(60); + _errorReplyCode = DNSReplyCode::NonExistentDomain; + _dnsHeader = NULL; + _buffer = NULL; + _currentPacketSize = 0; + _port = 0; +} + +bool DNSServer::start(const uint16_t &port, const String &domainName, + const IPAddress &resolvedIP) +{ + _port = port; + _buffer = NULL; + _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(); + free(_buffer); + _buffer = NULL; +} + +void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) +{ + domainName.toLowerCase(); + domainName.replace("www.", ""); +} + +void DNSServer::processNextRequest() +{ + _currentPacketSize = _udp.parsePacket(); + if (_currentPacketSize) + { + if (_buffer != NULL) free(_buffer); + _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char)); + if (_buffer == NULL) return; + _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); + _buffer = NULL; + } +} + +bool DNSServer::requestIncludesOnlyOneQuestion() +{ + return ntohs(_dnsHeader->QDCount) == 1 && + _dnsHeader->ANCount == 0 && + _dnsHeader->NSCount == 0 && + _dnsHeader->ARCount == 0; +} + +String DNSServer::getDomainNameWithoutWwwPrefix() +{ + String parsedDomainName = ""; + if (_buffer == NULL) return 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() +{ + if (_buffer == NULL) return; + _dnsHeader->QR = DNS_QR_RESPONSE; + _dnsHeader->ANCount = _dnsHeader->QDCount; + _dnsHeader->QDCount = 0; + + _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); + _udp.write(_buffer, _currentPacketSize); + _udp.write((unsigned char*)&_ttl, 4); + _udp.write((uint8_t)0); + _udp.write((uint8_t)4); + _udp.write(_resolvedIP, 4); + _udp.endPacket(); +} + +void DNSServer::replyWithCustomCode() +{ + if (_buffer == NULL) return; + _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(); +} diff --git a/libraries/DNSServer/src/DNSServer.h b/libraries/DNSServer/src/DNSServer.h new file mode 100644 index 00000000..570e9df6 --- /dev/null +++ b/libraries/DNSServer/src/DNSServer.h @@ -0,0 +1,76 @@ +#ifndef DNSServer_h +#define DNSServer_h +#include + +#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 + union { + struct { + uint16_t RD : 1; // recursion desired + uint16_t TC : 1; // truncated message + uint16_t AA : 1; // authoritive answer + uint16_t OPCode : 4; // message_type + uint16_t QR : 1; // query/response flag + uint16_t RCode : 4; // response code + uint16_t Z : 3; // its z! reserved + uint16_t RA : 1; // recursion available + }; + uint16_t Flags; + }; + 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 \ No newline at end of file