dennisdebel
4 years ago
7 changed files with 370 additions and 0 deletions
Binary file not shown.
Binary file not shown.
@ -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,71 @@ |
|||
#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,18 @@ |
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
|||
<html> |
|||
<!--do some user agent checks...--> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
|
|||
hi |
|||
<img src="pic.jpg"> |
|||
<br><br> |
|||
<a href="/CONNECT">CONNECT (Close Captive Portal)</a> |
|||
|
|||
|
|||
|
|||
|
|||
</body> |
|||
</html> |
After Width: | Height: | Size: 108 KiB |
@ -0,0 +1,115 @@ |
|||
// 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 "\xF0\x9F\x9B\xB4 beta" |
|||
//#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()); |
|||
|
|||
//Over-the-Air updates
|
|||
ArduinoOTA.setHostname("ESP8266"); |
|||
//ArduinoOTA.setPassword("change-me"); //enabling password disables SPIFFS upload
|
|||
ArduinoOTA.begin(); |
|||
SPIFFS.begin(); |
|||
|
|||
//close the captive portal screen on ios!
|
|||
server.on("/CONNECT", closeCNAdelay); |
|||
|
|||
//if page is not found, redirect traffic to index.html (cheap way to redirect all traffic to our lading page)
|
|||
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; |
|||
} |
|||
|
|||
void handleAppleCaptivePortal() { |
|||
String Page = F("<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>"); |
|||
server.sendHeader("Cache-Control","no-cache, no-store, must-revalidate"); |
|||
server.sendHeader("Pragma", "no-cache"); |
|||
server.sendHeader("Expires", "-1"); |
|||
server.send(200, "text/html", Page); |
|||
return; |
|||
} |
|||
|
|||
void closeCNAdelay(){ |
|||
server.on("/hotspot-detect.html", handleAppleCaptivePortal); |
|||
return; |
|||
} |
Loading…
Reference in new issue