mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-21 10:26:06 +03:00
Add DNS forwarder to DNSServer (#7237)
The key functions added are: `bool enableForwarder(const String &domainName=emptyString, const IPAddress &dns=uint32_t)0)` If specified, `enableForwarder` will update the `domainName` that is used to match DNS request to this AP's IP Address. A non-matching request will be forwarded to the DNS server specified by `dns`. Returns `true` on success. Returns `false`, * when forwarding `dns` is not set, or * unable to allocate resources for managing the DNS forward function. `void disableForwarder(const String &domainName=emptyString, bool freeResources=false)` `disableForwarder` will stop forwarding DNS requests. If specified, updates the `domainName` that is matched for returning this AP's IP Address. Optionally, resources used for the DNS forward function can be freed.
This commit is contained in:
parent
1a49a0449b
commit
bcb5464167
@ -0,0 +1,391 @@
|
|||||||
|
/*
|
||||||
|
This example shows the use of the 'DNS forwarder' feature in the DNSServer.
|
||||||
|
It does so by combining two examples CaptivePortalAdvanced and
|
||||||
|
RangeExtender-NAPT. Additionally the CaptivePortalAdvanced part has a few
|
||||||
|
upgrades to the HTML presentation to improve readability and ease of use on
|
||||||
|
mobile devices.
|
||||||
|
|
||||||
|
Also for an example of using HTML chunked response, see handleWifi() in
|
||||||
|
handleHttp.ino.
|
||||||
|
|
||||||
|
This example starts up in Captive Portal mode by default.
|
||||||
|
It starts the SoftAP and NAPT w/o connecting the WLAN side.
|
||||||
|
|
||||||
|
You connect your computer or mobile device to the WiFi Network 'MagicPortal'
|
||||||
|
password 'ShowTime'. Your device should shortly notify you of a Captive
|
||||||
|
Portal and the need to login. If it fails to do so in a timely maner,
|
||||||
|
navigate to http://172.217.28.1/wifi and configure it there.
|
||||||
|
|
||||||
|
Note, until a successful WLAN connection is made all DNS lookups will point
|
||||||
|
back to the SoftAP at 172.217.28.1. This is the Captive Portal element of
|
||||||
|
this example.
|
||||||
|
|
||||||
|
Once the WLAN is connected, your device should notify you that you are
|
||||||
|
connected. This, of course, assumes your WLAN connection has a path to the
|
||||||
|
Internet.
|
||||||
|
|
||||||
|
At this stage we are no longer running as a Captive Portal, but a regular
|
||||||
|
NAPT. The DNSServer will be running with the DNS forwarder enabled. The
|
||||||
|
DNSServer will resolve lookups for 'margicportal' to point to 172.217.28.1
|
||||||
|
and all other lookup request will be forwarded to the 1st DNS server that was
|
||||||
|
in the DHCP response for the WLAN interface.
|
||||||
|
|
||||||
|
You should now be able to access things on the Internet. The ease of access
|
||||||
|
to devices on your home Network may vary. By IP address it should work.
|
||||||
|
Access by a hostname - maybe. Some home routers will use the hostname
|
||||||
|
supplied during DHCP to support a local DNS table; some do not.
|
||||||
|
|
||||||
|
There is an additional possible complication for using the local DNS, the DNS
|
||||||
|
suffix list, this subject is seldom discussed. It is normally handled
|
||||||
|
automaticly by the host computers DNS lookup code. For the DHCP case, the
|
||||||
|
DHCP server will supply a suffix list, if there is one. Then when a name
|
||||||
|
lookup fails and the name does not have a trailing (.)dot the host computer
|
||||||
|
will append a suffix from the list and try again, until successful or the
|
||||||
|
list is exhaused. This topic I fear can become a TL;DR. A quick wrapup by way
|
||||||
|
of an example. On an Ubuntu system run `nmcli dev show eth0 | grep
|
||||||
|
IP4\.DOMAIN` that may show you a suffix list. (replace eth0 with your wlan
|
||||||
|
interface name) Try adding them to the local name you are failing to connect
|
||||||
|
to. For example, assume 'myhost' fails. You see that 'lan' is in the suffix
|
||||||
|
list. Try connecting to 'myhost.lan'.
|
||||||
|
|
||||||
|
mDNS names also will not work. We do not have a way to pass those request
|
||||||
|
back and forth through the NAPT.
|
||||||
|
|
||||||
|
Note if hostnames are going to work for an ESP8266 device on your home
|
||||||
|
Network, you have to have the call to WiFi.hostname(...) before you call
|
||||||
|
WiFi.begin().
|
||||||
|
|
||||||
|
In this example the SoftAP in 'Captive Portal' uses the same public address
|
||||||
|
that was used in the CaptivePortalAdvanced example. Depending on your devices
|
||||||
|
you may or may not be successful in using a private address. A previous
|
||||||
|
PR-author discovered a fix that made the CaptivePortalAdvanced example work
|
||||||
|
better with Android devices. That fix was to use that public address. At this
|
||||||
|
time, this PR-author with a different Android device running the latest
|
||||||
|
version of Android has seen no problems in using either. At least not yet :)
|
||||||
|
FWIW: My device also works with the original CaptivePortalAdvanced example
|
||||||
|
when using a private address. I would suggest keeping the public address
|
||||||
|
for a while. At lest until you are confident everything is working well
|
||||||
|
before experimenting with a private address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <lwip/napt.h>
|
||||||
|
#include <lwip/dns.h>
|
||||||
|
#include <dhcpserver.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
#define NAPT 1000
|
||||||
|
#define NAPT_PORT 10
|
||||||
|
|
||||||
|
/*
|
||||||
|
Some defines for debugging
|
||||||
|
*/
|
||||||
|
#ifdef DEBUG_ESP_PORT
|
||||||
|
#define CONSOLE DEBUG_ESP_PORT
|
||||||
|
#else
|
||||||
|
#define CONSOLE Serial
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define _PRINTF(a, ...) printf_P(PSTR(a), ##__VA_ARGS__)
|
||||||
|
#define _PRINT(a) print(String(F(a)))
|
||||||
|
#define _PRINTLN(a) println(String(F(a)))
|
||||||
|
#define _PRINTLN2(a, b) println(String(F(a)) + b)
|
||||||
|
|
||||||
|
#define CONSOLE_PRINTF CONSOLE._PRINTF
|
||||||
|
#define CONSOLE_PRINT CONSOLE._PRINT
|
||||||
|
#define CONSOLE_PRINTLN CONSOLE._PRINTLN
|
||||||
|
#define CONSOLE_PRINTLN2 CONSOLE._PRINTLN2
|
||||||
|
|
||||||
|
#ifdef DEBUG_SKETCH
|
||||||
|
#define DEBUG_PRINTF CONSOLE_PRINTF
|
||||||
|
#define DEBUG_PRINT CONSOLE_PRINT
|
||||||
|
#define DEBUG_PRINTLN CONSOLE_PRINTLN
|
||||||
|
#define DEBUG_PRINTLN2 CONSOLE_PRINTLN2
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define DEBUG_PRINTF(...) \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#define DEBUG_PRINT(...) \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#define DEBUG_PRINTLN(...) \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#define DEBUG_PRINTLN2(...) \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Set these to your desired softAP credentials. They are not configurable at runtime */
|
||||||
|
#ifndef APSSID
|
||||||
|
#define APSSID "MagicPortal"
|
||||||
|
#define APPSK "ShowTime"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *softAP_ssid = APSSID;
|
||||||
|
const char *softAP_password = APPSK;
|
||||||
|
|
||||||
|
/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
|
||||||
|
const char *myHostname = "magicportal";
|
||||||
|
|
||||||
|
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
|
||||||
|
char ssid[33] = "";
|
||||||
|
char password[65] = "";
|
||||||
|
uint8_t bssid[6];
|
||||||
|
WiFiEventHandler staModeConnectedHandler;
|
||||||
|
WiFiEventHandler staModeDisconnectedHandler;
|
||||||
|
|
||||||
|
// DNS server
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
// Web server
|
||||||
|
ESP8266WebServer server(80);
|
||||||
|
|
||||||
|
/* Soft AP network parameters */
|
||||||
|
IPAddress apIP(172, 217, 28, 1);
|
||||||
|
IPAddress netMsk(255, 255, 255, 0);
|
||||||
|
|
||||||
|
|
||||||
|
/** Should I connect to WLAN asap? */
|
||||||
|
bool connect = false;
|
||||||
|
|
||||||
|
/** Set to true to start WiFi STA at setup time when credentials loaded successfuly from EEPROM */
|
||||||
|
/** Set to false to defer WiFi STA until configured through web interface. */
|
||||||
|
bool staReady = false; // Don't connect right away
|
||||||
|
|
||||||
|
/** Last time I tried to connect to WLAN */
|
||||||
|
unsigned long lastConnectTry = 0;
|
||||||
|
|
||||||
|
/** Current WLAN status */
|
||||||
|
unsigned int status = WL_IDLE_STATUS;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
WiFi.persistent(false); // w/o this a flash write occurs at every boot
|
||||||
|
WiFi.mode(WIFI_OFF); // Prevent use of SDK stored credentials
|
||||||
|
CONSOLE.begin(115200);
|
||||||
|
CONSOLE_PRINTLN("\r\n\r\nNAPT with Configuration Portal ...");
|
||||||
|
|
||||||
|
staModeConnectedHandler = WiFi.onStationModeConnected(
|
||||||
|
[](const WiFiEventStationModeConnected &data) {
|
||||||
|
// Keep a copy of the BSSID for the AP that WLAN connects to.
|
||||||
|
// This is used in the WLAN report on WiFi Details page.
|
||||||
|
memcpy(bssid, data.bssid, sizeof(bssid));
|
||||||
|
});
|
||||||
|
|
||||||
|
staModeDisconnectedHandler = WiFi.onStationModeDisconnected(
|
||||||
|
[](const WiFiEventStationModeDisconnected &) {
|
||||||
|
if (dnsServer.isForwarding()) {
|
||||||
|
dnsServer.disableForwarder("*");
|
||||||
|
dnsServer.setTTL(0);
|
||||||
|
// Reminder, Serial.println() will not work from these callbacks.
|
||||||
|
// For debug printf use ets_uart_printf().
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
While you can remove the password parameter to make the AP open.
|
||||||
|
You will be operating with less security and allowing snoopers to see
|
||||||
|
the credentials you use for your WiFi.
|
||||||
|
*/
|
||||||
|
WiFi.softAPConfig(apIP, apIP, netMsk);
|
||||||
|
WiFi.softAP(softAP_ssid, softAP_password);
|
||||||
|
// The following comment for delay(500) was committed Aug 19, 2015; is it
|
||||||
|
// still true? Commented out for verification. - APR 2020
|
||||||
|
// delay(500); // Without delay I've seen the IP address blank
|
||||||
|
CONSOLE_PRINTF("SoftAP '%s' started\r\n", softAP_ssid);
|
||||||
|
CONSOLE_PRINTLN2(" IP address: ", WiFi.softAPIP().toString());
|
||||||
|
|
||||||
|
/* Captive portals will usually use a TTL of 0 to avoid DNS cache poisoning. */
|
||||||
|
dnsServer.setTTL(0);
|
||||||
|
|
||||||
|
/* Setup the DNS server redirecting all the domains to the apIP */
|
||||||
|
dnsServer.start(IANA_DNS_PORT, "*", apIP);
|
||||||
|
CONSOLE_PRINTLN("DNSServer started:");
|
||||||
|
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
|
||||||
|
CONSOLE_PRINTF(" Resolve all domain lookups, '%s', to this AP's IP address, '%s' %s.\r\n",
|
||||||
|
dnsServer.getDomainName().c_str(),
|
||||||
|
softAP_ssid,
|
||||||
|
WiFi.softAPIP().toString().c_str());
|
||||||
|
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
|
||||||
|
|
||||||
|
/*
|
||||||
|
Do some NAPT startup stuff
|
||||||
|
*/
|
||||||
|
CONSOLE_PRINTLN("Begin NAPT initialization:");
|
||||||
|
CONSOLE_PRINTF(" Heap before NAPT init: %d\r\n", ESP.getFreeHeap());
|
||||||
|
|
||||||
|
err_t ret = ip_napt_init(NAPT, NAPT_PORT);
|
||||||
|
CONSOLE_PRINTF(" ip_napt_init(%d,%d): ret=%d (OK=%d)\r\n", NAPT, NAPT_PORT, (int)ret, (int)ERR_OK);
|
||||||
|
if (ret == ERR_OK) {
|
||||||
|
ret = ip_napt_enable_no(SOFTAP_IF, 1);
|
||||||
|
CONSOLE_PRINTF(" ip_napt_enable_no(SOFTAP_IF): ret=%d (OK=%d)\r\n", (int)ret, (int)ERR_OK);
|
||||||
|
if (ret == ERR_OK) {
|
||||||
|
CONSOLE_PRINTF(" NAPT AP '%s' started.\r\n", softAP_ssid);
|
||||||
|
if (WiFi.localIP().isSet()) {
|
||||||
|
CONSOLE_PRINTF(" It is an extension of '%s' made through WLAN interface.\r\n", ssid);
|
||||||
|
CONSOLE_PRINTF(" Remote WLAN IP Address: %s.\r\n", WiFi.localIP().toString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CONSOLE_PRINTF(" Heap after NAPT init: %d\r\n", ESP.getFreeHeap());
|
||||||
|
if (ret != ERR_OK) {
|
||||||
|
CONSOLE_PRINTF(" NAPT initialization failed!!!\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/wifi", handleWifi);
|
||||||
|
server.on("/wifisave", handleWifiSave);
|
||||||
|
server.on("/generate_204", handleRoot); // Android captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
server.begin(); // Web server start
|
||||||
|
CONSOLE_PRINTLN("HTTP server started");
|
||||||
|
loadCredentials(); // Load WLAN credentials from network
|
||||||
|
connect = (strlen(ssid) > 0 && staReady); // Request WLAN connect if there is a SSID and we want to connect at startup
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectWifi() {
|
||||||
|
CONSOLE_PRINTF("Connecting as wifi client, WLAN, to '%s' ...\r\n", ssid);
|
||||||
|
WiFi.disconnect();
|
||||||
|
/*
|
||||||
|
A call to set hostname, must be set before the call to WiFi.begin, otherwise
|
||||||
|
the name may be missing from the routers DNS lookup results. Note, not all
|
||||||
|
routers will import registered DHCP host names from clients into the active
|
||||||
|
local DNS resolver. For those that do, it is best to set hostname before
|
||||||
|
calling WiFi.begin().
|
||||||
|
*/
|
||||||
|
WiFi.hostname(myHostname);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
int connRes = WiFi.waitForConnectResult();
|
||||||
|
if (-1 == connRes) {
|
||||||
|
CONSOLE_PRINTLN(" WiFi.waitForConnectResult() timed out.");
|
||||||
|
} else {
|
||||||
|
CONSOLE_PRINTF(" Connection status: %s, %d\r\n", getWiFiStatusString(connRes).c_str(), connRes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (connect) {
|
||||||
|
connect = false;
|
||||||
|
connectWifi();
|
||||||
|
lastConnectTry = millis();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
unsigned int s = WiFi.status();
|
||||||
|
if (s == 0 && millis() > (lastConnectTry + 60000) && ssid[0] && staReady) {
|
||||||
|
/* When all of the following conditions are true, try to connect */
|
||||||
|
/* 1) If WLAN disconnected */
|
||||||
|
/* 2) Required idle time between connect attempts has passed. */
|
||||||
|
/* 3) We have an ssid configured */
|
||||||
|
/* 4) We are ready for the STA to come up */
|
||||||
|
/* Don't set retry time too low as retry interfere the softAP operation */
|
||||||
|
connect = true;
|
||||||
|
}
|
||||||
|
if (status != s) { // WLAN status change
|
||||||
|
CONSOLE_PRINTF("WLAN Status changed:\r\n");
|
||||||
|
CONSOLE_PRINTF(" new status: %s, %d\r\n", getWiFiStatusString(s).c_str(), s);
|
||||||
|
CONSOLE_PRINTF(" previous status: %s, %d\r\n", getWiFiStatusString(status).c_str(), status);
|
||||||
|
status = s;
|
||||||
|
if (s == WL_CONNECTED) {
|
||||||
|
/* Just connected to WLAN */
|
||||||
|
CONSOLE.println();
|
||||||
|
if (WiFi.localIP().isSet() && WiFi.softAPIP().isSet()) {
|
||||||
|
CONSOLE_PRINTF("NAPT AP '%s' status:\r\n", softAP_ssid);
|
||||||
|
if (WiFi.localIP().isSet()) {
|
||||||
|
CONSOLE_PRINTF(" It is an extension of '%s' made through WLAN interface.\r\n", ssid);
|
||||||
|
CONSOLE_PRINTF(" WLAN connected with IP Address: %s.\r\n", WiFi.localIP().toString().c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CONSOLE_PRINT("WLAN connected to ");
|
||||||
|
CONSOLE.println(ssid);
|
||||||
|
CONSOLE_PRINT(" IP address: ");
|
||||||
|
CONSOLE.println(WiFi.localIP());
|
||||||
|
}
|
||||||
|
// Setup MDNS responder
|
||||||
|
if (!MDNS.begin(myHostname, WiFi.localIP())) {
|
||||||
|
CONSOLE_PRINTLN(" Error setting up MDNS responder!");
|
||||||
|
} else {
|
||||||
|
CONSOLE_PRINTLN(" mDNS responder started");
|
||||||
|
// Add service to MDNS-SD
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Setup the DNSServer to respond only to request for our hostname and
|
||||||
|
forward other name request to the DNS configured to the WLAN.
|
||||||
|
*/
|
||||||
|
dnsServer.setTTL(600); // 10 minutes
|
||||||
|
dnsServer.enableForwarder(myHostname, WiFi.dnsIP(0));
|
||||||
|
CONSOLE_PRINTF("DNSServer changes/status:\r\n");
|
||||||
|
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
|
||||||
|
CONSOLE_PRINTF(" Resolve '%s' to this AP's IP address, '%s' %s.\r\n",
|
||||||
|
dnsServer.getDomainName().c_str(),
|
||||||
|
softAP_ssid,
|
||||||
|
WiFi.softAPIP().toString().c_str());
|
||||||
|
if (dnsServer.isDNSSet()) {
|
||||||
|
CONSOLE_PRINTF(" Forward other lookups to DNS: %s\r\n", dnsServer.getDNS().toString().c_str());
|
||||||
|
}
|
||||||
|
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Captive portals will usually use a TTL of 0 to avoid DNS cache poisoning. */
|
||||||
|
dnsServer.setTTL(0);
|
||||||
|
/* Setup the DNSServer to redirect all the domain lookups to the apIP */
|
||||||
|
dnsServer.disableForwarder("*");
|
||||||
|
CONSOLE_PRINTF("DNSServer changes/status:\r\n");
|
||||||
|
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
|
||||||
|
CONSOLE_PRINTF(" Resolve all domain lookups, '%s', to this AP's IP address, '%s' %s.\r\n",
|
||||||
|
dnsServer.getDomainName().c_str(),
|
||||||
|
softAP_ssid,
|
||||||
|
WiFi.softAPIP().toString().c_str());
|
||||||
|
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
|
||||||
|
|
||||||
|
// Note, it is not necessary to clear the DNS forwarder address. This
|
||||||
|
// is being done here, to test that methods isDNSSet() and setDNS() work.
|
||||||
|
dnsServer.setDNS(0U);
|
||||||
|
if (dnsServer.isDNSSet()) {
|
||||||
|
CONSOLE_PRINTF(" DNS forwarder address: %s\r\n", dnsServer.getDNS().toString().c_str());
|
||||||
|
} else {
|
||||||
|
CONSOLE_PRINTF(" DNS forwarder address not set.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s == WL_NO_SSID_AVAIL) {
|
||||||
|
WiFi.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s == WL_CONNECTED) {
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do work:
|
||||||
|
// DNS
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
// HTTP
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
void setup() {
|
||||||
|
WiFi.persistent(false);
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.printf("\n\nNAPT not supported in this configuration\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LWIP_FEATURES && !LWIP_IPV6
|
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
#if LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
// Substitution list:
|
||||||
|
// {t} - target name
|
||||||
|
// {1} - The target to redirect to, in absolute URL form.
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char portalRedirectHTML[] PROGMEM = R"EOF(
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta name='viewport' content='width=device-width'>
|
||||||
|
<meta http-equiv='Refresh' content='5; url={1}'>
|
||||||
|
<title>Redirecting</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Captive Portal Redirect</h1>
|
||||||
|
<p>Redirecting to <a href='{1}'>{t}</a></p>
|
||||||
|
<p>If you do not see the menu in 5 seconds, please click on the above link!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
#else
|
||||||
|
static const char portalRedirectHTML[] PROGMEM = R"EOF(
|
||||||
|
<!DOCTYPE html><html lang='en'><head><meta name='viewport' content='width=device-width'><meta http-equiv='Refresh' content='5; url={1}'><title>Redirecting</title></head><body><h1>Captive Portal Redirect</h1><p>Redirecting to <a href='{1}'>{t}</a></p><p>If you do not see the menu in 5 seconds, please click on the above link!</p></body></html>
|
||||||
|
)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void sendPortalRedirect(String path, String targetName) {
|
||||||
|
CONSOLE_PRINTLN2("Request redirected to captive portal, original request was for ", server.hostHeader());
|
||||||
|
/* There are 3 points of redirection here:
|
||||||
|
1) "Location" element in the header
|
||||||
|
2) HTML meta element to redirect
|
||||||
|
3) Click on link to redirect
|
||||||
|
If the "Location" header element works the HTML stuff is never seen.
|
||||||
|
*/
|
||||||
|
// https://tools.ietf.org/html/rfc7231#section-6.4.3
|
||||||
|
server.sendHeader("Location", path, true);
|
||||||
|
addNoCacheHeader();
|
||||||
|
String reply = FPSTR(portalRedirectHTML);
|
||||||
|
reply.reserve(reply.length() + 2 * path.length() + 80);
|
||||||
|
reply.replace("{t}", targetName);
|
||||||
|
reply.replace("{1}", path);
|
||||||
|
server.send(302, "text/html", reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LWIP_FEATURES && !LWIP_IPV6
|
325
libraries/DNSServer/examples/NAPTCaptivePortal/WifiHttp.ino
Normal file
325
libraries/DNSServer/examples/NAPTCaptivePortal/WifiHttp.ino
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
|
||||||
|
// #define DEBUG_VIEW
|
||||||
|
// The idea here is to debug HTML with DEBUG_VIEW defined then, when finished,
|
||||||
|
// use one of the minify web applications to strip the comments and nice
|
||||||
|
// formating spaces. Then update the minified version below.
|
||||||
|
//
|
||||||
|
// Also there are comment sections at the top and bottom of each block of HTML
|
||||||
|
// code. The purpose is to move the lines of C code near by into the blocked
|
||||||
|
// comment sections. Then you have a large block of continguious HTML that can
|
||||||
|
// be copy/pasted into one of the online web HTML checkers. You can adjust the
|
||||||
|
// code there till it is correct then copy/paste back here. Then, you can move
|
||||||
|
// comment boarders around until the C code is back in place.
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configHead[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Captive Portal html for WiFi Config
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name='viewport' content='width=device-width'>
|
||||||
|
<title>WiFi</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
vertical-align:middle;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=submit] {
|
||||||
|
width: 98%;
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mono, td, input {
|
||||||
|
font-family: 'Source Code Pro', monospace;
|
||||||
|
height: 1.5em;
|
||||||
|
font-size: 1em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg {
|
||||||
|
font-size: 1em;
|
||||||
|
height: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#outer {
|
||||||
|
width:100%
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#inner {
|
||||||
|
margin: .75em auto;
|
||||||
|
max-width: 500px;
|
||||||
|
min-width: 428px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload='ol()'>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// short hand
|
||||||
|
function ge(i) {
|
||||||
|
return document.getElementById(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle password view
|
||||||
|
function pv() {
|
||||||
|
let e = ge('p');
|
||||||
|
if (e.type === 'password') {
|
||||||
|
e.type = 'text';
|
||||||
|
} else {
|
||||||
|
e.type = 'password';
|
||||||
|
}
|
||||||
|
e.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy SSID value to input element for SSID
|
||||||
|
// Move focus to password input element and clear
|
||||||
|
function cs(i){
|
||||||
|
ge('s').value = i.innerText;
|
||||||
|
let e = ge('p');
|
||||||
|
e.type = 'password';
|
||||||
|
e.value = '';
|
||||||
|
e.focus();
|
||||||
|
e.scrollIntoView();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configHead[] PROGMEM = R"EOF(<!DOCTYPE html><html lang="en"><head><meta name='viewport' content='width=device-width'><title>WiFi</title><style>html,body{width:100%;font-family:'Open Sans',Helvetica,Arial,sans-serif;font-size:16px}h1,h2,h3,h4{vertical-align:middle;text-align:center}input[type=submit]{width:98%;font-family:'Open Sans',Helvetica,Arial,sans-serif;height:3em}a{text-decoration:none;color:blue}.mono,td,input{font-family:'Source Code Pro',monospace;height:1.5em;font-size:1em;vertical-align:middle}.lg{font-size:1em;height:1.8em}.left{text-align:left}.center{text-align:center}.right{text-align:right}#outer{width:100% overflow-y: auto}#inner{margin: .75em auto;max-width:500px;min-width:428px;text-align:left}</style></head><body onload='ol()'> <script>function ge(i){return document.getElementById(i);} function pv(){let e=ge('p');if(e.type==='password'){e.type='text';}else{e.type='password';} e.focus();} function cs(i){ge('s').value=i.innerText;let e=ge('p');e.type='password';e.value='';e.focus();e.scrollIntoView();}</script> )EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configPresetInput[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
Prefill input fields with the last credentials used.
|
||||||
|
{s} - Network Name/SSID
|
||||||
|
{p} - Password
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
// After web page load init SSID/PSK field
|
||||||
|
// onload copy credentials
|
||||||
|
function ol() {
|
||||||
|
ge('s').value='{s}';
|
||||||
|
ge('p').value='{p}';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configPresetInput[] PROGMEM = R"EOF(<script>function ol(){ge('s').value='{s}';ge('p').value='{p}';}</script>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configConnection[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
{w} - SoftAP info "SoftAP: softAP_ssid"
|
||||||
|
or - WiFi Network connection info "WiFi Network: ssid"
|
||||||
|
-->
|
||||||
|
<div id='outer'>
|
||||||
|
<div id='inner'>
|
||||||
|
<h1>WiFi Details and Config</h1>
|
||||||
|
<p>You are connected through the {w}</p>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configConnection[] PROGMEM = R"EOF(<div id='outer'><div id='inner'><h1>WiFi Details and Config</h1><p>You are connected through the {w}</p>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configAPInfo[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
{s} - SSDI of Network
|
||||||
|
{i} - IP address on that Network, WiFi.softAPIP or WiFi.localIP
|
||||||
|
{a} - Number of Stations connected to AP
|
||||||
|
-->
|
||||||
|
<br />
|
||||||
|
<h2>SoftAP Details</h2>
|
||||||
|
<table>
|
||||||
|
<tr><td>SSI</td><td>{s}</td></tr>
|
||||||
|
<tr><td>BSSID </td><td>{b}</td></tr>
|
||||||
|
<tr><td>IP</td><td>{i}</td></tr>
|
||||||
|
<tr><td>STA</td><td>Count: {a}</td></tr>
|
||||||
|
</table>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configAPInfo[] PROGMEM = R"EOF(<br /><h2>SoftAP Details</h2><table><tr><td>SSI</td><td>{s}</td></tr><tr><td>BSSID </td><td>{b}</td></tr><tr><td>IP</td><td>{i}</td></tr><tr><td>STA</td><td>Count: {a}</td></tr></table>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configWLANInfo[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
{s} - SSDI of Network
|
||||||
|
{b} - BSSID
|
||||||
|
{c} - Channel
|
||||||
|
{p} - PHY Mode
|
||||||
|
{r} - RSSI
|
||||||
|
{i} - IP address on that Network, WiFi.softAPIP or WiFi.localIP
|
||||||
|
{m} - Subnet Mask
|
||||||
|
{g} - Gateway
|
||||||
|
{1} - DNS1
|
||||||
|
{2} - DNS2
|
||||||
|
-->
|
||||||
|
<br />
|
||||||
|
<h2>WLAN Details</h2>
|
||||||
|
<table>
|
||||||
|
<tr><td>SSID</td><td>{s}</td></tr>
|
||||||
|
<tr><td>BSSID </td><td>{b}</td></tr>
|
||||||
|
<tr><td>CH</td><td>{c}</td></tr>
|
||||||
|
<tr><td>PHY</td><td>{p}</td></tr>
|
||||||
|
<tr><td>RSSI</td><td>{r}</td></tr>
|
||||||
|
<tr><td>IP</td><td>{i}</td></tr>
|
||||||
|
<tr><td>GW</td><td>{g}</td></tr>
|
||||||
|
<tr><td>Mask</td><td>{m}</td></tr>
|
||||||
|
<tr><td>DNS1</td><td>{1}</td></tr>
|
||||||
|
<tr><td>DNS2</td><td>{2}</td></tr>
|
||||||
|
</table>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configWLANInfo[] PROGMEM = R"EOF(<br /><h2>WLAN Details</h2><table><tr><td>SSID</td><td>{s}</td></tr><tr><td>BSSID </td><td>{b}</td></tr><tr><td>CH</td><td>{c}</td></tr><tr><td>PHY</td><td>{p}</td></tr><tr><td>RSSI</td><td>{r}</td></tr><tr><td>IP</td><td>{i}</td></tr><tr><td>GW</td><td>{g}</td></tr><tr><td>Mask</td><td>{m}</td></tr><tr><td>DNS1</td><td>{1}</td></tr><tr><td>DNS2</td><td>{2}</td></tr></table>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configWLANOffline[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
<br />
|
||||||
|
<h2>WLAN - offline</h2>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configWLANOffline[] PROGMEM = R"EOF(<br /><h2>WLAN - offline</h2>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configList[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
<br />
|
||||||
|
<h2>WLAN Network List</h2>
|
||||||
|
<h4>(refresh if any are missing)</h4>
|
||||||
|
<table>
|
||||||
|
<tr><th class='left'>Network Name/SSID</th>
|
||||||
|
<th style='width: 2em;' class='center'>CH</th>
|
||||||
|
<th style='width: 1em;' class='center'></th>
|
||||||
|
<th class='right'>RSSI</th></tr>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configList[] PROGMEM = R"EOF(<br /><h2>WLAN Network List</h2><h4>(refresh if any are missing)</h4><table><tr><th class='left'>Network Name/SSID</th><th style='width: 2em;' class='center'>CH</th><th style='width: 1em;' class='center'></th><th class='right'>RSSI</th></tr>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configItem[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
use this with each AP when APs are found in a scan
|
||||||
|
{s} - WiFi.SSID
|
||||||
|
{c} - channel
|
||||||
|
{l} - For a secure Network replace with 🔒 (lock symbol)
|
||||||
|
{r} - WiFi.RSSI
|
||||||
|
-->
|
||||||
|
<tr><td><a class='mono' onclick='cs(this)' title='{t}'>{s}</a></td>
|
||||||
|
<td class='center'>{c}</td>
|
||||||
|
<td class='center'>{l}</td>
|
||||||
|
<td class='right'>{r}</td></tr>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configItem[] PROGMEM = R"EOF(<tr><td><a class='mono' onclick='cs(this)' title='{t}'>{s}</a></td><td class='center'>{c}</td><td class='center'>{l}</td><td class='right'>{r}</td></tr>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configNoAPs[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
or this when no APs are found
|
||||||
|
-->
|
||||||
|
<tr><td>No WLAN found</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td></tr>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configNoAPs[] PROGMEM = R"EOF(<tr><td>No WLAN found</td><td></td><td></td><td></td></tr>)EOF";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configEnd[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
</table>
|
||||||
|
<br /><form method='POST' action='wifisave'><h4>Connect to Network:</h4>
|
||||||
|
<input id='s' class='lg' type='text' size=32 maxlength=32 placeholder='Network Name/SSID' name='n' spellcheck='false' data-gramm_editor='false'/>
|
||||||
|
<br /><br />
|
||||||
|
<input id='p' class='lg' type='password' size=32 maxlength=64 placeholder='password' name='p' spellcheck='false' data-gramm_editor='false'/>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_VIEW
|
||||||
|
static const char configEnd2[] PROGMEM = R"EOF(
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
<a class='lg' onclick='pv();'>👁</a>
|
||||||
|
<br /><br /><input type='submit' value='Connect/Disconnect'/>
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
<p>You may want to <a href='/'>return to the home page</a>.</p>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!--
|
||||||
|
-->
|
||||||
|
)EOF";
|
||||||
|
#else
|
||||||
|
static const char configEnd[] PROGMEM = R"EOF(</table> <br /><form method='POST' action='wifisave'><h4>Connect to Network:</h4> <input id='s' class='lg' type='text' size=32 maxlength=32 placeholder='Network Name/SSID' name='n' spellcheck='false' data-gramm_editor='false'/> <br /><br /> <input id='p' class='lg' type='password' size=32 maxlength=64 placeholder='password' name='p' spellcheck='false' data-gramm_editor='false'/> <a class='lg' onclick='pv();'>👁</a> <br /><br /><input type='submit' value='Connect/Disconnect'/></form> <br /><p>You may want to <a href='/'>return to the home page</a>.</p><p></p></div></div></body></html>)EOF";
|
||||||
|
#endif
|
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
#if LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
/** Load WLAN credentials from EEPROM */
|
||||||
|
void loadCredentials() {
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.get(0, ssid);
|
||||||
|
EEPROM.get(0 + sizeof(ssid), password);
|
||||||
|
char ok[2 + 1];
|
||||||
|
EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
|
||||||
|
EEPROM.end();
|
||||||
|
if (String(ok) != String("OK")) {
|
||||||
|
ssid[0] = 0;
|
||||||
|
password[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_PRINTLN("Recovered credentials:");
|
||||||
|
CONSOLE_PRINTF(" %s\r\n", ssid);
|
||||||
|
CONSOLE_PRINTF(" %s\r\n", strlen(password) > 0 ? "********" : "<no password>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Store WLAN credentials to EEPROM */
|
||||||
|
void saveCredentials() {
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.put(0, ssid);
|
||||||
|
EEPROM.put(0 + sizeof(ssid), password);
|
||||||
|
char ok[2 + 1] = "OK";
|
||||||
|
EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
|
||||||
|
EEPROM.commit();
|
||||||
|
EEPROM.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LWIP_FEATURES && !LWIP_IPV6
|
250
libraries/DNSServer/examples/NAPTCaptivePortal/handleHttp.ino
Normal file
250
libraries/DNSServer/examples/NAPTCaptivePortal/handleHttp.ino
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
#if LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
#ifndef TCP_MSS
|
||||||
|
#define TCP_MSS 1460
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
Use kMaxChunkSize to limit size of chuncks
|
||||||
|
*/
|
||||||
|
constexpr inline size_t kMaxChunkSize = TCP_MSS;
|
||||||
|
String& sendIfOver(String& str, size_t threshold = kMaxChunkSize / 2);
|
||||||
|
size_t sendAsChunks_P(PGM_P content, size_t chunkSize = kMaxChunkSize);
|
||||||
|
|
||||||
|
size_t maxPage = 0;
|
||||||
|
|
||||||
|
void addNoCacheHeader() {
|
||||||
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
server.sendHeader("Pragma", "no-cache");
|
||||||
|
server.sendHeader("Expires", "-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String& sendIfOver(String& str, size_t threshold) {
|
||||||
|
size_t len = str.length();
|
||||||
|
if (len > threshold) {
|
||||||
|
// Use later to determine if we reserved enough room in page to avoid realloc
|
||||||
|
maxPage = std::max(maxPage, len);
|
||||||
|
server.sendContent(str);
|
||||||
|
str = "";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The idea here is to avoid a large allocation by sendContent_P to copy a
|
||||||
|
big PROGMEM string. Slice PROGMEM string into chuncks and send.
|
||||||
|
*/
|
||||||
|
size_t sendAsChunks_P(PGM_P content, size_t chunkSize) {
|
||||||
|
size_t len = strlen_P(content);
|
||||||
|
for (size_t pos = 0; pos < len; pos += chunkSize) {
|
||||||
|
server.sendContent_P(&content[pos], ((len - pos) >= chunkSize) ? chunkSize : len - pos);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle root or redirect to captive portal */
|
||||||
|
void handleRoot() {
|
||||||
|
if (captivePortal()) {
|
||||||
|
// If captive portal is needed, redirect instead of displaying the page.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addNoCacheHeader();
|
||||||
|
|
||||||
|
String Page;
|
||||||
|
Page += F(
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
"<html lang='en'><head><meta name='viewport' content='width=device-width'>"
|
||||||
|
"<title>ADV CAP Portal Example</title>"
|
||||||
|
"</head><body>"
|
||||||
|
"<h1>HELLO WORLD!!</h1>");
|
||||||
|
if (server.client().localIP() == apIP) {
|
||||||
|
Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
|
||||||
|
} else {
|
||||||
|
Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
|
||||||
|
}
|
||||||
|
Page += F(
|
||||||
|
"<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
|
||||||
|
"</body></html>");
|
||||||
|
|
||||||
|
server.send(200, F("text/html"), Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Redirect to the captive portal if we got a request for another domain.
|
||||||
|
Return true in that case, so the page handler does not try to handle
|
||||||
|
the request again.
|
||||||
|
*/
|
||||||
|
boolean captivePortal() {
|
||||||
|
IPAddress hAddr, cAddr;
|
||||||
|
|
||||||
|
cAddr = server.client().localIP();
|
||||||
|
if (!cAddr.isSet()) {
|
||||||
|
// The connection closed prematurely on us.
|
||||||
|
// Return true, so no further action is taken.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hAddr.fromString(server.hostHeader()) && hAddr == cAddr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hAddr.isSet() || (server.hostHeader() != (String(myHostname) + ".local") && // arrived here by mDNS
|
||||||
|
server.hostHeader() != String(myHostname))) { // arrived here by local router DNS
|
||||||
|
String whereTo = String("http://") + server.client().localIP().toString();
|
||||||
|
sendPortalRedirect(whereTo, F("Captive Portal Example"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Wifi Details and Config page handler */
|
||||||
|
void handleWifi() {
|
||||||
|
addNoCacheHeader();
|
||||||
|
|
||||||
|
// use HTTP/1.1 Chunked response to avoid building a huge temporary string
|
||||||
|
if (!server.chunkedResponseModeStart(200, F("text/html"))) {
|
||||||
|
server.send(505, F("text/plain"), F("HTTP1.1 required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a few chunks of the HTML that don't need to change.
|
||||||
|
sendAsChunks_P(configHead);
|
||||||
|
|
||||||
|
String page;
|
||||||
|
|
||||||
|
CONSOLE_PRINTLN2("sizeof(configHead): ", (sizeof(configHead)));
|
||||||
|
CONSOLE_PRINTLN2("sizeof(configWLANInfo): ", (sizeof(configWLANInfo)));
|
||||||
|
CONSOLE_PRINTLN2("sizeof(configList): ", (sizeof(configList)));
|
||||||
|
CONSOLE_PRINTLN2("sizeof(configEnd): ", (sizeof(configEnd)));
|
||||||
|
// Just do max on some of the visually larger HTML chunks that will be loaded
|
||||||
|
// into page and add a little for growth when substituting values in.
|
||||||
|
size_t thisMany = std::max(sizeof(configWLANInfo), sizeof(configList)) + 200;
|
||||||
|
CONSOLE_PRINTLN2("Estimate Minimum page reserve size: ", (thisMany));
|
||||||
|
page.reserve(std::max(kMaxChunkSize, thisMany));
|
||||||
|
|
||||||
|
page = FPSTR(configPresetInput);
|
||||||
|
/*
|
||||||
|
Set previously used/entered credentials as a default entries.
|
||||||
|
This allows an opportunity to correct them and try again.
|
||||||
|
*/
|
||||||
|
page.replace("{s}", String(ssid));
|
||||||
|
page.replace("{p}", String(password));
|
||||||
|
sendIfOver(page);
|
||||||
|
|
||||||
|
page += FPSTR(configConnection);
|
||||||
|
if (server.client().localIP() == apIP) {
|
||||||
|
page.replace("{w}", String(F("SoftAP: ")) + softAP_ssid);
|
||||||
|
} else {
|
||||||
|
page.replace("{w}", String(F("WiFi Network: ")) + ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To avoid sending lots of small packets. We call this function frequently,
|
||||||
|
to check if the 'page' has gone over 512 bytes and if so send.
|
||||||
|
*/
|
||||||
|
sendIfOver(page);
|
||||||
|
|
||||||
|
page += FPSTR(configAPInfo);
|
||||||
|
{
|
||||||
|
uint8_t sta_cnt = wifi_softap_get_station_num();
|
||||||
|
page.replace("{s}", String(softAP_ssid));
|
||||||
|
page.replace("{b}", String(WiFi.softAPmacAddress()));
|
||||||
|
page.replace("{i}", WiFi.softAPIP().toString());
|
||||||
|
page.replace("{a}", String(sta_cnt));
|
||||||
|
sendIfOver(page);
|
||||||
|
if (sta_cnt) {
|
||||||
|
page += String(F("\r\n<PRE>\r\n"));
|
||||||
|
struct station_info* info = wifi_softap_get_station_info();
|
||||||
|
IPAddress addr;
|
||||||
|
while (info != NULL) {
|
||||||
|
addr = info->ip;
|
||||||
|
page += macToString(info->bssid) + F(" ") + addr.toString() + F("\r\n");
|
||||||
|
info = STAILQ_NEXT(info, next);
|
||||||
|
sendIfOver(page);
|
||||||
|
}
|
||||||
|
page += F("</PRE>\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Before we prepare a large block for sending, we call 'sendIfOver' with a
|
||||||
|
threshold of 0 to force the sending of the current 'page' content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (WiFi.localIP().isSet()) {
|
||||||
|
sendIfOver(page, 0);
|
||||||
|
page += FPSTR(configWLANInfo);
|
||||||
|
page.replace("{s}", String(ssid));
|
||||||
|
page.replace("{b}", macToString(bssid));
|
||||||
|
page.replace("{c}", String(WiFi.channel()));
|
||||||
|
page.replace("{p}", String(F("802.11")) + (getPhyModeChar(WiFi.getPhyMode())));
|
||||||
|
page.replace("{r}", String(WiFi.RSSI()));
|
||||||
|
page.replace("{i}", WiFi.localIP().toString());
|
||||||
|
page.replace("{g}", WiFi.gatewayIP().toString());
|
||||||
|
page.replace("{m}", WiFi.subnetMask().toString());
|
||||||
|
page.replace("{1}", WiFi.dnsIP(0).toString());
|
||||||
|
page.replace("{2}", WiFi.dnsIP(1).toString());
|
||||||
|
} else {
|
||||||
|
page += FPSTR(configWLANOffline);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendIfOver(page, 0);
|
||||||
|
sendAsChunks_P(configList);
|
||||||
|
|
||||||
|
CONSOLE_PRINTLN("scan start");
|
||||||
|
int n = WiFi.scanNetworks();
|
||||||
|
CONSOLE_PRINTLN("scan done");
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
for (size_t i = 0; i < (size_t)n; i++) {
|
||||||
|
page += FPSTR(configItem);
|
||||||
|
page.replace("{s}", WiFi.SSID(i));
|
||||||
|
page.replace("{t}", WiFi.BSSIDstr(i));
|
||||||
|
page.replace("{c}", String(WiFi.channel(i)));
|
||||||
|
page.replace("{l}", (WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F("") : F("🔒"));
|
||||||
|
page.replace("{r}", String(WiFi.RSSI(i)));
|
||||||
|
sendIfOver(page);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
page += FPSTR(configNoAPs);
|
||||||
|
}
|
||||||
|
sendIfOver(page, 0); // send what we have buffered before next direct send.
|
||||||
|
sendAsChunks_P(configEnd);
|
||||||
|
|
||||||
|
CONSOLE_PRINTLN2("MAX String memory used: ", (maxPage));
|
||||||
|
server.chunkedResponseFinalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle the WLAN save form and redirect to WLAN config page again */
|
||||||
|
void handleWifiSave() {
|
||||||
|
CONSOLE_PRINTLN("wifi save");
|
||||||
|
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
|
||||||
|
server.arg("p").toCharArray(password, sizeof(password) - 1);
|
||||||
|
sendPortalRedirect(F("wifi"), F("Wifi Config"));
|
||||||
|
saveCredentials();
|
||||||
|
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNotFound() {
|
||||||
|
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String message = F("File Not Found\r\n\r\n");
|
||||||
|
message += F("URI: ");
|
||||||
|
message += server.uri();
|
||||||
|
message += F("\r\nMethod: ");
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += F("\r\nArguments: ");
|
||||||
|
message += server.args();
|
||||||
|
message += F("\r\n");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\r\n");
|
||||||
|
}
|
||||||
|
addNoCacheHeader();
|
||||||
|
server.send(404, F("text/plain"), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LWIP_FEATURES && !LWIP_IPV6
|
84
libraries/DNSServer/examples/NAPTCaptivePortal/tools.ino
Normal file
84
libraries/DNSServer/examples/NAPTCaptivePortal/tools.ino
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
These functions may exist in other projects
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if LWIP_FEATURES && !LWIP_IPV6
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a descriptive string for WiFi.status() value
|
||||||
|
*/
|
||||||
|
String getWiFiStatusString(uint32_t status) {
|
||||||
|
const __FlashStringHelper* r;
|
||||||
|
switch (status) {
|
||||||
|
case WL_IDLE_STATUS:
|
||||||
|
r = F("WL_IDLE_STATUS");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_NO_SSID_AVAIL:
|
||||||
|
r = F("WL_NO_SSID_AVAIL");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_SCAN_COMPLETED:
|
||||||
|
r = F("WL_SCAN_COMPLETED");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_CONNECTED:
|
||||||
|
r = F("WL_CONNECTED");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_CONNECT_FAILED:
|
||||||
|
r = F("WL_CONNECT_FAILED");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_CONNECTION_LOST:
|
||||||
|
r = F("WL_CONNECTION_LOST");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_DISCONNECTED:
|
||||||
|
r = F("WL_DISCONNECTED");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WL_NO_SHIELD:
|
||||||
|
r = F("WL_NO_SHIELD");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return String(F("Unknown: 0x")) + String(status, HEX);
|
||||||
|
}
|
||||||
|
return String(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a single charcter to append to a "802.11" string to describe the PHY
|
||||||
|
mode of a WiFi device. Can be used with the value returned by WiFi.getPhyMode().
|
||||||
|
*/
|
||||||
|
char getPhyModeChar(WiFiPhyMode_t i) {
|
||||||
|
switch (i) {
|
||||||
|
case WIFI_PHY_MODE_11B:
|
||||||
|
return 'b'; // = 1
|
||||||
|
case WIFI_PHY_MODE_11G:
|
||||||
|
return 'g'; // = 2,
|
||||||
|
case WIFI_PHY_MODE_11N:
|
||||||
|
return 'n'; // = 3,
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return a String of 6 colon separated hex bytes.
|
||||||
|
This format is commonly used when printing 6 byte MAC addresses.
|
||||||
|
*/
|
||||||
|
String macToString(const unsigned char* mac) {
|
||||||
|
char buf[20];
|
||||||
|
int rc = snprintf(buf, sizeof(buf), PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
|
if (rc < 0 || rc >= (int)sizeof(buf)) {
|
||||||
|
return emptyString;
|
||||||
|
}
|
||||||
|
return String(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LWIP_FEATURES && !LWIP_IPV6
|
@ -1,33 +1,139 @@
|
|||||||
|
#include <ESP8266WiFi.h>
|
||||||
#include "DNSServer.h"
|
#include "DNSServer.h"
|
||||||
#include <lwip/def.h>
|
#include <lwip/def.h>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
extern struct rst_info resetInfo;
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_PORT
|
#ifdef DEBUG_ESP_PORT
|
||||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
#define CONSOLE DEBUG_ESP_PORT
|
||||||
#else
|
#else
|
||||||
#define DEBUG_OUTPUT Serial
|
#define CONSOLE Serial
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define _ETS_PRINTF(a, ...) ets_uart_printf(a, ##__VA_ARGS__)
|
||||||
|
#define _ETS_PRINTFNL(a, ...) ets_uart_printf(a "\n", ##__VA_ARGS__)
|
||||||
|
#define _PRINTF(a, ...) printf_P(PSTR(a), ##__VA_ARGS__)
|
||||||
|
#define _PRINT(a) print(String(F(a)))
|
||||||
|
#define _PRINTLN(a) println(String(F(a)))
|
||||||
|
#define _PRINTLN2(a, b) println(String(F(a)) + b )
|
||||||
|
|
||||||
|
#define ETS_PRINTF _ETS_PRINTF
|
||||||
|
#define ETS_PRINTFNL _ETS_PRINTFNL
|
||||||
|
#define CONSOLE_PRINTF CONSOLE._PRINTF
|
||||||
|
#define CONSOLE_PRINT CONSOLE._PRINT
|
||||||
|
#define CONSOLE_PRINTLN CONSOLE._PRINTLN
|
||||||
|
#define CONSOLE_PRINTLN2 CONSOLE._PRINTLN2
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_DNSSERVER
|
||||||
|
#define DEBUG_PRINTF CONSOLE_PRINTF
|
||||||
|
#define DEBUG_PRINT CONSOLE_PRINT
|
||||||
|
#define DEBUG_PRINTLN CONSOLE_PRINTLN
|
||||||
|
#define DEBUG_PRINTLN2 CONSOLE_PRINTLN2
|
||||||
|
#define DBGLOG_FAIL LOG_FAIL
|
||||||
|
|
||||||
|
#define DEBUG_(...) do { (__VA_ARGS__); } while(false)
|
||||||
|
#define DEBUG__(...) __VA_ARGS__
|
||||||
|
#define LOG_FAIL(a, fmt, ...) do { if (!(a)) { CONSOLE.printf_P( PSTR(fmt " line: %d, function: %s\r\n"), ##__VA_ARGS__, __LINE__, __FUNCTION__ ); } } while(false);
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define DEBUG_PRINTF(...) do { } while(false)
|
||||||
|
#define DEBUG_PRINT(...) do { } while(false)
|
||||||
|
#define DEBUG_PRINTLN(...) do { } while(false)
|
||||||
|
#define DEBUG_PRINTLN2(...) do { } while(false)
|
||||||
|
#define DEBUG_(...) do { } while(false)
|
||||||
|
#define DEBUG__(...) do { } while(false)
|
||||||
|
#define LOG_FAIL(a, ...) do { a; } while(false)
|
||||||
|
#define DBGLOG_FAIL(...) do { } while(false)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DNS_HEADER_SIZE sizeof(DNSHeader)
|
#define DNS_HEADER_SIZE sizeof(DNSHeader)
|
||||||
|
|
||||||
|
// Want to keep IDs unique across restarts and continquious
|
||||||
|
static uint32_t _ids __attribute__((section(".noinit")));
|
||||||
|
|
||||||
DNSServer::DNSServer()
|
DNSServer::DNSServer()
|
||||||
{
|
{
|
||||||
|
// I have observed that using 0 for captive and non-zero (600) when
|
||||||
|
// forwarding, will help Android devices recognize the change in connectivity.
|
||||||
|
// They will then report connected.
|
||||||
_ttl = lwip_htonl(60);
|
_ttl = lwip_htonl(60);
|
||||||
|
|
||||||
|
if (REASON_DEFAULT_RST == resetInfo.reason ||
|
||||||
|
REASON_DEEP_SLEEP_AWAKE <= resetInfo.reason) {
|
||||||
|
_ids = random(0, BIT(16) - 1);
|
||||||
|
}
|
||||||
|
_ids += kDNSSQueSize; // for the case of restart, ignore any inflight responses
|
||||||
|
|
||||||
_errorReplyCode = DNSReplyCode::NonExistentDomain;
|
_errorReplyCode = DNSReplyCode::NonExistentDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DNSServer::start(const uint16_t &port, const String &domainName,
|
void DNSServer::disableForwarder(const String &domainName, bool freeResources)
|
||||||
const IPAddress &resolvedIP)
|
|
||||||
{
|
{
|
||||||
_port = port;
|
_forwarder = false;
|
||||||
|
if (!domainName.isEmpty()) {
|
||||||
|
_domainName = domainName;
|
||||||
|
downcaseAndRemoveWwwPrefix(_domainName);
|
||||||
|
}
|
||||||
|
if (freeResources) {
|
||||||
|
_dns = (uint32_t)0;
|
||||||
|
if (_que) {
|
||||||
|
_que = nullptr;
|
||||||
|
DEBUG_PRINTF("from stop, deleted _que\r\n");
|
||||||
|
DEBUG_(({
|
||||||
|
if (_que_ov) {
|
||||||
|
DEBUG_PRINTLN2("DNS forwarder que overflow or no reply to request: ", (_que_ov));
|
||||||
|
}
|
||||||
|
if (_que_drop) {
|
||||||
|
DEBUG_PRINTLN2("DNS forwarder que wrapped, reply dropped: ", (_que_drop));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DNSServer::enableForwarder(const String &domainName, const IPAddress &dns)
|
||||||
|
{
|
||||||
|
disableForwarder(domainName, false); // Just happens to have the same logic needed here.
|
||||||
|
|
||||||
|
if (dns.isSet()) {
|
||||||
|
_dns = dns;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_dns.isSet()) {
|
||||||
|
if (!_que) {
|
||||||
|
_que = std::unique_ptr<DNSS_REQUESTER[]> (new (std::nothrow) DNSS_REQUESTER[kDNSSQueSize]);
|
||||||
|
DEBUG_PRINTF("Created new _que\r\n");
|
||||||
|
if (_que) {
|
||||||
|
for (size_t i = 0; i < kDNSSQueSize; i++) {
|
||||||
|
_que[i].ip = 0;
|
||||||
|
}
|
||||||
|
DEBUG_((_que_ov = 0));
|
||||||
|
DEBUG_((_que_drop = 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_que) {
|
||||||
|
_forwarder = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _forwarder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DNSServer::start(const uint16_t &port, const String &domainName,
|
||||||
|
const IPAddress &resolvedIP, const IPAddress &dns)
|
||||||
|
{
|
||||||
|
_port = (port) ? port : IANA_DNS_PORT;
|
||||||
|
|
||||||
_domainName = domainName;
|
|
||||||
_resolvedIP[0] = resolvedIP[0];
|
_resolvedIP[0] = resolvedIP[0];
|
||||||
_resolvedIP[1] = resolvedIP[1];
|
_resolvedIP[1] = resolvedIP[1];
|
||||||
_resolvedIP[2] = resolvedIP[2];
|
_resolvedIP[2] = resolvedIP[2];
|
||||||
_resolvedIP[3] = resolvedIP[3];
|
_resolvedIP[3] = resolvedIP[3];
|
||||||
downcaseAndRemoveWwwPrefix(_domainName);
|
|
||||||
|
if (!enableForwarder(domainName, dns) && (dns.isSet() || _dns.isSet())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return _udp.begin(_port) == 1;
|
return _udp.begin(_port) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +147,15 @@ void DNSServer::setTTL(const uint32_t &ttl)
|
|||||||
_ttl = lwip_htonl(ttl);
|
_ttl = lwip_htonl(ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t DNSServer::getTTL()
|
||||||
|
{
|
||||||
|
return lwip_ntohl(_ttl);
|
||||||
|
}
|
||||||
|
|
||||||
void DNSServer::stop()
|
void DNSServer::stop()
|
||||||
{
|
{
|
||||||
_udp.stop();
|
_udp.stop();
|
||||||
|
disableForwarder(emptyString, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
|
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
|
||||||
@ -53,7 +165,58 @@ void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
|
|||||||
domainName.remove(0, 4);
|
domainName.remove(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
void DNSServer::forwardReply(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
if (!_forwarder || !_que) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DNSHeader *dnsHeader = (DNSHeader *)buffer;
|
||||||
|
uint16_t id = dnsHeader->ID;
|
||||||
|
// if (kDNSSQueSize <= (uint16_t)((uint16_t)_ids - id)) {
|
||||||
|
if ((uint16_t)kDNSSQueSize <= (uint16_t)_ids - id) {
|
||||||
|
DEBUG_((++_que_drop));
|
||||||
|
DEBUG_PRINTLN2("Forward reply ID: 0x", (String(id, HEX) + F(" dropped!")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t i = id & (kDNSSQueSize - 1);
|
||||||
|
|
||||||
|
// Drop duplicate packets
|
||||||
|
if (0 == _que[i].ip) {
|
||||||
|
DEBUG_PRINTLN2("Duplicate reply dropped ID: 0x", String(id, HEX));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dnsHeader->ID = _que[i].id;
|
||||||
|
_udp.beginPacket(_que[i].ip, _que[i].port);
|
||||||
|
_udp.write(buffer, length);
|
||||||
|
_udp.endPacket();
|
||||||
|
DEBUG_PRINTLN2("Forward reply ID: 0x", (String(id, HEX) + F(" to ") + IPAddress(_que[i].ip).toString()));
|
||||||
|
_que[i].ip = 0; // This gets used to detect duplicate packets and overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSServer::forwardRequest(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
if (!_forwarder || !_dns.isSet() || !_que) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DNSHeader *dnsHeader = (DNSHeader *)buffer;
|
||||||
|
++_ids;
|
||||||
|
size_t i = _ids & (kDNSSQueSize - 1);
|
||||||
|
DEBUG_(({
|
||||||
|
if (0 != _que[i].ip) {
|
||||||
|
++_que_ov;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_que[i].ip = _udp.remoteIP();
|
||||||
|
_que[i].port = _udp.remotePort();
|
||||||
|
_que[i].id = dnsHeader->ID;
|
||||||
|
dnsHeader->ID = (uint16_t)_ids;
|
||||||
|
_udp.beginPacket(_dns, IANA_DNS_PORT);
|
||||||
|
_udp.write(buffer, length);
|
||||||
|
_udp.endPacket();
|
||||||
|
DEBUG_PRINTLN2("Forward request ID: 0x", (String(dnsHeader->ID, HEX) + F(" to ") + _dns.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
||||||
{
|
{
|
||||||
DNSHeader *dnsHeader;
|
DNSHeader *dnsHeader;
|
||||||
uint8_t *query, *start;
|
uint8_t *query, *start;
|
||||||
@ -64,23 +227,30 @@ void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
|||||||
dnsHeader = (DNSHeader *)buffer;
|
dnsHeader = (DNSHeader *)buffer;
|
||||||
|
|
||||||
// Must be a query for us to do anything with it
|
// Must be a query for us to do anything with it
|
||||||
if (dnsHeader->QR != DNS_QR_QUERY)
|
if (dnsHeader->QR != DNS_QR_QUERY) {
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If operation is anything other than query, we don't do it
|
// If operation is anything other than query, we don't do it
|
||||||
if (dnsHeader->OPCode != DNS_OPCODE_QUERY)
|
if (dnsHeader->OPCode != DNS_OPCODE_QUERY) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::NotImplemented);
|
replyWithError(dnsHeader, DNSReplyCode::NotImplemented);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Only support requests containing single queries - everything else
|
// Only support requests containing single queries - everything else
|
||||||
// is badly defined
|
// is badly defined
|
||||||
if (dnsHeader->QDCount != lwip_htons(1))
|
if (dnsHeader->QDCount != lwip_htons(1)) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::FormError);
|
replyWithError(dnsHeader, DNSReplyCode::FormError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// We must return a FormError in the case of a non-zero ARCount to
|
// We must return a FormError in the case of a non-zero ARCount to
|
||||||
// be minimally compatible with EDNS resolvers
|
// be minimally compatible with EDNS resolvers
|
||||||
if (dnsHeader->ANCount != 0 || dnsHeader->NSCount != 0
|
if (dnsHeader->ANCount != 0 || dnsHeader->NSCount != 0
|
||||||
|| dnsHeader->ARCount != 0)
|
|| dnsHeader->ARCount != 0) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::FormError);
|
replyWithError(dnsHeader, DNSReplyCode::FormError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Even if we're not going to use the query, we need to parse it
|
// Even if we're not going to use the query, we need to parse it
|
||||||
// so we can check the address type that's being queried
|
// so we can check the address type that's being queried
|
||||||
@ -89,15 +259,19 @@ void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
|||||||
remaining = length - DNS_HEADER_SIZE;
|
remaining = length - DNS_HEADER_SIZE;
|
||||||
while (remaining != 0 && *start != 0) {
|
while (remaining != 0 && *start != 0) {
|
||||||
labelLength = *start;
|
labelLength = *start;
|
||||||
if (labelLength + 1 > remaining)
|
if (labelLength + 1 > remaining) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::FormError);
|
replyWithError(dnsHeader, DNSReplyCode::FormError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
remaining -= (labelLength + 1);
|
remaining -= (labelLength + 1);
|
||||||
start += (labelLength + 1);
|
start += (labelLength + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 octet labelLength, 2 octet qtype, 2 octet qclass
|
// 1 octet labelLength, 2 octet qtype, 2 octet qclass
|
||||||
if (remaining < 5)
|
if (remaining < 5) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::FormError);
|
replyWithError(dnsHeader, DNSReplyCode::FormError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
start += 1; // Skip the 0 length label that we found above
|
start += 1; // Skip the 0 length label that we found above
|
||||||
|
|
||||||
@ -109,23 +283,33 @@ void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
|||||||
queryLength = start - query;
|
queryLength = start - query;
|
||||||
|
|
||||||
if (qclass != lwip_htons(DNS_QCLASS_ANY)
|
if (qclass != lwip_htons(DNS_QCLASS_ANY)
|
||||||
&& qclass != lwip_htons(DNS_QCLASS_IN))
|
&& qclass != lwip_htons(DNS_QCLASS_IN)) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain,
|
replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain, query, queryLength);
|
||||||
query, queryLength);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (qtype != lwip_htons(DNS_QTYPE_A)
|
if (qtype != lwip_htons(DNS_QTYPE_A)
|
||||||
&& qtype != lwip_htons(DNS_QTYPE_ANY))
|
&& qtype != lwip_htons(DNS_QTYPE_ANY)) {
|
||||||
return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain,
|
replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain, query, queryLength);
|
||||||
query, queryLength);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If we have no domain name configured, just return an error
|
// If we have no domain name configured, just return an error
|
||||||
if (_domainName.isEmpty())
|
if (_domainName.isEmpty()) {
|
||||||
return replyWithError(dnsHeader, _errorReplyCode,
|
if (_forwarder) {
|
||||||
query, queryLength);
|
return true;
|
||||||
|
} else {
|
||||||
|
replyWithError(dnsHeader, _errorReplyCode, query, queryLength);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we're running with a wildcard we can just return a result now
|
// If we're running with a wildcard we can just return a result now
|
||||||
if (_domainName == "*")
|
if (_domainName == "*") {
|
||||||
return replyWithIP(dnsHeader, query, queryLength);
|
DEBUG_PRINTF("dnsServer - replyWithIP\r\n");
|
||||||
|
replyWithIP(dnsHeader, query, queryLength);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
matchString = _domainName.c_str();
|
matchString = _domainName.c_str();
|
||||||
|
|
||||||
@ -139,24 +323,32 @@ void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
|
|||||||
labelLength = *start;
|
labelLength = *start;
|
||||||
start += 1;
|
start += 1;
|
||||||
while (labelLength > 0) {
|
while (labelLength > 0) {
|
||||||
if (tolower(*start) != *matchString)
|
if (tolower(*start) != *matchString) {
|
||||||
return replyWithError(dnsHeader, _errorReplyCode,
|
if (_forwarder) {
|
||||||
query, queryLength);
|
return true;
|
||||||
|
} else {
|
||||||
|
replyWithError(dnsHeader, _errorReplyCode, query, queryLength);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
++start;
|
++start;
|
||||||
++matchString;
|
++matchString;
|
||||||
--labelLength;
|
--labelLength;
|
||||||
}
|
}
|
||||||
if (*start == 0 && *matchString == '\0')
|
if (*start == 0 && *matchString == '\0') {
|
||||||
return replyWithIP(dnsHeader, query, queryLength);
|
replyWithIP(dnsHeader, query, queryLength);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (*matchString != '.')
|
if (*matchString != '.') {
|
||||||
return replyWithError(dnsHeader, _errorReplyCode,
|
replyWithError(dnsHeader, _errorReplyCode, query, queryLength);
|
||||||
query, queryLength);
|
return false;
|
||||||
|
}
|
||||||
++matchString;
|
++matchString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return replyWithError(dnsHeader, _errorReplyCode,
|
replyWithError(dnsHeader, _errorReplyCode, query, queryLength);
|
||||||
query, queryLength);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSServer::processNextRequest()
|
void DNSServer::processNextRequest()
|
||||||
@ -182,7 +374,14 @@ void DNSServer::processNextRequest()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_udp.read(buffer.get(), currentPacketSize);
|
_udp.read(buffer.get(), currentPacketSize);
|
||||||
respondToRequest(buffer.get(), currentPacketSize);
|
if (_dns.isSet() && _udp.remoteIP() == _dns) {
|
||||||
|
// _forwarder may have been set to false; however, for now allow inflight
|
||||||
|
// replys to finish. //??
|
||||||
|
forwardReply(buffer.get(), currentPacketSize);
|
||||||
|
} else
|
||||||
|
if (respondToRequest(buffer.get(), currentPacketSize)) {
|
||||||
|
forwardRequest(buffer.get(), currentPacketSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSServer::writeNBOShort(uint16_t value)
|
void DNSServer::writeNBOShort(uint16_t value)
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
#define DNSServer_h
|
#define DNSServer_h
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
|
||||||
|
// #define DEBUG_DNSSERVER
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt
|
||||||
|
#ifndef IANA_DNS_PORT
|
||||||
|
#define IANA_DNS_PORT 53 // AKA domain
|
||||||
|
constexpr inline uint16_t kIanaDnsPort = IANA_DNS_PORT;
|
||||||
|
#endif
|
||||||
|
|
||||||
#define DNS_QR_QUERY 0
|
#define DNS_QR_QUERY 0
|
||||||
#define DNS_QR_RESPONSE 1
|
#define DNS_QR_RESPONSE 1
|
||||||
#define DNS_OPCODE_QUERY 0
|
#define DNS_OPCODE_QUERY 0
|
||||||
@ -45,6 +53,15 @@ struct DNSHeader
|
|||||||
uint16_t ARCount; // number of resource entries
|
uint16_t ARCount; // number of resource entries
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr inline size_t kDNSSQueSizeAddrBits = 3; // The number of bits used to address que entries
|
||||||
|
constexpr inline size_t kDNSSQueSize = BIT(kDNSSQueSizeAddrBits);
|
||||||
|
|
||||||
|
struct DNSS_REQUESTER {
|
||||||
|
uint32_t ip;
|
||||||
|
uint16_t port;
|
||||||
|
uint16_t id;
|
||||||
|
};
|
||||||
|
|
||||||
class DNSServer
|
class DNSServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -52,24 +69,60 @@ class DNSServer
|
|||||||
~DNSServer() {
|
~DNSServer() {
|
||||||
stop();
|
stop();
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
|
If specified, `enableForwarder` will update the `domainName` that is used
|
||||||
|
to match DNS request to this AP's IP Address. A non-matching request will
|
||||||
|
be forwarded to the DNS server specified by `dns`.
|
||||||
|
|
||||||
|
Returns `true` on success.
|
||||||
|
|
||||||
|
Returns `false`,
|
||||||
|
* when forwarding `dns` is not set, or
|
||||||
|
* unable to allocate resources for managing the DNS forward function.
|
||||||
|
*/
|
||||||
|
bool enableForwarder(const String &domainName = emptyString, const IPAddress &dns = (uint32_t)0);
|
||||||
|
/*
|
||||||
|
`disableForwarder` will stop forwarding DNS requests. If specified,
|
||||||
|
updates the `domainName` that is matched for returning this AP's IP Address.
|
||||||
|
Optionally, resources used for the DNS forward function can be freed.
|
||||||
|
*/
|
||||||
|
void disableForwarder(const String &domainName = emptyString, bool freeResources = false);
|
||||||
|
bool isForwarding() { return _forwarder && _dns.isSet(); }
|
||||||
|
void setDNS(const IPAddress& dns) { _dns = dns; }
|
||||||
|
IPAddress getDNS() { return _dns; }
|
||||||
|
bool isDNSSet() { return _dns.isSet(); }
|
||||||
|
|
||||||
void processNextRequest();
|
void processNextRequest();
|
||||||
void setErrorReplyCode(const DNSReplyCode &replyCode);
|
void setErrorReplyCode(const DNSReplyCode &replyCode);
|
||||||
void setTTL(const uint32_t &ttl);
|
void setTTL(const uint32_t &ttl);
|
||||||
|
uint32_t getTTL();
|
||||||
|
String getDomainName() { return _domainName; }
|
||||||
|
|
||||||
// Returns true if successful, false if there are no sockets available
|
// Returns true if successful, false if there are no sockets available
|
||||||
bool start(const uint16_t &port,
|
bool start(const uint16_t &port,
|
||||||
const String &domainName,
|
const String &domainName,
|
||||||
const IPAddress &resolvedIP);
|
const IPAddress &resolvedIP,
|
||||||
|
const IPAddress &dns = (uint32_t)0);
|
||||||
// stops the DNS server
|
// stops the DNS server
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WiFiUDP _udp;
|
WiFiUDP _udp;
|
||||||
uint16_t _port;
|
|
||||||
String _domainName;
|
String _domainName;
|
||||||
unsigned char _resolvedIP[4];
|
IPAddress _dns;
|
||||||
|
std::unique_ptr<DNSS_REQUESTER[]> _que;
|
||||||
uint32_t _ttl;
|
uint32_t _ttl;
|
||||||
|
#ifdef DEBUG_DNSSERVER
|
||||||
|
// There are 2 possiblities for OverFlow:
|
||||||
|
// 1) we have more than kDNSSQueSize request already outstanding.
|
||||||
|
// 2) we have request that never received a reply.
|
||||||
|
uint32_t _que_ov;
|
||||||
|
uint32_t _que_drop;
|
||||||
|
#endif
|
||||||
DNSReplyCode _errorReplyCode;
|
DNSReplyCode _errorReplyCode;
|
||||||
|
bool _forwarder;
|
||||||
|
unsigned char _resolvedIP[4];
|
||||||
|
uint16_t _port;
|
||||||
|
|
||||||
void downcaseAndRemoveWwwPrefix(String &domainName);
|
void downcaseAndRemoveWwwPrefix(String &domainName);
|
||||||
void replyWithIP(DNSHeader *dnsHeader,
|
void replyWithIP(DNSHeader *dnsHeader,
|
||||||
@ -81,7 +134,9 @@ class DNSServer
|
|||||||
size_t queryLength);
|
size_t queryLength);
|
||||||
void replyWithError(DNSHeader *dnsHeader,
|
void replyWithError(DNSHeader *dnsHeader,
|
||||||
DNSReplyCode rcode);
|
DNSReplyCode rcode);
|
||||||
void respondToRequest(uint8_t *buffer, size_t length);
|
bool respondToRequest(uint8_t *buffer, size_t length);
|
||||||
|
void forwardRequest(uint8_t *buffer, size_t length);
|
||||||
|
void forwardReply(uint8_t *buffer, size_t length);
|
||||||
void writeNBOShort(uint16_t value);
|
void writeNBOShort(uint16_t value);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user