@ -0,0 +1,166 @@ | |||
#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.", ""); | |||
domainName.replace("https://", ""); | |||
} | |||
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(); | |||
} |
@ -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 | |||
@ -0,0 +1,11 @@ | |||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |||
<html> | |||
<head> | |||
<title></title> | |||
</head> | |||
<body> | |||
hi i am a NEW spiffs file | |||
</body> | |||
</html> |
@ -0,0 +1,95 @@ | |||
// Captive portal with (arduino) OTA + SPIFFS | |||
#include <Arduino.h> | |||
#include <ESP8266WiFi.h> | |||
#include <ESP8266mDNS.h> | |||
#include <WiFiUdp.h> | |||
#include <ArduinoOTA.h> // Over-the-Air updates | |||
#include <ESP8266WebServer.h> | |||
#include "./DNSServer.h" // Dns server | |||
#include <FS.h> // SPIFFS | |||
DNSServer dnsServer; | |||
const byte DNS_PORT = 53; | |||
ESP8266WebServer server(80); | |||
#ifndef STASSID | |||
#define STASSID "my-ssid" | |||
//#define STASPSK "mypassword" | |||
#endif | |||
IPAddress apIP(192, 168, 4, 1); | |||
const char* ssid = STASSID; | |||
//const char* password = STAPSK; | |||
void setup() { | |||
Serial.begin(115200); | |||
Serial.println("Booting"); | |||
WiFi.mode(WIFI_AP); | |||
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); | |||
WiFi.softAP(ssid); | |||
dnsServer.start(DNS_PORT, "*", apIP); // redirect dns request to AP ip | |||
MDNS.begin("esp8266", WiFi.softAPIP()); | |||
Serial.println("Ready"); | |||
Serial.print("IP address: "); | |||
Serial.println(WiFi.softAPIP()); | |||
ArduinoOTA.begin(); | |||
SPIFFS.begin(); | |||
//redirect all traffic to index.html | |||
server.onNotFound([]() { | |||
if(!handleFileRead(server.uri())){ | |||
const char *metaRefreshStr = "<head><meta http-equiv=\"refresh\" content=\"0; url=http://192.168.4.1/index.html\" /></head><body><p>redirecting...</p></body>"; | |||
server.send(200, "text/html", metaRefreshStr); | |||
} | |||
}); | |||
server.begin(); | |||
} | |||
void loop() { | |||
dnsServer.processNextRequest(); | |||
ArduinoOTA.handle(); | |||
server.handleClient(); | |||
delay(50); | |||
} | |||
String getContentType(String filename){ | |||
if(server.hasArg("download")) return "application/octet-stream"; | |||
else if(filename.endsWith(".htm")) return "text/html"; | |||
else if(filename.endsWith(".html")) return "text/html"; | |||
else if(filename.endsWith(".css")) return "text/css"; | |||
else if(filename.endsWith(".js")) return "application/javascript"; | |||
else if(filename.endsWith(".png")) return "image/png"; | |||
else if(filename.endsWith(".gif")) return "image/gif"; | |||
else if(filename.endsWith(".jpg")) return "image/jpeg"; | |||
else if(filename.endsWith(".ico")) return "image/x-icon"; | |||
else if(filename.endsWith(".xml")) return "text/xml"; | |||
else if(filename.endsWith(".mp4")) return "video/mp4"; | |||
else if(filename.endsWith(".pdf")) return "application/x-pdf"; | |||
else if(filename.endsWith(".zip")) return "application/x-zip"; | |||
else if(filename.endsWith(".gz")) return "application/x-gzip"; | |||
return "text/plain"; | |||
} | |||
//Given a file path, look for it in the SPIFFS file storage. Returns true if found, returns false if not found. | |||
bool handleFileRead(String path){ | |||
if(path.endsWith("/")) path += "index.html"; | |||
String contentType = getContentType(path); | |||
String pathWithGz = path + ".gz"; | |||
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ | |||
if(SPIFFS.exists(pathWithGz)) | |||
path += ".gz"; | |||
File file = SPIFFS.open(path, "r"); | |||
size_t sent = server.streamFile(file, contentType); | |||
file.close(); | |||
return true; | |||
} | |||
return false; | |||
} |