Skip to content

Commit 0bf63f1

Browse files
mhightower83hasenradball
authored andcommitted
Add DNS forwarder to DNSServer (esp8266#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.
1 parent 57abfb8 commit 0bf63f1

File tree

8 files changed

+1431
-46
lines changed

8 files changed

+1431
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
/*
2+
This example shows the use of the 'DNS forwarder' feature in the DNSServer.
3+
It does so by combining two examples CaptivePortalAdvanced and
4+
RangeExtender-NAPT. Additionally the CaptivePortalAdvanced part has a few
5+
upgrades to the HTML presentation to improve readability and ease of use on
6+
mobile devices.
7+
8+
Also for an example of using HTML chunked response, see handleWifi() in
9+
handleHttp.ino.
10+
11+
This example starts up in Captive Portal mode by default.
12+
It starts the SoftAP and NAPT w/o connecting the WLAN side.
13+
14+
You connect your computer or mobile device to the WiFi Network 'MagicPortal'
15+
password 'ShowTime'. Your device should shortly notify you of a Captive
16+
Portal and the need to login. If it fails to do so in a timely maner,
17+
navigate to http://172.217.28.1/wifi and configure it there.
18+
19+
Note, until a successful WLAN connection is made all DNS lookups will point
20+
back to the SoftAP at 172.217.28.1. This is the Captive Portal element of
21+
this example.
22+
23+
Once the WLAN is connected, your device should notify you that you are
24+
connected. This, of course, assumes your WLAN connection has a path to the
25+
Internet.
26+
27+
At this stage we are no longer running as a Captive Portal, but a regular
28+
NAPT. The DNSServer will be running with the DNS forwarder enabled. The
29+
DNSServer will resolve lookups for 'margicportal' to point to 172.217.28.1
30+
and all other lookup request will be forwarded to the 1st DNS server that was
31+
in the DHCP response for the WLAN interface.
32+
33+
You should now be able to access things on the Internet. The ease of access
34+
to devices on your home Network may vary. By IP address it should work.
35+
Access by a hostname - maybe. Some home routers will use the hostname
36+
supplied during DHCP to support a local DNS table; some do not.
37+
38+
There is an additional possible complication for using the local DNS, the DNS
39+
suffix list, this subject is seldom discussed. It is normally handled
40+
automaticly by the host computers DNS lookup code. For the DHCP case, the
41+
DHCP server will supply a suffix list, if there is one. Then when a name
42+
lookup fails and the name does not have a trailing (.)dot the host computer
43+
will append a suffix from the list and try again, until successful or the
44+
list is exhaused. This topic I fear can become a TL;DR. A quick wrapup by way
45+
of an example. On an Ubuntu system run `nmcli dev show eth0 | grep
46+
IP4\.DOMAIN` that may show you a suffix list. (replace eth0 with your wlan
47+
interface name) Try adding them to the local name you are failing to connect
48+
to. For example, assume 'myhost' fails. You see that 'lan' is in the suffix
49+
list. Try connecting to 'myhost.lan'.
50+
51+
mDNS names also will not work. We do not have a way to pass those request
52+
back and forth through the NAPT.
53+
54+
Note if hostnames are going to work for an ESP8266 device on your home
55+
Network, you have to have the call to WiFi.hostname(...) before you call
56+
WiFi.begin().
57+
58+
In this example the SoftAP in 'Captive Portal' uses the same public address
59+
that was used in the CaptivePortalAdvanced example. Depending on your devices
60+
you may or may not be successful in using a private address. A previous
61+
PR-author discovered a fix that made the CaptivePortalAdvanced example work
62+
better with Android devices. That fix was to use that public address. At this
63+
time, this PR-author with a different Android device running the latest
64+
version of Android has seen no problems in using either. At least not yet :)
65+
FWIW: My device also works with the original CaptivePortalAdvanced example
66+
when using a private address. I would suggest keeping the public address
67+
for a while. At lest until you are confident everything is working well
68+
before experimenting with a private address.
69+
*/
70+
71+
72+
#if LWIP_FEATURES && !LWIP_IPV6
73+
74+
#include <ESP8266WiFi.h>
75+
#include <lwip/napt.h>
76+
#include <lwip/dns.h>
77+
#include <dhcpserver.h>
78+
#include <WiFiClient.h>
79+
#include <ESP8266WebServer.h>
80+
#include <DNSServer.h>
81+
#include <ESP8266mDNS.h>
82+
#include <EEPROM.h>
83+
84+
#define NAPT 1000
85+
#define NAPT_PORT 10
86+
87+
/*
88+
Some defines for debugging
89+
*/
90+
#ifdef DEBUG_ESP_PORT
91+
#define CONSOLE DEBUG_ESP_PORT
92+
#else
93+
#define CONSOLE Serial
94+
#endif
95+
96+
#define _PRINTF(a, ...) printf_P(PSTR(a), ##__VA_ARGS__)
97+
#define _PRINT(a) print(String(F(a)))
98+
#define _PRINTLN(a) println(String(F(a)))
99+
#define _PRINTLN2(a, b) println(String(F(a)) + b)
100+
101+
#define CONSOLE_PRINTF CONSOLE._PRINTF
102+
#define CONSOLE_PRINT CONSOLE._PRINT
103+
#define CONSOLE_PRINTLN CONSOLE._PRINTLN
104+
#define CONSOLE_PRINTLN2 CONSOLE._PRINTLN2
105+
106+
#ifdef DEBUG_SKETCH
107+
#define DEBUG_PRINTF CONSOLE_PRINTF
108+
#define DEBUG_PRINT CONSOLE_PRINT
109+
#define DEBUG_PRINTLN CONSOLE_PRINTLN
110+
#define DEBUG_PRINTLN2 CONSOLE_PRINTLN2
111+
112+
#else
113+
#define DEBUG_PRINTF(...) \
114+
do { \
115+
} while (false)
116+
#define DEBUG_PRINT(...) \
117+
do { \
118+
} while (false)
119+
#define DEBUG_PRINTLN(...) \
120+
do { \
121+
} while (false)
122+
#define DEBUG_PRINTLN2(...) \
123+
do { \
124+
} while (false)
125+
#endif
126+
127+
128+
129+
/* Set these to your desired softAP credentials. They are not configurable at runtime */
130+
#ifndef APSSID
131+
#define APSSID "MagicPortal"
132+
#define APPSK "ShowTime"
133+
#endif
134+
135+
const char *softAP_ssid = APSSID;
136+
const char *softAP_password = APPSK;
137+
138+
/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
139+
const char *myHostname = "magicportal";
140+
141+
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
142+
char ssid[33] = "";
143+
char password[65] = "";
144+
uint8_t bssid[6];
145+
WiFiEventHandler staModeConnectedHandler;
146+
WiFiEventHandler staModeDisconnectedHandler;
147+
148+
// DNS server
149+
DNSServer dnsServer;
150+
151+
// Web server
152+
ESP8266WebServer server(80);
153+
154+
/* Soft AP network parameters */
155+
IPAddress apIP(172, 217, 28, 1);
156+
IPAddress netMsk(255, 255, 255, 0);
157+
158+
159+
/** Should I connect to WLAN asap? */
160+
bool connect = false;
161+
162+
/** Set to true to start WiFi STA at setup time when credentials loaded successfuly from EEPROM */
163+
/** Set to false to defer WiFi STA until configured through web interface. */
164+
bool staReady = false; // Don't connect right away
165+
166+
/** Last time I tried to connect to WLAN */
167+
unsigned long lastConnectTry = 0;
168+
169+
/** Current WLAN status */
170+
unsigned int status = WL_IDLE_STATUS;
171+
172+
void setup() {
173+
WiFi.persistent(false); // w/o this a flash write occurs at every boot
174+
WiFi.mode(WIFI_OFF); // Prevent use of SDK stored credentials
175+
CONSOLE.begin(115200);
176+
CONSOLE_PRINTLN("\r\n\r\nNAPT with Configuration Portal ...");
177+
178+
staModeConnectedHandler = WiFi.onStationModeConnected(
179+
[](const WiFiEventStationModeConnected &data) {
180+
// Keep a copy of the BSSID for the AP that WLAN connects to.
181+
// This is used in the WLAN report on WiFi Details page.
182+
memcpy(bssid, data.bssid, sizeof(bssid));
183+
});
184+
185+
staModeDisconnectedHandler = WiFi.onStationModeDisconnected(
186+
[](const WiFiEventStationModeDisconnected &) {
187+
if (dnsServer.isForwarding()) {
188+
dnsServer.disableForwarder("*");
189+
dnsServer.setTTL(0);
190+
// Reminder, Serial.println() will not work from these callbacks.
191+
// For debug printf use ets_uart_printf().
192+
}
193+
});
194+
195+
/*
196+
While you can remove the password parameter to make the AP open.
197+
You will be operating with less security and allowing snoopers to see
198+
the credentials you use for your WiFi.
199+
*/
200+
WiFi.softAPConfig(apIP, apIP, netMsk);
201+
WiFi.softAP(softAP_ssid, softAP_password);
202+
// The following comment for delay(500) was committed Aug 19, 2015; is it
203+
// still true? Commented out for verification. - APR 2020
204+
// delay(500); // Without delay I've seen the IP address blank
205+
CONSOLE_PRINTF("SoftAP '%s' started\r\n", softAP_ssid);
206+
CONSOLE_PRINTLN2(" IP address: ", WiFi.softAPIP().toString());
207+
208+
/* Captive portals will usually use a TTL of 0 to avoid DNS cache poisoning. */
209+
dnsServer.setTTL(0);
210+
211+
/* Setup the DNS server redirecting all the domains to the apIP */
212+
dnsServer.start(IANA_DNS_PORT, "*", apIP);
213+
CONSOLE_PRINTLN("DNSServer started:");
214+
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
215+
CONSOLE_PRINTF(" Resolve all domain lookups, '%s', to this AP's IP address, '%s' %s.\r\n",
216+
dnsServer.getDomainName().c_str(),
217+
softAP_ssid,
218+
WiFi.softAPIP().toString().c_str());
219+
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
220+
221+
/*
222+
Do some NAPT startup stuff
223+
*/
224+
CONSOLE_PRINTLN("Begin NAPT initialization:");
225+
CONSOLE_PRINTF(" Heap before NAPT init: %d\r\n", ESP.getFreeHeap());
226+
227+
err_t ret = ip_napt_init(NAPT, NAPT_PORT);
228+
CONSOLE_PRINTF(" ip_napt_init(%d,%d): ret=%d (OK=%d)\r\n", NAPT, NAPT_PORT, (int)ret, (int)ERR_OK);
229+
if (ret == ERR_OK) {
230+
ret = ip_napt_enable_no(SOFTAP_IF, 1);
231+
CONSOLE_PRINTF(" ip_napt_enable_no(SOFTAP_IF): ret=%d (OK=%d)\r\n", (int)ret, (int)ERR_OK);
232+
if (ret == ERR_OK) {
233+
CONSOLE_PRINTF(" NAPT AP '%s' started.\r\n", softAP_ssid);
234+
if (WiFi.localIP().isSet()) {
235+
CONSOLE_PRINTF(" It is an extension of '%s' made through WLAN interface.\r\n", ssid);
236+
CONSOLE_PRINTF(" Remote WLAN IP Address: %s.\r\n", WiFi.localIP().toString().c_str());
237+
}
238+
}
239+
}
240+
CONSOLE_PRINTF(" Heap after NAPT init: %d\r\n", ESP.getFreeHeap());
241+
if (ret != ERR_OK) {
242+
CONSOLE_PRINTF(" NAPT initialization failed!!!\r\n");
243+
}
244+
245+
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
246+
server.on("/", handleRoot);
247+
server.on("/wifi", handleWifi);
248+
server.on("/wifisave", handleWifiSave);
249+
server.on("/generate_204", handleRoot); // Android captive portal. Maybe not needed. Might be handled by notFound handler.
250+
server.on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
251+
server.onNotFound(handleNotFound);
252+
server.begin(); // Web server start
253+
CONSOLE_PRINTLN("HTTP server started");
254+
loadCredentials(); // Load WLAN credentials from network
255+
connect = (strlen(ssid) > 0 && staReady); // Request WLAN connect if there is a SSID and we want to connect at startup
256+
}
257+
258+
void connectWifi() {
259+
CONSOLE_PRINTF("Connecting as wifi client, WLAN, to '%s' ...\r\n", ssid);
260+
WiFi.disconnect();
261+
/*
262+
A call to set hostname, must be set before the call to WiFi.begin, otherwise
263+
the name may be missing from the routers DNS lookup results. Note, not all
264+
routers will import registered DHCP host names from clients into the active
265+
local DNS resolver. For those that do, it is best to set hostname before
266+
calling WiFi.begin().
267+
*/
268+
WiFi.hostname(myHostname);
269+
WiFi.begin(ssid, password);
270+
int connRes = WiFi.waitForConnectResult();
271+
if (-1 == connRes) {
272+
CONSOLE_PRINTLN(" WiFi.waitForConnectResult() timed out.");
273+
} else {
274+
CONSOLE_PRINTF(" Connection status: %s, %d\r\n", getWiFiStatusString(connRes).c_str(), connRes);
275+
}
276+
}
277+
278+
void loop() {
279+
if (connect) {
280+
connect = false;
281+
connectWifi();
282+
lastConnectTry = millis();
283+
}
284+
{
285+
unsigned int s = WiFi.status();
286+
if (s == 0 && millis() > (lastConnectTry + 60000) && ssid[0] && staReady) {
287+
/* When all of the following conditions are true, try to connect */
288+
/* 1) If WLAN disconnected */
289+
/* 2) Required idle time between connect attempts has passed. */
290+
/* 3) We have an ssid configured */
291+
/* 4) We are ready for the STA to come up */
292+
/* Don't set retry time too low as retry interfere the softAP operation */
293+
connect = true;
294+
}
295+
if (status != s) { // WLAN status change
296+
CONSOLE_PRINTF("WLAN Status changed:\r\n");
297+
CONSOLE_PRINTF(" new status: %s, %d\r\n", getWiFiStatusString(s).c_str(), s);
298+
CONSOLE_PRINTF(" previous status: %s, %d\r\n", getWiFiStatusString(status).c_str(), status);
299+
status = s;
300+
if (s == WL_CONNECTED) {
301+
/* Just connected to WLAN */
302+
CONSOLE.println();
303+
if (WiFi.localIP().isSet() && WiFi.softAPIP().isSet()) {
304+
CONSOLE_PRINTF("NAPT AP '%s' status:\r\n", softAP_ssid);
305+
if (WiFi.localIP().isSet()) {
306+
CONSOLE_PRINTF(" It is an extension of '%s' made through WLAN interface.\r\n", ssid);
307+
CONSOLE_PRINTF(" WLAN connected with IP Address: %s.\r\n", WiFi.localIP().toString().c_str());
308+
}
309+
} else {
310+
CONSOLE_PRINT("WLAN connected to ");
311+
CONSOLE.println(ssid);
312+
CONSOLE_PRINT(" IP address: ");
313+
CONSOLE.println(WiFi.localIP());
314+
}
315+
// Setup MDNS responder
316+
if (!MDNS.begin(myHostname, WiFi.localIP())) {
317+
CONSOLE_PRINTLN(" Error setting up MDNS responder!");
318+
} else {
319+
CONSOLE_PRINTLN(" mDNS responder started");
320+
// Add service to MDNS-SD
321+
MDNS.addService("http", "tcp", 80);
322+
}
323+
/*
324+
Setup the DNSServer to respond only to request for our hostname and
325+
forward other name request to the DNS configured to the WLAN.
326+
*/
327+
dnsServer.setTTL(600); // 10 minutes
328+
dnsServer.enableForwarder(myHostname, WiFi.dnsIP(0));
329+
CONSOLE_PRINTF("DNSServer changes/status:\r\n");
330+
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
331+
CONSOLE_PRINTF(" Resolve '%s' to this AP's IP address, '%s' %s.\r\n",
332+
dnsServer.getDomainName().c_str(),
333+
softAP_ssid,
334+
WiFi.softAPIP().toString().c_str());
335+
if (dnsServer.isDNSSet()) {
336+
CONSOLE_PRINTF(" Forward other lookups to DNS: %s\r\n", dnsServer.getDNS().toString().c_str());
337+
}
338+
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
339+
340+
} else {
341+
/* Captive portals will usually use a TTL of 0 to avoid DNS cache poisoning. */
342+
dnsServer.setTTL(0);
343+
/* Setup the DNSServer to redirect all the domain lookups to the apIP */
344+
dnsServer.disableForwarder("*");
345+
CONSOLE_PRINTF("DNSServer changes/status:\r\n");
346+
CONSOLE_PRINTF(" DNS Forwarding is %s\r\n", dnsServer.isForwarding() ? "on" : "off");
347+
CONSOLE_PRINTF(" Resolve all domain lookups, '%s', to this AP's IP address, '%s' %s.\r\n",
348+
dnsServer.getDomainName().c_str(),
349+
softAP_ssid,
350+
WiFi.softAPIP().toString().c_str());
351+
CONSOLE_PRINTF(" TTL set to %u\r\n", dnsServer.getTTL());
352+
353+
// Note, it is not necessary to clear the DNS forwarder address. This
354+
// is being done here, to test that methods isDNSSet() and setDNS() work.
355+
dnsServer.setDNS(0U);
356+
if (dnsServer.isDNSSet()) {
357+
CONSOLE_PRINTF(" DNS forwarder address: %s\r\n", dnsServer.getDNS().toString().c_str());
358+
} else {
359+
CONSOLE_PRINTF(" DNS forwarder address not set.\r\n");
360+
}
361+
362+
if (s == WL_NO_SSID_AVAIL) {
363+
WiFi.disconnect();
364+
}
365+
}
366+
}
367+
if (s == WL_CONNECTED) {
368+
MDNS.update();
369+
}
370+
}
371+
// Do work:
372+
// DNS
373+
dnsServer.processNextRequest();
374+
// HTTP
375+
server.handleClient();
376+
}
377+
378+
#else // LWIP_FEATURES && !LWIP_IPV6
379+
380+
#include <ESP8266WiFi.h>
381+
void setup() {
382+
WiFi.persistent(false);
383+
WiFi.mode(WIFI_OFF);
384+
Serial.begin(115200);
385+
Serial.printf("\n\nNAPT not supported in this configuration\n");
386+
}
387+
388+
void loop() {
389+
}
390+
391+
#endif // LWIP_FEATURES && !LWIP_IPV6

0 commit comments

Comments
 (0)