Arduino ESP8266 DNSServer.h
ESP8266 DNSServer——真正的域名服務
- 名轉成對應對映的地址
1.2 DNS server庫#
ESP8266使用DNS服務(一般和WebServer服務一起使用,WebServer請回顧 ESP8266開發之旅 網路篇⑪ WebServer——ESP8266WebServer庫的使用),請在程式碼中加入以下標頭檔案:
#include <DNSServer.h>
講解方法之前,先來看看博主總結的百度腦圖:
常用方法非常簡單,就4個方法,畢竟DNS伺服器的功能比較單一。
1.2.1 start —— 啟動DNS伺服器#
函式說明:
/** * 啟動DNS伺服器 * @param port 埠號 DNS埠一般佔用53 * @param domainName 對映域名 * @param resolvedIP 對映IP地址 * @return bool 是否啟動成功 */ bool start(const uint16_t &port, const String &domainName, const IPAddress &resolvedIP);
原始碼說明:
bool DNSServer::start(const uint16_t &port, const String &domainName, const IPAddress &resolvedIP) { _port = port; _buffer = NULL; _domainName = domainName; _resolvedIP[0] = resolvedIP[0]; _resolvedIP[1] = resolvedIP[1]; _resolvedIP[2] = resolvedIP[2]; _resolvedIP[3] = resolvedIP[3]; downcaseAndRemoveWwwPrefix(_domainName); //啟動了UDP服務 監聽客戶端向DNS伺服器查詢域名 return _udp.begin(_port) == 1; }
1.2.2 stop —— 停止DNS伺服器#
函式說明:
/** * 停止DNS伺服器 */ void stop();
原始碼說明:
void DNSServer::stop() { //停止udp服務 _udp.stop(); free(_buffer); _buffer = NULL; }
1.2.3 setErrorReplyCode —— 設定錯誤響應碼#
函式說明:
/** * 設定錯誤響應碼 * @param DNSReplyCode 錯誤響應碼 */ void setErrorReplyCode(const DNSReplyCode &replyCode);
DNSReplyCode 定義如下:
enum class DNSReplyCode { NoError = 0, FormError = 1, ServerFailure = 2, //服務錯誤 NonExistentDomain = 3, NotImplemented = 4,//未定義 Refused = 5,//拒絕訪問 YXDomain = 6, YXRRSet = 7, NXRRSet = 8 };
1.2.4 processNextRequest —— 處理DNS請求服務#
函式說明:
/** * 處理DNS請求服務 */ void processNextRequest();
原始碼說明:
/** * 處理DNS請求服務 */ void DNSServer::processNextRequest() { //獲取UDP請求內容 _currentPacketSize = _udp.parsePacket(); if (_currentPacketSize) { if (_buffer != NULL) free(_buffer); _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char)); if (_buffer == NULL) return; _udp.read(_buffer, _currentPacketSize); _dnsHeader = (DNSHeader*) _buffer; //判斷請求是否查詢域名對映的IP地址 *在這裡有非常特殊作用 讀者請注意 if (_dnsHeader->QR == DNS_QR_QUERY && _dnsHeader->OPCode == DNS_OPCODE_QUERY && requestIncludesOnlyOneQuestion() && (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName) ) { //返回IP地址 replyWithIP(); } else if (_dnsHeader->QR == DNS_QR_QUERY) { //響應錯誤碼 replyWithCustomCode(); } free(_buffer); _buffer = NULL; } } /** * 響應域名對應的IP地址 */ void DNSServer::replyWithIP() { if (_buffer == NULL) return; _dnsHeader->QR = DNS_QR_RESPONSE; _dnsHeader->ANCount = _dnsHeader->QDCount; _dnsHeader->QDCount = _dnsHeader->QDCount; //_dnsHeader->RA = 1; _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); _udp.write(_buffer, _currentPacketSize); _udp.write((uint8_t)192); // answer name is a pointer _udp.write((uint8_t)12); // pointer to offset at 0x00c _udp.write((uint8_t)0); // 0x0001 answer is type A query (host address) _udp.write((uint8_t)1); _udp.write((uint8_t)0); //0x0001 answer is class IN (internet address) _udp.write((uint8_t)1); _udp.write((unsigned char*)&_ttl, 4); // Length of RData is 4 bytes (because, in this case, RData is IPv4) _udp.write((uint8_t)0); _udp.write((uint8_t)4); _udp.write(_resolvedIP, sizeof(_resolvedIP)); _udp.endPacket(); #ifdef DEBUG_ESP_DNS DEBUG_ESP_PORT.printf("DNS responds: %s for %s\n", IPAddress(_resolvedIP).toString().c_str(), getDomainNameWithoutWwwPrefix().c_str() ); #endif } /** * 響應錯誤碼 */ void DNSServer::replyWithCustomCode() { if (_buffer == NULL) return; _dnsHeader->QR = DNS_QR_RESPONSE; _dnsHeader->RCode = (unsigned char)_errorReplyCode; _dnsHeader->QDCount = 0; _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); _udp.write(_buffer, sizeof(DNSHeader)); _udp.endPacket(); }
注意點:
- ESP8266 DNSServer 運行於UDP協議之上;
- ESP8266 DNSServer只能支援一個域名對映;
- 當ESP8266設定的域名為“*”,意味著所有請求都會被連結到該IP地址,我們可以利用這一點做一些特殊操作;
1.3 例項#
1.3.1 訪問主機名#
實驗說明
在手機瀏覽器訪問 "www.danpianji.com"會顯示“Hello World”
實驗原始碼
/** * 功能描述:在手機瀏覽器訪問 "www.danpianji.com"會顯示“Hello World” */ #include <ESP8266WiFi.h> #include <DNSServer.h> #include <ESP8266WebServer.h> //除錯定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) #define DebugPrintF(...) Serial.printf( __VA_ARGS__ ) const byte DNS_PORT = 53; IPAddress apIP(192, 168, 1, 1); DNSServer dnsServer; ESP8266WebServer webServer(80); void setup() { DebugBegin(115200); WiFi.mode(WIFI_AP); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP("DNSServer example"); // modify TTL associated with the domain name (in seconds) // default is 60 seconds dnsServer.setTTL(300); // set which return code will be used for all other domains (e.g. sending // ServerFailure instead of NonExistentDomain will reduce number of queries // sent by clients) // default is DNSReplyCode::NonExistentDomain dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); // 啟動DNS server,對映主機名為 www.danpianji.com bool status = dnsServer.start(DNS_PORT, "www.danpianji.com", apIP); if(status){ DebugPrintln("start dnsserver success."); }else{ DebugPrintln("start dnsserver failed."); } // simple HTTP server to see that DNS server is working webServer.onNotFound([]() { String message = "Hello World!\n\n"; message += "URI: "; message += webServer.uri(); webServer.send(200, "text/plain", message); }); webServer.begin(); } void loop() { dnsServer.processNextRequest(); webServer.handleClient(); }
實驗結果
會看到一個DNSServer example開放式AP熱點,連線上:
然後在手機瀏覽器訪問 www.danpianji.com
1.3.2 Portal 認證#
實驗說明
通常,當我們連上一些wifi熱點,只要沒有認證手機號碼資訊,無論訪問哪個頁面都會彈出一個web認證頁面(這就是商家用來收集手機使用者資訊的一種手段,慎重),這就是 Portal 認證。
Portal伺服器也就是接收Portal客戶端認證請求的伺服器端系統,其主要作用是提供免費的門戶服務和基於Web認證的介面,以及接入裝置互動認證客戶端的認證資訊。其中的Web認證方案首先需要給使用者分配一個地址,用於訪問入口網站。
Portal 基於瀏覽器,採用的是B/S構架, 對不同許可權的使用者下發不同的VLAN 訪問不同的伺服器資源,當通過認證後才能訪問internet資源,Portal認證方式不需要安裝認證客戶端, 減少了客戶端的維護工作量,便於運營。
可以在Portal頁面上開展業務拓展,如展示商家廣告, 聯絡方式等基本資訊。Portal廣泛應用於運營商、學校等網路。
通過DNSServer,我們也可以使用到Portal認證
實驗原始碼
/** * 功能描述:portal認證 */ #include <DNSServer.h> #include <ESP8266WebServer.h> #include <ESP8266WiFi.h> //除錯定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) #define DebugPrintF(...) Serial.printf( __VA_ARGS__ ) const byte DNS_PORT = 53; IPAddress apIP(192, 168, 1, 1); DNSServer dnsServer; ESP8266WebServer webServer(80); String responseHTML = "" "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>" "<h1>Hello World!</h1><p>This is a captive portal example. All requests will " "be redirected here.</p></body></html>"; void setup() { DebugBegin(115200); WiFi.mode(WIFI_AP); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP("DNSServer CaptivePortal example"); // 所有請求都對映到一個具體地址 dnsServer.start(DNS_PORT, "*", apIP); // replay to all requests with same HTML webServer.onNotFound([]() { DebugPrintln("webServer handle."); webServer.send(200, "text/html", responseHTML); }); webServer.begin(); } void loop() { dnsServer.processNextRequest(); webServer.handleClient(); }
實驗結果會看到一個 DNSServer CaptivePortal example 開放式AP熱點,連線上:
然後在手機瀏覽器訪問 www.danpianji.com
1.4 總結#
DNSServer也是相對來說非常重要的一章,特別對於web配網,需要使用到它,請讀者認真理解使用。