forked from esp8266/Arduino
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNAPTCaptivePortal.ino
391 lines (339 loc) · 15 KB
/
NAPTCaptivePortal.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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